学习参考文章,视频:
【CSAPP-深入理解计算机系统】3-1.程序的机器级表示_哔哩哔哩_bilibili
程序的机器级表示_晨哥是个好演员的博客-CSDN博客_程序的机器级表示
【CSAPP】第三章 程序的机器级表示 - 码农教程 (manongjc.com)
程序编码、
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设定编译优化级别,-Og 为原生样式,与源代码最相近,使用优化可以提高性能,不过优化后代码会和原先的代码有不同。
现在有两个文件,一个为 mstroe.c,另一个为main.c
//mstore.c
double mult2(double, double);
void multstore(double x, double y, double *dest)
{
double t = mult2(x,y);
*dest = t;
}
//main.c
#include<stdio.h>
#include<stdlib.h>
#include<mstore.c>
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 汇编后,gcc mstore.c -S -Og -o mstore.s,得到一个汇编文件,打开看是下面这样的
.file "mstore.c"
.text
.globl multstore
.type multstore, @function
multstore:
.LFB0:
.cfi_startproc
pushq %rbx
.cfi_def_cfa_offset 16
.cfi_offset 3, -16
movq %rdi, %rbx
call mult2
movsd %xmm0, (%rbx)
popq %rbx
.cfi_def_cfa_offset 8
ret
.cfi_endproc
.LFE0:
.size multstore, .-multstore
.ident "GCC: (GNU) 8.5.0 20210514 (Red Hat 8.5.0-15)
.section .note.GNU-stack,"",@progbits
其中以 . 开头的行都是指导汇编器和链接器工作的伪指令,也就是说我们完全可以忽略这些以 . 开头的行,删除掉无关信息后,剩余的这些汇编代码就是与源文件的c代码有关的代码。
multstore:
pushq %rbx
movq %rdi, %rbx
call mult2
movsd %xmm0, (%rbx)
popq %rbx
ret
简单介绍一下寄存器的知识:
在牙膏厂x86_64位的处理器中包含有16个通用目的的寄存器,这些寄存器用来存放整数数据和指针,且都是以%r开头。

介绍两个概念 -- 调用者保存寄存器和被调用者保存寄存器:
假设有如下场景:函数A调用了函数B,函数A称为调用者,函数B称为被调用者。由于调用了函数B,寄存器rbx的值被修改了,但是逻辑上寄存器rbx的值需要在调用B函数前后保持一致。解决者问题有下面两个方法。
func A:
...
call func B
...
func B:
...
调 用 者 保 存 寄 存 器 :
func A:
...
save register %rbx 在调用B函数之前保存寄存器rbx的值
call func B
restore register %rbx 调用结束后回复寄存器rbx的值
...
func B:
...
如上述代码所示,寄存器%rbx是由函数B的调用者,即函数func A来保存并且恢复的。函数B感知不到这个情况,可以尽情使用寄存器%rbx。
被 调 用 者 保 存 寄 存 器 :
func A:
...
call func B
...
func B:
save register %rbx 函数B在使用寄存器rbx之前先保存寄存器rbx的值
...
restore register %rbx 使用完后回复寄存器rbx的值
如代码所示,寄存器%rbx是由被调用函数,即函数B来保存并回复的。函数A感知不到这个情况。
对于这16个寄存器要使用哪一种策略,有具体的定义,如下所示:
- 调用者保存: %rax,%rdi,%rsi,%rdx,%rcx,%8,%r9,%r10,%r11
- 被调用者保存: %rbx ,%rbp,%r12,%r13,%r14,%r15
接下来我们先去了解几个主要寄存器的功能:


可以看到rax
一般用于存放要返回的值,rdi
、rsi
、rdx
、rcx
、r8
、r9
分别用于存放当前函数的六个参数值,这七个寄存器均采用调用者保存寄存器策略,rsp
寄存器在上面提到过,是栈顶指针寄存器。
了解了这些后,这样就可以解释一些汇编代码了:
multstore:
pushq %rbx
movq %rdi, %rbx
call mult2
movsd %xmm0, (%rbx)
popq %rbx
ret
double mult2(double, double);
void multstore(double x, double y, double *dest)
{
double t = mult2(x,y);
*dest = t;
}
寄存器%xmm0 和 movsd 都是新的玩意,我是在linux云服务器编译的,与原书本上的不同,但是功能是相同的。
pushq %rbx 的意思为保存寄存器 rbx 的内容,而 popq %rbx 的意思为恢复寄存器 rbx 的内容。
movq %rdi,%rbx 的意思为将寄存器 rdi 的值放到寄存器 rbx 上。而 mov 后面的 q 表示的是数据的大小,如下所示:

继续分析我们的代码,根据上面不同寄存器具有不同功能得知,mulstore
函数的三个参数分别保存至rdi
、rsi
、rdx
中:
call mult2 则是调用mult2函数,将得到的结果保存在寄存器 %xmm0 中。
movsd %xmm0, (%rbx) 则是将寄存器 %xmm0 的结果发送到内存中保存,可以看到rbx寄存器外加了括号,这类似C语言中指针解引用,说明rbx存放的是地址,我们通过地址寻找到具体地址中存放的值,即代码中dest指针指向的值,然后将 xmm0 寄存器的值移动至rbx中,xmm0 存放着mult2函数的返回值。
ret 则是函数返回,退出。
Comments NOTHING