计算机系统课程 笔记总结 CSAPP第三章 程序的机器级表示(3.5-3.7)

目录

3.5 算术和逻辑操作

3.5.1 加载有效地址 lea

3.5.2 一元和二元操作

一元操作:inc、dec、neg、not

二元操作:add、sub、imul、xor、or、and

3.5.3 移位操作 sal、shl、sar、shr

3.5.3 特殊的算术操作

3.6 控制

3.6.1 条件码

3.6.2 访问条件码

3.6.3 跳转指令

3.6.5 用条件控制来实现条件分支

3.6.6 用条件控制来实现条件分支

3.6.7 循环

3.6.7 switch语句

3.7 过程

3.7.1 运行时栈

3.7.2 转移控制

3.7.3 数据传送

3.7.4 栈上的局部存储

3.7.5 寄存器中的局部存储空间

3.7.6 递归过程


3.5 算术和逻辑操作

指令操作分为4组:

  • 加载有效地址
  • 一元操作
  • 二元操作
  • 移位

每种指令操作各有针对 b/w/l/q 的操作

3.5.1 加载有效地址 lea

加载有效地址(load effective address,LEA)指令

  • 类似于MOV,是MOV指令的变形
  • 内存读数据到寄存器,没有引用内存(存储器)
  • 不是从指定的位置读入数据,而是将有效地址写入到目的操作数
  • 常用用途
    • C语言中‘&’操作,指针,取地址
    • 算术操作:若%rdx的值为x,leaq 7(%rdx,%rdx,4), %rax 将%rax的值设置为5*x+7

3.5.2 一元和二元操作

一元操作:inc、dec、neg、not

  • inc D            1
  • dec D           1
  • neg D          取负
  • not D           取补
  • 只有一个操作数,既是源又是目的,可以是寄存器或存储器

二元操作:add、sub、imul、xor、or、and

  • add S,D       D <-- D+S     
  • sub S,D       D <-- D-S      
  • imul S,D     D <-- D*S     
  • xor S,D       D <-- D^S      异或
  • or S,D         D <-- D|S     
  • and S,D      D <-- D&S     
  • 第一个操作数可以任意,第二个操作数可以是寄存器或存储器位置,且两个操作数不能同时是存储器位置
  • 第二个操作数既是源又是目的,可以联想到C语言的x+=y

