美文网首页
Golang汇编

Golang汇编

作者: 酱油王0901 | 来源:发表于2020-04-18 15:50 被阅读0次

.... to be continued


Golang中很多代码实现,例如strings.Index(),调度器以及初始化等等都是用汇编实现的,因此需要对汇编有基本的了解。同时本文只专注于 AMD64 Linux 平台下 AT&T 格式的汇编指令。另外本文主要是作为一个学习笔记和总结,很多地方会引用参考文献中的内容。

基础知识

程序的存储空间布局

Program是为了完成指定的任务而准备好的一个指令序列。
C编译器将每个源文件(source file)翻译成object file,然后编译器将单独地object files与必需的库链接起来形成一个可执行模块(executable module)。当程序运行或执行时,操作系统将可执行模块拷贝到主内存中的程序镜像(program image)里。

进程(Process)是一个正在执行的程序实例(instance)。每个实例有自己的地址空间和执行状态。当操作系统向内核数据结构中添加了适当的信息,并为运行程序代码分配必要的资源之后,程序就变成了进程。

线程是代表了进程内执行线程(a thread of execution within a process)的一种抽象数据类型。线程有自己的执行栈,程序计数器值,寄存器集合和状态。

Layout of program image

一个程序本质上都是由 BSS 段data段text段三个组成的。可以看到一个可执行程序在存储(没有调入内存)时分为代码段、数据区和未初始化数据区三部分。

BSS段(未初始化数据区):在采用段式内存管理的架构中,BSS段(bss segment)通常是指用来存放程序中未初始化的全局变量的一块内存区域。BSS是英文Block Started by Symbol的简称。BSS段属于静态内存分配。
数据段:在采用段式内存管理的架构中,数据段(data segment)通常是指用来存放程序中已初始化的全局变量的一块内存区域。数据段属于静态内存分配。
代码段:在采用段式内存管理的架构中,代码段(text segment)通常是指用来存放程序执行代码的一块内存区域。这部分区域的大小在程序运行前就已经确定,并且内存区域属于只读。在代码段中,也有可能包含一些只读的常数变量,例如字符串常量等。

图片来自于Unix环境高级编程P164

可以通过size命令查看可执行二进制程序的section size以及total size,通过指定-A参数使其按照System V size输出。默认是按照Berkeley size输出。

(ENV) [root@ceph-2 ~]# size bazil
   text    data     bss     dec     hex filename
8373080  315568  144968 8833616  86ca50 bazil

可执行程序在运行时又多出两个区域:栈区堆区

  • 栈区:由编译器自动释放,存放函数的参数值、局部变量等。每当一个函数被调用时,该函数的返回类型和一些调用的信息被存放到栈中。然后这个被调用的函数再为他的自动变量和临时变量在栈上分配空间。每调用一个函数一个新的栈就会被使用。栈区是从高地址位向低地址位增长的,是一块连续的内存区域,最大容量是由系统预先定义好的,申请的栈空间超过这个界限时会提示溢出,用户能从栈中获取的空间较小。
  • 堆区:用于动态分配内存,位于BSS和栈中间的地址区域。由程序员申请分配和释放。堆是从低地址位向高地址位增长,采用链式存储结构。频繁的 malloc/free造成内存空间的不连续,产生碎片。当申请堆空间时库函数是按照一定的算法搜索可用的足够大的空间。因此堆的效率比栈要低的多。

指令集

可以通过如下的命令查询CPU支持的指令集。

