zoukankan      html  css  js  c++  java
  • 2018-2019-1 20189215 《深入理解计算机系统》第三章学习总结

    《第3章 程序的机器级表示》


    汇编代码是机器代码的文本表示,是与特定机器密切相关的。用高级语言编写的程序可以在很多不同的机器上编译和执行。

    3.2 程序编码

    • 汇编代码表示非常接近于机器代码。与机器代码的二进制格式相比,汇编代码的主要特点是它用可读性更好的文本格式表示,能够理解汇编代码以及它与原始代码的联系,是理解九三级如何执行程序的关键一步。x96-64的机器代码和原始的C代码差别非常大。一些通常对C语言程序员隐藏的处理器状态都是可见的:

    1.程序计数器(通常称为“PC”,在x86-64中用%rip表示)给出将要执行的下一条指令在内存中的地址。
    2.整数寄存器文件包含16个命名的位置,分别存储64位的值。这些寄存器可以存储地址(对应于C语言的指针)或整数数据。有的寄存器被用来记录某些重要的程序状态,而其他的寄存器用来保存临时数据,例如过程的参数和局部变量,以及函数的返回值。
    3.条件码寄存器保存着最近执行的算术或逻辑指令的状态信息。它们用来实现控制或数据流中的条件变化,比如说用来实现if和while语旬。
    4.一组向量寄存器可以存放一个或多个整数或浮点数值。

    • 一些机器级代码和它的反汇编表示的特性值得注意:

    1.x86-64的指令长度从1~15字节不等。
    2.设计指令格式的方式是,从某个给定位置开始,可以将字节唯一地解码成机器指令。
    3.反汇编器只是基于机器代码文件中的字节序列来确定汇编代码, 不需要访问程序的源代码或汇编代码。
    4.生成可执行的代码需要对一组目标代码文件运行链接器,而这一组目标代码文件中必须含有一个main函数。

    • 汇编代码中所有以.开头的行都是指导汇编器和链接器工作的伪指令。
    • 我们的表述是ATT(根据“AT&T”命名的, AT&T是运营贝尔实验室多年的公 司)格式的汇编代码,这是GCC、 OBJDUMP和其他一些我们使用的工具的默认格式。 其他一些编程工具,包括Microsoft的工具,以及来自Intel的文档,其汇编代码都是Intel格式的。这两种格式在许多方面有所不同。比如下面的几点:

    1.intel代码省略了指示大小的后缀。比如使用push和pop,而不是pushl和popl。
    2.intel代码省略了寄存器前面的“%”符号,用的是rbx而不是%rbx。
    3.intel代码用不同的方式来描述内存中的位置,例如是“QWORD PTR[rbx]”,而不是“(%rbx)”。
    4.在带有多个操作数的指令情况下,列出操作数的顺序相反。当在两种格式之间进 行转换的时候,这一点非常令人困惑。

    3.3 数据格式

    由于是从16位体系结构扩展成32位的,Intel用术语“字(word)”表示16位数据类型。因此,称32位数为“双字(double words)”,称64位数为“四字(quad words)”。 在x86-64中,标准int值存储为双字(32位),指针(在此用char *表示)存储为8字节的四字。

    3.4 访问信息

    • 一个x86-64的中央处理单元(CPU)包含一组16个存储64位值的通用目的寄存器。 这些寄存器用来存储整数数据和指针。
    • 操作数被分为三种类型:立即数、寄存器和内存引用。
    • 多种不同的寻址模式
    • 简单的数据传送指令
    • 零扩展数据传送指令
    • 符号扩展数据传送指令
    • 压入和弹出栈数据

    1.将一个四字值压人栈中,首先要将栈指针减8,然后将值写到新的栈顶地址。因此,指令pushq %rbp的行为等价于下面两条指令:
    subq $8,%rsp
    movq %rbp, (%rsp)
    2.弹出一个四字的操作包括从栈顶位置读出数据,然后将栈指针加8。 因此,指令popq %rax等价于下面两条指令:
    movq (%rsp) ,%rax
    addq $8,%rsp

    3.5 算术和逻辑操作

    • 可以分为四种::加载有效地址、一元操作、二元操作和移位。整数算术操作如下:
    • 加载有效地址(load effective address)指令leaq命令实际上是movq指令的变形。指令中第一个操作数是将有效地址写入到目的操作数(并不是内存引用),同时目的操作数必须是一个寄存器。
    • 特殊的算术操作

    3.6 控制

    • 条件码
      除了整数寄存器, CPU还维护着一组单个位的条件码(condition code)寄存器,它们描述了最近的算术或逻辑操作的属性。可以检测这些寄存器来执行条件分支指令。最常用的条件码有:

    1.CF:进位标志。最近的操作使最高位产生了进位。可用来检查无符号操作的溢出。
    2.ZF:零标志。最近的操作得出的结果为0。
    3.SF:符号标志。最近的操作得到的结果为负数。
    4.OF:溢出标志。最近的操作导致一个补码溢出一正溢出或负溢出。
    对于逻辑操作,例如xoR,进位标志和溢出标志会设置成0。对于移位操作,进位标志将设置为最后一个被移出的位,而溢出标志设置为0。INC和DEC指令会设置溢出和零标志,但是不会改变进位标志。

    • 条件码不会被直接读取,一般常使用的方法有三种:

    1.可以根据条件码的某种组合,将一个字节设置为0或1

    2.可以跳转到程序的某个其他部分
    3.可以有条件地传送数据

    • 跳转指令

    1.直接跳转:跳转目标是作为指令的一部分编码的。
    2.间接跳转:跳转目标是从寄存器或内存位置读出的,写法是*后面跟一个操作数指示符。
    3.上图中jmp是无条件跳转指令,它可以是直接跳转,也可以是间接跳转。
    4.上图中其他指令都是有条件的,条件跳转只能是直接跳转。

    • 条件分支的实现

    1.使用条件控制实现条件分支
    C语言中的if-else语句的通用形式模版如下:

    if (test-expr)>
        then-statement
    else
        else-statement
    

    汇编通常会使用下面的形式,为then-statementelse-statement产生各自的代码块。它会插入条件和无条件分支,以保证能执行正确的代码块。

        t = test-expr;
        if (!t)
            goto false;
        then-statement
        goto done;
    false:
        else-statement
    done:
    

    2.使用条件传送实现条件分支
    条件传送指令

    • C语言提供了多种循环结构,即do-WhileWhilefor。汇编中没有相应的指令存在,可以用条件测试和跳转组合起来实现循环的效果。
    • switch(开关)语句可以根据一个整数索引值进行多重分支,不仅提高了C代码的可读性,而且通过跳转表这种数据结构使得实现更加高效。跳转表是一个数组,表项i是一个代码段的地址,这个代码段实现当索引值i时程序应该采取的动作。当开关的情况比较多,值的跨度范围较小时,就会使用跳转表。

    3.7 过程

    • 运行时栈
      x86-64过程需要的存储空间超出寄存器能够存放的大小后,会在栈上分配空间。这个部分称为过程的栈帧(stack fram)。
      以过程P调用Q,Q执行后返回P为例。P调用Q会将Q返回后下一条执行的P的代码作为P栈帧的一部分。Q的代码则会扩展当前栈的边界,分配其栈帧所需要的空间。
    • 栈上的局部存储
      有些时候,局部数据必须存放在内存中,常见的情况包括:

    1.寄存器不足够存放所有的本地数据。
    2.对一个局部变量使用地址运算符“&”,因此能够为它产生一个地址。
    3.某些局部变量是数组或结构,因此必须能够通过数组或结构引用被访问到。
    一般来说,过程通过减小栈指针在栈上分配空间。分配的结果作为栈帧的一部分,标 号为“局部变量”。

    • 寄存器组是唯一被所有过程共享的资源
    • 递归调用一个函数本身与调用其他函数是一样的。

    3.8 数组分配和访问

    • C语言中的数组是一种将标量数据聚集成更大数据类型的方式。C语言实现数组的方式非常简单,因此很容易翻译成机器代码。C语言的一个不同寻常的特点是可以产生指向数组中元素的指针,并对这些指针进行运算。在机器代码中,这些指针会被翻译成地址计算。优化编译器非常善于简化数组索引所使用的地址计算。
    • 指针计算
    • 变长数组
      ISO C99引人了一种功能,允许数组的维度是表达式,在数组被分配的时候才计算出来。 在变长数组的C版本中,我们可以将一个数组声明如下:
      int A[exp1][exp2];
      它可以作为一个局部变量,也可以作为一个函数的参数,然后在遇到这个声明的时候,通过对表达式exp1exp2求值来确定数组的维度。因此,例如要访问n×n的数组的元素i,j,我们可以写一个如下的函数:
    int var_ele(long n, int A[n][n],long i, long j) {
        return A[i][j];
    }
    

    参数n必须写在参数A[n][n]之前,这样函数就可以在遇到这个数组的时候计算出数组的维度。
    对应的汇编代码如下:

    /* int var_ele(long n, int A[n][n],long i, long j)
        n in %rdi, A in %rsi, i in %rdx, j in %rcx */ 
    var_ele :
        imulq     %rdx, %rdi
        leaq       (%rsi, %rdi, 4) , %rax
        movl      (%rax,%rcx,4) , %eax
        ret
    

    3.9 异质的数据结构

    • 结构
      C语言的struct声明创建一个数据类型,将可能不同类型的对象聚合到一个对象中,用名字来引用结构的各个组成部分。类似于数组的实现,结构的所有组成部分都存放在内存中一段连续的区域内,而指向结构的指针就是结构第一个字节的地址。编译器维护关于每个结构类型的信息,指示每个字段(field)的字节偏移。它以这些偏移作为内存引用指令中的位移,从而产生对结构元素的引用。
    • 联合
      联合提供了一种方式,能够规避C语言的类型系统,允许以多种类型来引用一个对象。联合声明的语法与结构的语法一样,只不过语义相差比较大。它们是用不同的字段来引用相同的内存块。
    • 数据对齐
      许多计算机系统对基本的数据类型的合法地址做出了一些限制,要求某种类型对象的地址必须是某个值K(通常是2,4,8)的倍数。这种对齐限制简化了处理器和内存系统之间接口的硬件设计。无论数据是否对齐,x86-64的硬件都能正确工作。不过,intel还是建议要对齐数据以提高系统性能。
    • 数据对齐让我想到了安装内存或者系统的过程中的对齐,以前不懂为何要这样做,现在通过这一节明白了数据对齐的重要性,可以提高系统性能,减少不必要的工作。

    3.10 在机器级程序中将控制与数据结合起来

    • 理解指针:

    1.每个指针都对应一个类型.这个类型表明该指针指向的是哪一类对象。
    2.每个指针都有一个值,这个值是某个指定类型的对象的地址。
    3.指针用&运算符创建.这个运算符可以应用到任何lvalue类的C表达式上。lvalue意指可以出现在赋值语句左边的表达式。
    4.*操作符用于间接引用指针,它的类型与该指针的类型一致。
    5.数组与指针緊密联系,一个数组的名字可以像一个变量一样引用(但是不能修改)。数组引用与指针运算和间接引用有一样的效果。
    6.将指针从一种类型强制转换成另一种类型,只改变它的类型,而不改变它的值。
    7.指针也可以指向函数。

    • GDB调试器常用命令
    • 内存越界引用和缓冲区溢出
      C对数组不进行任何边界检查,因此当对越界的数组元素进行写操作会破坏存储在栈中的信息。一种特别常见的状态破坏称为缓冲区溢出。缓冲区溢出一个很致命的使用就是让程序执行它本来不想执行函数,这是一种常见的通过计算机网络攻击系统安全的方法。输入给程序一个字符串,这个字符串包含一些可执行代码的字节编码,称为攻击代码,另外还有一些字节会用一个指向攻击代码的指针覆盖返回区域。那么执行ret指令的效果就是跳转到攻击代码。
    • 对抗缓冲区溢出可以采取以下方法:

    1.栈随机化。使得栈的位置在程序每次运行时都有变化。
    2.栈破坏检测。能够检测到栈何时已被破坏。最新的GCC版本加入了一种栈保护者机制来检测缓冲区越界。其思想是在任何局部缓冲区与栈状态之间存储一个特殊的金丝雀值。
    3.限制可执行代码区域。消除攻击者向系统中插入可执行代码的能力。

    3.11 浮点代码

    • 浮点传送指令
    • 浮点转换指令

    1.双操作数浮点转换指令

    2.三操作数浮点转换指令

    • 过程中的浮点代码
      XMM寄存器用来向函数传递浮点参数,以及从函数返回浮点值。有如下规则:

    1.XMM寄存器%xmm0~%xmm7最多可以传递8个浮点参数。
    2.函数使用%xmm0来返回浮点值。
    3.所有XMM寄存器都是调用者保存。被调用者可以不用保存就覆盖这些寄存器中任意一个。

    • 浮点数运算操作

    学习进度条

    章节数(新增/累积) 博客量(新增/累积)
    目标 共12章 共12篇
    2018.10.21 1/1 1/1
    2018.11.04 1/2 1/2
    2018.12.08 1/3 1/3

    第三章拖得时间有点久了,但是内容很多,还有很多内容没有深化理解,本书需要多遍阅读,细细体会。

    参考资料

  • 相关阅读:
    C#网络编程简单实现通信小例子-1
    对象序列化
    C#网络编程(1)
    Web前端基础——jQuery(三)
    Web前端基础——jQuery(二)
    Web前端基础——jQuery(一)
    Java基础——正则表达式
    Java基础——Servlet(八)文件上传下载
    Java基础——Servlet(七)过滤器&监听器 相关
    Java基础——Servlet(六)分页相关
  • 原文地址:https://www.cnblogs.com/jsjliyang/p/10087427.html
Copyright © 2011-2022 走看看