Implementing setjmp and longjmp in C without built in functions or assembly (getting incorrect return values)

泄露秘密 提交于 2019-12-22 06:58:04

问题


I'm trying to test 2 of my functions that sort of mimic setjmp and longjmp for a homework - which is pretty difficult since we're not allowed to use built in functions or assembly asm() to implement the longjmp and setjmp functions. (Yes, that's really the assignment.)

Problem: I keep getting wrong return values. So, in short, when main() calls foo() and foo() calls bar(), and bar() calls longjump(), then bar() should not return to foo() but instead setjmp() should return to main with return value of 1 which should print "error" (see main() below).

Instead, my output comes out as:

start foo
start bar
segmentation fault

The segmentation fault, i tried fixing by initializing the pointer *p with malloc, but that didn't seem to do anything. Although, would the segmentation fault, be the reason why im not getting the correct return values?

code:

#include <stdio.h>
#include <stdlib.h>

int setjmp(int v);
int longjmp(int v);
int foo(void);
int bar(void);
int *add;


int main(void) {

    int r;

    r = setjmp(r);
    if(r == 0) {
        foo();
        return(0);
    } else {
        printf("error\n");
        return(2);
    }

}

int _main(void) {
    return(0);
}

int setjmp(int v)
{
    add = &v;
    return(0);
}

int longjmp(int v)
{
    int *p;
    p = &v;
    *(p - 1) = *add;
    return(1);
}

int foo(void) {
    printf("start foo\n");
    bar();
    return(0);
}

int bar(void) {
    int d;
    printf("start bar\n");
    longjmp(d);
    return(0);
}

回答1:


Implementing setjmp() and longjmp() requires access to the stack pointer. Unfortunately, the assignment you're working from has explicitly banned you from using every sensible method to do this (i.e, using assembly, or using compiler builtins to access the stack pointer).

What's worse is, they've mangled the definition of setjmp() and longjmp() in their sample code. The argument needs to be a type that resolves to an array (e.g, typedef int jmp_buf[1]), not an int

Anyways. You need some way to reliably find the old stack pointer from a stack frame in C. Probably the best way of doing this will be to define an array on the stack, then look "behind" it…

void get_sp(void) {
    int x[1];
    sp = x[-1]; // or -2 or -3, etc…

The exact offset will depend on what compiler you're using, as well as possibly on what arguments your function takes and what other local variables the function has. You will need to experiment a bit to get this right. Run your application in the simulator, and/or look at the generated assembly, to make sure you're picking up the right value.

The same trick will probably work to set the stack pointer when "returning" from longjmp(). However, certain compiler optimizations may make this difficult, especially on architectures with a link register -- such as MIPS. Make sure compiler optimizations are disabled. If all else fails, you may need to call a dummy function in longjmp() to force the compiler to save the link register on the stack, rather than leaving it in a register (where it can't be overwritten).




回答2:


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!



来源:https://stackoverflow.com/questions/44273496/implementing-setjmp-and-longjmp-in-c-without-built-in-functions-or-assembly-get

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!