(ENV) [root@ceph-2 ~]# cat /proc/cpuinfo | grep flags
flags       : fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush dts mmx fxsr sse sse2 ss ht syscall nx rdtscp lm constant_tsc arch_perfmon pebs bts nopl xtopology tsc_reliable nonstop_tsc aperfmperf pni pclmulqdq vmx ssse3 cx16 pcid sse4_1 sse4_2 x2apic popcnt tsc_deadline_timer aes xsave avx hypervisor lahf_lm tpr_shadow vnmi ept vpid tsc_adjust dtherm arat pln pts
flags       : fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush dts mmx fxsr sse sse2 ss ht syscall nx rdtscp lm constant_tsc arch_perfmon pebs bts nopl xtopology tsc_reliable nonstop_tsc aperfmperf pni pclmulqdq vmx ssse3 cx16 pcid sse4_1 sse4_2 x2apic popcnt tsc_deadline_timer aes xsave avx hypervisor lahf_lm tpr_shadow vnmi ept vpid tsc_adjust dtherm arat pln pts
flags       : fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush dts mmx fxsr sse sse2 ss ht syscall nx rdtscp lm constant_tsc arch_perfmon pebs bts nopl xtopology tsc_reliable nonstop_tsc aperfmperf pni pclmulqdq vmx ssse3 cx16 pcid sse4_1 sse4_2 x2apic popcnt tsc_deadline_timer aes xsave avx hypervisor lahf_lm tpr_shadow vnmi ept vpid tsc_adjust dtherm arat pln pts
flags       : fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush dts mmx fxsr sse sse2 ss ht syscall nx rdtscp lm constant_tsc arch_perfmon pebs bts nopl xtopology tsc_reliable nonstop_tsc aperfmperf pni pclmulqdq vmx ssse3 cx16 pcid sse4_1 sse4_2 x2apic popcnt tsc_deadline_timer aes xsave avx hypervisor lahf_lm tpr_shadow vnmi ept vpid tsc_adjust dtherm arat pln pts

汇编知识介绍

AT&T 汇编指令的基本格式为:

操作码 [操作数]

如果操作数有两个,则第一个为源操作数,第二个为目的操作数,目的操作数表示这条指令执行完后结果应该保存的地方。
对于 AT&T 格式的汇编指令,一些说明如下:

  1. 寄存器名需要加 % 作为前缀,立即数前加 $
  2. 寄存器间接寻址的格式为 offset(%register),如果 offset 为 0,则可以略去偏移不写直接写成 (%register)
  3. 与内存相关的一些指令的操作码会加上 bwlq 字母分别表示操作的内存是 124 还是 8 个字节,比如指令 movl $0x0,-0x8(%rbp) ,操作码 movl 的后缀字母 l 说明我们要把从 -0x8(%rbp) 这个地址开始的 4 个内存单元赋值为 0。

寄存器

应用层代码一般会用到三类 19 个寄存器:

  1. 通用寄存器(64 位):rax, rbx, rcx, rdx, rsi, rdi, rbp, rsp, r8, r9, r10, r11, r12, r13, r14, r15 寄存器。CPU 对这 16 个通用寄存器的用途没有做特殊规定,程序员和编译器可以自定义其用途(下面会介绍,rsp/rbp 寄存器其实是有特殊用途的);
  2. 程序计数寄存器(64 位,PC寄存器,有时也叫 IP 寄存器):rip 寄存器。它用来存放下一条即将执行的指令的地址,这个寄存器决定了程序的执行流程;
  3. 段寄存器fsgs 寄存器(两个都是 16 位)。一般用它来实现线程本地存储(TLS),比如 AMD64 linux 平台下 go 语言和 pthread 都使用 fs 寄存器来实现系统线程的 TLS。

有4个核心的伪寄存器,这4个寄存器是编译器用来维护上下文、特殊标识等作用的:

  • FP(Frame pointer): arguments and locals
  • PC(Program counter): jumps and branches
  • SB(Static base pointer): global symbols
  • SP(Stack pointer): top of stack

Plan 9汇编

LEAMOV,其中LEA用于操作地址;而MOV用于操作数据。例如:

LEAQ 8(SP), SI // argv 把 8(SP)地址放入 SI 寄存器中
MOVQ 0(SP), DI // argc 把0(SP)内容放入 DI 寄存器中

Go Assembler

