计算机系统课程 笔记总结 CSAPP第三章 程序的机器级表示(3.2-3.4)

目录

3.2 程序编码

3.3 数据格式

3.4 访问信息

3.4.1 操作数指示符

3.4.2 数据传送指令

3.4.3 数据传送实例

3.4.4 压入和弹出栈数据


 

3.2 程序编码

  • gcc编译器以汇编代码形式产生输出,汇编代码是机器代码的文本表示,给出了程序中的每一条命令。
  • 然后gcc调用汇编器链接器根据汇编代码生成的可执行机器代码
  • Linux使用了平坦寻址方式,因此可以将整个存储空间(包括栈、堆等)看做一个大的字节数组

 

执行 gcc -Og -o p p1.c p2.c

  • 编译选项 -Og 告诉编译器使用会产生符合原始C代码整体结构的机器代码优化等级

执行 gcc -O1 -o p p1.c p2.c

  • -O1 第一级优化
  1. C预处理器扩展源代码,插入所有用#include指定的文件,并扩展所有用#define定义的宏;
  2. 编译器产生两个源代码的汇编代码p1.s和p2.s ;
  1. 汇编器将汇编代码转化为二进制目标代码p1.o和p2.o ,目标代码是机器代码的一种形式,包含所有指令的二进制表示,但是还没有填入地址的全局值
  2. 链接器将两个目标代码文件与实现函数库的代码合并,并产生最终的可执行文件p

 

