#include <stdlib.h>
int sub(int x, int y){
return 2*x+y;
}
int main(int argc, char ** argv){
int a;
a = atoi(argv[1]);
return sub(argc,a);
}
用gcc 5.4.0编译,目标32位x86。我在拆卸时得到以下信息:
0804841b <main>:
804841b: 8d 4c 24 04 lea 0x4(%esp),%ecx
804841f: 83 e4 f0 and q4312078qxfffffff0,%esp
8048422: ff 71 fc pushl -0x4(%ecx)
8048425: 55 push %ebp
8048426: 89 e5 mov %esp,%ebp
8048428: 53 push %ebx
8048429: 51 push %ecx
804842a: 83 ec 10 sub q4312078qx10,%esp
804842d: 89 cb mov %ecx,%ebx
....
push %ebp
之前的前三个说明是什么?我没有在较旧的gcc编译二进制文件中看到这些文件。#1 楼
这些指令在做什么push %ebp
之前执行的前三个指令是什么?即
804841b: 8d 4c 24 04 lea 0x4(%esp),%ecx <- 1
804841f: 83 e4 f0 and >>> x/x $esp+4
0xffffd140: 0x01
xfffffff0,%esp <- 2
8048422: ff 71 fc pushl -0x4(%ecx) <- 3
这很容易看出是否使用
gdb
(或其他调试器)来单步执行代码。804841b: 8d 4c 24 04 lea 0x4(%esp),%ecx
此时,在寄存器
$esp
中的内存地址为0xffffd13c
,因此4(%esp)
= $esp+4
= 0xffffd140
:0xffffd13c: 11111111111111111101000100111100
0xfffffff0: AND 11111111111111111111111111110000
-------------------------------------
11111111111111111101000100110000
这意味着
lea
指令将0x4(%esp)
的有效地址0xffffd140
装入$ecx
中。804841f: 83 e4 f0 and $esp
xfffffff0,%esp
Next ,则
0xffffd13c
中的值0xfffffff0
与0xffffd130
进行“与”运算: lea 0x4(%esp),%ecx // load 0xffffd140 into $ecx
and 8048425: 55 push %ebp
8048426: 89 e5 mov %esp,%ebp
xfffffff0,%esp // subtract 0x0c (decimal 12) from $esp
pushl -0x4(%ecx) // decrement $esp by 4, save 0xffffd13c on stack
这将得出值
$esp
,该值存储在0xffffd13c
中。这等效于0x0c
-0xffffd130
= 0xfffffff0
。这会在进程运行时堆栈上创建12个字节的空间。附带说明一下,值-16将表示为
and and $-16,%esp
xfffffff0,%esp
,因此我们可以将8048422: ff 71 fc pushl -0x4(%ecx)
视为
lea 0x4(%esp),%ecx
这样做是为了使堆栈与16个字节的边界对齐,因为下一条指令(请参见3)将堆栈指针减4,然后将值保存到堆栈中。
$ecx
由于前面的
$esp+4
,0xffffd140
中的值等于-0x4(%ecx)
(即0xffffd140
) )。结果,0xffffd13c
= $esp
-4 = main()
。这是
pushl
开头$ebp+4
的值。现在,该值通过$ebp+4
指令保存在进程运行时堆栈中。 摘要:
>>> x/x $ecx-4
0xffffd13c: 0xf7e12637
>>> x/x 0xf7e12637
0xf7e12637 <__libc_start_main+247>: 0x8310c483
这些说明的目的
这些说明的主要目的是什么?
有关这些指令目的的线索是它们在常规功能序言之前执行:
根据System V应用程序二进制接口Intel386体系结构处理器,功能序言
8048422: ff 71 fc pushl -0x4(%ecx)
执行后,第四版的补充内容是返回地址在运行时堆栈上的位置。 通过指令
0xffffd13c
保存在栈中的地址
0xf7e12637
是__libc_start_main()
。这是指向main()
的指针,它是__libc_start_main()
中的偏移量247的地址: 对于
$ecx
而言,该寄存器仅保存argc
的值:>>> x/x $ecx
0xffffd140: 0x00000001
请注意,由于从未使用变量
a
,因此编译器会进行优化拨出电话至atoi
。 因此,为了直接回答问题,在序言之前
main()
中的指令将参数传递给main()
(argc
的值),并将main()
的返回地址保存在运行时堆栈中。C运行时环境和Linux进程剖析
自然,下一个问题是“
__libc_start_main
是什么?”根据Linux标准基础PDA规范3.0RC1:__libc_start_main()
函数应初始化进程,使用适当的参数调用主函数,并处理main()
的返回值。 br /> 那么
__libc_start_main()
来自哪里?简短的答案是,它是共享对象/lib/i386-linux-gnu/libc-2.23.so
中的一个函数,该对象动态链接到可执行的ELF二进制文件中:同样是过程初始化的一部分,它也动态链接到可执行的ELF二进制文件: $ ldd [binary_name]
linux-gate.so.1 => (0xf7764000)
libc.so.6 => /lib/i386-linux-gnu/libc.so.6 (0xf7586000)
/lib/ld-linux.so.2 (0x56640000)
这是完整的图片,来自Linux x86程序启动,或者-如何获得main()?通过Patrick Horgan:
最后一点,如果仔细检查
__libc_start_main()
的__gmon_start__
的返回地址,我们会发现该地址位于main()
段之外以及运行时堆栈。该地址位于0xf7e12637
中,实际上位于虚拟内存的内存映射段中,如Gustavo Duarte的文章《内存中的程序剖析》中的示意图所示:#2 楼
这三个语句用于将
main
的堆栈帧(从其返回地址开始)移动到下一个16字节对齐的地址。lea 0x4(%esp),%ecx # save address of arguments
and %esp+8: argv (a pointer to an array of pointers)
%esp+4: argc (a 32-bit integer)
%esp+0: return address (from call)
xfffffff0,%esp # align stack
pushl -0x4(%ecx) # move return address
... # continue normal preamble
同时,不会移动
main
(argc
和argv
)的参数,因此将指向它们的指针保存在%ecx
中。输入main
:%ecx+4: argv pointer
%ecx+0: argc
%ecx-4: original return address
...
%esp+4: copy of return address
%esp+0: saved base pointer
参数位于返回地址的正上方,因此在调整堆栈指针之前将
%esp+4
保存到%ecx
。接下来,也请
%ecx
用作我们定位原始返回地址-4(%ecx)
的指针,我们将其推入新的堆栈框架。在其余序言之后,堆栈将如下所示:
...
mov -0x8(%ebp),%ecx # load pointer to argc
leave # unwind stack frame, pop %ebp
lea -0x4(%ecx),%esp # restore original stack pointer
ret # jump out, using the original return address!
在您的代码中,您还可以看到
%ecx
在前导之后被压入堆栈(即保存为局部变量);它将在该函数的结尾处从那里恢复,如下所示:sub q4312078qxc,%esp # pad stack by 12 bytes
push %eax # push 4-byte argument
call puts
为什么这一切都做完了?
由于各种原因,诸如数据对齐到16字节边界的现代处理器;某些操作可能会严重影响性能,否则其他操作可能根本无法工作。
一次调整
main
堆栈框架可以使其余代码无需进一步调整即可运行,只要注意始终分配调用前以16字节的倍数堆叠。这就是为什么您经常会看到以下内容的原因:q4312078q
main上找到帧调整-堆栈已对齐。
评论
欢迎!根据您到目前为止所写的内容,我希望阅读您以后的文章。
– julian♦
18年8月3日在23:30
谢谢!我来到这里进行查询,然后感到,虽然您的回答很详尽,但它缺少一些细节。由于作为新用户我无法发表评论,所以我自己拍摄了一张照片。希望你不要介意! :)
– Pesco
18年8月4日在13:14
我喜欢这个比@SYS_V的答案要好(对SYS_V没有冒犯)。我不相信SYS_V的回答地址“目的是什么”。它在解释指令的作用方面做得很好。答案似乎非常非常简单。除了明显的对齐优化之外,“同时,main的参数(argc和argv)没有移动,因此指向它们的指针保存在%ecx中。”美丽。万分感谢。
–埃文·卡洛尔(Evan Carroll)
18年11月6日在22:02