Pretty recently I learned about
longjmp(). They’re a
neat pair of libc functions which allow you to save your program’s current
execution context and resume it at an arbitrary point in the future (with
If you’re wondering why this is particularly useful, to quote
the manpage, one of their main use cases
is “…for dealing with errors and
interrupts encountered in a low-level subroutine of a program.” These functions
can be used for more sophisticated error handling than simple error
code return values.
I was curious how these functions worked, so I decided to take a look at musl libc’s implementation for x86. First, I’ll explain their interfaces and show an example usage program. Next, since this post isn’t aimed at the assembly wizard, I’ll cover some basics of x86 and Linux calling convention to provide some required background knowledge. Lastly, I’ll walk through the source, line by line.
setjmp() takes a single
jmp_buf opaque type, returns 0, and continues
execution afterward normally. A
jmp_buf is the
setjmp() will save the calling execution context in. We’ll
examine it more closely later on.
longjmp() takes a
jmp_buf and an
int, simply returning back the given
int value (unless it was 0, in which case it returns 1). The unusual aspect
is that when it returns, the program’s execution resumes as if
just been called. This allows the user to jump back an arbitrary amount of
frames on the current call stack (presumably out of some deep routine which had
an error). The return value allows the code following the
setjmp() call to
longjmp() had just been called, and proceed
Here’s a simple example.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
1 2 3 4
The above code creates a
jmp_buf and calls
setjmp(), saving the current
execution context. Since
setjmp() returns 0, the code follows the
first branch, calling
forwarding on the
fancy_func() does some fancy stuff, then calls
longjmp(), passing in the
jmp_buf and 1. Execution returns to the if
statement on line 9, except this time,
ret is 1 instead of 0, because
we’re returning from
longjmp(). Now the code
else path which prints and exits. 2
I’ve mentioned “execution context” a few times, but let’s make that a little more concrete. In this case, a program’s execution context can be defined by the state of the processor’s registers.
On x86, the relevant registers are the general purpose, index, and pointer registers.
1 2 3
ebx, ecx, edx, esi, and edi don’t have particularly special meaning here and can be thought of as arbitrary 32 bit storage locations. However eax, ebp, and eip are a little different.
- eax is used for function return values (specified by the cdecl calling convention)
- ebp, the frame pointer, contains a pointer to the start of the current stack frame.
- eip, the instruction pointer, contains a pointer to the next instruction to execute.
With this in mind, I initially thought that a
jmp_buf would be an array
of 9 ints or something, in order to hold each register.
As it happens,
jmp_buf is instead declared as (link):
1 2 3 4 5
And for x86,
__jmp_buf is declared as (link):
I had never seen this syntax of using bracket operators at the end of a typedef
but searched and found out that the
declares a fixed size array of 6 unsigned longs, and the
declares an array of 1
struct __jmp_buf_tag. The reason for the array of 1
is so the pointer semantics of arrays kick in and the
is actually passed by reference in calls to
longjmp() (as opposed
to being copied).
Anyway, apparently my guess of 9 ints was incorrect, and it’s actually 6 (longs).
can dig into the source to understand why this is,
we need to understand what the state of
the program stack is at the point
setjmp() is called, and to do that, we need
which calling convention is being used. Since we assume x86 Linux, this will
be cdecl. The
relevant parts of cdecl for this case are:
- arguments passed on the stack
- integer values and memory addresses returned in eax (as mentioned above)
- eax, ecx, edx are caller saved, the rest are callee saved
setjmp()’s code executes immediately
call instruction, so at the point the first
setjmp() executes, the stack looks something like this.
1 2 3 4 5 6 7 8 9 10 11 12
(In this illustration, the stack grows down.)
At the top of the stack is the eip value that the
call instruction pushed,
or where to return to after
setjmp() finishes. Above that is the first,
and only argument, the pointer to the given
jmp_buf. Lastly, above that is
the caller’s stack frame.
esp points to the top
of the stack as usual, and ebp is still pointing to the start of the caller’s
frame. Usually the first thing a function does is push
ebp on the stack, and set ebp to esp to now point to the current stack frame (a.k.a the prologue),
setjmp() is such a minimal function, it doesn’t do this.
Furthermore, since ebp is one of the registers that needs to be saved,
needs it to be unperturbed.
setjmp() returns, the stack will look something like this:
1 2 3 4 5 6 7 8 9 10 11
It’s nearly identical, except eip has been popped off the stack, and is now
executing the next instruction after the caller’s
call setjmp. esp has
also been updated accordingly. This is the state of the program that
will need to record, and that
longjmp() will restore.
Before reading the source I tried to reason about what I expected would happen. I presume:
- General purpose and index registers (eax, ebx, ecx, edx, esi, edi) which don’t have any effect on control flow can be trivially saved and restored
- ebp can similarly be saved “as is”, since its value when
setjmp()executes is exactly what it needs to be restored to in
- esp cannot be saved “as is” because when
setjmp()executes, there is the extra eip on the stack that is not there after the function returns. Therefore, the value for esp that should be saved is esp+4 to match the expected state of the stack after return
- The eip that should be saved is the address of the instruction after the
call setjmpinstruction, which can be retrieved from the top of the stack by dereferencing esp
With all that out of the way, let’s read the source (all annotations by me) (link). Since this type of low level register manipulation isn’t available from C (modulo compiler intrinsics), both of these functions are necessarily written in assembly.
1 2 3 4 5 6 7 8 9 10 11 12
The first line retrieves the argument off the stack, placing a pointer to
jmp_buf (remember, an array of 6 unsigned longs) in eax. It then moves
edi, and ebp “as is” into the int array. It adds 4 to esp with a
stores that next. Next, it dereferences esp and stores that in the last slot
in the array. Lastly, it zeroes out eax and returns.
The final state of the
setjmp() returns looks like:
Now let’s look at
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
The first two lines retrieve the arguments (pointer to
jmp_buf, int return
val) from the stack into edx and eax, respectively. The int val is
incremented to 1 if it is 0, according to the spec. Next, ebx, esi, edi, and
ebp are reset
to their saved state, stored in the
jmp_buf, in a straightforward manner.
As you can see, both
longjmp() need to precisely agree on
where each particular register is saved in the
esp is mysteriously restored in an indirect manner, via ecx3 and
finally, eip is reset to the saved state via an indirect jump.
So I was mostly correct, but it seems like eax, ecx, and edx were not saved
If we look back on the details of cdecl, it becomes clear why.
- eax doesn’t need to be saved, because it is reserved for the return value
- ecx and edx are caller saved. This means that a callee subroutine is
free to trash these registers, and it is the responsibility for the caller
to save and restore them after the subroutine returns. Because of this,
if the function that calls
setjmp()needs to use ecx or edx after the call, it will already have code to save and restore those registers before and after the function call. Since
longjmp()resumes execution as if
setjmp()had immediately returned, execution will automatically hit the code that restores ecx and edx, making it unnecessary to save them in the
This is just one example of how great musl libc is at providing an understandable resource for learning the internals of systems software. I often find myself referencing it when I’m curious about libc internals, and if you’re not familiar with it, I highly recommend checking it out!
Mainly, that the function which called
setjmp()cannot have returned before the corresponding
A slight aside: This situation where a function returns different values based on the execution context also appears in
fork(), in which the caller uses the return value to differentiate whether it is now executing in the child or parent process. ↩
I actually have no good explanation for this and am pretty curious why it’s done this way. Something like
mov $esp, [$edx+16]is a perfectly valid instruction (tested with
rasm2)! I asked on the musl mailing list, but no one responded :(. If you have an explanation, please let me know!↩