Featured image of post 程序的机器级表示

程序的机器级表示

继续之前的CSAPP读书笔记,这是第3章的读书笔记。

第3章介绍计算机的底层机器编码,包括常见的基本数据、为了执行机器指令而设置的寄存器、算术和逻辑操作以及控制的指令。3.7节详细介绍了过程的概念,最后是其他数据结构在计算机机器代码的表示。

实验工具

要想研究底层机器指令,最直观的就是汇编程序,参考书本里面的介绍,以linux系统的gcc程序为工具,有两种方式可以得到。如下图所示,是一个 hello.c的程序从高级语言程序编译成机器代码的过程。

  • 使用编译器运行如下命令

    1
    
    gcc -Og -S hello.c
    

    可以得到 hello.s的汇编文件,该文件仅仅包括汇编指令,没有二进制的机器码,如下图所示

    不过对于理解本章节的内容已经足够

  • 使用汇编器进一步可以将 hello.s翻译成机器语言指令,称为可重定位目标程序,里面全是二进制码,这个就是底层机器指令,肉眼看过去就是一串二进制码,根本不知道是什么东西。可以基于反汇编的方法将该文件反汇编成汇编的代码,

    1
    2
    
    gcc -Og -c hello.c #生成hello.o文件
    objdump -d hello.o #反汇编文件
    

    得到的文件,如下所示

    与上面的方法相比,除了汇编指令之外,还有这些指令的二进制编码。

基础概念

数据格式和指令

这些是本章的主要内容,也是理解汇编代码的基础。具体的技术细节书本里面已经说清楚了,此处仅仅对于需要特别注意的地方做一个总结。

  • ISA指令包括16个长度是64bit的寄存器,这是计算机运行的数据保存的地方。需要注意,每个寄存器有长度,可以覆盖1、2、4和8个字节的所有长度。

  • 计算机的寻址方式有如下几种

  • 数据传送指令需要注意下面的点

    • 传送的指令的两个操作数不能都指向内存位置。因此,没有指令可以直接将内存中的位置A搬移到内存中的位置B,必须先将A搬移到寄存器,之后再从寄存器搬移到内存B;

    • movl这个指令比较特殊,它是传送双字(4个字节)的数据,但是也会同时将目的寄存器的高4个字节一并清零。

  • 所有的汇编指令的立即数都是二进制补码编码的,如果是十进制数,也是有符号的整数。

  • 一般的栈结构,栈底是高地址,栈顶是低地址,数据在栈顶进出,%rsp指向栈顶。常见的栈结构如下所示

  • 算术指令的所有操作都是在寄存器中进行的,换句话说,如果需要将内存中的两个数相加,第一步是先把它们搬移到寄存器中,然后调用ADD指令再相加,结果也会保存在寄存器中。

  • 根据TEST指令可以判断一个数的正负或者零。因为testq %rax %rax指令不会改变寄存器里面的值,但是会根据(%rax) && (%rax) = (%rax)的值来设置条件码的标志位。比如,可以根据指令结束之后的ZF的值判断是否是0,SF的值判断是否是负数等。

  • 根据上一条指令的cmp结果设置指定寄存器的值,使用下面的表格实现,想起来挺烧脑,了解即可

  • 跳转指令的底层机器编码中的跳转地址使用相对编码的形式。注意,汇编器生成的*.s中是汇编语言,里面跳转指令的地址偏移是绝对值,但是通过链接器或者反汇编得到的二进制编码就是用相对寻址编码的。还是书里面的例子。汇编文件如下

    *.o反汇编之后的文件如下

    注意第2行的跳转指令的地址二进制编码是03,这个是下一条指令地址的相对值,也就是跳转的地址(8) = 下一条指令的地址(5) + 偏移量(3)。第5行也一样,5 = 0xd + f8(-8)。这样做的好处之一,是可重定位目标程序通过链接器链接之后,指令的虚拟地址发生变化,但是二进制编码依然保持不变

函数跳转

问题解答

学习本章的内容之后,应该可以回答下面的问题

为什么switch的case必须是整数?


还没写完🚀

Licensed under CC BY-NC-SA 4.0
comments powered by Disqus