You are going to need to deal with the link register, the stack pointer, and the frame pointer (you would normally also have to save and restore all of the save registers, but I don't think we need to in order to make this example work).
Take a look at the arg3caller
function here. Upon entry, it stores the link register and the frame pointer on the stack, and sets the frame pointer to point to the new stack frame. It then calls args3
, sets the return value, and, most importantly, copies the frame pointer back into the stack pointer. It then pops the link register and the original frame pointer from where the stack pointer is now located, and jumps to the link register. If you look at args3
, it saves the frame pointer into the stack and then restores it from the stack.
So, arg3caller
can be longjmp
, but if you want it to return with a different stack pointer than it entered with, you are going to have to change the frame pointer, because the frame pointer gets copied into the stack pointer at then end. The frame pointer can be modified by having args3
(a dummy function called by longjmp
) modify the copy of the frame pointer that it saved in the stack.
You will need to make setjmp
also call a dummy function in order to get the link register and frame pointer stored on the stack in the same way. You can then copy the link register and frame pointer out of setjmp
's stack frame into globals (normally, setjmp
would copy stuff into the provided jmpbuf
, but here, the arguments to setjmp
and longjmp
are useless, so you have to use globals), as well as the address of the frame. Then, longjmp
must copy the saved link register and frame pointer back into the same address, and have the dummy leaf function change the saved frame pointer to that same address. Thus, the dummy leaf function will copy that address into the frame pointer and return to longjmp
, which will copy it into the stack pointer. It will then restore the frame pointer and the link register from that stack frame (that you populated), thus returning with everything in the state that it was when setjmp
originally returned (except the return value will be different).
Note that you can access these fields by using the negative indexing of a local array trick described by @duskwuff. You should initially compile with the -S
flag so that you can see what asm gcc is generating so that you can see where the important registers are being saved in the stack (and how your code might perturb all of that).
Edit:
I don't have immediate access to a MIPS gcc, but I found this, and put it in MIPS gcc 5.4 mode. Playing around, I found that non-leaf functions store lr
and fp
immediately below where the argument would be placed on the stack (the argument is actually passed in a0
, but gcc leaves room for it on the stack in case the callee needs to store it). By having setjmp
call a leaf function, we can ensure that setjmp
is a non-leaf so that its lr
is saved on the stack. We can then save the address of the arg, and the lr
and fp
that are stored immediately below it (using negative indexing), and return 0. Then, in longjmp
, we can call a leaf function to ensure the lr
is saved on the stack, but also have the leaf change its stacked fp
to the saved sp
. Upon return to longjmp
, the fp
will be pointing at the original frame, which we can re-populate with the saved lr
and fp
. Returning from longjmp
will copy the fp
back into the sp
and restore the lr
and fp
from our re-populated frame, making it appear that we are returning from setjmp
. This time however, we return 1 so the caller can differentiate the true return from setjmp
to the fake one engineered by longjmp
.
Note that I have only eyeballed this code, and have not actually executed it!! Also, it must be compiled with optimization disabled (-O0
). If you enable any kind of optimization, the compiler inlines the leaf functions and turns both setjmp
and longjmp
into empty functions. You should see what your compiler does with this to understand how the stack frames are constructed. Again, we're well and truly in the land of undefined behavior, and even changes in gcc version could upset everything. You should also single step the program (using gdb
or spim
) to make sure you understand what's going on.
struct jmpbuf {
int lr;
int fp;
int *sp;
};
static struct jmpbuf ctx;
static void setjmp_leaf(void) { }
int setjmp(int arg)
{
// call the leaf so that our lr is saved
setjmp_leaf();
// the address of our arg should be immediately
// above the lr and fp
ctx.sp = &arg;
// lr is immediately below arg
ctx.lr = (&arg)[-1];
// fp is below that
ctx.fp = (&arg)[-2];
return 0;
}
static void longjmp_leaf(int arg)
{
// overwrite the caller's frame pointer
(&arg)[-1] = (int)ctx.sp;
}
int longjmp(int arg)
{
// call the leaf so that our lr is saved
// but also to change our fp to the save sp
longjmp_leaf(arg);
// repopulate the new stack frame with the saved
// lr and fp. &arg is calculated relative to fp,
// which was modified by longjmp_leaf. &arg isn't
// where it used to be!
(&arg)[-1] = ctx.lr;
(&arg)[-2] = ctx.fp;
// this should restore the saved fp and lr
// from the new frame, so it looks like we're
// returning from setjmp
return 1;
}
Good luck!