AnKate
1440 words
7 minutes
CSAPP 3.4
2020-10-14

访问信息#

在x86-64架构下,CPU有一组16个能够存储64位的寄存器,它们能够用来管理栈、传递函数所用到的变量、从函数返回值或是存储局部或全局变量。

操作数指示符#

大多数指令中会有一个或多个操作数,用于指示指令的源头和目标位置。

操作数可以分为三类:

  • Immediate,在中文版中译作立即数,实际即是常数的意思。通常用’$‘符号后接一个标准C语言表示法的常数。
  • Register,即寄存器,表示的是寄存器中的内容。用rar_a表示该寄存器,R[ra]R[r_a]表示该寄存器中的内容的值。
  • Memory,即内存,需要通过计算得出的地址访问内存中的位置,该地址通常被称作有效地址。用Mb[Addr]M_b[Addr]表示一个被存储在地址AddrAddr中的大小为b(单位:ByteByte)的值,下标可省略。、

下表展示了不同的表示形式对应的操作数:

操作数类型表示形式操作数实际数值名称
Immediate$ImmImmImmImmediate
Registerrar_aR[ra]R[r_a]Register
MemoryImmImmM[Imm]M[Imm]Absolute
Memory(ra)(r_a)M[R[ra]]M[R[r_a]]Indirect
MemoryImm(rb)Imm(r_b)M[Imm+R[rb]]M[Imm+R[r_b]]Base+displacement
Memory(rb,ri)(r_b,r_i)M[R[rb]+R[ri]]M[R[r_b]+R[r_i]]Indexed
MemoryImm(rb,ri)Imm(r_b,r_i)M[Imm+R[rb]+R[ri]]M[Imm+R[r_b]+R[r_i]]Indexed
Memory(,ri,s)(,r_i,s)M[R[ri]s]M[R[r_i]*s]Scaled indexed
MemoryImm(,ri,s)Imm(,r_i,s)M[Imm+R[ri]s]M[Imm+R[r_i]*s]Scaled indexed
Memory(rb,ri,s)(r_b,r_i,s)M[R[rb]+R[ri]s]M[R[r_b]+R[r_i]*s]Scaled indexed
MemoryImm(rb,ri,s)Imm(r_b,r_i,s)M[Imm+R[rb]+R[ri]s]M[Imm+R[r_b]+R[r_i]*s]Scaled indexed

其中ss是比例因子,必须是1,2,4,8中的一个。

从表中可以看出,除去最基础的几种直接访问外,通常用到的较多的几种寻址方法中都会带有偏移量以及比例因子,如结构体中的元素、或是访问数组元素。

数据转移指令#

相同数据类型的转移#

将数据从一个位置转移到另一个位置时将会用到数据转移指令,其中后缀对应的数据类型与前几张中所涉及的并无出入,具体情况见下表:

指令描述
movbMove byte
movwMove word
movlMove double word
movqMove quad word
movabsqMove absolute quad word

指令的格式如下:

movq  Source, Destinationmovq~~Source,~Destination

其中的Source和Destination可以有如下搭配:

移出来源移入目标示例指令C语言中的等价表达式
ImmImmRegRegmovq $0x4,%raxtmp = 0x4;
ImmImmMemMemmovq $-147,(%rax)*p = -147;
RegRegRegRegmovq %rax,%rdxtemp2 = temp1;
RegRegMemMemmovq %rax,(%rdx)*p = tmp;
MemMemRegRegmovq (%rax),%rdxtmp = *p;

简单概括即为:

  • 不能将常数ImmImm作为移入的目标
  • MemMem不能同时为移出的源头与移入的目标,要想实现该操作需要进行两步:
    • 将目标值存入寄存器
    • 从寄存器中取出该值移入内存
  • 当移出和移入的均为寄存器时,两个寄存器应保持大小相同(见练习3.3)

指令的后缀需要同时以移出和移入的目标为依据进行判断,由于内存的大小不固定,故通常是根据寄存器大小进行判断(见练习3.2)。

以上所述的数据转移是在移出移入的数据类型相同的前提下进行的,但更多时候会遇到的是不同的数据类型转换,需要进行扩展或截断,而截断不需要通过指令进行,故需要进行扩展的指令介绍。

需要进行类型转换的数据转移#

零扩展:

指令对应含义
movzbw在byte前加0使其成为word
movzbl在byte前加0使其成为double word
movzwl在word前加0使其成为double word
movzbq在byte前加0使其成为quad word
movzwq在word前加0使其成为quad word

符号扩展:

指令对应含义
movsbw在byte前加最高位值使其成为word
movsbl在byte前加最高位值使其成为double word
movswl在word前加最高位值使其成为double word
movsbq在byte前加最高位值使其成为quad word
movswq在word前加最高位值使其成为quad word
movslq在double word前加最高位值使其成为quad word
cltq将%eax符号扩展为%rax

**在C语言标准中,若同时需要进行符号转换和大小转换,需要先保证大小变化。**例如charchar转化为unsignedunsigned时,需要进行的是符号扩展。(见练习3.4)

数据的入栈和出栈#

在程序运行中,若用于保存变量的六个寄存器(%rdi, %rsi, %rdx, %rcx, %r8, %r9)均已存放数据,即程序用到六个以上的变量时,便会需要将变量存入中。只有当需要时,才会向内存申请栈空间用于存放数据。

这里的栈与数据结构中的栈的性质相同,被保存在内存的某一部分。与数据结构中的栈不同的是,这里的栈是向下开辟空间的。栈底的地址最大,栈顶的地址最小,且随着数据入栈,栈顶的地址将逐渐减小。寄存器中的%rsp用于记录栈顶的地址。

用指令pushqpushqpopqpopq进行数据的入栈与出栈。

数据入栈#

数据入栈的指令为pushq Srcpushq~Src,此时需要先将栈顶指针向下移动8个ByteByte以开辟新的栈空间,随后将数据存入栈中。

假设数据存放在%rbp中,那么以下代码与pushq Srcpushq~Src等价:

subq $8, %rsp		减少栈顶指针的值
movq %rbp, (%rsp)		将数据入栈

但是pushqpushq指令只需要一个字节,而上述指令则需要八个字节。

数据出栈#

数据出栈的指令为popq Srcpopq~Src,与入栈时操作顺序相反,首先需要根据%rsp所指的地址读取存放在该处的数据,随后再将%rsp增加(上移)8个字节。

以下代码与popq Srcpopq~Src等价:

movq (%rsp), %rax	读取栈顶指针所指的数据
addq $8, %rsp		将栈顶指针向栈底移动

需要注意,此时存放在原来%rsp所指的地址的数值依然保留,直到它下一次被数据入栈的操作覆盖才会消失,改变的只有%rsp所指的地址。

CSAPP 3.4
https://ankate.github.io/posts/csapp-34/
Author
AnKate
Published at
2020-10-14