CSAPP读书笔记 第3章 程序的机器级表示 访问信息

本文跟随CSAPP新版书,主要针对x86-64架构。

整数寄存器

一个x86-64的CPU包含一组16个存储64位值的通用寄存器。这些寄存器用来存储整数数据和指针。图1-1显示了这16个寄存器。



这16个寄存器的名字都以%r开头,后面跟着一些不同命名规则的名字,是指令集的历史演化造成的。最初的8086有8个16位寄存器,即图1-1中的%ax到%bp。每个寄存器都有特殊的用途,它们的名字就反应了它们的用途,如图1-1中最右边的说明。扩展到IA32架构,即通常说的32位系统,这些寄存器也扩展成为了32位,标号从%eax到%ebp。扩展到现在的x86-64后,原来的8个32位寄存器也扩展到了64位,标号从%rax到%rbp。除此之外,还增加了8个新的寄存器,它们的标号按照新的命名规则:从%r8到%r15.

如图1-1中嵌套的方框表明,指令可以对这个16个寄存器的低位字节中存放的不同大小的数据进行操作。字节级操作可以访问最低的字节,16位操作可以访问最低的两个字节,32位可以访问最低的4个字节,而64位操作可以访问整个寄存器。

操作数指示符

大多数指令有一个或多个操作数(operand),指示执行一个操作要使用的源数据值,以及放置结果的目的位置。x86-64支持多种操作数格式,如图1-2所示。

源数据值可以以常数形式给出,或是从,寄存器或者内存读出。结果可以存放在寄存器或内存中。因此,操作数被分为三类。第一种是立即数(immediate),用来表示常数值,书写方式是’$‘后面跟一个标准C表示的整数。第二种类型是寄存器(register),它表示某个寄存器的内容,用ra表示任意寄存器a,用引用R[ra]表示它的值。第三类操作数是内存引用,它会根据计算出来的地址(通常是有效地址)访问某个内存位置。我们用符号Mb[Addr]表示对存储在内存中从地址Addr开始的b个字节值的引用。一般省去下标b。如图1-2有多种不同的寻址模式。

压入和弹出栈数据

C语言程序在调用函数的时候,用到的最常见的操作就是压栈和弹栈操作,理解和熟悉栈操作有助于帮助我们调试函数调用中的一些错误。关于栈的定义和操作这里不详细赘述。在x86-64中,程序栈存放在内存中的某个区域。如图3-1所示,栈向下增长,这样一来,栈顶元素的地址是所有栈中元素地址最低的。(这里,栈“顶”在图的底部)栈指针%rsp保存着栈顶元素的地址。

表3-1 入栈和出栈

指令 效果 描述
pushq S R[%rsp] ← R[%rsp]-8;
M[R[%rsp]] ← S
将四字压入栈
popq D D ← M[R[%rsp]];
R[%rsp] ← R[%rsp]+8
将四字弹出栈

pushq的指令的功能是把数据压入到栈上,而popq的指令是弹出数据。这些指令都只有一个操作数–压入的数据源和弹出的数据目的。

将一个四字值压入栈中,首先将栈指针减8,然后将值写到新的栈顶地址。因此,pushq %rbp的行为等价于下面两条指令:

1
2
subq $8, %rsp       将栈指针的值减8
movq %rbp, (%rsp) 将%rbp的值存到内存中

如图3-1前两栏给出的是,当%rsp为0x108,%rax为0x123时,执行指令pushq %rax的效果。首先%rsp会减8,得到0x100,然后将0x123存放到内存地址0X100处。

图3-1的第三栏说明的时在执行完pushq后立即执行指令popq %rdx的效果。先从内存中读出值0x123,再写到寄存器%rdx中,然后,寄存器%rsp的值会回到0x108,值0x123仍然会保持在内存位置0x100中,直到被覆盖。无论如何,%rsp指向的地址总是栈顶。