文章目录
一、第三章:整章阅读的目的和获得好处
1)了解由C编译器生成的x86-64机器代码,说明为不同控制结构(比如条件、循环和开关语句)生成的基本指令模式。
2)了解栈分配、寄存器使用惯例和参数传递过程的实现。
3)了解不同数据结构(如结构、联合和数组)的分配和访问方式
4)了解整数和浮点数算术运算的指令实现
5)通过分析程序在机器级的方法来理解常见的代码安全漏洞(例如缓冲区溢出),以理解程序员、编译器和操作系统可以采取的减轻这些威胁的措施。
二、编译系统过程
- 结果
hello.c通过gcc编译编程可执行文件hello
- 过程
hello.c->预处理->hello.i->编译->hello.s->汇编->hello.o 和.h文件链接成可执行文件
- 因特尔器处理器的发展历史
1978年发布了第一款微处理器8086,到如今,发展的大意实图如下所示
三、涉及到的代码
- 代码
-
- main.c
#include <stdio.h>
void mulstore(long,long,long*);
int main()
{
long d;
multstore(2,3,&d);
printf("2 * 3 --> %ld\n",d);
return 0;
}
long mult2(long a, long b)
{
long s = a*b;
return s;
}
- mstore.c
long mult2(long , long );
void mulstore(long x, long y ,long *dest)
{
long t = mult2(x,y);
* dest = t;
}
- 编译指令讲解
gcc -Og -o prog main.c mstore.c
1)-Og是告诉编译器生成符合编译器生成符合原始C代码整体结构的机器代码,实际项目中会使用-O1
或-O2,甚至更高的编译优化选项,但是使用高级别的优化产生的代码会严重变形,导致产生的机器代码与最初的源代码之间的关系难以理解(这里为了方便理解,选择-Og这个优化选项)
2)-o 后面的参数prog表示生成可执行文件的文件名
四、.c文件生成汇编文件及详细说明
4.1 汇编代码及生成
- 指令
gcc -Og -S mstore.c
-
作用
生成mstore.c 对应的汇编文件mstore.s -
参数解释
-S这个编译选项就是告诉编译器GCC产生的文件为汇编文件 -
汇编指令
.file "mstore.c"
.text
.globl mulstore
.type mulstore, @function
mulstore:
.LFB0:
.cfi_startproc
pushq %rbx
.cfi_def_cfa_offset 16
.cfi_offset 3, -16
movq %rdx, %rbx
call mult2@PLT
movq %rax, (%rbx)
popq %rbx
.cfi_def_cfa_offset 8
ret
.cfi_endproc
.LFE0:
.size mulstore, .-mulstore
.ident "GCC: (Ubuntu 7.5.0-3ubuntu1~18.04) 7.5.0"
.section .note.GNU-stack,"",@progbits
4.2 汇编代码讲解
1)其中以“.”开头的行都是指导汇编器和链接器工作的伪指令,也就是说可以忽略以"."开够行
2)push %rbq 将寄存器rbx的值压入程序栈进行保存,保存寄存器rbx的内容,在函数返回之前使用了pop指令,恢复寄存器rbx的内容
3)movq %rdx , %rbx
将寄存器rdx的内容复制到寄存器rbx,这条指令结束后,寄存器rbx与寄存器rdx的内容一致,都是dst指针所指向的内存地址
(根据寄存器用法的定义,函数multstore的三个参数分别保存在寄存器rdi,rsi,rdx中)
4)move指令的后缀"q"表示数据的大小
5)由于早期的机器是16位,后来才扩展到32位和64位,因此intel用字(word)来表示16位的数据类型,所以32位的数据类型称为双字,64位的数据类型称为四字
movb -》是move byte的缩写,表示传送字节
movw -》是move word的缩写,表示传送字
movl -》表示传送双字(move double word),其中l是long word的缩写
movq -》 表示传送四字
6)call指令对应于C代码中的函数调用,这一行代码比较容易理解,该函数的返回值会保存在寄存器rax中,rax保存了x和y的乘积结果
7)吓一跳指令movq %rax ,(%rbx)
将寄存器rax的值送到内存中,内存的地址就存放在寄存器rbx中(由于是被调用者寄存器测录)
8)最后一条ret就是函数返回
五、寄存器简单介绍
-
分类
64位操作系统有16个寄存器,可简单分类为调用者保存寄存器和被调用者保存寄存器 -
例子(函数A调用了函数B)
(1)函数A称为调用者,函数B称为被调用者
(2)由于调用了函数B,寄存器rbx在函数B中被修改了
(3)逻辑上寄存器rbx的内容在调用函数B的前后应该保持一致,为了实现这个一致,有两个策略:
策略一
:函数A在调用函数B之前,提前保存寄存器rbx的内容,执行函数B之后,再恢复寄存器rbx原来存储的内容,这种策略称为调用者保存
;
策略二
:函数B在使用寄存器rbx之前,先保存寄存器rbx的值,在函数B返回之前,先恢复rbx原来存储的内容,这种策略被称之为被调用者保存
-
寄存器使用策略选择
不同的寄存器被定义成不同的策略,具体如下所示
Callee saved: (被调用者保存模式)
Caller saved : (调用者保存模式)
五、C代码如何翻译成机器代码
- 编译指令
gcc -Og -c mstore.c
-
指令执行结果
执行这条命令即可产生mstore.c所对应的机器代码文件mstore.o,由于该文件是二进制的,所以无法直接查看,这里需要借助反汇编工具-objdump,汇编器将汇编代码翻译成二进制的机器代码(.o文件),反汇编器就是将机器代码(.o文件)翻译成汇编代码(.s代码) -
反汇编指令
objdump -d mstore.o
- 汇编代码
root@iZbp1do67v9l7zwkcs2b22Z:~/code/test# objdump -d mstore.o
mstore.o: file format elf64-x86-64
Disassembly of section .text:
0000000000000000 <mulstore>:
0: 53 push %rbx
1: 48 89 d3 mov %rdx,%rbx
4: e8 00 00 00 00 callq 9 <mulstore+0x9>
9: 48 89 03 mov %rax,(%rbx)
c: 5b pop %rbx
d: c3 retq
root@iZbp1do67v9l7zwkcs2b22Z:~/code/test#
- 对比汇编得到的汇编代码和编译得到的汇编代码(反汇编代码省略了很多指令后缀的"q",但在call 、ret指令添加了后缀"q")