主页

索引

模块索引

搜索页面

汇编语言

原代码:

#include <stdio.h>
int main(int argc, char* argv[]){
    printf("Hello %s!\n", "Richard");
    return 0;
}

生成的汇编代码:

$ clang -S -O2 hello.c -o hello.s
或者:
$ gcc -S -O2 hello.c -o hello.s

-S 参数就是告诉编译器把源代码编译成汇编代码
-O2 参数告诉编译器进行 2 级优化

把这段汇编代码编译成可执行文件:

$ as hello.s -o hello.o   //用汇编器编译成目标文件
$ gcc hello.o -o hello   //链接成可执行文件
$ ./hello                 //运行程序
1. 指令(instruction)
    直接由 CPU 进行处理的命令
    如:
    pushq   %rbp
    movq    %rsp, %rbp
    说明:
        开头的一个单词是助记符(mnemonic),后面跟着的是操作数(operand)
        有多个操作数时以逗号分隔
2. 伪指令
    伪指令以“.”开头,末尾没有冒号“:”
    伪指令是是辅助性的,汇编器在生成目标文件时会用到这些信息,
    但伪指令不是真正的 CPU 指令,就是写给汇编器的
3. 标签
    标签以冒号“:”结尾,用于对伪指令生成的数据或指令做标记
    L_.str: 标签是对一个字符串做了标记
    标签很有用,它可以代表一段代码或者常量的地址(也就是在代码区或静态数据区中的位置)
4. 注释
    以“#”号开头,这跟 C 语言中以 // 表示注释语句是一样的
https://img.zhaoweiguo.com/knowledge/images/cores/compilers/assembler1.jpeg

助记符“movq”“xorl”中的“mov”和“xor”是指令,而“q”和“l”叫做后缀,表示操作数的位数。后缀一共有 b, w, l, q 四种,分别代表 8 位、16 位、32 位和 64 位

指令中使用操作数,可以使用四种格式,它们分别是:

1. 立即数
    立即数以 $ 开头, 比如 $40
    如: 把 40 这个数字拷贝到 %eax 寄存器
        movl $40, %eax

2. 寄存器
    注: GNU 的汇编器规定寄存器一定要以 % 开头

3. 直接内存访问
    操作数是一个数字时,它其实指的是内存地址(不要误以为它是一个数字,因为数字立即数必须以 $ 开头)
    汇编代码里的标签,也会被翻译成直接内存访问的地址。
        比如“callq   _printf”中的“_printf”是一个函数入口的地址
        汇编器帮我们计算出程序装载在内存时,每个字面量和过程的地址。
4. 间接内存访问
    带有括号,比如(%rbp),它是指 %rbp 寄存器的值所指向的地址

    间接内存访问的完整形式是:
        偏移量(基址,索引值,字节数)
        其地址是:基址 + 索引值 * 字节数 + 偏移量

举例来说:

8(%rbp),是比 %rbp 寄存器的值加 8
-8(%rbp),是比 %rbp 寄存器的值减 8
(%rbp, %eax, 4)的值,等于 %rbp + %eax*4
    这个地址格式相当于访问 C 语言中的数组中的元素,数组元素是 32 位的整数,
    其索引值是 %eax,而数组的起始位置是 %rbp。其中字节数只能取 1,2,4,8 四个值

几个常用的指令:

1. mov 指令
    格式: mov 寄存器|内存|立即数, 寄存器|内存

2. lea 指令
    lea 是“load effective address”的意思,装载有效地址
    格式: lea 源,目的
    例:
        leaq    L_.str(%rip), %rdi
        把字符串的地址加载到 %rdi 寄存器

3. 算术运算的指令:

     a) add
     b) sub
     c) imul
     d) xor
     e) or
     f) and
     g) inc
     h) dec
     i) neg
     ii) not

4. 与栈有关的操作

     a) push 源
     b) pop 目的

5. 跳转类

     a) jmp 标签/地址


6. 过程调用

     a) call 标签/地址
     b) ret

7. 比较操作

     a) cmp 源1, 源2
     b) test 源1, 源2

x86-64 架构的 CPU 最常用的是 16 个 64 位的通用寄存器:

%rax,%rbx,%rcx,%rdx,%rsi,%rdi,%rbp,%rsp
%r8,%r9,%r10,%r11,%r12,%r13,%r14,%r15

寄存器在历史上有各自的用途,比如:
    rax 中的“a”,是 Accumulator(累加器) 的意思,这个寄存器是累加寄存器

但随着技术的发展,这些寄存器基本上都成为了通用的寄存器
    为了方便软件的编写,我们还是做了一些约定,给这些寄存器划分了用途

    1. %rax 除了其他用途外,通常在函数返回的时候,把返回值放在这里
    2. %rsp 作为栈指针寄存器,指向栈顶
    3. %rdi,%rsi,%rdx,%rcx,%r8,%r9 给函数传整型参数,依次对应第 1 参数到第 6 参数
        超过 6 个参数怎么办?放在栈桢里
    4. 如果程序要使用 %rbx,%rbp,%r12,%r13,%r14,%r15 这几个寄存器
        是由被调用者(Callee)负责保护的,也就是写到栈里,在返回的时候要恢复这些寄存器中原来的内容。
        其他寄存器的内容,则是由调用者(Caller)负责保护,
        如果不想这些寄存器中的内容被破坏,那么要自己保护起来。

参考

主页

索引

模块索引

搜索页面