【CSAPP】第三章 程序的机器级表示
1. 数据的编码与存储
- 数据类型编码

- x86-64寄存器

Note
寄存器可以分为4组:
函数参数寄存器:rdi rsi rdx rcx r8 r9。这六个寄存器用于传递函数的参数,如果多于6个参数,需要在栈上申请空间,算在被调用函数的栈帧中
函数返回值寄存器:rax。返回函数的结果,当然也可以将存储结果的空间的指针传递给被调函数,例如:
400efe: 48 83 ec 28 sub $0x28,%rsp
400f02: 48 89 e6 mov %rsp,%rsi
400f05: e8 52 05 00 00 callq 40145c <read_six_numbers>
被解析的字符串的地址放在了rdi寄存器中,调用者在自己的栈帧上开辟了40个字节的空间用于存储被调函数的结果
栈指针寄存器:rsp。指向栈顶的
保留寄存器:rbx rbp r10 r13 r14 r15,也是通用寄存器,但是需要保证在调用函数前后里面的内容不变,也就是函数在使用他们之前要压栈,返回之前要弹栈
同一个寄存器有1字节 2字节 4字节 8字节,对1 2字节部分操作不会影响其余部分的内容,对4字节操作会将剩余的4字节置零,例如:
2. 汇编指令
2.1 数据传送指令
访存方式

Note
比例变址寻址可以用于数组的访问,
数据传送指令
语句基本格式为MOV src, dest,表示将数据从dest传送到src,地址的格式遵循访存方式。
功能:
- 相等长度的数据传送。传送的源和目的的位数必须相同,并在
mov后面指明为b/w/l/q

短数字-->长数字。传送前必须要做扩展,并指明在
mov后面,分为两种情况:- 无符号扩展:高位补0
- 有符号扩展:高位补符号位
mov后第一个表示扩展0(z)或者符号(s),第二和第三分别表示源和目的操作数的长度

长数字--> 短数字。如果保留低位,直接按低位寄存器操作,比如将
rax中的低8位移动到dil,直接mov %al, %dil即可;如果保留高位,先位移,然后按移动低位操作进行
Note
- dest不能是立即数
- src和dest不能同时为内存地址,即将内存的数据传送到内存中另一个位置必须经过寄存器
- 零扩展传送中没有
movzlq指令,这是因为操作双字时,会将高4字节置零cltq等效于movslq %eax, %rax
入栈出栈

- 对rsp寄存器操作
- 向下生长
- 按字节编址
2.2 算术/逻辑指令
分为四组指令:
- 取有效地址。相当于C语言中的取地址运算符,可以替代一些算术运算。比如,如果%rax的值为x,%rdx为y,则
leaq 7(%rax, %rdx, 5), %rax就是在计算x+5y+7的值 - 一元操作指令
- 二元算术指令,操作数的顺序与intel的规定相反
- 位移。分为算术和逻辑位移

2.3 过程控制指令
控制码
进位:CF
零标志:ZF
负数标志:SF
溢出标志:OF
比较指令
cmp S1, S2:基于S2-S1进行
test S1, S2:基于S1&S2
- 都有b l w q四种变种
- 只设置条件码,不改变寄存器
跳转指令

条件设置指令

Note
比较指令配合跳转指令即可实现循环和分支结构
3. 程序设计
3.1 循环
do-while循环
C代码:
long fact_do(long n) {
long result = 1;
do {
result *= n;
n = n - 1;
} while(n > 1);
return result;
}
汇编:
fact_do:
# 初始化
movl $1, %eax
.L2:
# 循环体
imulq %rdi, %rax
# 边界判断
subq $1, %rdi
cmpq 1, %rdi
jg .L2
# 结束
ret
while循环
C代码:
long fact_while(long n) {
long result = 1;
while(n > 1){
result *= n;
n = n - 1;
}
return result;
}
- 中间跳转法。将test代码写在最后,在初始化之后直接跳转到测试部分,相比于guarded-do可以少写一段测试的代码
goto test
loop:
body-statement
test:
t = test-expr
if(t) goto loop
fact_while:
# 初始化
movl $1, %eax
jmp .L5
.L6:
# 循环体
imulq %rdi, %rax
subq $1, %rdi
.L5:
#边界判断
cmpq 1, %rdi
jg .L6
# 结束
ret
- guarded-do。在进入循环之前就测试
t = test-expr
if(!t) goto done
loop:
body-statement
t = test-expr
if(t) goto loop
done:
fact_while:
# 进入前测试
cmpq $1, %rdi
jle .L7
# 初始化
movl $1, %eax
.L6:
# 循环体
imulq %rdi, %rax
subq $1, %rdi
# 边界判断
cmpq 1, %rdi
jne .L6
ret
# 直接退出
.L7:
movl $1, %eax
ret
for循环,类似于while循环的实现
Note
在阅读汇编代码中的循环时,首先根据sub+cmp+jmp辨别出循环代码的部分,sub或者cmp指令中涉及的寄存器就是循环变量,jmp到前面的地址就是循环体的开始部分,循环体之前部分就是初始化;然后根据sub/cmp/jmp的具体类型和分布判断循环的类型;最后分析循环体的功能
3.2 if-else分支
goto翻译,与循环类似,先计算test-exp,然后根据结果跳转到不同部分执行
条件传送。将goto中的跳转结构变成了顺序指令,保证了流水线的稳定
- step1: 计算test-exp
- step2:计算if分支
- step3:计算else分支
- step4:cmovge if, else
例如:
long asbdiff(long x, long y) {
long result;
if(x < y) {
result = y - x;
} else {
retult = x - y;
}
return result;
}
翻译成:
# x in %rdi, y in %rsi
absdiff:
movq %rsi, %rax
subq %rdi, %rax # y - x
movq %rdi, %rdx
subq %rsi, %rdx # x - y
cmpq %rsi, %rdi
cmovge %rdx, %rax x >= y, 传送x - y, 否则传送y - x
ret
3.3 switch分支
采用了跳转表+间接跳转


