【CSAPP】第三章 程序的机器级表示

第三章 程序的机器级表示

1. 程序编码

1.1 gcc编译C程序

编译过程:

  • 预处理:宏定义展开、头文件展开、条件编译等,同时将代码中的注释删掉,这里并不会检查语法;
  • 编译:检查语法,将预处理后的文件编译成汇编文件;
  • 汇编: 将汇编文件生成目标文件(二进制文件);
  • 链接: 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
", 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

1.2 Cmake使用

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的高级特性

  1. 自动推导:make可以识别一个.o文件,自动将对应的.c文件加在依赖关系中。并且也会自动推导出相关的编译命令。因此上述的makefile文件可以只包含前两行

  2. 使用变量

    objs = main.o mstore.o
    test : $(objs)
    	gcc $(objs) -o test
    

1.3 反汇编

机器代码反汇编到汇编:

objdump -d target.o

如:

汇编到C?

2. 数据表示

  • 针对不同大小,数据传送指令分为movb(字节), movw(字), movl(双字), movq(四字)
  • 后缀 l 同时表示4字节整数和8字节双精度浮点数,根据指令和寄存器进行区分

3. 数据访问

3.1 整数寄存器

x86-64处理器包含了16个64bit的通用目的寄存器,存储整数和指针

明明规则:

  • 前8组以16bit寄存器为主,64 32和8bit分别带r e 和l的前缀或者后缀;
  • 后8组以64bit寄存器为主,命名为r8~r15,32 16 8bit分别带d w b后缀

功能:

  • 函数返回值:ax
  • 函数参数:di si dx cx r8 r9共9个,分别保存1到6个函数参数,超出6个的参数被放在被调函数的栈帧上
  • 被调用者保存寄存器:剩余的都是,不管哪个程序都可以使用,但是在使用前需要保存寄存器原始数据,使用完要恢复(在函数调用时,保存现场和恢复现场由被调函数完成)

3.2 寻址

共三类:

  • 立即数:只能是整数,浮点数没有立即数
  • 寄存器:16个寄存器的低位1 2 4 8字节
  • 内存引用:M[Addr]

寻址方式:有效地址=Imm + R[rb] + R[ri]*s,表示为Imm(rb, ri, s)

3.3 数据传送指令

简单传送指令

  • movabsq的目的地之只能是64位寄存器
  • movl会将寄存器高32位置0

扩充传送(将少位数的扩充后传送到多位数的)

3.4 出栈入栈

  • 对rsp寄存器操作
  • 向下生长
  • 按字节编址

3.5 算逻运算

  • 结果都存储在第二个寄存器处

  • 除了leaq外,其余指令都有 q l w b四个变种

  • 特殊算数运算:针对8字(128位)的运算

rax存乘数、乘积低位、商,rdx存乘积高位、余数

3.6 控制

控制码

进位:CF

零标志:ZF

负数标志:SF

溢出标志:OF

比较和测试

cmp S1, S2:基于S2-S1进行

test S1, S2:基于S1&S2

  • 都有b l w q四种变种
  • 只设置条件码,不改变寄存器

访问条件码

set指令

总结:需要区分有符号数和无符号数的指令有移位、特殊除法、set指令

跳转指令

  • 直接跳转:jump Label
  • 间接跳转:jump *Adrr

if语句实现

C语言模板:

if(test-expr){
    then-statement
}else{
    else-statement
}

汇编控制流:

t = test-expr
if(!t)
	goto false;
then-statement
goto done
false:
	else-statement
done:

用条件传送实现分支

  • 实现:计算出两种操作的结果,然后根据条件选择传送一种

    例如对于条件赋值表达式:v = test-expr ? then-expr : else-expr

    条件传送的控制流为:

    v = then-expr
    ve = else-expr
    t = test-expr
    if(!t)v = ve
    
  • 副作用:需要对then-expr和else-expr都求值,一方面增加了计算量,另一方面可能导致非法行为(非法地址访问)

循环实现

do-while循环

loop:
body-staement
t = test-expr
if(t) goto loop

while循环

  1. 中间跳转法
	goto test
loop:
	body-statement
test:
	t = test-expr
	if(t) goto loop
  1. guarded-do
t = test-expr
if(!t) goto done
loop:
	body-statement
	t = test-expr
	if(t) goto loop
done:

for循环,类似于while循环的实现

switch实现

跳转表+间接跳转

3.7 函数调用

函数调用的实现机制(P调用Q):

  • 控制转移:将Q的入口地址读到rsp中,将P的断点存到栈中
  • 传递数据:P将Q需要的参数传入,优先传到rdi rsi rdx rcx r8 r9
  • 分配和释放内存:Q的局部变量
原文地址:https://www.cnblogs.com/vinnson/p/13466078.html