第一次作业 图解汇编代码以及分析计算机是如何工作的

 朱毅   原创作品转载请注明出处 

 《Linux内核分析》MOOC课程http://mooc.study.163.com/course/USTC-1000029000 

C程序代码如下:

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

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

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

在ubuntu中使用如下命令得到汇编文件

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


汇编文件如下:

g:
	pushl	%ebp
	movl	%esp, %ebp
	movl	8(%ebp), %eax
	addl	$2, %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	$3, (%esp)
	call	f
	addl	$1, %eax
	leave
	ret

C程序从main函数开始执行,我们也从汇编代码中的main过程开始分析。先假设整个栈空间是空的,并假设栈从地址0x1000开始向地地址扩展,即esp = 0x1000,此处ebp为何值并不影响整个汇编代码的分析,但为了方便和简单,我们同样假设ebp = 0x1000。

在main过程中,首先是保存ebp的值,然后将esp的值赋值给ebp,此时esp = ebp = 0x0FFC,然后esp -= 4,并且将立即数3存入esp所指的栈空间中(这就是C代码中的参数传递,C中所有的实参和局部变量都是存储在栈空间中的),最后利用call指令调用f过程,call指令会先将eip的值压栈,eip永远指向当前正在执行的指令的下一条指令的首地址,所以此时压入的eip的值应该是addl $1, %eax这条指令的地址,在本例中,我们将该地址成为"main address"来表示是返回到main过程的地址,此时整个栈空间的状态如下图所示


此时的ebp = 0x0FFC,而esp = 0x0FF4,我们需要格外注意下ebp的值,因为我们需要依靠ebp的值来维护栈空间。现在程序已经跳转到f过程中进行执行,f的执行过程与main过程类似。首先仍然是将ebp的值压栈,并将esp的值赋给ebp,然后esp的值再减去4,此时的esp = 0x0FEC,而ebp = 0x0FF0。我们现在需要取得main过程传递过来的参数,也就是立即数3,由于我们是将C代码汇编为32位的汇编代码的,因此栈空间中的每个栈单元所占的内存空间为4个字节(16位汇编代码中是2个字节),因此movl 8(%ebp), %eax就可以取得立即数3,并将3保存在esp所指向的栈空间,这也是f需要向g传递的参数。再call g,此时的栈空间如下图所示



现在进入g过程,同样是ebp压栈,ebp = esp,然后从栈中取得f传递过程的参数,此时,esp = ebp= 0x0FE4。(注意在g中我们没有对esp减4,稍后会解释为什么)。现在eax中已经取得了f传递过来的参数3,然后再执行eax += 2,此时的eax = 5,栈空间如下图所示:


此时,我们g函数的功能已经完成了,我们对照c代码可以发现,现在需要执行的是返回操作,在汇编代码中,返回值默认是存放在eax中的。现在eax中存放的就是我们需要返回的值5。popl%ebp后,ebp = 0xFF0,ret指令会从桟中弹出一个值并用该值修改eip的值,也就是说ret执行之时,从栈中弹出的刚好是f address,也即f过程中call g指令的后一条指令的地址。此时程序又处于f过程中,esp与ebp的值分别为:ebp = 0x0FF0,esp = 0x0FEC。注意此时esp指向的栈空间中存放的是3,就是f传递给g的参数,被调函数执行完之后,为了传递实参而开辟的栈空间需要撤销掉,此处使用的就是leave指令,leave指令完成的功能是esp = ebp,pop ebp,ebp = 0x0FFC, esp = 0x0FF4,此时的栈空间如下


然后就是ret指令,与前面的类型,会从栈中弹出main address,并且跳转到addl$1, %eax处执行,此时eax = 6,最后是leave和ret指令,最终整个栈空间又清空。

也许第一次看到这些汇编代码会感觉头晕,可是其实每个过程的代码都是有一部分相同的,就是每个子过程的开始的几行代码

pushl%ebp
movl %esp, %ebp

将ebp的值压栈自然是因为后面需要用到ebp寄存器,所以先将该寄存器中的值保存在栈中,在这里我们ebp最主要的作用是为了维护帮助主调函数维护因传递参数而开辟的栈空间的。上面已经说了,在C语言中实参与局部变量都是保存在栈中的,现在我们只考虑实参,那么就会有一个问题,为了传递实参而开辟的栈空间最后由谁来回收?此处我们是由主调函数来回收的,也就是谁开辟谁回收。所以我们需要记录下没有传递参数之前的esp的值,此处的方法是movl%esp, %ebp,即ebp用来保存没有传递实参之前的esp的值。然后就是sub指令,将esp减去4来保存实参,接着调用被调函数,在被调函数中也做类似的事情(例如f),g稍有不同,它也保存了esp的值,但因为它不需要调用其他的过程,所以没有减去esp的值,所以最后还是将ebp的值出栈(这也是为什么只有g中没有leave指令)。最后实参所使用的栈空间的回收就是使用leave指令完成的,上面已经解释了,所以不再赘述。到此,整个汇编代码分析完成。


关于计算机是如何工作的,我们首先需要介绍一个概念,就是冯诺依曼体系结构,此处不再解释,请自行百度。其中比较重要的一点是,数据与指令不加区分混合存储在同一个存储器中,cpu会从内存中取出指令或数据来并进行相关的操作。


Cpu就是这样不定的取指令,分析指令,执行指令的。

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值