Repository navigation
libap: ucontext's three switches must be assembly, not C wrappers - #718
Merged
Merged
Conversation
The VM refuted the C-wrapper design on the first run. ucontext-test section 0 printed its PASS line once instead of twice and reported `setcontext came back round: got 1 want 2'. What a C getcontext saves is a point inside getcontext itself. That is fine until the context is resumed after getcontext has returned -- by then the wrapper's frame is dead stack, its own return-address slot has been reused by whatever the caller did next, and the resumed epilogue RETs to that. Here it landed after the setcontext CALL, because setcontext's return address was sitting in the slot, so control appeared to flow straight on with the counter still at 1. The same reasoning says why swapcontext got away with it, and that half is worth keeping so the lesson is not over-general: a context saved by swapcontext is resumed while its stack is parked, so nothing has run on it and the frame is intact. Sections 1 to 4 all passed. getcontext is the one that returns and is then resumed, and the only one that could have shown this. All three are assembly now, which is what setjmp.s was all along, and the files are named for what they hold: ucontext.s and makecontext.c. The cost is one shared offset, uc_sigmask at 64, and makecontext.c turns a change to it into a compile error rather than a switch that quietly restores the wrong mask -- verified by swapping two members and watching `size of array _uc_layout_assert is negative'. The mask is handled as sigsetjmp handles it, through _psigblocked(SB), with MOVL because sigset_t is long and kencc's long is four bytes. Section 5's failure was the test's own, and of the one kind gcc cannot catch: `volatile long a = 0x1111111111111111L' holds 0x11111111 here, so the comparison against the full constant was false and a working switch reported got 0 want 1. long long now. The glibc run could not have found it, because the difference IS the reference's width. makectx-xcheck follows the rename and stubs setcontext instead of the three primitives; its two frame controls still give 3 and 1. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WGAwvvTwDg2yknFkmZ3qzs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The VM refuted the C-wrapper design on the first run. ucontext-test section 0 printed its PASS line once instead of twice and reported `setcontext came back round: got 1 want 2'.
What a C getcontext saves is a point inside getcontext itself. That is fine until the context is resumed after getcontext has returned -- by then the wrapper's frame is dead stack, its own return-address slot has been reused by whatever the caller did next, and the resumed epilogue RETs to that. Here it landed after the setcontext CALL, because setcontext's return address was sitting in the slot, so control appeared to flow straight on with the counter still at 1.
The same reasoning says why swapcontext got away with it, and that half is worth keeping so the lesson is not over-general: a context saved by swapcontext is resumed while its stack is parked, so nothing has run on it and the frame is intact. Sections 1 to 4 all passed. getcontext is the one that returns and is then resumed, and the only one that could have shown this.
All three are assembly now, which is what setjmp.s was all along, and the files are named for what they hold: ucontext.s and makecontext.c. The cost is one shared offset, uc_sigmask at 64, and makecontext.c turns a change to it into a compile error rather than a switch that quietly restores the wrong mask -- verified by swapping two members and watching `size of array _uc_layout_assert is negative'. The mask is handled as sigsetjmp handles it, through _psigblocked(SB), with MOVL because sigset_t is long and kencc's long is four bytes.
Section 5's failure was the test's own, and of the one kind gcc cannot catch: `volatile long a = 0x1111111111111111L' holds 0x11111111 here, so the comparison against the full constant was false and a working switch reported got 0 want 1. long long now. The glibc run could not have found it, because the difference IS the reference's width.
makectx-xcheck follows the rename and stubs setcontext instead of the three primitives; its two frame controls still give 3 and 1.
Claude-Session: https://claude.ai/code/session_01WGAwvvTwDg2yknFkmZ3qzs