csapp阅读笔记(3)、程序的机器级表示和x86汇编语言

csapp阅读笔记(3)、程序的机器级表示和x86汇编语言计算机执行的是机器代码。机器代码翻译成可读形式就是汇编,也是逆向工程。 在我们使用gcc的时候实际上是生成了汇编的,只是过程被省略了,在学习的时候我们指定优化等级为 -Og 一些在c语言中被省略的细节

计算机执行的是机器代码。机器代码翻译成可读形式就是汇编,也是逆向工程。 在我们使用gcc的时候实际上是生成了汇编的,只是过程被省略了,在学习的时候我们指定优化等级为 -Og

一些在c语言中被省略的细节在汇编语言中是可见的。

  • 程序计数器: 在x86-64中用 %rip表示,给出将要执行的下一条指令在内存中的位置
  • 整数寄存器包含16个命名的位置,分别存储64位的值。这些寄存器可以存放地址的值或者整数。有的寄存器用来保存一个临时的状态
  • 条件码寄存器保存着最近执行的算术或者逻辑指令的状态信息。用来实现控制流的变化
  • 一组向量寄存器可以存放一个或多个整型或浮点数值 让我们来看书上的关于汇编的一个例子 编写一个mstore.c文件。含有以下内容
 long mult2(long, long);
 void multstore(long x, long y, long *des) {
     long t = mult2(x, y);
     *des = t;                                                                                
 }

image.png

$gcc -Og -S mstore.c       #生成汇编文件
$gcc -Og -c mstore.c       #会生成二进制文件(.o)

image.png 生成一个.o文件和.s文件 查看.s文件中multstore的定义

image.png 里面每一行表示一条机器指令(除了一些伪指令) .o文件里面含有的都是二进制数据 要查看机器代码的内容,我们需要使用反汇编器。在linux里面可以使用objdump命令

$ objdump -d mstore.o
mstore.o:     file format elf64-x86-64


Disassembly of section .text:

0000000000000000 <multstore>:
   0:   f3 0f 1e fa             endbr64
   4:   53                      push   %rbx
   5:   48 89 d3                mov    %rdx,%rbx
   8:   e8 00 00 00 00          callq  d <multstore+0xd>
   d:   48 89 03                mov    %rax,(%rbx)
  10:   5b                      pop    %rbx
  11:   c3                      retq

image.png 生成可执行文件还有一步很重要的就是链接。 假设我们有一个main.c文件,含有以下代码

#include <stdio.h>

void multstore(long, long, long *);

int main() {
    long d;
    multstore(2, 3, &d);
    printf("2 * 3 ---> %ld\n", d);
    return 0;
}

long mult2(long a, long b) {
    long s = a * b;
    return s;
}

我们可以用以下方式生成可执行文件prog

gcc -Og -o prog main.c mstore.c

image.png 然后反汇编器生成反汇编代码

$ objdump -d prog > prog.s
## 里面有这样一段,可以看到几乎和mstore.c反汇编出来是一样的
00000000000011d5 <multstore>:
    11d5:       f3 0f 1e fa             endbr64
    11d9:       53                      push   %rbx
    11da:       48 89 d3                mov    %rdx,%rbx
    11dd:       e8 e7 ff ff ff          callq  11c9 <mult2>
    11e2:       48 89 03                mov    %rax,(%rbx)
    11e5:       5b                      pop    %rbx
    11e6:       c3                      retq
    11e7:       66 0f 1f 84 00 00 00    nopw   0x0(%rax,%rax,1)
    11ee:       00 00

image.png 所有以.开头的行都是指导链接器和汇编器的伪指令。我们通常忽略这些字段。

数据格式

Intel用术语表示16位数据类型。因此,称32位数为双字,称64位数为四字。

image.png 多数gcc生成的汇编代码都有一个字符的后缀表明操作数的大小。例如数据传输有四个变种:movb(传送字节)、movw(传送字)、movl(传送双字)、movq(传送四字)。l表示双字。汇编代码也使用后缀l表示四字节整数和八字节双精度浮点数,因为使用的指令不同,所以不会产生歧义。

16个通用寄存器

image.png

操作数指示符

image.png

数据传送指令

注意,第一个是源操作数,第二个是目的操作数

image.png

将较小的源值复制到较大的目的时有两种扩展方式,零扩展和符号扩展

image.png

