从C程序分析计算机执行指令的过程

来源:互联网 发布:携程首席数据官 编辑:程序博客网 时间:2024/06/05 09:07

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

本文章是参加网易云课堂MOOC课程的第一次作业


冯·诺依曼结构

    也叫存储程序计算机工作模型。将指令存储在内存中,CPU从内存中取出指令执行。在X86 CPU有寄存器IP(Instruction Pointer), CS(Code Segment),这两个寄存器来确定CPU取值地址:
下一条要执行的命令的物理地址 = CS * 16 + IP
当CPU从内存取一条指令时,IP会自动增加该指令的长度个字节,使得CS:IP永远指向下一条要执行的指令。在汇编语言中CALL, RET, JMP,条件JMP可以改变CS与IP的值。


ABI

      应用程序二进制接口(application binary interface)。包括以下:
  • 数据类型的大小、布局和对齐;
  • 调用约定(控制着函数的参数如何传送以及如何接受返回值),例如,是所有的参数都通过栈传递,还是部分参数通过寄存器传递;哪个寄存器用于哪个函数参数;通过栈传递的第一个函数参数是最先push到栈上还是最后;
  • 系统调用的编码和一个应用如何向操作系统进行系统调用;
  • 以及在一个完整的操作系统ABI中,目标文件的二进制格式、程序库等等

通用寄存器:



段寄存器:

  • CS——代码段寄存器(Code Segment Register),其值为代码段的段值;
  • DS——数据段寄存器(Data Segment Register),其值为数据段的段值;
  • ES——附加段寄存器(Extra Segment Register),其值为附加数据段的段值;
  • SS——堆栈段寄存器(Stack Segment Register),其值为堆栈段的段值;
  • FS——附加段寄存器(Extra Segment Register),其值为附加数据段的段值;
  • GS——附加段寄存器(Extra Segment Register),其值为附加数据段的段值。

部分基本汇编指令

movl %eax, %edx#将eax的值赋给edx,寄存器寻址movl $0x123, %edx#将值0x123赋给edx,立即数寻址movl 0x123, %edx#将内存地址为0x123中的值赋给edx,直接寻址movl (%ebx), %edx#将内存地址为ebx的值,该内存地址所存储的数据赋给edx。间接寻址movl 4(%ebx), %edx#将内存地址为ebx的值 + 4,该内存地址所存储的数据赋给edx。变址寻址

pushl %eax  
入栈指令,相当于执行

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


popl %eax

出栈指令,相当于:
movl (%esp), %eaxaddl $4, %esp

call 0x12345
跳转指令,现将eip的值压栈,在更改eip的值。相当于
pushl (%eip)                        #该命令不存在movl $0x12345, %eip          #该命令不存在

ret
返回指令
popl %eip         #该命令不存在

enter
相当于
pushl %ebpmovl %esp,%ebp

leave
相当于  
movl %ebp,%esppopl %ebp 

b, w, l, q分别代表8位, 16位, 32位, 64位
linux下gcc采用的是AT&T的汇编格式,而windows下都是采用Intel汇编格式,他们之间的差别主要如下:
  • 寄存器命名: AT&T中寄存器名字前带%,如%eax,而intel不带%,如eax
  • 源,目的操作数顺序 : AT&T中源在前,目的在后,如  mov src,dst  ; intel则相反
  • 操作数长度 : AT&T中每个操作都有一个长度后缀,如movb (字节),movl(双字),movw(字),而intel则没有这种后缀
  • 立即数格式: AT&T中立即数前有$,如$0x01;intel则没有$,如0x01
  • 间接寻址的语法 : INTEL 中基地址使用“[” 、“]” ,而在 AT&T 中使用“(”、“)”

编译一个C程序,分析其汇编代码执行过程

    实验环境为WMWare + Ubuntu2010
main.c内容如下:
int g(int x){  return x + 3;} int f(int x){  return g(x);} int main(void){  return f(9) + 1;}

使用命令
gcc –S –o main.s main.c -m32
得到main.s内容精简如下:
g:pushl%ebpmovl%esp, %ebpmovl8(%ebp), %eaxaddl$3, %eaxpopl%ebpretf:pushl%ebpmovl%esp, %ebpsubl$4, %espmovl8(%ebp), %eaxmovl%eax, (%esp)callgleaveretmain:pushl%ebpmovl%esp, %ebpsubl$4, %espmovl$9, (%esp)callfaddl$1, %eaxleaveret
程序从main函数的第一句开始执行,也就是第20行的代码开始。为了方便分析,将上面汇编代码命令的行号当做命令的段内地址赋给EIP,假定程序开始的ebp的值。命令的执行过程中内存的变化如下图,图中指令表示正在执行的指令,内存和部分寄存器的值表示命令完成后的状态。







实验部分截图如下:



总结:

    现在计算机大多采用冯·诺依曼结构,程序员的工作就是提前编号程序存储起来。CPU有通过CS:IP确定要执行的命令的位置。ABI则规定CPU中寄存器的作用。程序的执行过程就是将程序加载到内存,设置好各个寄存器的初始值,然后一步步执行指令。






0 0
原创粉丝点击