对于机器级编程,两种抽象尤为重要:

  • 机器级程序的格式和行为:
    • 定义为指令集体系结构(Instruction set architecture,它定义了处理器状态、指令的格式、每条指令对状态的影响
    • 大多数ISA把程序的行为描述成好像每条指令是按顺序执行的,实际处理器硬件远比描述的精细复杂,它们并发地执行许多指令,但是可以采取措施保证整体行为与ISA指定的顺序执行完全一致。
  • 机器级程序使用的存储器地址,是虚拟地址:
    • 提供的存储器模型看上去是一个非常大的字节数组,存储器系统的实际实现是将多个硬件存储器和操作系统软件组合起来。
    • 虚拟内存:将主存和I/O设备抽象成一个非常大的字节数组

 

 

  • 程序计数器:给出将要执行的下一条指令在内存中的地址
  • 整数寄存器:存储地址或整数数据
  • 条件码计数器:保存着最近执行的算术或逻辑指令的状态信息
  • 向量寄存器:存放一个或多个整数或浮点数值

 

编译器会将C语言提供的相对比较抽象的执行模型表示的程序转化为处理器执行的非常基本的指令,即汇编代码。汇编代码的表示非常接近于机器代码。

 

sum:
  pushl %ebp
  movl  %esp,%ebp
  movl  12(%ebp),%eax
  addl  8(%ebp),%eax
  addl  %eax,accum
  popl  %ebp
  ret

 

例如:

 

int accum = 0 ;
int sum(int x, int y){
        int t = x + y;
        accum += t;
        return t;
}

 

执行 gcc -O1 -S code.c

将产生code.s

 

  • 代码去除所有关于局部变量名或数据类型的信息
  • 有一个对全局变量accum的引用——因为编译器还不能确定这个变量会放在存储器的哪个位置

 

执行 gcc -O1 -c code.c

将产生code.o

二进制文件

 

可以找到:

55 89 e5 8b 45 0c 03 45 08 01 05 00 00 00 00 5d c3

  • 机器实际执行的程序只是对一系列指令进行编码的字节序列

 

执行 objdump -d code.o

(反汇编器)

 

00000000 <sum>:
 0:55                     pushl %ebp
 1:89 e5                  movl  %esp,%ebp
 3:8b 45 0c               movl  12(%ebp),%eax
 6:03 45 08               addl  8(%ebp),%eax
 9:01 05 00 00 00 00      addl  %eax,accum
 f:5d                     popl  %ebp
10:c3                     ret

 

生成实际可执行的代码需要对一组目标代码运行链接器,这一组目标代码文件中必须含有一个main函数。链接器将代码的地址移动到一段地址范围中,且会确定全局变量的地址:

 

  • 每行前面的地址变更-->

 

08048394 <sum>:
 8048394:55                     pushl %ebp
 8048395:89 e5                  movl  %esp,%ebp
 8048397:8b 45 0c               movl  12(%ebp),%eax
 804839a:03 45 08               addl  8(%ebp),%eax
 804839d:01 05 00 00 00 00      addl  %eax,0x804a018
 80483a3:5d                     popl  %ebp
 80483a4:c3                     ret

 

  • Q:最终链接后生成的文件会比较大?
  • A:是因为它不仅包含了多个文件的代码,还包含了用来启动和终止程序的信息,以及用来与操作系统交互的信息。

3.3 数据格式

  • 由于是从16位体系结构扩展成32位的,Intel用术语“字(word)”表示16位数据类型。
  • 因此,称32位数为“双字(double words)”,称64位数为“四字(quad words)”。
  • X86-64指令集包括完整的针对字节、字和双字的指令。
  • 汇编代码也使用后缀“l”来表示4字节整数和8字节双精度浮点数。
    • 这不会产生歧义——因为浮点数使用的是完全不同的指令和寄存器

 

 


 

3.4 访问信息

  • 一个x86-64的中央处理单元(CPU)包含一组16个存储64位值的通用目的寄存器
  • IA32包含一组8个存储32位值的寄存器,这些寄存器用来存储整数数据和指针
  • 大多数指令有一个或多个操作数用于之处执行一个操作中引用得源数据值,以及放置结果得目标位置。
  • 源数据值可以以常数形式(如$12)给出,或者是从寄存器(如%eax)、存储器(如0x804a018)中读出。
  • 结果可以存放在寄存器或者存储器中。

 

 

 

 

 

 

 

 

 

  • 64位(r开头)
    • r…x
    • r…I
    • r…p
    • r…(数字)
  • 32位(e开头)
    • e…x
    • e…I
    • e…p
    • r…d
  • 16位(2个字母)
    • …x
    • …I
    • …I
    • r…w
  • 8位
    • …l(2个字母)
    • ……l(3个字母)
    • r……b(4个字母)

 

 

 

  • 16位操作可以访问最低的2个字节
  • 32位操作可以访问最低的4个字节
  • 64位操作可以访问整个寄存器

当这些指令以寄存器作为目标时,对于生成小于8字节结果的指令,寄存器剩下的字节:

  • 生成1字节和2字节数字的指令会保持剩下的字节不变
  • 生成4字节数字的指令会把高位4个字节置为0

3.4.1 操作数指示符

  • 大多数指令有一个或多个操作数,指示出执行一个操作中要使用的源数据值,以及放置结果的目的位置。
  • 源数据值可以以常数形式给出,或是从寄存器或内存中读出。
  • 结果可以放在寄存器或内存中。
  • 各种不同的操作数的可能性被分为三种类型:
    • 立即数:用来表示常数值,立即数的书写方式是'$'后面跟一个标准C表示法表示的整数
    • 寄存器:表示某个寄存器的内容。用符号ra来表示任意寄存器a,用引用R[ra]来表示它的值,这是寄存器集合看成一个数组R,用寄存器标识符作为索引。
    • 存储器(内存引用):根据计算出来的地址访问某个内存位置。用符号Mb[Addr]表示对存储在内存中从地址Addr开始的b个字节值的引用,为了简便,通常省去下标b。

 

 

 

 

 

 

 

 

 

 

 

 

 

  • 立即数寻址 & 绝对寻址
  • $Imm:常数地址
  • Imm:存储器中包含M[Imm]地址

 

 

 

 

 

 

 

 

有括号-->值

 

 

 

 

 

 

第一行是立即数,第二行则是寄存器,剩下的全部是存储器。

其中最后一行存储器语法Imm(Eb,Ei,s),表示的是最常用的形式,分为四个部分:

  1. Imm 立即偏移数
  2. Eb    基址寄存器
  3. Ei     变址寄存器
  4. s      比例因子,必须是 1、2、4或8

有效地址计算公式为:Imm + R[Eb] + R[Ei] * s

例:对于2(%esp,%eax,4)这个操作数来讲,它代表的是内存地址为2+%esp+4*%eax的存储器区域的值。

3.4.2 数据传送指令

数据传送指令:将数据从一个位置复制到另一个位置的指令。

 

 

 

 

 

 

 

 

 

mov

  • +b : 字节(8位)
  • +w : 字(16位)
  • +l : 双字(32位)
  • +q : 四字(64位)
  • +absq : 绝对的四字(64位)

 

 

 

 

 

 

 

 

MOV指令:mov + 源操作数 + 目的操作数

  • movl $0x4050, %eax     立即数-->寄存器 4字节
  • movw %bp, %sp             寄存器-->寄存器 2字节
  • movb (%rdi, %rcx), %al 存储器-->寄存器 1字节
  • movb $-17, (%rsp)         立即数-->存储器 1字节
  • movq %rax, -12(%rbp)  寄存器-->存储器 8字节

 

 

 

 

注:x86-64限制,两个操作数不能都指向内存

 

 

 

移送数据命令MOVZ和MOVS,将较小的源值复制到较大的目的时使用

  • MOVZ:把目的中剩余的字节填充为0
  • MOVS:通过符号扩展来填充,把操作的最高位进行复制(0/1)

 

  • movsbq $0xAA %rax       -->      %rax=FF…FFAA       因为0xA=1010,最高位是1,所以将其他字节设为1
  • movzbq $0xAA %rax       -->      %rax=00…00AA
  • movz/movs + bw/bl/bq/lw/lq/wq
    • 两种移动数据方式+(低存储单位)+(高存储单位)
  • cltq:专用——%rax <-- 符号扩展(%eax

 

 

 

 

 

 

 

 

 

  • 源操作数为值:
    • 依据目的操作数的大小
  • 源操作数为地址
    • 依据源操作数的大小

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

  • %ebx(基地址寄存器)不能做为地址寄存器
  • 应为movq
  • 源和目的不能同时是存储器
  • 没有名叫%sl的寄存器
  • 立即数不能作为目的
  • %eax --> %rdx 应为movq ???
  • 应为movw

 

 

 

 

 

 

 

 

 

 

 

 

3.4.3 数据传送实例

 

 

 

 

 

 

 

 

 

  • %eax是返回值
  • C语言中的“指针”就是地址
  • 间接引用指针就是将该指针放在一个寄存器中,在内存引用(存储器)中使用这个寄存器
  • 局部变量通常保存在寄存器中,而不是内存中
  • 访问寄存器比访问内存快很多

 

 

 

3.4.4 压入和弹出栈数据

 

 

 

 

 

 

 

 

 

第三栏:执行完pushq后立刻执行popq

  1. 从内存中读取值0x123
  2. 写到寄存器%rdx中
  3. 寄存器%rsp的值将增加回到0x108

 

值0x123仍然会被保存在内存位置0x100,直到被覆盖

%rsp总是指向栈顶

 

 

 

 

 

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值