3.4 函数调用
运行时栈
- 存放着传递控制(pc指针,即返回地址)和数据(函数参数等信息)、内存分配的信息(寄存器的值或者栈上分配的变量空间)
- 不是所有的函数都需要栈帧
调用的实现
step1: 传递参数,将参数放到rdi rsi rdx rcx r8 r9寄存器以及栈上
step2:执行call指令,进入被调函数,运行
step3:将操作结果放入rax或者指定位置,恢复保留寄存器的值,返回
以拆炸弹实验代码为例:
# 后面要用到rbp rbx寄存器,先压栈
400efc: 55 push %rbp
400efd: 53 push %rbx
# 可以看出栈指针被当做第二个参数传递给了read_six_numbers函数,此处的28为十六进制数,
400efe: 48 83 ec 28 sub $0x28,%rsp
400f02: 48 89 e6 mov %rsp,%rsi # 传递参数
400f05: e8 52 05 00 00 callq 40145c <read_six_numbers> # 调用函数,运行
3.5 递归
主要是保护好调用者保留寄存器

4. 工具使用
4.1 gcc
编译过程:

- 预处理:宏定义展开、头文件展开、条件编译等,同时将代码中的注释删掉,这里并不会检查语法;
- 编译:检查语法,将预处理后的文件编译成汇编文件;
- 汇编: 将汇编文件生成目标文件(二进制文件);
- 链接: C语言写的程序是需要依赖各种库的,所以编译之后还需要把库链接到可执行程序中去。
命令:
| 步骤 | 命令 |
|---|---|
| 预处理 | gcc -E hello.c -o hello.i |
| 编译 | gcc -S hello.i -o hello.s |
| 汇编 | gcc -c hello.s -o hello.o |
| 链接 | gcc hello.o -o hello_elf |
如果要一步到位直接生成可执行文件,命令为
gcc hello.c -o hello可以在gcc后指定 -Og/-O1/-O2设定编译优化级别
以一下代码为例:
//main.c
#include<stdio.h>
#include<stdlib.h>
void multstore(double, double, double*);
int main(){
double d;
multstore(2, 3, &d);
printf("2 * 3-->%ld\n", d);
system("pause");
return 0;
}
double mult2(double a, double b){
double s = a * b;
return s;
}
//mstore.c
double mult2(double, double);
void multstore(double x, double y, double *dest){
double t = mult2(x,y);
*dest = t;
}
预处理:
gcc main.c -o main.i
gcc mstore.c -o mstore.i
编译:
gcc -S main.i -o main.s
gcc -S mstore.i -o mstore.s
汇编:
gcc -c main.s -o main.o
gcc -c mstore.s -o mstore.o
链接:
gcc main.o mstore.o //不指定名称,默认为a.exe
一步:
gcc main.c mstore.c -o test//指定名称为test.exe
4.2 gdb&objdump
机器代码反汇编到汇编:
objdump -d target.o
如:

