计算机是如何工作的?--通过汇编一个简单的C程序,分析汇编代码理解计算机是如何工作的

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

一、理论基础
(1)冯诺依曼体系结构:存储程序计算机的工作模型
这里写图片描述
从硬件角度看程序的执行过程:其中IP(instruction pointer)为CPU中的寄存器,指向内存中的某一块,CPU执行指令时,会从IP中取出一条指令后执行,执行过后,IP自加一(增加一个指令的长度),取下一条指令执行。
从程序员的角度看: CPU是一个大的for循环,不停地执行next instruction命令,存储器主要负责数据和代码等信息的存储,CPU与Main Memory通过总线进行连接。
注意:
1.程序员不可以直接修改IP的值,只能通过一些指令,如CALL、RET、JMP等间接修改IP的值
2.IP在32位机器中表示为eip(本次实验以32位作为分析),在64位机器中表示为rip。
(2)x86汇编基础知识(32位机器)
一.寄存器:在x86体系中,寄存器可分为通用寄存器、段寄存器、标志寄存器
1通用寄存器:
EAX:累加器
EBX:基地址寄存器
ECX:计数寄存器
EDX:数据寄存器
EBP:堆栈基指针
ESI,EDI:变址寄存器
ESP:堆栈顶指针
2.段寄存器:CS,DS,ES,SS,FS,GS
3.标志寄存器
CPU在实际取指令时根据CS:eip来准确确定某一个指令在内存中的地址。
二:汇编指令(以常见的MOV,PUSH,POP,CALL等进行说明)
(1)MOV指令及寻址方式
1.MOV指令,能实现以下操作:
① CPU内部寄存器之间数据的任意传送(除了码段寄存器CS和指令指针IP以外)。
② 立即数传送至CPU内部的通用寄存器组(即AX、BX、CX、DX、BP、SP、SI、DI),给这些寄存器赋初值。
③ CPU内部寄存器(除了CS和IP以外)与存储器(所有寻址方式)之间的数据传送,可以实现一个字节或一个字的传送。
④ 能实现用立即数给存储单元赋初值。
movb,movl,movw,movq分别针对8位,16位,32位,64位系统
(2)pushl和pop指令,pushl用于入栈(栈的扩张),pop用于出栈(栈的收缩)
栈是向下生长的,也就是说栈底位于高地址,栈顶位于低地址,用ebp指向栈底,用esp指向栈顶。
入栈操作:pushl %eax完成的操作包括两步

subl $4,%esp
movl %eax,(%esp)

出栈操作:pop %eax完成的操作包括两步

movl (%esp),%eax
addl $4,%esp

(3)CALL主要用于函数调用
call 0x12345 完成的操作包括两步

push %eip
movl %0x12345,%eip

(4)RET指令用于恢复操作,完成pop %eip操作
(5)enter和leave操作
enter 置为空栈,完成的操作包括两步

pushl %ebp
movl %esp,%ebp

leave用于撤销函数调用堆栈,完成的操作包括两步

movl %ebp,%esp
popl %ebp

注:
(1)函数调用堆栈是由逻辑上多个堆栈叠加起来的(比如函数的嵌套)
(2)函数的返回值默认使用eax寄存器存储返回给上一级函数
(3)一定要注意的是eip不能由程序员直接修改,程序员想修改eip只能通过特殊指令间接修改。
2.寻址方式
(1)寄存器寻址(不访问内存)
movl % eax,% edx <==>edx= eax,将eax中的值赋值给edx
(2)立即寻址(不访问内存)用$表示立即数
movl $ 0x1234,%edx <==>edx = 0x1234
(3)直接寻址
movl 0x123,%edx <==>edx = * (int32_t*)0x123
将内存地址0x123所指向的内存数据赋值给edx
(4)间接寻址
movl (%ebx),%edx <==>edx = * (int32_t*)ebx
(5)变址寻址
movl 4(%ebx),%edx <==>edx = * (int32_t*)(ebx+4)
二、通过实验分析计算机是如何工作的
(1)实验代码与截图
在实验楼的linux终端下创建一个main.c文件(注意实验楼的环境为64位)
main.c中的内容
利用指令(64位下生成32位的汇编文件)

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

这里写图片描述
进入main.s汇编文件,去掉所有以.开始的代码行(以.开始的代码是连接时的辅助信息)得到纯汇编代码
这里写图片描述
可以看到刚才介绍过的指令操作。
(2)实验分析——-对执行过程的分析
与C语言类似,汇编代码的入口也为main函数
初始时栈的状态
这里写图片描述
进入main函数,执行18行代码

pushl %ebp

执行后栈的状态
这里写图片描述
0标号下下存放ebp的内容,esp指向标号1处

movl %esp,%ebp

这里写图片描述
esp和ebp指向相同位置

subl $4,%esp

