CSAPP阅读笔记-程序的机器级表示--过程

程序的机器级表示--过程

过程的概念

定义:用一组指定的参数和一个可选的返回值实现了某种功能。

原则:作为抽象机制:

  1. 隐藏某个行为的具体实现
  2. 同时提供清晰简介的调用接口定义

形式:函数、方法、子例程、处理函数等

特性:

  1. 传递控制
  2. 传递数据
  3. 分配和释放内存(栈结构的后进先出内存管理原则)。

传递控制

运行时栈

过程调用:

P过程调用Q过程的时候,P过程将暂时被挂起。

Q运行的时候,需要为局部变量分配空间,或者设置到另一个过程的调用。

Q返回的时候,分配的所有局部空间都被释放。

栈数据:

栈和程序寄存器存放着传递控制和数据、分配内存所需要的信息。

x86栈向低地址的方向增长,栈指针%rsp指向栈顶元素。

pushq和popq指令分别对应数据存入栈与数据从栈中取出。

通过栈指针减少一定量来在栈上分配空间,增加值来回收空间。、

栈帧:

寄存器存不下数据的时候会将数据存在栈上,这部分叫做过程的栈帧。

正在执行的过程的帧总在栈顶。

过程P调用Q的时候会将返回地址压入栈中,返回地址是P栈帧的一部分。

栈帧可以用来存放寄存器的值,分配局部变量空间,为调用的过程设置参数。

栈帧大多数是定长的,在过程开始的时候就已经确定,也有部分变长的。

寄存器最多传递6个整数值(指针与整数),多出的时候放在自己的栈帧中。

为了提高空间与效率,x86只分配自己所需要的栈帧。

参数小于6个时候就只需通过寄存器传递即可,所以实际上许多函数并不需要栈帧(不调用其他函数且参数小于6个)。


通用的栈帧结构

转移控制

P转移到Q只要将程序计数器(PC)设置为Q的代码的起始位置。从Q放回的时候,只要从记录好的P继续执行的代码位置继续执行。x86通过指令call Q来记录,指令会将地址A压入栈中,并将PC设置为Q的起始地址。被压入的地址称为返回地址,是紧跟call指令后面的指令地址。对应指令ret会从栈中弹出地址A,并把PC设置为A。

调用可以是直接的,也可以是间接的。直接调用是一个符号,间接调用是*后面跟一个操作数。

举个例子:

Beginning of function  multstore
400540

400540:53               push %rbx
400541:48 89 d4         mov  %rdx,%rbx
...
Return from funtion multstore
40054d:c3               retq
Call to multstore from main
400563:e8 d8 ff ff ff   callq 400540
400568:48 8b 54 24 08   mov   0x8(%rsp),%rdx

函数调用期间的栈与PC变化如下


函数调用期间的栈变化

传递数据

调用过程的时候除了传递参数,也会传递数据。返回的时候也可能带一个返回值。

P调用Q的时候必须首先把参数复制到适当的寄存器中。Q返回P时,P可以访问寄存器%rax中的返回值。

寄存器最多存6个参数,且有特殊顺序,寄存器的名字取决与数据类型大小,例如第一个参数是32位的时候,可以用%edi来访问。

传参超过6个时,栈要分配容纳参数7n的空间,超过的部分用栈传递,参数16复制到寄存器上,参数7~n放栈上。

所有数据大小都向8对齐。

参数到位后,用call调用Q过程,Q通过寄存器访问参数,必要是通过栈访问参数。

举个例子:

void proc(long a1, long *a1p, int a2, int *a2p,short a3,short *a3p, char a4, char *a4p){
    *a1p += a1;
    *a2p += a2;
    *a3p += a3;
    *a4p += a4;
}


//汇编如下
void proc(a1, a1p, a2, a2p, a3, a3p, a4, a4p)
//参数传递如下
    a1  in %rdi     (64 bit)
    a1p in %rsi     (64 bit)
    a2  in %edx     (32 bit)
    a2p in %rcx     (64 bit)
    a3  in %r8w     (16 bit)
    a3P in %r9      (64 bit)
    a4  in %rsp+8   ( 8 bit)
    a4p in %rsp+16  (64 bit)
proc:
    movq    16(%rsp), %rax      Fetch a4p   (64bit)
    addq    %rdi, (%rsi)        *a1p += a1  (64bit)
    addl    %edx, (%rcx)        *a2p += a2  (32bit)
    addw    %r8w, (%r9)         *a3p += a3  (16bit)
    movl    8(%rsp), %edx       Fetch a4    ( 8bit)
    addb    %rdl, (%rax)        *a4p += a4  ( 8bit)
    ret

留意数据大小与对应的指令!

分配和释放内存

栈上的局部存储

局部数据有些时候需要存放在内存中,常见情况如:

  • 寄存器房放不下所有的本地数据。
  • 对变量使用&运算符。
  • 某些局部变量是数组或者结构。

举个例子:

long swap_add(long *xp, long *yp){
    long x = *xp;
    long y = *yp;
    *xp = y;
    *yp = x;
    return x + y;
}

long caller(){
    long arg1 = 534;
    long arg2 = 1057;
    long sum = swap_add(&arg1, &arg2);
    long diff = arg1 - arg2;
    return sum * diff;
}

//汇编代码如下:
long caller()
caller:
    subq    $16, %rsp       Allocate 16 bytes for stack frame
    movq    $534, (%rsp)    Store 534 in arg1
    movq    $1057, 8(%rsp)  Store 1057 in arg1
    leaq    8(%rsp), %rsi   Compute &arg2 as second argument
    movq    %rsp, %rdi      Compute &arg1 as second argument
    call    swap_add        Call swap_add(&arg1, &arg2)
    movq    (%rsp), %rdx    Get arg1
    subq    8(%rsp), %rdx   Compute diff = arg1 - arg2
    imulq   %rdx, %rax      Compute sum * diff 
    addq    $16, %rsp       Deallocate stack frame
    ret

寄存器中的局部存储

寄存器是唯一被所有过程共享的资源。我们必须确保被调用者不会覆盖调用要使用的寄存器值!

要保证寄存器值不变:

  1. 根本不去改变它;
  2. 把原始数据压入栈中,改变寄存器的值,返回前再从栈中弹出旧值。被压入寄存器的值会被标记为“被保存的寄存器”的一部分。

所有寄存器,除了栈寄存器%rsp,都被分成调用者保存寄存器,即任何过程都能修改他们,所以保存好数据是调用者的责任(P过程)。

递归调用一个函数本身与调用其他函数是相同的,每次函数都有他自己的私有的状态信息(1.保存的返回位置;2.被调用者保存寄存器的值;3.局部变量),栈分配和释放的规则很自然的与函数调用的上述特性顺序匹配。

举个例子:

long P(long x, long y){
    long u = Q(y);
    long v = Q(x);
    return u + v;
}

long P(long x, long y)
x in %rdi, y in %rsi
P:
    pushq   %rbp            Save &rbp
    pushq   %rbx            Save &rbx
    subq    $8,%rsp         Align stack frame
    movq    %rdi, %rbp      Save x
    movq    %rsi, %rdi      Move y to first argument
    call    Q               Call Q(y)
    movq    %rax, %rbx      Save result
    movq    %rbp, %rdi      Move x to first argument
    call    Q               Call Q(x)
    addq    %rbx, %rax      add Q(y) to Q(x)
    addq    $8, %rsp        Deallocate last part of stack
    popq    %rbx            Rstore &rbx
    popq    %rbp            Rstore &rbp
    ret

你可能感兴趣的:(CSAPP阅读笔记-程序的机器级表示--过程)