C 以前的堆栈变量
我有这个问题,我递归调用C中的一个函数,C是词汇范围的,所以我只能访问当前堆栈帧。我想从上一个堆栈帧中提取参数和局部变量,该堆栈帧是在当前堆栈帧上im时在上一个函数调用下创建的 我知道上一个递归调用的值仍然在堆栈上,但我无法访问这些值,因为它们“隐藏”在活动堆栈框架下 我想从上一个堆栈中提取参数和局部变量,并将它们复制到copy_of_burned_arg和copy_of_burned_loc 这是一个要求,使用内联汇编使用气体提取变量,这是我迄今为止,我尝试了一整天,我似乎无法理解,我画了纸上的堆栈,并做了计算,但没有工作,我还尝试删除调用printf,使堆栈将更干净,但我无法找出正确的算法。这是到目前为止的代码,我的函数在第二次迭代时停止C 以前的堆栈变量,c,gcc,assembly,x86,C,Gcc,Assembly,X86,我有这个问题,我递归调用C中的一个函数,C是词汇范围的,所以我只能访问当前堆栈帧。我想从上一个堆栈帧中提取参数和局部变量,该堆栈帧是在当前堆栈帧上im时在上一个函数调用下创建的 我知道上一个递归调用的值仍然在堆栈上,但我无法访问这些值,因为它们“隐藏”在活动堆栈框架下 我想从上一个堆栈中提取参数和局部变量,并将它们复制到copy_of_burned_arg和copy_of_burned_loc 这是一个要求,使用内联汇编使用气体提取变量,这是我迄今为止,我尝试了一整天,我似乎无法理解,我画了纸上
#include <stdio.h>
char glo = 97; // just for fun 97 is ascii lowercase 'a'
int copy_of_buried_arg;
char copy_of_buried_loc;
void rec(int arg) {
char loc;
loc = glo + arg * 2; // just for fun, some char arithmetic
printf("inside rec() arg=%d loc='%c'\n", arg, loc);
if (arg != 0) {
// after this assembly code runs, the copy_of_buried_arg and
// copy_of_buried_loc variables will have arg, loc values from
// the frame of the previous call to rec().
__asm__("\n\
movl 28(%esp), %eax #moving stack pointer to old ebp (pointing it to old ebp)\n\
addl $8, %eax #now eax points to the first argument for the old ebp \n\
movl (%eax), %ecx #copy the value inside eax to ecx\n\
movl %ecx, copy_of_buried_arg # copies the old argument\n\
\n\
");
printf("copy_of_buried_arg=%u copy_of_buried_loc='%c'\n",
copy_of_buried_arg, copy_of_buried_loc);
} else {
printf("there is no buried stack frame\n");// runs if argument = 0 so only the first time
}
if (arg < 10) {
rec(arg + 1);
}
}
int main (int argc, char **argv) {
rec(0);
return 0;
}
#包括
char glo=97;//只是为了好玩97是ascii小写字母“a”
嵌入参数的整数副本;
已埋的字符副本;
无效记录(内部参数){
char-loc;
loc=glo+arg*2;//只是为了好玩,一些字符算法
printf(“内部rec()arg=%d loc='%c'\n',arg,loc”);
如果(arg!=0){
//运行此程序集代码后,_的副本_将_arg和
//_-loc变量的副本_将具有来自的arg、loc值
//上一次调用rec()的帧。
__asm\uuuu(“\n\
movl 28(%esp),%eax#将堆栈指针移动到旧ebp(将其指向旧ebp)\n\
addl$8,%eax#现在eax指向旧ebp的第一个参数\n\
movl(%eax),%ecx#将eax内的值复制到ecx\n\
movl%ecx,复制_参数的_#复制旧参数\n\
\n\
");
printf(“copy_of_burned_arg=%u copy_of_burned_loc='%c'\n”,
埋置参数的副本,埋置位置的副本);
}否则{
printf(“没有隐藏的堆栈帧”);//在参数=0时运行,因此仅第一次运行
}
如果(arg<10){
rec(arg+1);
}
}
int main(int argc,字符**argv){
rec(0);
返回0;
}
我可以尝试提供帮助,但没有Linux或GAS中的汇编。但计算结果应该类似:
这是经过几次调用后的堆栈。典型的堆栈帧设置会创建堆栈帧的链接列表,其中EBP是当前堆栈帧,并指向上一个堆栈帧的旧值
+-------+
ESP-> |loc='c'| <- ESP currently points here.
+-------+
EBP-> |oldEBP |--+ <- rec(0)'s call frame
+-------+ |
|retaddr| | <- return value of rec(1)
+-------+ |
|arg=1 | | <- pushed argument of rec(1)
+-------+ |
|loc='a'| | <- local variable of rec(0)
+-------+ |
+--|oldEBP |<-+ <- main's call frame
| +-------+
| |retaddr| <- return value of rec(0)
| +-------+
| |arg=0 | <- pushed argument of rec(0)
| +-------+
\|/
to main's call frame
或者我对GAS的最佳猜测(我不知道,但知道它是向后到MASM的):
在Windows上使用VS2010使用my MASM输出代码:
inside rec() arg=0 loc='a'
there is no buried stack frame
inside rec() arg=1 loc='c'
copy_of_buried_arg=0 copy_of_buried_loc='a'
inside rec() arg=2 loc='e'
copy_of_buried_arg=1 copy_of_buried_loc='c'
inside rec() arg=3 loc='g'
copy_of_buried_arg=2 copy_of_buried_loc='e'
inside rec() arg=4 loc='i'
copy_of_buried_arg=3 copy_of_buried_loc='g'
inside rec() arg=5 loc='k'
copy_of_buried_arg=4 copy_of_buried_loc='i'
inside rec() arg=6 loc='m'
copy_of_buried_arg=5 copy_of_buried_loc='k'
inside rec() arg=7 loc='o'
copy_of_buried_arg=6 copy_of_buried_loc='m'
inside rec() arg=8 loc='q'
copy_of_buried_arg=7 copy_of_buried_loc='o'
inside rec() arg=9 loc='s'
copy_of_buried_arg=8 copy_of_buried_loc='q'
inside rec() arg=10 loc='u'
copy_of_buried_arg=9 copy_of_buried_loc='s'
在我的编译器(gcc 3.3.4)中,我最终得出以下结论:
#include <stdio.h>
char glo = 97; // just for fun 97 is ascii lowercase 'a'
int copy_of_buried_arg;
char copy_of_buried_loc;
void rec(int arg) {
char loc;
loc = glo + arg * 2; // just for fun, some char arithmetic
printf("inside rec() arg=%d loc='%c'\n", arg, loc);
if (arg != 0) {
// after this assembly code runs, the copy_of_buried_arg and
// copy_of_buried_loc variables will have arg, loc values from
// the frame of the previous call to rec().
__asm__ __volatile__ (
"movl 40(%%ebp), %%eax #\n"
"movl %%eax, %0 #\n"
"movb 31(%%ebp), %%al #\n"
"movb %%al, %1 #\n"
: "=m" (copy_of_buried_arg), "=m" (copy_of_buried_loc)
:
: "eax"
);
printf("copy_of_buried_arg=%u copy_of_buried_loc='%c'\n",
copy_of_buried_arg, copy_of_buried_loc);
} else {
printf("there is no buried stack frame\n");// runs if argument = 0 so only the first time
}
if (arg < 10) {
rec(arg + 1);
}
}
int main (int argc, char **argv) {
rec(0);
return 0;
}
从ebp
(40和31)开始的偏移量最初设置为任意猜测值(例如0),然后通过观察拆卸和一些简单计算进行细化
请注意,当函数递归调用自身时,它使用额外的12+4=16字节堆栈进行对齐和参数调用:
subl $12, %esp
movl 8(%ebp), %eax
incl %eax
pushl %eax
call _rec
addl $16, %esp
还有4个字节的返回地址
然后,函数对旧的ebp
及其局部变量使用4+8=12字节:
_rec:
pushl %ebp
movl %esp, %ebp
subl $8, %esp
因此,每次递归调用,堆栈总的增长量为16+4+12=32字节
现在,我们知道如何通过ebp
获取本地arg
和loc
:
movl 8(%ebp), %eax ; <- arg
addl %eax, %eax
addb _glo, %al
movb %al, -1(%ebp) ; <- loc
movl8(%ebp),%eax;您不能将上一次调用的局部变量的地址传递给下一个函数吗?在这里,混用汇编语言可能不会带来任何好处。在调试器中运行它并从中获取数字如何?@GregHewgill我们应该使用汇编从上一个堆栈中挖掘变量,这是一项要求,我看到了。在问题中指出这类事情会很有用。@Alex我该怎么做呢。事实上,我也有这个问题,是否有一个程序以图形方式显示堆栈?我猜想任何类型的堆栈保护都会使这种方法完全不可移植。@tbert:堆栈保护与此有什么关系?OP的代码是不可移植的,因为当使用不同的优化选项或不同版本的编译器进行编译时,它会崩溃。我很轻,我只是做了一个噩梦,人们对此提出了大量问题,他们认为将信息传递给函数的进一步递归迭代是一种可行的技术。如果讲师不确定有一长串的警告与此一起出现,那么我会确保互联网上的某个地方有人说“警告清空”。请注意,至少x86-64参数实际到达寄存器(大部分)中,而不是堆栈中,所以这是不可能的。@perh问题没有提到64位。此外,如果寄存器不足以保存其中的所有内容,那么堆栈上将不可避免地存储一些内容。
subl $12, %esp
movl 8(%ebp), %eax
incl %eax
pushl %eax
call _rec
addl $16, %esp
_rec:
pushl %ebp
movl %esp, %ebp
subl $8, %esp
movl 8(%ebp), %eax ; <- arg
addl %eax, %eax
addb _glo, %al
movb %al, -1(%ebp) ; <- loc