您将需要处理链接寄存器,堆栈指针和帧指针(通常您还必须保存和恢复所有保存寄存器,但我认为我们不需要为了使这个例子工作)。
看看arg3caller
函数here。输入时,它将链接寄存器和帧指针存储在堆栈上,并将帧指针设置为指向新的堆栈帧。然后它调用args3
,设置返回值,最重要的是,将帧指针复制回堆栈指针。然后弹出链接寄存器和原始帧指针,从堆栈指针现在所在的位置跳转到链接寄存器。如果你看看args3
,它会将帧指针保存到堆栈中,然后从堆栈中恢复它。
因此,arg3caller
可以是longjmp
,但是如果你希望它返回一个与它输入不同的堆栈指针,你将不得不改变帧指针,因为帧指针被复制到堆栈指针中然后结束。通过让args3
(由longjmp
调用的虚拟函数)修改它保存在堆栈中的帧指针的副本,可以修改帧指针。
您需要使setjmp
也调用一个虚函数,以便以相同的方式获取存储在堆栈中的链接寄存器和帧指针。然后,您可以将链接寄存器和帧指针从setjmp
的堆栈帧中复制到全局数据库中(通常,setjmp
将复制到所提供的jmpbuf
中,但在这里,参数setjmp
和longjmp
无用,因此您必须使用全局变量) ,以及框架的地址。然后,longjmp
必须将保存的链接寄存器和帧指针复制回相同的地址,并使虚拟叶功能将保存的帧指针更改为同一地址。因此,虚拟叶功能会将该地址复制到帧指针并返回到longjmp
,这将复制到堆栈指针。然后,它将从该堆栈帧(您填充的)中恢复帧指针和链接寄存器,从而返回原来返回setjmp
(除了返回值不同)之外的所有状态。
请注意,您可以通过使用@duskwuff描述的局部数组技巧的负向索引访问这些字段。您最初应该使用-S
标志进行编译,以便您可以查看gcc正在生成的asm,以便您可以看到重要寄存器在堆栈中的保存位置(以及您的代码如何干扰所有这些)。
编辑:
我没有到MIPS的gcc立即访问,但我发现this,并把它放在MIPS GCC 5.4模式。玩了一圈,我发现非叶功能存储lr
和fp
正下方的参数将被放置在堆栈(该参数实际上传递a0
,但海湾合作委员会在堆栈留下空间,以防被调用者需要存储它)。通过让setjmp
调用叶函数,我们可以确保setjmp
是非叶,因此它的lr
保存在堆栈中。然后,我们可以保存arg的地址,以及存储在其下面的lr
和fp
(使用负向索引),并返回0.然后,在longjmp
中,我们可以调用叶函数以确保lr
保存在堆栈,但也有叶变其堆积fp
到保存sp
。返回到longjmp
后,fp
将指向原始帧,我们可以使用保存的lr
和fp
重新填充原始帧。从longjmp
返回时,将fp
复制回sp
,并从我们重新填充的框架中恢复lr
和fp
,看起来我们从setjmp
返回。但是,这次我们返回1,因此来电者可以将真实的退货从setjmp
区分为longjmp
工程的假货。
注意,我只直勾勾这个代码,并没有实际执行它!此外,它必须编译禁用优化(-O0
)。如果启用了任何优化,编译器会内嵌叶函数,并将setjmp
和longjmp
都变为空函数。你应该看看你的编译器用这个来理解栈帧是如何构建的。再一次,我们确实处于未定义行为的领域,即使是海湾合作委员会版本的变化也会让所有的事情都感到失望。您还应该单步执行该程序(使用gdb
或spim
)以确保您了解正在发生的事情。
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;
}
祝你好运!
来源
2017-05-31 08:11:17
pat
有没有办法实现'setjmp'和'longjmp'自己的标准。如果这真的是你的任务,你会跳水深入到未定义行为的土地。 – user2357112
这是未定义的行为,可以使用与标准库函数相同的名称编写自己的函数(即使您没有包含标题)。你shoudl调用您的函数别的 –
你'setjmp的(R)'读取未初始化的变量,这也是不确定的行为在这种情况下,不清楚是什么你正在尝试做有 –