32位汇编语言helloworld_手撕编译原理:GNU 汇编语言

3237728c70a3045b2da860c0cb7811d9.png

image.png

我们目前用的电脑,CPU 一般是 x86-64 架构,是 64 位机。

main.c

#include 

int main(int argc, char *argv[]) {
printf("Hello, 1519!\n");
return 0;
}

gcc -S -O2 main.c -o main.s

    .section    __TEXT,__text,regular,pure_instructions
.build_version macos, 10, 15 sdk_version 10, 15, 4
.globl _main ## -- Begin function main
.p2align 4, 0x90
_main: ## @main
.cfi_startproc
## %bb.0:
pushq %rbp
.cfi_def_cfa_offset 16
.cfi_offset %rbp, -16
movq %rsp, %rbp
.cfi_def_cfa_register %rbp
leaq L_str(%rip), %rdi
callq _puts
xorl %eax, %eax
popq %rbp
retq
.cfi_endproc
## -- End function
.section __TEXT,__cstring,cstring_literals
L_str: ## @str
.asciz "Hello, 1519!"


.subsections_via_symbols
as main.s -o main.o
gcc main.o -o main
./main

Hello, 1519!

汇编语言的组成元素这段代码里有指令、伪指令、标签和注释四种元素,每个元素单独占一行。

  • 指令(instruction)是直接由 CPU 进行处理的命令:

开头的一个单词是助记符(mnemonic),后面跟着的是操作数(operand),有多个操作数时以逗号分隔。

    movq    %rsp, %rbp
  • 伪指令以“.”开头,末尾没有冒号“:”

.subsections_via_symbols
  • 标签以冒号“:”结尾,用于对伪指令生成的数据或指令做标记。例如 L_.str: 标签是对一个字符串做了标记。其他代码可以访问标签,例如跳转到这个标签所标记的指令。

L_str:                                  ## @str
.asciz "Hello, 1519!"
  • 第四种元素,注释,以“#”号开头,这跟 C 语言中以 // 表示注释语句是一样的。

详细了解指令这个元素

33e25355d1113ea00549cd8dfd4ddfd9.png

image.png

而在指令中使用操作数,可以使用四种格式,它们分别是:立即数、寄存器、直接内存访问和间接内存访问。

立即数以 $ 开头

GNU 的汇编器规定寄存器一定要以 % 开头。

movl $15, %eax

直接内存访问

当我们在代码中看到操作数是一个数字时,它其实指的是内存地址。

间接内存访问:带有括号,比如(%rbp),它是指 %rbp 寄存器的值所指向的地址。

间接内存访问的完整形式是:

偏移量(基址,索引值,字节数)

这样的格式。

其地址是:

基址 + 索引值 * 字节数 + 偏移量

mov 指令

mov 寄存器|内存|立即数, 寄存器|内存

lea 指令,lea 是“load effective address”的意思,装载有效地址。

lea 源,目的

add 指令是做加法运算,它可以采取下面的格式:

add 立即数, 寄存器
add 寄存器, 寄存器
add 内存, 寄存器
add 立即数, 内存
add 寄存器, 内存

其他算术运算的指令:

779b2b9760e29547ebf5fb3f5ad585c0.png

image.png

栈:

8a118824614d6925bad48afba343de14.png

image.png

b40a3d6e5fd4073fdcac1fb051f63fe5.png

image.png

eaa92943b8e362e18b5fe2a0c7d1dbc5.png

image.png

6 个的参数

4ccc9514242cb7a35f787768811a6bab.png

image.png

超过 6 个的参数

ca4ec191916cc2ec400990e10a45bbaa.png

image.png

跳转类:

6c1c5614e7318e1a3c82561ff1c556c3.png

image.png

过程调用:

8e8682bfce33c2c94a523318be3b9e70.png

image.png

比较操作:

9fc7ddc699fa39efa15357bc0efc730f.png

image.png

022e38f7c5f1a63fb957185838798e55.png

image.png

x86-64 架构的寄存器

在汇编代码中,我们经常要使用各种以 % 开头的寄存器的符号。初学者阅读这些代码时,通常会有一些疑问:有几个寄存器可以用?我可以随便用它们吗?使用不当会不会造成错误?等等。所以,有必要让你熟悉一下这些寄存器,了解它们的使用方法。x86-64 架构的 CPU 里有很多寄存器,我们在代码里最常用的是 16 个 64 位的通用寄存器,分别是:

%rax,%rbx,%rcx,%rdx,%rsi,%rdi,%rbp,%rsp,
%r8,%r9,%r10,%r11,%r12,%r13,%r14,%r15。
  • %rax 除了其他用途外,通常在函数返回的时候,把返回值放在这里。

  • %rsp 作为栈指针寄存器,指向栈顶。

  • %rdi,%rsi,%rdx,%rcx,%r8,%r9 给函数传整型参数,依次对应第 1 参数到第 6 参数。超过 6 个参数怎么办?放在栈桢里。

  • 如果程序要使用 %rbx,%rbp,%r12,%r13,%r14,%r15 这几个寄存器,是由被调用者(Callee)负责保护的,也就是写到栈里,在返回的时候要恢复这些寄存器中原来的内容。其他寄存器的内容,则是由调用者(Caller)负责保护,如果不想这些寄存器中的内容被破坏,那么要自己保护起来。

8255de376048bc2a2dff4d76ffe60839.png

image.png

寄存器

8d3c732b2608cb885789bd2866216131.png

寄存器

  • 8 个 80 位的 x87 寄存器,用于做浮点计算;

  • 8 个 64 位的 MMX 寄存器,用于 MMX 指令(即多媒体指令),这 8 个跟 x87 寄存器在物理上是相同的寄存器。在传递浮点数参数的时候,要用 mmx 寄存器。

  • 16 个 128 位的 SSE 寄存器,用于 SSE 指令。我们将在应用篇里使用 SSE 指令,讲解 SIMD 的概念。

  • 指令寄存器,rip,保存指令地址。CPU 总是根据这个寄存器来读取指令。

  • flags(64 位:rflags, 32 位:eflags)寄存器:每个位用来标识一个状态。比如,它们会用于比较和跳转的指令,比如 if 语句翻译成的汇编代码,就会用它们来保存 if 条件的计算结果。

参考:https://time.geekbang.org/column/intro/219

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值