(ENV) 🍺 /Users/xsky/go/src/github.com/microyahoo/go-exercises ☞ git:(master) ✗ go tool compile -S x.go
os.(*File).close STEXT dupok nosplit size=26 args=0x18 locals=0x0
    0x0000 00000 (<autogenerated>:1)    TEXT    os.(*File).close(SB), DUPOK|NOSPLIT|ABIInternal, $0-24
    0x0000 00000 (<autogenerated>:1)    FUNCDATA    $0, gclocals·e6397a44f8e1b6e77d0f200b4fba5269(SB)
    0x0000 00000 (<autogenerated>:1)    FUNCDATA    $1, gclocals·69c1753bd5f81501d95132d08af04464(SB)
    0x0000 00000 (<autogenerated>:1)    FUNCDATA    $2, gclocals·9fb7f0986f647f17cb53dda1484e0f7a(SB)
    0x0000 00000 (<autogenerated>:1)    PCDATA  $0, $1
    0x0000 00000 (<autogenerated>:1)    PCDATA  $1, $1
    0x0000 00000 (<autogenerated>:1)    MOVQ    ""..this+8(SP), AX
    0x0005 00005 (<autogenerated>:1)    MOVQ    (AX), AX
    0x0008 00008 (<autogenerated>:1)    PCDATA  $0, $0
    0x0008 00008 (<autogenerated>:1)    PCDATA  $1, $0
    0x0008 00008 (<autogenerated>:1)    MOVQ    AX, ""..this+8(SP)
    0x000d 00013 (<autogenerated>:1)    XORPS   X0, X0
    0x0010 00016 (<autogenerated>:1)    MOVUPS  X0, "".~r0+16(SP)
    0x0015 00021 (<autogenerated>:1)    JMP os.(*file).close(SB)
(ENV) 🍺 /Users/xsky/go/src/github.com/microyahoo/go-exercises ☞ git:(master) ✗ go build -o test_map2 test_map2.go
(ENV) 🍺 /Users/xsky/go/src/github.com/microyahoo/go-exercises ☞ git:(master) ✗ go tool objdump -s main.main test_map2
TEXT main.main(SB) /Users/xsky/go/src/github.com/microyahoo/go-exercises/test_map2.go
  test_map2.go:7    0x1099850       65488b0c2530000000  MOVQ GS:0x30, CX
  test_map2.go:7    0x1099859       488d442490      LEAQ -0x70(SP), AX
  test_map2.go:7    0x109985e       483b4110        CMPQ 0x10(CX), AX
  test_map2.go:7    0x1099862       0f8645030000        JBE 0x1099bad
  test_map2.go:7    0x1099868       4881ecf0000000      SUBQ $0xf0, SP
  test_map2.go:7    0x109986f       4889ac24e8000000    MOVQ BP, 0xe8(SP)
  test_map2.go:7    0x1099877       488dac24e8000000    LEAQ 0xe8(SP), BP
  test_map2.go:8    0x109987f       e88c28f7ff      CALL runtime.makemap_small(SB)
  test_map2.go:8    0x1099884       488b0424        MOVQ 0(SP), AX
  test_map2.go:8    0x1099888       4889442440      MOVQ AX, 0x40(SP)
  test_map2.go:9    0x109988d       488d0dcc890100      LEAQ runtime.types+100000(SB), CX
  test_map2.go:9    0x1099894       48890c24        MOVQ CX, 0(SP)
  test_map2.go:9    0x1099898       4889442408      MOVQ AX, 0x8(SP)
  test_map2.go:9    0x109989d       48c744241001000000  MOVQ $0x1, 0x10(SP)
  test_map2.go:9    0x10998a6       e8255af7ff      CALL runtime.mapassign_fast64(SB)
  test_map2.go:9    0x10998ab       488b7c2418      MOVQ 0x18(SP), DI
  test_map2.go:9    0x10998b0       48c7470801000000    MOVQ $0x1, 0x8(DI)
  test_map2.go:9    0x10998b8       833d21b20f0000      CMPL $0x0, runtime.writeBarrier(SB)
  test_map2.go:9    0x10998bf       0f85d7020000        JNE 0x1099b9c
  test_map2.go:9    0x10998c5       488d050f700300      LEAQ go.string.*+27(SB), AX
  test_map2.go:9    0x10998cc       488907          MOVQ AX, 0(DI)
  test_map2.go:10   0x10998cf       488d058a890100      LEAQ runtime.types+100000(SB), AX
  test_map2.go:10   0x10998d6       48890424        MOVQ AX, 0(SP)
  test_map2.go:10   0x10998da       488b4c2440      MOVQ 0x40(SP), CX
  test_map2.go:10   0x10998df       48894c2408      MOVQ CX, 0x8(SP)
  test_map2.go:10   0x10998e4       48c744241002000000  MOVQ $0x2, 0x10(SP)
  test_map2.go:10   0x10998ed       e8de59f7ff      CALL runtime.mapassign_fast64(SB)

