通过反汇编程序观察计算机执行过程

在线学习了Mooc的《计算机内核分析》课程,为了探究计算机运行过程,现做博文记录实验过程。


首先打开虚拟机中的linux环境,输入C语言代码:

int g(int x)
{
  return x + 3;
}

int f(int x)
{
  return g(x);
}

int main(void)
{
  return f(8) + 1;
}


保存为main.c文件


使用反汇编命令

gcc -S -o main.s main,c -m32

-S : 编译为汇编语言程序

-o : 指定目标程序名称

-m32 : 指定编译为32位环境的汇编代码


编译结果:

	.file	"main.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
	movl	8(%ebp), %eax
	addl	$3, %eax
	popl	%ebp
	.cfi_def_cfa 4, 4
	.cfi_restore 5
	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
	subl	$4, %esp
	movl	8(%ebp), %eax
	movl	%eax, (%esp)
	call	g
	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
	subl	$4, %esp
	movl	$8, (%esp)
	call	f
	addl	$1, %eax
	leave
	.cfi_restore 5
	.cfi_def_cfa 4, 4
	ret
	.cfi_endproc
.LFE2:
	.size	main, .-main
	.ident	"GCC: (GNU) 4.6.2 20111027 (Red Hat 4.6.2-1)"
	.section	.note.GNU-stack,"",@progbits

除去代码中的标志符号后:

g:
	pushl	%ebp
	movl	%esp, %ebp
	movl	8(%ebp), %eax
	addl	$3, %eax
	popl	%ebp
	ret
f:
	pushl	%ebp
	movl	%esp, %ebp
	subl	$4, %esp
	movl	8(%ebp), %eax
	movl	%eax, (%esp)
	call	g
	leave
	ret
main:
	pushl	%ebp
	movl	%esp, %ebp
	subl	$4, %esp
	movl	$8, (%esp)
	call	f
	addl	$1, %eax
	leave
	ret

下面对此段汇编代码进行分析,首先要熟悉一些基本的汇编知识:

为了计算方便,加快计算机运行速度,计算机中配置了很多寄存器,这些寄存器在计算机工作中起到各种不同的重要作用:

eax默认作为函数返回值保存寄存器

ebp为栈低指针寄存器

esp为栈顶指针寄存器(运行中的每个程序都有一段堆栈空间,用来存放程序运行时数据)

eip为当前指令位置寄存器

push为压栈指令

pop为出栈指令

mov为转移指令,通过该指令有七种寻址方式:
mov eax,0x1234 //立即寻址
mov eax,ebx //寄存器寻址
mov eax,[ebx] //寄存器间接寻址
mov eax,[0x1234] //直接寻址
mov eax,[ebx+0x1234] //寄存器相对寻址
mov eax,[esi+edi] //基址变址寻址
mov eax,[esi+edi+0x1234] //基址变址相对寻址

add为加法指令

此外还有一些宏指令,它们对应一行或多行汇编代码:

ret : pop %eip(实则返回指令)

leave : mov %ebp %esp

pop %ebp

enter :  push %ebp

mov %esp %ebp

call 0x12345 : push %eip

mov $0x12345 , %eip

在指令后加上b、l、w、q分别代表8位、16位、32位、64位操作


下面直接通过分析代码来熟悉这些指令:

首先程序从main函数开始执行,

pushl	%ebp

将基址寄存器的值进栈,用于系统控制main函数返回,此时堆栈情况如图:


movl	%esp, %ebp

此行代码将esp寄存器的内容赋值给ebp,即ebp和esp都指向地址1的位置

subl	$4, %esp

将esp的指针指向下面一个位置

movl	$8, (%esp)

将立即数8存放在当前esp所指位置,堆栈的情况如图:


call	f

调用f函数,即执行下面的指令

push %eip //将当前的eip入栈,即将下一条指令[addl $1, %eax]的地址入栈

mov f, %eip //函数f的入口地址赋值到eip中,即程序跳转到f函数入口处

pushl	%ebp

f函数的第一条指令,同main函数,如图:


movl	%esp, %ebp

初始化f函数的堆栈,如图:


subl	$4, %esp<span style="white-space:pre">		</span>//栈顶下移

movl	8(%ebp), %eax<span style="white-space:pre">		</span>//栈低加8对应地址的数据存入eax中
movl	%eax, (%esp)<span style="white-space:pre">			</span>//eax中数据存入esp所指空间
如图:


call	g

调用g函数

pushl	%ebp
movl	%esp, %ebp
movl	8(%ebp), %eax
addl	$3, %eax

如图:


popl	%ebp

出栈,如图:


ret<span style="white-space:pre">		</span>//pop %eip

eip指向f函数中leave指令的地址,开始继续执行


leave

mov %ebp %esp

pop %ebp


ret

返回main函数


addl	$1, %eax<span style="white-space:pre">		</span>//eax = 12
leave<span style="white-space:pre">			</span>//
ret

如图:



程序执行完毕,main函数返回交给系统处理。


计算机执行就是顺序执行的过程,借助着寄存器和堆栈实现程序的跳转执行。

Allen 原创作品转载请注明出处 《Linux内核分析》MOOC课程http://mooc.study.163.com/course/USTC-1000029000

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值