0x00401170 push %ebp
0x00401171 mov %esp,%ebp
0x00401173 pop %ebp
所以目的是什么esp移到ebp的方法?
#1 楼
将esp
移入ebp
是为了提供调试帮助,在某些情况下还用于异常处理。 ebp
通常被称为帧指针。考虑到这一点,请考虑一下调用多个函数会发生什么。 ebp
指向您将旧的ebp
推入的内存块,旧的ebp
本身又指向另一个已保存的gcc -m32 -g test.c
,依此类推。这样,您就有了堆栈帧的链表。从这些中,您可以查看返回地址(在堆栈帧中,它们总是比帧指针高4个字节),以找出哪一行代码称为有问题的堆栈帧。指令指针可以告诉您当前执行的位置。通过显示整个程序的执行流,这可以使您生成对调试有用的堆栈跟踪。作为一个实际示例,请考虑以下代码:
void foo();
void bar();
void baz();
void quux();
void foo() {
bar();
}
void bar() {
baz();
quux();
}
void baz() {
//do nothing
}
void quux() {
*(int*)(0) = 1; //SEGFAULT!
}
int main() {
foo();
return 0;
}
这会生成以下程序集(带有Debian gcc 4.7.2-4
leave
,已片段化): 080483dc <foo>:
80483dc: 55 push %ebp
80483dd: 89 e5 mov %esp,%ebp
80483df: 83 ec 08 sub mov %ebp, %esp
pop %ebp
x8,%esp
80483e2: e8 02 00 00 00 call 80483e9 <bar>
80483e7: c9 leave
80483e8: c3 ret
080483e9 <bar>:
80483e9: 55 push %ebp
80483ea: 89 e5 mov %esp,%ebp
80483ec: 83 ec 08 sub #0 0x08048408 in quux () at test.c:20
#1 0x080483f9 in bar () at test.c:12
#2 0x080483e7 in foo () at test.c:7
#3 0x0804841b in main () at test.c:24
x8,%esp
80483ef: e8 07 00 00 00 call 80483fb <baz>
80483f4: e8 07 00 00 00 call 8048400 <quux>
80483f9: c9 leave
80483fa: c3 ret
080483fb <baz>:
80483fb: 55 push %ebp
80483fc: 89 e5 mov %esp,%ebp
80483fe: 5d pop %ebp
80483ff: c3 ret
08048400 <quux>:
8048400: 55 push %ebp
8048401: 89 e5 mov %esp,%ebp
8048403: b8 00 00 00 00 mov q4312078qx0,%eax
8048408: c7 00 01 00 00 00 movl q4312078qx1,(%eax)
804840e: 5d pop %ebp
804840f: c3 ret
08048410 <main>:
8048410: 55 push %ebp
8048411: 89 e5 mov %esp,%ebp
8048413: 83 e4 f0 and q4312078qxfffffff0,%esp
8048416: e8 c1 ff ff ff call 80483dc <foo>
804841b: b8 00 00 00 00 mov q4312078qx0,%eax
8048420: c9 leave
8048421: c3 ret
考虑到这一点,以及x86上的标准C调用约定,我们知道segfault上的堆栈看起来像:
主计算机堆栈框架的顶部
主计算机的堆栈空间-在这种情况下,足以对齐16个字节
0x0804841b
call foo
的返回地址指向
1.
的指针foo的堆栈空间
0x080483e7
指向call bar
的返回地址指向
4.
的指针bar的堆栈空间
0x080483f9
返回call quux
的地址指向
7.
的指针quux的堆栈空间
指令指针将为
0x08048408
。 ebp
将指向10.
。此时,处理器会生成一个异常,操作系统会对其进行处理。然后,它将
SIGSEGV
发送到该进程,该进程必须终止并转储核心。然后,您使用gdb -c core
在gdb中启动核心转储,然后键入file a.out
和bt
,它会给您响应:指针。然后,转到
#0
(10),查看堆栈(9)上的前一项,并生成ebp
。它跟随#1
(即ebp
)至(7),并在(6)之上查找4个字节以生成mov %ebp, (%ebp)
。最后,它遵循(7)至(4)并查看(3)来生成#2
。注意:这只是进行此类堆栈跟踪的一种方法。 GDB非常非常聪明,即使使用
#3
,也可以执行堆栈跟踪。但是,在非常基本的实现中,这可能是生成堆栈跟踪的最简单方法。评论
为什么不首先使用gcc -S并获得带注释的代码?
– 0xC0000022L♦
13年5月22日在16:37
我想拥有真实的内存地址,使其与实时程序中的堆栈跟踪相匹配。我也认为objdump / gdb示例更适合rev-eng主题。
–罗伯特·梅森
13年5月22日在17:36
#2 楼
我喜欢罗伯特的解释,它有一个很好的例子,但是..我认为它没有抓住要点,这才是本指令的真正目的。是作为调试辅助工具,在某些情况下还可以用于异常处理。
..不是真的,不仅如此。它是x86(32位)标准函数序言的一部分,并且是(通用)设置函数栈帧的技术,因此,参数和局部变量可以作为
ebp
的固定偏移量进行访问,毕竟, * B * ase帧* P * ointer。在函数入口处使
ebp
等于esp
,您将在堆栈内部拥有一个固定的相对指针,在该函数的生命周期内不会改变,您将能够分别以ebp
(作为(固定)正和(固定)负偏移量)访问参数和局部变量。您可以或不能在发行版优化代码中看到此标准序言:优化程序可以(并且经常这样做)使用FPO(帧指针优化)来摆脱
ebp
,而只需在函数内部使用esp
即可访问参数和局部变量。这非常棘手(我不会手动完成),因为esp
在函数生存期内可能会发生变化,因此例如可以在代码的两个不同点使用2个不同的偏移量来访问参数。评论
的确如此,但是在大量已编译的代码中,堆栈指针在函数的持续时间内不会发生变化,因为编译器只执行一次子操作,并使用固定偏移量进行推/弹出操作。出于反向工程的目的,我更感兴趣的是ebp可以告诉我有关程序结构的信息,而不是它如何使程序员的工作变得更轻松(尤其是在编译代码中)。
–罗伯特·梅森
13年5月26日在20:59
话虽这么说,感谢您填补我在解释中留下的(而不是巨大的)漏洞。
–罗伯特·梅森
13年5月26日21:00
评论
邓诺(Dunno),这里是否有这些话题?无论如何请看这里:stackoverflow.com/questions/2515598/push-ebp-movlesp-ebp我认为是的,尽管非常基础,因为拆卸问题是主题。此外,还有理由使用与逆向工程(例如调试,运行时代码分析)更相关的框架指针(例如易于实现的alloca(),易于记住偏移量)