这里写图片描述

movl $8,(%esp)

这里写图片描述

call f

执行call时实际执行两个动作,pushl %eip ; movl f ,%eip
此时eip指向第23行代码
这里写图片描述
执行后,eip指向f
跳转到f中执行:

pushl %ebp
movl %esp,%ebp
subl $4,%esp

这里写图片描述
在执行过movl%esp,%ebp后,esp和ebp置于相同的标号处(4)
执行subl $4,%esp,esp向下移动到标号5
这里写图片描述

movl 8(%ebp),%eax
movl %eax,(%esp)

movl 8(%ebp),%eax 变址寻址,将ebp(此处为标号4)加8(向上移动两个标号,也就是标号2处)的值赋给eax,所以%eax = 8
movl %eax,(%esp),将eax的内容也就是8赋值到esp下也就是标号5处
这里写图片描述

call g
leave

执行call时实际执行两个动作,pushl %eip ; movl g,%eip
此时eip指向第15行代码leave的位置,此时eip指向g跳转到g中去执行
这里写图片描述
在g中执行:

pushl %ebp
movl %esp,%ebp

pushl %ebp同上,将esp向下移动,将ebp(标号为4)压栈
这里写图片描述
movl %esp,%ebp
esp和ebp指向相同的位置
这里写图片描述

movl 8(%ebp),%eax

强ebp向上移动两个标号的值(也就是8)赋给eax

addl $57,%eax
popl %ebp
ret

%eax中存放的值为8,addl操作,将eax的值与立即数57相加,结果为65,将65存回到eax
popl %ebp,将ebp的值放回到ebp,执行效果:ebp重新指向标号为4的位置,同时esp减4
这里写图片描述
ret执行popl %eip,也就是说esp向上移动指向5的标号的位置,同时eip指向15行指令的位置(call的下一条指令)
这里写图片描述
回到f中执行

leave
ret

leave执行两条指令,
movl %ebp,%esp
popl %ebp
首先,将esp指向ebp相同的位置(也就是标号4的位置),popl %ebp,将ebp出栈,此时ebp指回标号1的位置,由于popl,esp向上移动
这里写图片描述

这里写图片描述
ret执行popl %eip
由于popl,esp向上增加一个,指向标号2,eip指向第23行代码处
这里写图片描述
eip执行第23行代码,回到main处,执行

addl $1%eax
leave
ret

eax此时的值为65,执行addl,65+1 = 66,将66存回到eax。
函数返回值默认使用eax来存储
执行leave,分为两步,
movl %ebp,%esp,将esp指向ebp的位置,popl %ebp,将ebp出栈(ebp指向0的位置),popl指令时esp向上移动,也就是说esp,ebp均指向标号0的位置,栈回到main函数最初的状态。
ret,return的是main函数之前的堆栈,此处由操作系统管理。
这里写图片描述
栈向下生长,向上还原,增增减减,将程序变为指令流,从CPU上流过。
此时,小程序执行完成
三、实验总结–对计算机如何工作的理解
1.计算机的基本原理是存储程序和程序控制,预先要把指挥计算机如何进行操作的指令序列(称为程序)和原始数据通过输入设备输送到计算机内存贮器中。每一条指令中明确规定了计算机从哪个地址取数,进行什么操作,然后送到什么地址去等步骤。
计算机在运行时,先从内存中取出第一条指令,通过控制器的译码,按指令的要求,从存储器中取出数据进行指定的运算和逻辑操作等加工,然后再按地址把结果送到内存中去。接下来,再取出第二条指令,在控制器的指挥下完成规定操作。依此进行下去。直至遇到停止指令。简单来说就是CPU负责处理和运算,存储器负责保存指令和数据。通过操作系统得调度和安排,不停地进行取址、译码、执行的循环。
2.汇编代码是什么?
计算机语言的发展过程从机器语言(计算机能直接识别的二进制0和1的组合)->汇编语言(为了减轻使用机器语言编程的痛苦,人们进行了一种有益的改进:用一些简洁的英文字母、符号串来替代一个特定的指令的二进制串,依赖于硬件)->高级语言(接近于数学语言或人的自然语言,同时又不依赖于计算机硬件,编出的程序能在所有机器上通用)。
我们编写了一个小程序,比如上面实验中写到的main.c文件,编译器执行的过程,这里写图片描述
可执行的二进制文件是计算机“认识”的文件,可以直接执行。
3.以上便是我对这次实验的总结,计算机很“单纯”,它可以执行很多复杂的指令,但它也是被“告诉”要执行什么,才会去执行什么,通过对汇编语言的分析可以方便我们理解计算机处理的过程,了解计算机如何工作等等,这也会成为我今后学习的重点。感谢为我们辛苦准备课程的老师!

展开阅读全文

没有更多推荐了,返回首页