CSAPP (2)


深入理解计算机系统(第三版)/ CSAPP 杂谈,第3章:程序的机器级表示

  • x86-64寄存器起名:8位,%al;16位,%ax;32位,%eax;64位,%rax

  • x86-64寄存器作用:

  • %rax: 返回值
  • %rbx: 被调用者保存
  • %rcx: 第4个参数
  • %rdx: 第3个参数
  • %rsi: 第2个参数
  • %rdi: 第1个参数
  • %rbp: 被调用者保存
  • %rsp: 栈指针
  • %r8: 第5个参数
  • %r9: 第6个参数
  • %r10: 调用者保存
  • %r11: 调用者保存
  • %r12: 被调用者保存
  • %r13: 被调用者保存
  • %r14: 被调用者保存
  • %r15: 被调用者保存

  • 操作数分三种:立即数,寄存器,内存引用

  • 汇编立即数表示:以$开头,跟着标准C表示法,如$-577,$0x1F。不同的指令允许的立即数范围不同

  • 寻址方式:立即数寻址,立即数作为值;寄存器寻址,寄存器值作为值;绝对寻址,立即数对应内存地址的内存值;间接寻址,寄存器值对应内存地址的内存值;变址寻址,运算表达式对应内存地址的内存值

  • 表达式:(s取值1,2,4,8)

  • $Imm: Imm,立即数寻址

  • ra: R[ra],寄存器寻址
  • Imm: M[Imm],绝对寻址
  • (ra): M[R[ra]],间接寻址
  • Imm(rb): M[Imm + R[rb]],(基址+偏移量)寻址
  • (rb, ri): M[R[rb] + R[ri]],变址寻址
  • Imm(rb, ri): M[Imm + R[rb] + R[ri]],变址寻址
  • (,ri, s): M[R[ri] * s],比例变址寻址
  • Imm(,ri, s): M[Imm + R[ri] * s],比例变址寻址
  • (rb,ri, s): M[R[rb] + R[ri] * s],比例变址寻址
  • Imm(rb,ri, s): M[Imm + R[rb] + R[ri] * s],比例变址寻址

—- 2018.11.11 —-

  • movb, movw, movl, movq 分别表示把1,2,4,8字节的数据从源位置复制到目标位置。x86-64 限制源位置和目标位置不能两个都是内存。大部分指令都会有这4种变种,但如pushq,popq,leaq等指令没有

  • movz,movs是0扩展和符号扩展指令,将较小的数从寄存器或内存转移到寄存器中。

  • x86-64中,栈中低地址是栈顶,高地址是栈底。pushq时%rsp减小,popq时%rsp增大。

  • leaq S,D: 将S所表示的地址值赋值给D,经常用来做一些简单的算法。比如z = x+4*y可能表示为leaq (%rdi, %rsi, 4), %rax

  • 一元操作中,操作数既是源也是目的。操作数可以是内存地址。

  • 二元操作中,源操作是第一个,目标操作是第二个。如subq %rax, %rdx等价与%rdx = %rdx – %rax。如果第二个操作数是内存地址,则必须读出,执行,再写入。注:此时应该会生成多条机器指令,则该操作不是原子操作。否则i++可以写成addq $1, (%rax)岂不是原子操作?所以推断该操作不是原子操作。

  • sarq,shrq表示算术右移和逻辑右移。salq, shlq表示算术左移和逻辑左移,但由于补码的特性,他们的操作结果是一样的。

*x86-64还为128位操作提供有限支持,当imulq和mulq为双操作数时,是64位乘法;当为单操作数时,另一个乘数将视为%rax,而结果将存放在%rdx(高64位),%rax(低64位)中。

