C语言逆向分析

下面有这样一个简单的c 程序

//file 1.c
#include <stdio.h>
int add(int a,int b){
return a+b;
}
int main(){
int result = add(1,2);
printf("%d\n",result);
return 0;
}

我们使用 gcc -S 1.c ,可以将 上面这段代码转换为同目录下的 1.s 汇编文件,其内容如下:

	.file	"1.c"
	.text
	.globl	add
	.type	add, @function
add:
.LFB0:
	.cfi_startproc
	pushq	%rbp
	.cfi_def_cfa_offset 16
	.cfi_offset 6, -16
	movq	%rsp, %rbp
	.cfi_def_cfa_register 6
	movl	%edi, -4(%rbp)
	movl	%esi, -8(%rbp)
	movl	-4(%rbp), %edx
	movl	-8(%rbp), %eax
	addl	%edx, %eax
	popq	%rbp
	.cfi_def_cfa 7, 8
	ret
	.cfi_endproc
.LFE0:
	.size	add, .-add
	.section	.rodata
.LC0:
	.string	"%d\n"
	.text
	.globl	main
	.type	main, @function
main:
.LFB1:
	.cfi_startproc  
	pushq	%rbp
	.cfi_def_cfa_offset 16
	.cfi_offset 6, -16
	movq	%rsp, %rbp
	.cfi_def_cfa_register 6
	subq	$16, %rsp
	movl	$2, %esi
	movl	$1, %edi
	call	add
	movl	%eax, -4(%rbp)
	movl	-4(%rbp), %eax
	movl	%eax, %esi
	leaq	.LC0(%rip), %rdi
	movl	$0, %eax
	call	printf@PLT
	movl	$0, %eax
	leave
	.cfi_def_cfa 7, 8
	ret
	.cfi_endproc
.LFE1:
	.size	main, .-main
	.ident	"GCC: (Ubuntu 7.5.0-3ubuntu1~18.04) 7.5.0"
	.section	.note.GNU-stack,"",@progbits

我们将编译器生成的一些注释信息给去掉,不影响程序功能。并且注释如下:

add:
    # 当我们调用 call 指令的时候,会自动将 main 函数里面 调用 add 的那一行的下一行代码压入栈
	# 现在rbp 存的是调用者 main 函数的栈帧的栈底地址,下面这句将 rbp 的全部64位入栈
	pushq	%rbp 
	# 现在 rsp 存的是main 函数栈顶地址,下面这一句将 rsp指针作为当前栈帧的栈底指针(上一个栈帧的栈顶作为当前栈帧的栈底,栈帧直接是紧挨着的)
	movq	%rsp, %rbp
	# 在 main 函数中,已经将入参分别放到了 edi,esi 寄存器中,所以下面这两句是将第一个参数和第二个参数入栈,-4 和-8表示的都是地址偏移多少字节
	movl	%edi, -4(%rbp)
	movl	%esi, -8(%rbp)
	# 这两句又将栈里面的两个参数复制到 edx,eax 寄存器
	movl	-4(%rbp), %edx
	movl	-8(%rbp), %eax
	# 在这里进行加法操作,将 edx 的值加到 eax 里面去,因此这里 eax 存放的就是最终结果
	addl	%edx, %eax
	# 下面这句将原先存入到栈里面的,main 函数的栈底指针恢复
	popq	%rbp
	ret
LC0:
	.string	"%d\n"
	.globl	main
main:
    # 保存上一个栈帧的栈底地址
	pushq	%rbp
	# 将上一个栈帧的栈顶地址作为本栈帧的栈底地址
	movq	%rsp, %rbp
	# 将 本栈帧的栈顶地址往栈容量增大方向挪动16个字节
	subq	$16, %rsp
	# 将 add 函数的入参分别存到 esi、edi 寄存器
	movl	$2, %esi
	movl	$1, %edi
	# 跳转到子函数
	call	add
	# 子函数返回之后,eax 保存的是返回值,下面这句将返回值存到本栈帧底部的第一个位置
	movl	%eax, -4(%rbp)
	# 又从该位置保存会 eax 寄存器,这是为啥?这不是多此一举吗?
	movl	-4(%rbp), %eax
	# 又把 eax 的值复制到 esi
	movl	%eax, %esi
	# 下面这句 将 LC0地址+rip地址,结果放到 rdi 中,成为后面printf@PLT系统调用的参数
	leaq	LC0(%rip), %rdi
	# 这句可省略
	movl	$0, %eax
	call	printf@PLT
	# 这句也可以省略
	movl	$0, %eax
	# 下面这句相当于 movq %rbp %rsp;popq $rbp,即回复调用 main 函数前的栈底和栈顶指针
	leave
	# 返回
	ret

可以看出,c 编译 出的汇编还是有一定的冗余的,上面的代码至少还可以注释掉6句而保持功能不变,如下:

add:
	pushq	%rbp
	movq	%rsp, %rbp
	# movl	%edi, -4(%rbp)
	# movl	%esi, -8(%rbp)
	# movl	-4(%rbp), %edx
	# movl	-8(%rbp), %eax
	addl	%edx, %eax
	popq	%rbp
	ret
	
LC0:
	.string	"%d\n"
	.globl	main
main:
	pushq	%rbp

	movq	%rsp, %rbp

	subq	$16, %rsp
	movl	$2, %eax
	movl	$1, %edx
	call	add
	movl	%eax, -4(%rbp)
	# movl	-4(%rbp), %eax
	movl	%eax, %esi
	leaq	LC0(%rip), %rdi
	# movl	$0, %eax
	call	printf@PLT
	# movl	$0, %eax
	leave
	ret
	
上面的代码直接 用eax、edx 保存了入参,而不使用 esi,edi 和栈进行中转,从而节省了6条机器指令时间
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

cc_coders

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值