3.5.3 移位操作 sal、shl、sar、shr

  • sal k,D      D <-- D<<k        左移
  • shl k,D      D <-- D<<k        左移(等同于sal
  • sar k,D      D <-- D >>A    算术右移
  • shr k,D      D <-- D >>k     逻辑右移
  • 第一个操作数是位移量
  • sal和shl都是左移指令,效果一样,移动后右边补0
  • 右移指令不同
    • 算术右移sar是补上符号位(右边第一位)        '>>'
    • 逻辑右移shr是补上0            '>>>'

例如8位二进制数11001101分别右移一位。

  • 逻辑右移就是[0]1100110
  • 算术右移就是[1]1100110

3.5.3 特殊的算术操作


3.6 控制

  • 条件语句 if
  • 循环语句 for
  • 分支语句 switch

3.6.1 条件码

条件码(condition code)寄存器,描述了最近的算术或逻辑操作的属性,可以检测这些寄存器来执行条件分支指令。

  • CF:进位标志——检查无符号操作的溢出
  • ZF:零标志——最近操作结果为0
  • SF:符号标志——最近操作结果为负数
  • OF:溢出标志——导致一个补码溢出,正溢出或负溢出

其中,leaq操作不会改变条件码,因为leaq是操作地址

  • cmp s1,s2 用于比较s1,s2大小
  • test %rdx, %rdx 判断是0还是正数

3.6.2 访问条件码

条件码通常不会直接读取,常用的使用方法有3种

  1. 可以根据条件码的某种组合,将一个字节设置为0或1
  2. 可以条件跳转到程序的某个其他部分
  3. 可以有条件地传送数据

SET指令:根据条件码的某种组合,将一个字节设置为0或1

e:euqal

z:zero

s:sign 有符号-->负数

n:not

l:lower

e:equal

g:greater

a:above

b:below

e:equal

3.6.3 跳转指令

je: jump if equal

n:not

n:not

l:lower

e:equal

g:greater

a:above

b:below

e:equal

3.6.5 用条件控制来实现条件分支

int absdiff(int x, int y){
        if(x < y){
                return y - x;
        }
        else{
                return x - y;
        }
}

  movl 8(%ebp),%edx        获取x
  movl 12(%ebp),%eax      获取y
  cmpl %eax,%edx             比较x和y
  jge  .L2                             如果>=,则跳转.L2
  subl %edx,%eax              计算y-x
  jmp  .L3                           无条件跳转.L3
.L2:
  subl  %eax,%edx             计算x-y
  movl  %edx,%eax            两种情况下返回值都放在%eax中
.L3:                                    结束

C语言代码将被编译为如下汇编代码:

  • x在%ebp+8中
  • y在%ebp+12中

3.6.6 用条件控制来实现条件分支

  • 实现条件操作的传统方法是利用控制的条件转移(3.6.5),当条件满足时程序沿着一条执行路径进行,而当条件不满足时,就走另一条路径。这种机制比较简单,但在现代处理器上可能会非常低效
  • 条件转移是一种替代策略:先计算一个条件操作的两种结果,然后再根据条件是否满足从而选取一个,只有在一些受限的情况下这种策略才可行,但是如果可行,就可以用一条简单的条件传送指令来实现它。条件传送指令更好地匹配了现代处理器的性能特性。

优化

int absdiff(int x,int y){
        return x < y ? y-x :x-y;
}

  movl 8(%ebp),%ecx        获取x
  movl 12(%ebp),%edx     获取y
  movl %edx,%ebx            复制y
  subl %ecx,%ebx              计算y-x
  movl %ecx,%eax             复制x
  subl %edx,%eax              计算x-y,并设置为返回值
  cmpl %edx,%ecx             比较x,y
  cmovl %ebx,%eax           如果<,则将返回值替换为y-x    !!! 仅需一条指令图(条件传送指令

类似于下面代码

  int tval = y-x;
  int rval = x-y;
  int test = x<y;

// 只需一条指令
  if(test) rval = tval;

return rval;

使用 x < y ? x : y 更为高效

3.6.7 循环

大多数汇编器会根据一个循环的do-while形式来产生循环代码,其他的循环会首先转换成do-while形式,再编译成机器代码。

3.6.7 switch语句



3.7 过程

假设过程P调用过程Q,Q执行后返回到P。这些动作包括下面一个或多个机制:

  1. 传递控制。再进入过程Q的时候,程序计数器必须被设置为Q的代码的起始地址,然后在返回时,要把程序计数器设置为P中调用Q后面那条指令的地址。
  2. 传递数据。P必须能够向Q提供一个或多个参数,Q必须能够向P返回一个值。
  3. 分配或释放内存。在开始时,Q可能需要为局部变量分配空间,而在返回前,又必须释放这些存储空间。

3.7.1 运行时栈

  • 机器用栈来传递参数、存储返回信息、保存寄存器用于以后恢复以及本地存储等。
  • 为单个过程分配的那部分栈称为栈帧(stack frame)。
  • 栈帧以两个指针界定,寄存器%ebp为帧指针,寄存器%esp为栈指针,当程序执行时,栈指针可以移动,因此大多数信息的访问都是相对于帧指针(即帧指针为当前栈帧的固定起点)的。

               [栈底]
+================+
-
                                           -
-
                 ...                      -     [较早的帧]
-               
                           -
+================+
-     
            ...                      -   
-    
          参数n                   -     [调用者的帧]
-    
          参数1                   -
-   
         返回地址                -
+================+
-     
         %ebp                  -
-    
             ...                      -
-   
          临时变量               -     [当前帧]
-               
                           
+================+  <-- %esp
    
            [栈顶]

       假设过程P调用过程Q,则Q的参数放在P的栈帧中,P中的返回地址被压入栈中,形成P的栈帧的末尾。返回地址就是当程序从Q返回时应该继续执行的地方。过程Q也用栈来保存其他不能存放在寄存器中的局部变量,以及Q调用的其他过程的参数。

3.7.2 转移控制

注:

  • 这些指令在程序objdump产生的反汇编输出中被称为 callq 和 retq 。
  • 添加后缀 q 只是为了强调这些事 x86-64 版本的调用和返回,而不是 IA32 的。
  • 在 x86-64 汇编代码中,两种版本可以互换。

call和ret指令

  • call指令会把地址A(称为“返回地址”)压入栈中,并将PC设置为设置为Q的起始地址。
  • 返回地址A是紧跟在call指令后面那条指令的地址。
  • 对应的指令ret会从栈中弹出地址A,并将PC设置为A。

3.7.3 数据传送

  • 参数
    • x86-64中,可以通过寄存器最多传递6个整型(例如整数和指针)参数
    • 会根据参数在参数列表中的顺序为它们分配寄存器。
    • 可以通过64位寄存器适当的部分访问小于64位的参数。
    • 例如,如果第一个参数是32位的,那么可以用%edi来访问它
  • 返回值
    • 当Q返回到P时,P的代码可以访问寄存器%rax中的返回值。

3.7.4 栈上的局部存储

局部数据必须存放在内存中,常见情况包括:

  1. 寄存器不足够存放所有的本地数据
  2. 对于一个局部变量使用地址运算符‘&’,因此必须能够为它产生一个地址
  3. 某些局部变量是数组或结构,因此必须能够为它产生一个地址

 

  • 一般来说,过程通过减小栈指针在栈上分配空间
  • 分配的结果作为栈帧的一部分,标号为“局部变量”。
  • 减小栈指针,分配2×8字节空间
  • arg1arg2存储在相对栈指针的偏移量分别为08的位置上
  • 增加栈指针,释放栈帧

运行时,栈提供了一种

  • 简单的
  • 在需要时分配的
  • 函数完成时释放的

局部存储的机制。

3.7.5 寄存器中的局部存储空间

寄存器组是唯一被所有过程共享的资源。根据惯例:

  • 被调用者保存寄存器
    • %rbx、%rbp和%r12~%r15
    • 当过程P调用过程Q时,Q必须保存这些寄存器的值,并保证它们的值在Q返回到P时与Q被调用时是一样的
    • 使用:
      • 不去改变寄存器的值
      • 原始值压入栈中,改变寄存器的值,然后在返回之前从栈中弹出旧值
  • 调用者保存寄存器
    • 除了%rsp的其它寄存器
    • 任何函数都能修改
    • 如何理解“调用者保存”:
      • 过程P在某个调用者保存寄存器中有局部数据,然后调用过程Q;因为Q可以随意修改这个寄存器,所以在调用之前首先保存好这个数据是P(调用者)的责任

3.7.6 递归过程

%rax保存递归返回值

同时用做当前函数返回值

发布了36 篇原创文章 · 获赞 11 · 访问量 3516

猜你喜欢

转载自blog.csdn.net/gzn00417/article/details/104236154
今日推荐