CSAPP 笔记三 程序的机器级表示
更新历史
- 23.05.24:初稿
系列
Basics
Intel x86 Processors
CISC - Complex 复杂指令集,RISC - Reduced 精简指令集。
x86-64,Linux的命名
ARM架构- Acorn RISC Machine
两种汇编语法:AT&T和Intel。Linux和课程使用的是AT&T。
C,assembly, machine code
- Micro architecture:微指令架构,CSAPP中很少涉及
Turning C into Object Code
- 编译命令
gcc -Og p1.c p2.c -o p
gcc -Og -S sum.c
:-S
:编译阶段终止,生成汇编语言-Og
:Optimize编译优化,O1
是过去的优化级别,g
级可以使代码更可读
Assembly Characteristics
Data Types
integer: 以1,2,4,8字节存储
数据和地址都用整型保存,不区分无符号和有符号。floating point: 4,8,10字节
没有数组以及结构体,这些是由编译器构造的。
Operations
执行算术操作
在内存和寄存器之间传送数据
转移控制
Disassembling Object Code
指令长度在1-15个字节
反汇编:将目标代码文件反汇编为类汇编语言。
objdump -d sum
: 反汇编
Assembly Basics: Registers, operands, move
Integer Registers
参数:1
rdi
, 2rsi
, 3rdx
, 4rcx
, 5r8
, 6r9
,只能传递整型或者指针,浮点数由另外一组单独的寄存器传递。超出的参数存在栈里。返回值:
rax
Moving Data
mov Source, Dest
不允许从内存取出,存入内存。从内存取出,只能存入寄存器。
mov $0x4, %rax
:目的地址是寄存器mov $0x4, (%rax)
: 目的地址是内存D(R)
: Mem[Reg[R] + D]D(Rb, Ri, S)
: Mem[Reg[Rb] + S * Reg[Ri] + D]
Arithmetic & logical operations
lea Src, Dst
C语言中的&,取地址
实际使用中C语言编译器喜欢用这条指令做算术运算,lea (%rdi, %rdi, 2), %rax
, mov是将地址指向的内存值取出,lea指令将内存值所在地址取出,相当于存入的是%rdi + %rdi * 2
。
Some ArTwo Operand Instructions
目的操作数作为第一个操作数,类似
x+=y
算术左移和逻辑左移相同
One Operand Instructions
Control
Condition Codes
Single bit registers
CF
: Carry Flag (for unsigned),同样表示无符号数溢出SF
: Sign Flag (for signed),负数为1ZF
: Zero FlagOF
: Overflow Flag (for signed)
Explicit Setting
Compare
cmpq Src2, Src1
类似减法,Src1 - Src2,但是不保存值,只改变四个条件码
溢出:
cmp b, a
,有符号补码
同号不会溢出,异号才会溢出负溢出:a-,b+, a-b>0
正溢出:a+,b-,a-b<0
Test
testq Src2, Src1
与运算,改变SF和ZF。
SetX Instructions
x86-64中4字节计算结果会将高4字节零填充,而2字节操作只会改变2字节。
Conditional branches
Jumping
JX Instructions
Using Conditional Moves
分支预测技术,更多时候分支预测正确率很低,选择执行分支的两部分指令,计算出两个结果,在最后一分钟选择需要的结果。只适用在两个分支都是简单计算。
cmovle
: 小于等于的时候移动
Loops
C代码中最底层的控制就是跳转和测试。
Switch Statements
用跳转表保存代码块的地址,可以快速跳转到该地址。
无论最小值是多少,通过增加偏置变为0
跳转表由编译器生成,汇编程序填写。
值范围很大,相对稀疏,会转变为条件树。
Procedures
Stack Structure
内存地址从下往上递增
栈底在上,push时rsp减小
Push & Pop
Push:先减小,在写入
Pop:rsp增加
Calling Conventions
Passing Control
call label
: 返回地址入栈,跳转到labelret
: 出栈,返回pc
寄存器就存在rip
。
Passing data
数据在寄存器和内存中传递,采用默认的规则,在不同的编译器下都可以传递参数。
Managing local data
Stack Frame
每个函数使用的内存块称为栈帧。
rbp
: 表示基指针,栈底。调用者的rbp
保存在被调用者的栈底。rsp
: 栈顶,当它被分配了多少字节,就知道需要释放多少字节
Illustration of Recursion
栈帧是递归调用的前提。
Data
Arrays
One-dimensional
- 指针加常量, 常量会被适当放缩。
Multi-dimensional(nested)
- 多维嵌套数组,按行或列连续存储。
Multi-level
- 三个数组,一个以为指针数组保存三个数组的起始地址。
Structures
Access
- 通过字节偏移访问对应的结构体成员。
Alignment
字节对齐,访问更方便,数据不会跨越多个块
占m字节的变量,就存放在m的倍数地址处
调整声明顺序,可以优化对齐
Floating Point
Advanced Topics
Memory Layout
目前64位的内存只允许使用47位。Linux栈的大小为8MB。
Stack: 局部变量
Text:执行的机器指令,只可读
Data:存放全局变量,静态变量,字符串常量
Heap:动态申请,malloc,calloc,new
Shared Libraries:存放库函数,动态加载
堆分配的内存是从高位低位向中间分配,中间的一部分没有分配的内存访问段错误。这是由操作系统的管理策略决定
Buffer Overflow
Vulnerability
gets()
: 函数不检查缓冲区大小,容易越界。strcpy
,strcat
,scanf
,fscanf
,sscanf
, 都有溢出风险。机器指令是小端存放的,低位在前。
Code Injection Attacks
代码注入攻击,把字符填充到缓冲区中,形成可执行的指令,修改返回指针。
Worm与Virus:蠕虫可以自己生存,病毒不能独自运行。
Protection
Randomized stack offsets栈随机化
栈随机化会让缓冲区随机变化,无法预测下一个地址。
Nonexecutable code segments
标记栈是不可执行的代码。
Stack Canaries
gcc -fstack-protector
默认启动栈保护
%fs
: 特殊寄存器,某块内存的值,如果Canay值改变了说明有溢出。Canay值是小端存放
。
Canay的值最低位字节为0,这是字符串的off-by-one bug,虽然字符串的空字符占用了Canay值,产生了溢出,但是检测不到溢出。
Return-Oriented Programming Attacks
Canay无法破解,但是我们知道代码在什么地方,全局变量和代码的位置没有改变,通过找到某段代码组合在一起,面向返回编程。
gadget: 通过截断一些指令,形成新的指令,替换了程序计数器
attack Lab中关闭了canary,可以通过缓冲区溢出设置需要的返回地址。
Unions
联合体只会为最大的域分配地址。
联合体可以用来做类型转换,不改变位。
大端小端
IA32:字节小端存放
x86-64:字节小端存放