可以用dlv查看程序运行时的寄存器的值

(ENV) 🍺 /Users/xsky/go/src/github.com/microyahoo/go-exercises ☞ git:(master) ✗ dlv exec ./test_map2
Type 'help' for list of commands.
(dlv) l
Stopped at: 0xeb8a19c
=>no source available
(dlv) regs
       rax = 0x0000000000000000
       rbx = 0x0000000000000000
       rcx = 0x0000000000000000
       rdx = 0x0000000000000000
       rdi = 0x0000000000000000
       rsi = 0x0000000000000000
       rbp = 0x0000000000000000
       rsp = 0x00007ffeefbff7e0
        r8 = 0x0000000000000000
        r9 = 0x0000000000000000
       r10 = 0x0000000000000000
       r11 = 0x0000000000000000
       r12 = 0x0000000000000000
       r13 = 0x0000000000000000
       r14 = 0x0000000000000000
       r15 = 0x0000000000000000
       rip = 0x000000000eb8a19c
    rflags = 0x0000000000000200
        cs = 0x000000000000002b
        fs = 0x0000000000000000
        gs = 0x0000000000000000
     fctrl = 0x037f
     fstat = 0x0000
      ftag = 0x0000
       fop = 0x0000
     fioff = 0x00000000
     fiseg = 0x0000
     fooff = 0x00000000
     foseg = 0x0000
     mxcsr = 0x00001f80 [RZ/RN=0 PM UM OM ZM DM IM]
 mxcsrmask = 0x0000ffff
    trapno = 0x00000000
       err = 0x00000000
faultvaddr = 0x0000000000000000
(dlv) b main.main
Breakpoint 1 set at 0x1099868 for main.main() ./test_map2.go:7
(dlv) c
> main.main() ./test_map2.go:7 (hits goroutine(1):1 total:1) (PC: 0x1099868)
Warning: debugging optimized function
     2:
     3: import (
     4:     "fmt"
     5: )
     6:
=>   7: func main() {
     8:     a := make(map[int]string)
     9:     a[1] = "a"
    10:     a[2] = "b"
    11:     b := get(a)
    12:     fmt.Println(b)
(dlv) regs
       rax = 0x000000c00006cee8
       rbx = 0x0000000000000000
       rcx = 0x000000c000000180
       rdx = 0x00000000010d7f10
       rdi = 0x000000c0000101f0
       rsi = 0x0000000000000001
       rbp = 0x000000c00006cfd0
       rsp = 0x000000c00006cf58
        r8 = 0x0000000000000011
        r9 = 0x0000000000000011
       r10 = 0x00000000010e88b0
       r11 = 0x0000000000000001
       r12 = 0xffffffffffffffff
       r13 = 0x0000000000000020
       r14 = 0x000000000000001f
       r15 = 0x0000000000000200
       rip = 0x0000000001099868
    rflags = 0x0000000000000206
        cs = 0x000000000000002b
        fs = 0x0000000000000000
        gs = 0x0000000000000000
     fctrl = 0x037f
     fstat = 0x0000
      ftag = 0x0000
       fop = 0x0000
     fioff = 0x00000000
     fiseg = 0x0000
     fooff = 0x00000000
     foseg = 0x0000
     mxcsr = 0x00001fa0 [RZ/RN=0 PM UM OM ZM DM IM PE]
 mxcsrmask = 0x0000ffff
    trapno = 0x00000001
       err = 0x00000000
faultvaddr = 0x00000000011b5000

References

相关文章

网友评论

      本文标题:Golang汇编

      本文链接:https://www.haomeiwen.com/subject/qsdbphtx.html