反汇编分析一个简单的c语言程序,主要是理解一下函数调用堆栈空间的分配
// test.c
int g(int x)
{
return x + 3;
}
int f(int x)
{
return g(x);
}
int main(void)
{
return f(8) + 1;
}
汇编后得到AT&T格式的汇编代码
gcc -m32 -S -o test.s test.c
.file "test.c"
.text
.globl g
.type g, @function
g:
.LFB0:
.cfi_startproc
pushl %ebp
.cfi_def_cfa_offset 8
.cfi_offset 5, -8
movl %esp, %ebp
.cfi_def_cfa_register 5
call __x86.get_pc_thunk.ax
addl $_GLOBAL_OFFSET_TABLE_, %eax
movl 8(%ebp), %eax
addl $3, %eax
popl %ebp
.cfi_restore 5
.cfi_def_cfa 4, 4
ret
.cfi_endproc
.LFE0:
.size g, .-g
.globl f
.type f, @function
f:
.LFB1:
.cfi_startproc
pushl %ebp
.cfi_def_cfa_offset 8
.cfi_offset 5, -8
movl %esp, %ebp
.cfi_def_cfa_register 5
call __x86.get_pc_thunk.ax
addl $_GLOBAL_OFFSET_TABLE_, %eax
pushl 8(%ebp)
call g
addl $4, %esp
leave
.cfi_restore 5
.cfi_def_cfa 4, 4
ret
.cfi_endproc
.LFE1:
.size f, .-f
.globl main
.type main, @function
main:
.LFB2:
.cfi_startproc
pushl %ebp
.cfi_def_cfa_offset 8
.cfi_offset 5, -8
movl %esp, %ebp
.cfi_def_cfa_register 5
call __x86.get_pc_thunk.ax
addl $_GLOBAL_OFFSET_TABLE_, %eax
pushl $8
call f
addl $4, %esp
addl $1, %eax
leave
.cfi_restore 5
.cfi_def_cfa 4, 4
ret
.cfi_endproc
.LFE2:
.size main, .-main
.section .text.__x86.get_pc_thunk.ax,"axG",@progbits,__x86.get_pc_thunk.ax,comdat
.globl __x86.get_pc_thunk.ax
.hidden __x86.get_pc_thunk.ax
.type __x86.get_pc_thunk.ax, @function
__x86.get_pc_thunk.ax:
.LFB3:
.cfi_startproc
movl (%esp), %eax
ret
.cfi_endproc
.LFE3:
.ident "GCC: (Ubuntu 7.5.0-3ubuntu1~18.04) 7.5.0"
.section .note.GNU-stack,"",@progbits
我们将以.开头的语句都去掉后,同时将addl $_GLOBAL_OFFSET_TABLE_, %eax
删掉,以及最后的movl (%esp), %eax ret
删掉,这与我们分析程序没有什么关系,直接删掉即可!
grep -v "\." test.s > test1.s
# test1.s
g:
pushl %ebp
movl %esp, %ebp
movl 8(%ebp), %eax
addl $3, %eax
popl %ebp
ret
f:
pushl %ebp
movl %esp, %ebp
pushl 8(%ebp)
call g
addl $4, %esp
leave
ret
main:
pushl %ebp
movl %esp, %ebp
pushl $8
call f
addl $4, %esp
addl $1, %eax
leave
ret
从main入口开始
首先将ebp压栈,此时esp-4后存到ebp中,现在esp和ebp指向同一个位置,立即数8是f的参数,将其压入栈后,执行call指令,首先把call指令的下一条指令addl $4, %esp
的地址压入栈中,之后跳转到f的位置,从pushl %ebp
开始,先将调用者main函数的ebp压入栈中,同时更改了esp的值,将esp的值赋给ebp后开辟了新的堆栈空间,pushl 8(%ebp)
是寄存器基址变址寻址方式,也就是ebp+8
所指向的内存空间,将其数值8压入栈中。这里为什么是加8?因为我们是32位系统,mian函数跳转到f时候,在栈空间存放了main函数的下一条指令的地址,以及ebp的值,两个4字节即8字节就到了我们压入的立即数8的位置。接着执行call g
,同样,将下一条指令的地址压入栈中,跳转到g开始执行,也是将ebp压栈后,更新esp以及ebp的值,同理movl 8(%ebp), %eax
的意思就是将8放到eax寄存器中,addl $3, %eax
就是8+3=11存放到eax寄存器中,popl %ebp
我们将刚才压入的ebp直接弹栈,恢复ebp的值,同时esp-4指向了f函数call指令压入的下一条指令的地址,执行ret
后,将该地址弹到eip中,即实现了跳转,现在回到了addl $4, %esp
,这里将esp的指针移动,因为已经恢复eip的值了,到了leave
这是一个红指令,撤销堆栈的意思。因为在该堆栈空间上压入了8,所以需要撤销这块空间,leave
等价于
movl %ebp, %esp
popl %ebp
这两条指令,现在esp指向了main函数中call指令的下一条指令addl $4, %esp
的地址,执行ret
后跳转到该位置,将esp的值加4,addl $1, %eax
,因为刚才我们的值保存在eax寄存器中了,这里加1即12+1=13,依然保存在eax寄存器中,leave
撤销堆栈,ret
直接跳转到调用main函数的系统位置继续后续的处理了。
画图我总感觉很麻烦,因为是一个动态的过程,esp,esp的指针都在变化,如果你不明白欢迎下方留言!