CSAPP 笔记三 程序的机器级表示

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

  • 参数:1rdi, 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),负数为1

  • ZF: Zero Flag

  • OF: 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: 返回地址入栈,跳转到label

  • ret: 出栈,返回

  • 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:字节小端存放