2017-05-30 29 views
4

我想测试2我的函数那种模拟setjmp和longjmp的作业 - 这是很难,因为我们是不允许使用内置函数或程序集asm()来实现longjmp和setjmp函数。 (是的,that's really the assignment。)在C中实现setjmp和longjmp没有内置的函数或程序集(获取不正确的返回值)

问题:我不断收到错误的返回值。因此,简而言之,当main()调用foo()和foo()调用bar()和bar()调用longjump()时,bar()不应该返回到foo(),而应该返回set()主返回值为1,应打印“错误”(参见下面的main())。

相反,我的输出出来为:

start foo 
start bar 
segmentation fault 

段故障,我试图通过初始化指针* P使用malloc固定,但似乎并没有做任何事情。虽然,分段错误,是我没有得到正确的返回值的原因?

代码:

#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); 
} 
+9

有没有办法实现'setjmp'和'longjmp'自己​​的标准。如果这真的是你的任务,你会跳水深入到未定义行为的土地。 – user2357112

+3

这是未定义的行为,可以使用与标准库函数相同的名称编写自己的函数(即使您没有包含标题)。你shoudl调用您的函数别的 –

+1

你'setjmp的(R)'读取未初始化的变量,这也是不确定的行为在这种情况下,不清楚是什么你正在尝试做有 –

回答

3

实施setjmp()longjmp()需要访问堆栈指针。不幸的是,你正在从事的任务有,明确指出禁止你使用任何明智的方法来做到这一点(即使用汇编或使用编译器内置函数访问堆栈指针)。

更糟糕的是,他们已经在他们的示例代码中破坏了setjmp()longjmp()的定义。参数需要是可以解析为数组的类型(例如,typedef int jmp_buf[1]),而不是int ...

反正。你需要一些方法来可靠地找到在C堆栈帧也许这样做的最好办法老堆栈指针将是定义在堆栈上的数组,然后看“后面”它...

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

的精确的偏移量将取决于您使用的编译器,以及可能取决于您的函数需要什么参数以及该函数具有哪些其他局部变量。 你需要尝试一点才能得到这个权利。在模拟器中运行您的应用程序,并/或查看生成的程序集,以确保获得正确的值。

当从longjmp()“返回”时,同样的技巧可能会设置堆栈指针。但是,某些编译器优化可能会使这一点变得困难,尤其是在具有链接寄存器的体系结构上 - 比如MIPS。确保编译器优化被禁用。如果一切都失败了,你可能需要在longjmp()中调用一个虚函数来强制编译器将链接寄存器保存在堆栈中,而不是将其保存在寄存器中(它不能被覆盖)。

+0

谢谢!我会试验这个。 – adventuredoge

+0

堆栈向下增长,因此您需要来自本地的正偏移量来访问'lr',而不是负偏移量。 – pat

1

您将需要处理链接寄存器,堆栈指针和帧指针(通常您还必须保存和恢复所有保存寄存器,但我认为我们不需要为了使这个例子工作)。

看看arg3caller函数here。输入时,它将链接寄存器和帧指针存储在堆栈上,并将帧指针设置为指向新的堆栈帧。然后它调用args3,设置返回值,最重要的是,将帧指针复制回堆栈指针。然后弹出链接寄存器和原始帧指针,从堆栈指针现在所在的位置跳转到链接寄存器。如果你看看args3,它会将帧指针保存到堆栈中,然后从堆栈中恢复它。

因此,arg3caller可以是longjmp,但是如果你希望它返回一个与它输入不同的堆栈指针,你将不得不改变帧指针,因为帧指针被复制到堆栈指针中然后结束。通过让args3(由longjmp调用的虚拟函数)修改它保存在堆栈中的帧指针的副本,可以修改帧指针。

您需要使setjmp也调用一个虚函数,以便以相同的方式获取存储在堆栈中的链接寄存器和帧指针。然后,您可以将链接寄存器和帧指针从setjmp的堆栈帧中复制到全局数据库中(通常,setjmp将复制到所提供的jmpbuf中,但在这里,参数setjmplongjmp无用,因此您必须使用全局变量) ,以及框架的地址。然后,longjmp必须将保存的链接寄存器和帧指针复制回相同的地址,并使虚拟叶功能将保存的帧指针更改为同一地址。因此,虚拟叶功能会将该地址复制到帧指针并返回到longjmp,这将复制到堆栈指针。然后,它将从该堆栈帧(您填充的)中恢复帧指针和链接寄存器,从而返回原来返回setjmp(除了返回值不同)之外的所有状态。

请注意,您可以通过使用@duskwuff描述的局部数组技巧的负向索引访问这些字段。您最初应该使用-S标志进行编译,以便您可以查看gcc正在生成的asm,以便您可以看到重要寄存器在堆栈中的保存位置(以及您的代码如何干扰所有这些)。

编辑:

我没有到MIPS的gcc立即访问,但我发现this,并把它放在MIPS GCC 5.4模式。玩了一圈,我发现非叶功能存储lrfp正下方的参数将被放置在堆栈(该参数实际上传递a0,但海湾合作委员会在堆栈留下空间,以防被调用者需要存储它)。通过让setjmp调用叶函数,我们可以确保setjmp是非叶,因此它的lr保存在堆栈中。然后,我们可以保存arg的地址,以及存储在其下面的lrfp(使用负向索引),并返回0.然后,在longjmp中,我们可以调用叶函数以确保lr保存在堆栈,但也有叶变堆积fp到保存sp。返回到longjmp后,fp将指向原始帧,我们可以使用保存的lrfp重新填充原始帧。从longjmp返回时,将fp复制回sp,并从我们重新填充的框架中恢复lrfp,看起来我们从setjmp返回。但是,这次我们返回1,因此来电者可以将真实的退货从setjmp区分为longjmp工程的假货。

注意,我只直勾勾这个代码,并没有实际执行它!此外,它必须编译禁用优化(-O0)。如果启用了任何优化,编译器会内嵌叶函数,并将setjmplongjmp都变为空函数。你应该看看你的编译器用这个来理解栈帧是如何构建的。再一次,我们确实处于未定义行为的领域,即使是海湾合作委员会版本的变化也会让所有的事情都感到失望。您还应该单步执行该程序(使用gdbspim)以确保您了解正在发生的事情。

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; 
} 

祝你好运!