image.png 零扩展是把多出来的位都用0填充,符号扩展则是用符号位填充 cltq指令没有操作数,总是以寄存器%eax作为源,%rax作为符号扩展的目的

数据传输示例

写这样一段代码

long exchange(long *xp, long y) {
    long x = *xp;
    *xp = y;
    return x;
}

可以看到下面的汇编代码

0000000000000000 <exchange>:
   0:   f3 0f 1e fa             endbr64
   4:   48 8b 07                mov    (%rdi),%rax
   7:   48 89 37                mov    %rsi,(%rdi)
   a:   c3                      retq

image.png

压入和弹出栈数据

pushq %rbq 等同于

image.png pop %rax 等价于

image.png

算术和逻辑操作

image.png

加载有效地址

加载有效地址指令leaq实际上是movq指令的变形。它的指令形式是从内存读数据到寄存器,实际上并没有引用内存。 让我们看书中的例子

long scale(long x, long y, long z) 
{
    long t = x + 4 * y + 12 * z;
    return t;
}

反汇编后是

image.png

image.png leaq指令能执行加法和有限的乘法

一元和二元操作

第二组的操作是一元操作。第三组是二元操作,第二个操作数既是源又是目的,当第二个操作数为内存地址时处理器必须从内存读出值。

移位操作

最后一组是移位操作,先给出移位量,然后第二项给出的是要移位的数。可以进行算术和逻辑右移。移位量可以是一个立即数,或者放在单字节寄存器%cl中。当%cl中的值为0xFF时,指令salb会移位7位,salw会移15位,sall会移31位,salq会移63位。 左移指令有两种: SAL和SHL。两者的效果是一样的,都是在右边填上0。右移指令则不同,SAR执行算术移位(填上符号位),SHL执行逻辑移位(填上0)。移位操作的目的操作数可以是一个寄存器或是一个内存位置。

特殊的算术操作

image.png imulq是补码乘法,mulq是无符号乘法。这两个操作计算128位乘积,两条指令要求一个参数必须在%rax中,而另外一个作为指令的源操作数给出。然后乘积存放在%rdx(高64位)和%rax(低64位)中,虽然imulq这个名字可以用于两个不同的乘法操作,但是汇编器能够通过计算操作数的数目,分辨出想用哪种指令。来看书中的例子

#include <inttypes.h>
typedef unsigned __int128 uint128_t;
void store_uprod(uint128_t *dest, uint64_t x, uint64_t y) {
    *dest=x* (uint128_t) y;
}

image.png

image.png 我们再来看看如何实现除法

void remdiv(long x, long y, long *qp, long *rp) {
    long q = x / y;
    long r = x % y;
    *qp = q;
    *rp = r;
}

image.png

image.png

控制

机器代码提供两种基本的低级机制来实现有条件的行为:测试数据值,然后根据测试的结果来改变控制流或者数据流

条件码

除了整数寄存器以外。cpu还维护这一组单个位的条件码寄存器。它们描述了最近的算术或逻辑操作的属性。最常用的条件码是

image.png 如果计算t = a + b。那么下面的c表达式来设置条件码

image.png

访问条件码

条件码通常不会直接读取,常用的使用方法有三种: 1)根据条件码的某种组合将一个字设置为0或者1。2)可以条件跳转到程序的某个其他的部分。3)可以有条件的传送数据。对于第一种,我们称为SET指令

image.png 我们来看书上的一个例子

image.png

跳转指令

jmp是无条件跳转,其他的是根据比较结果跳转 image.png

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

让我们来看一个例子

long lt_cnt = 0;
long ge_cnt = 0;
long absdiff_se(long x, long y)
{
    long result;
    if(x < y)
    {
        lt_cnt++;
        result = y - x;
    }
    else
    {
        ge_cnt++;
        result = x - y;
    }
    return result;
}

image.png

image.png

用条件传送来实现条件分支

传统的方法是通过使用控制的条件转移。当条件满足时,程序沿着一条执行路径执行,当条件不足时,就走另外一条路径。但是可能会非常低效。我们可以使用条件传送指令来实现,更加符合现代处理器的性能特性

image.png

image.png

今天的文章csapp阅读笔记(3)、程序的机器级表示和x86汇编语言分享到此就结束了,感谢您的阅读。

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
如需转载请保留出处:https://bianchenghao.cn/16511.html

(0)
编程小号编程小号

相关推荐

发表回复

您的电子邮箱地址不会被公开。 必填项已用*标注