汇编到C?
4.3 makefile
makefile的格式
target ... : prerequisites ...
command
...
...
target:输出文件的名称
prerequisites:输入的文件名称
command:一系列的gcc命令
test : main.o mstore.o
gcc main.o mstore.o -o test
main.o : main.s
gcc main.s -o main.o
mstore.o : mstore.s
gcc mstore.s - mstore.o
main.s : main.c
gcc main.c -o main.s
mstore.s : mstore.c
gcc mstore.c -o mstore.s
make的高级特性
自动推导:make可以识别一个
.o文件,自动将对应的.c文件加在依赖关系中。并且也会自动推导出相关的编译命令。因此上述的makefile文件可以只包含前两行使用变量
objs = main.o mstore.o
test : $(objs)
gcc $(objs) -o test
【CSAPP】第三章 程序的机器级表示的更多相关文章
- CSAPP:第三章程序的机器级表示2
CSAPP:程序的机器级表示2 关键点:算术.逻辑操作 算术逻辑操作1.加载有效地址2.一元二元操作3.移位操作 算术逻辑操作 如图列出了x86-64的一些整数和逻辑操作,大多数操作分成了指令类( ...
- CSAPP:第三章程序的机器级表示1
CSAPP:程序的机器级表示1 关键点:数据格式.操作数指示符. 数据格式访问信息操作数指示符举例说明 数据格式 术语字(word)表示16位数据类型,32位数为双字(double words), ...
- CSAPP:第三章程序的机器级表示3
程序的机器级表示3 关键点:过程.调试.指针 过程1.运行时栈2.转移控制3.数据传递4.栈上的局部存储5.寄存器中的局部存储空间理解指针使用GDB调试器 过程 1.运行时栈 x86-64的栈向低 ...
- 【CSAPP】三、程序的机器级表示
本章基于两种相关的机器语言:Intel IA32和x86-64,前者注重32位,后者注重64位. 本章脉络:c\汇编\机器码之间的关系,数据的表示,控制结构如何实现.运行栈,局部变量的存储,数据结构. ...
- 深入理解计算机系统 第三章 程序的机器级表示 Part2 第二遍
第一遍对应笔记链接 https://www.cnblogs.com/stone94/p/9943779.html 本章汇编代码中常出现的几个指令及其含义 1.push 操作数的个数:1 将操作数(一般 ...
- 深入理解计算机系统 第三章 程序的机器级表示 part1
如题所示,这一章讲解了程序在机器中是怎样表示的,主要讲汇编语言与机器语言. 学习什么,为什么学,以及学了之后有什么用 我们不用学习如何创建机器级的代码,但是我们要能够阅读和理解机器级的代码. 虽然现代 ...
- 深入理解计算机系统 第三章 程序的机器级表示 Part1 第二遍
第一遍对应笔记链接 https://www.cnblogs.com/stone94/p/9905345.html 机器级代码 计算机系统使用了多种不同形式的抽象,利用更简单的抽象模型来隐藏实现的细节. ...
- 深入理解计算机系统 第三章 程序的机器级表示 part2
这周由于时间和精力有限,只读一小节:3.4.4 压入和弹出栈数据 栈是一种特殊的数据结构,遵循“后进先出”的原则,可以用数组实现,总是从数组的一端插入和删除元素,这一端被称为栈顶. 栈有两个常用指令 ...
- 深入理解计算机系统 第三章 程序的机器级表示 part3
这周看了刘老师提供的相关视频,以及书中对应的章节“3.7 过程” 这一节分为运行时栈.转移控制.数据传送.栈上的局部存储.寄存器中的局部存储空间和递归过程这 6 个小节 其中前 3 小节看懂了一部分内 ...
随机推荐
- CF74A Room Leader 题解
Content 一场 CF 比赛有 \(n\) 个人,有 ABCDE 五道题目.在比赛过程中,参赛者还可以随时互相攻击,成功一次加 \(100\) 分,失败一次扣 \(50\)分,已知第 \(i\) ...
- Linux中磁盘管理与三剑客之awk初识
昨日内容回顾 1.用两种方法实现 将文件中以 # 开头的行 把 # 去掉 sed -r 's/^ *#//g' /etc/fstab cat /etc/fstab | tr -d '^#' 2.将文件 ...
- [C# Expression] 之基础概念
00 | 什么是表达式树 表达式树以树形数据结构表示代码,其中每一个节点都是一种表达式,比如方法调用和 x < y 这样的二元运算等.可以对表达式树中的代码进行编辑和运算. 这样能够动态修改可执 ...
- 前端实现list排序
需求 针对list中某个字段,实现list的升序和降序 效果图 代码 我是用在angular1.X中项目的,根据list中的sort字段进行排序. # sort.html <style> ...
- 解决Xshell 连接Linux 窗口不活动会自动断开连接
修改linux服务器ssh断开时间 修改profile配置 vim /etc/profile 增加配置 后面单位秒 这里就是三分钟不活动断开连接 TMOUT=180 然后使用 wq! 进行保存,使 ...
- RPA账户和密码管理方案
如何将登录业务系统的账户和密码"更好的,更合适"地交给RPA? 相信很多小伙伴们在做RPA的时候, 都会或多或少的遇到类似的问题. 正常情况下IT管理人员都会给真实的业务人员分配业 ...
- vue中的数据代理原理
const vm = new Vue({ data:{ name:'boos' } }) // 注意 :使用构造函数构建vue实例时,传入的是一个option对象,它包含了data,computed等 ...
- 【LeetCode】1462. 课程安排 IV Course Schedule IV (Python)
作者: 负雪明烛 id: fuxuemingzhu 个人博客:http://fuxuemingzhu.cn/ 目录 题目描述 题目大意 解题方法 DFS 日期 题目地址:https://leetcod ...
- 【LeetCode】846. Hand of Straights 解题报告(Python & C+)
作者: 负雪明烛 id: fuxuemingzhu 个人博客: http://fuxuemingzhu.cn/ 目录 题目描述 题目大意 解题方法 日期 题目地址:https://leetcode.c ...
- XSLT映射文件函数
任何的编程语言或者是SQL语句都有内置的函数或方法,而强大灵活的xslt技术也是如此.熟练掌握XSLT的常用函数的用法,XSLT的应用将变得如此轻松,你会发现XSLT比想象中还要牛!以下是xslt数值 ...
