反汇编分析之函数调用

反汇编分析一个简单的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的指针都在变化,如果你不明白欢迎下方留言!

发布了212 篇原创文章 · 获赞 7 · 访问量 1万+

猜你喜欢

转载自blog.csdn.net/weixin_43833642/article/details/104599189
今日推荐