—- 2018.11.18 —-

  • 条件码用于控制。最常用的四个是,CF: 进位标志,ZF:零标志,SF:符号标志,OF:溢出标志

  • 通过SET指令访问条件码,入sete/setz %al就是将ZF设置到%eax的最低byte上(0或1)

  • jmp表示跳转指令,一般汇编用一个标号来指定,如jmp .L1。也可以利用寄存器,如jmp *%rax表示跳转到%rax的值的地址,jmp *(%rax)表示跳转到%rax值对应的内存值的内存地址去。和SET一样,也有je,js,jle等指令

  • GCC当switch的情况较多,且跨度较小的时候,会使用地址跳转表来翻译switch

  • 当x86-64过程需要的存储空间超出寄存器能够存放的大小时,就回在栈上分配空间,这个部分称为过程的栈帧

  • call指令用于函数调用,此时将会把将紧跟着当前地址后面的那条指令的地址压入栈,并吧要跳转的函数地址设置到PC。退出时用ret。

  • 局部数据存到需要内存中的几种情况:

    1. 寄存器不够用
    2. 对局部变量使用了地址运算符&
    3. 某些局部变量是数组或结构体(如果优化了则不一定)
  • 数据对齐可提高软件性能,减少读的次数和额外的处理。无论数据是否对齐,x86-64都可以正确工作。汇编中在文件头部声明.align 8来保证后面的数据的起始地址都是8的倍数。

  • 对于结构体,字段之间也可能会存在间隙,以保证每个结构元素都满足它的对齐需求。

  • 对齐原则是任何K字节的基本对象的地址必须是K的倍数。

  • Linux上最新版的GCC已经会讲栈地址随机化,使得栈溢出攻击变得更加困难

版权所有,转载请注明出处:
https://sickworm.com/?p=471




深入理解计算机系统(第三版)/ CSAPP 杂谈,第2章:信息的表示和处理

  • gcc编译可以用 -std 和-ansi 来选择C语言规范版本,默认-std=c89,外加一些C99,C11特性

  • 大小端可通过 int* 强转为 char* 来判断得出

  • 一般C语言的 char 范围是-128~127,但其实C语言规范只要求最小的可取值范围为-127~127。类似还有 short,int,long,不过它们字节数要求是16位机器的要求。int32_t,int64_t C语言规范要求和C语言实现基本一致。#无用但有趣的冷知识

  • #define INT_MAX 2147483647
    #define INT_MIN (-INT_MAX – 1)
    这里没有简单地将INT_MIN赋值成-2147483647,是因为-2147483648对于编译器而言是个表达式,而2147483648对于32-bit整数是无法表示的

  • 扩展数字:有符号转无符号,按bit解析;扩展字节,正数补0,负数补1

  • 截断数字:无符号转有符号,按bit解析;压缩字节,保留符号位,其他直接截取

—- 2018.10.23 —-

  • 补码乘法:将补码看作无符号正整数,相乘后截断结果,结果即为乘法结果。e.g.

(13 x -7) mod 2^8 = -91 = 10100101

-7 = 11111001b = 249(看作无符号)

(12 * 249) mod 2^8 = 165 = 10100101 = -91(看作补码)

  • 浮点数基本都按照IEEE 754规范实现。1位用于表示正负,k位用于表示尾数,代表精度为1/2^k,n位表示阶码,代表绝对值取值范围为1 * 2^-(n-1) – 1, 2^(n-1)),分别是阶码取1和2^n-1时的值。尾数可任意取值时,绝对值范围为[1 * 2^-(n-1) – 1, 2 * 2^(n-1)))。阶码全0全1另有它用。具体看图2-36。

  • 单精度浮点数符号位 1,尾数位23,阶数位8;双精度位1,52,11。所以单精度的精度为1/2^23,绝对值取值范围为[2^-126,2^127]。尾数默认第一位为1,这样可以不存储在bits中,从而提高一位精度。但也因此不能表示0(阶码全0可表示)。

  • 阶码全0时可用于表示0,此时还会有+0.0和-0.0。阶码全1,尾数全0可表示正无穷和负无穷。尾数非全零表示NaN(Not a number)。

版权所有,转载请注明出处:
https://sickworm.com/?p=450