玩命加载中 . . .

3.1-程序的机器级表示


3.1 程序的机器级表示

编写main.cmstore.c

#include <stdio.h>

void mulstore(long, long, long*);

int main() {
    long d;
    mulstore(2, 3, &d);
    printf("2 * 3 --> %ld\n", d);
    return 0;
}

long mult2(long a, long b) {
    long s = a * b;
    return s;
}
long mul2(long, long);

void mulstore(long x, long y, long *dest) {
    long t = mul2(x, y);
    *dest = t;
}
x -> rdi
y -> rsi
dest -> rdx

两个文件一起编译生成可执行文件prog

gcc -Og -o prog main.c mstore.c

汇编

也可以编译单个文件,生成汇编文件mstore.s

gcc -Og -S mstore.c

删除部分无关代码
mulstore:
    pushq   %rbx
    movq    %rdx, %rbx
    call    mul2@PLT
    movq    %rax, (%rbx)
    popq    %rbx
    ret

pushq意思是将寄存器rbx的值压如程序栈进行保存

补充寄存器的知识:
在Intel x86-64的处理器中包含16个通用目的寄存器,这些寄存器用来存放整数数据和指针,都是以%r开头

为什么需要对寄存器进行压栈保存,因为在函数中修改了全局的东西,当函数调用完成之后要恢复原样

调用者保存寄存器和被调用者保存寄存器

函数A称为调用者(caller),函数B称为被调用者(callee),寄存器rbx在函数B中被修改了,逻辑上rbx的内容在调用函数B前后应该保持一致,解决这个问题有2个策略

  • 一个是函数A在调用函数B之前,提前保存寄存器rbx的内容,执行完函数B之后,再恢复rbx的内容,这个策略就称为调用者保存
  • 一个是函数B在使用寄存器rbx之前,先保存寄存器的内容,在函数B返回之前,先恢复rbx原来的内容,称之为被调用者保存

不同的寄存器采取不同的策略,具体如图所示

寄存器rbx被定义为被调用者保存寄存器,pushq就是用来保存rbx的内容,在函数返回之前,使用了pop指令,恢复rbx的内容

movq    %rdx, %rbx

第二行汇编的含义是将寄存器rdx的内容复制到寄存器rbx

根据寄存器用法的定义,函数mulstore的三个参数分别保存在rdi, rsi, rdx中,这条指令执行完,寄存器rbx的内容与寄存器rdx一致,都是dest指针所指向的内存的地址

movq指令的后缀q表示数据的大小,由于早期的机器是16位,后来才扩展到32位,因此,用字word表示16位的数据类型,32位称为双字,64位称为四字

图中给出了C语言的基本数据类型对应的汇编后缀表示

大多数GCC生成的汇编指令都有一个字符后缀来表示操作数的大小,例如,数据传送指令就有4个变种

  • movb: Move byte, 传送8位
  • movw: Move word, 传送16位
  • movl: Move double word, 传送32位
  • movq: Move quad word, 传送64位
call    mul2@PLT

call指令表示函数调用,返回值会保存在寄存器rax中,因此,寄存器rax中保存了xy的乘积结果

movq    %rax, (%rbx)

这条指令将寄存器rax的值送到内存中,内存的地址就存放在寄存器rbx

popq    %rbx

恢复寄存器rbx的值

反汇编

将编译选项-S替换为-c,就可以生成对应的机器码目标文件mstore.o

gcc -Og -c mstore.c

该文件是二进制文件无法直接查看,要用到反汇编工具objdump,将机器代码反汇编成汇编代码

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

通过对比反汇编得到的汇编代码与编译器直接生成的汇编代码,可以看到有细微的差异,反汇编代码省略了后缀q,但在callret指令后添加了后缀q,由于q只是表示大小指示符,大多数情况下是可以省略的


文章作者: kunpeng
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 kunpeng !
  目录