├── Q.md ├── images ├── asm_func.png └── do_you_want_power.jpg ├── layout.md ├── readme.md └── refs.md /Q.md: -------------------------------------------------------------------------------- 1 | Q: 为什么要学习汇编 2 | 3 | Q: 有人说 new(xxxStruct) 比 &xxxStruct{} 牛逼,因为前者只生成一个堆上变量。后者要生成变量,再做引用,所以效率低 4 | 5 | 那么,我们怎么样确定 Go 这两种写法到底是一样,还是不一样呢? 6 | 7 | Q: 他们老是说这个东西在 runtime 里对应 xxx 函数,我怎么知道他们没骗我呢? 8 | 9 | 对编译原理不太熟,也不太想看 Go 的编译器代码,代码量有点大(50w+)。想节省点时间。 10 | 11 | Q: 什么是寄存器 12 | 13 | Q: 什么是函数栈 14 | 15 | Q: 为什么函数调用会 Stack Overflow 16 | 17 | Q: 为什么 Go 可以实现函数多返回值 18 | 19 | Q: 一个参数,有的人说是值传递,有的人说是引用传递,我到底该相信谁的 20 | 21 | Q: 为什么要有伪寄存器的概念 22 | 23 | Q: 怎么判断我看到的 plan9 汇编代码中的寄存器是物理寄存器,还是伪寄存器? 24 | 25 | Q: plan9 汇编和 intel 汇编有啥异同? 26 | 27 | Q: 汇编结果中的 gobuf_sp() 表示的是别名,还是特殊含义 28 | 29 | Q: 我看社区里有好多人搞 goroutine id 呀,我怎么能实现这么一个工具呢 30 | 31 | Q: SIMD 指令要怎么用 32 | -------------------------------------------------------------------------------- /images/asm_func.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cch123/asmshare/64f1f995d670c5508a2054d2a2e58d3c1feba5c7/images/asm_func.png -------------------------------------------------------------------------------- /images/do_you_want_power.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cch123/asmshare/64f1f995d670c5508a2054d2a2e58d3c1feba5c7/images/do_you_want_power.jpg -------------------------------------------------------------------------------- /layout.md: -------------------------------------------------------------------------------- 1 | # 汇编 is so easy。 2 | 3 | ## 为什么要学习汇编 4 | 5 | - 装逼 6 | 7 | ![](images/do_you_want_power.jpg) 8 | 装逼是第一生产力,不服来打我 9 | 10 | - 防骗:各种专家和架构师,江湖骗子,牛鬼蛇神,拿着 Go 语言以外的过往经验就忽悠上了 11 | 12 | 1. 你这么写效率不高,下面可能会有逃逸,你看得按我教你的这么写 13 | 2. 我们 java 赋值为 null 就能帮助 GC 判断,所以我也需要用完了 x = nil 14 | 3. 这个东西 runtime 里是这样这样那样那样实现的,我这么多年编程经验,你信我的准没错 15 | 4. Go 的 xxx 类型参数是引用传递的, yyy 类型参数是值传递的,哥当年研究 C艹 的研究了好久,哥的理解肯定比你权威 16 | 5. Go 的调度老牛逼了,稳得很 17 | 6. 按照编程语言的一般理论,这个地方肯定就是这么实现的,没错 18 | 19 | ## 基本概念 20 | 21 | ### 程序的编译阶段 22 | 23 | ``` 24 | 编译 -> 汇编 -> 优化 -> 链接 25 | ``` 26 | 27 | 汇编代码在汇编阶段生成,链接阶段会对生成的汇编代码进行修改。(eg0) 28 | 29 | 实际上优化过程是在所有过程中都有的,这里我们只说汇编优化。 30 | 31 | 链接阶段会对汇编结果做调整(如将函数地址从偏移量转换为逻辑地址) 32 | 33 | ### 机器码 34 | 35 | 所有汇编指令都可以转换为 0101 的序列,如: 36 | 37 | ``` 38 | >> mov rax,0x1 39 | mnemonic : mov rax,0x1 => hex : 48c7c001000000 40 | ``` 41 | hex 是机器码的 16 进制编码。 42 | 43 | ### 指令集 44 | 45 | RISC/CISC 46 | 47 | intel? 难说。 48 | 49 | risc 的新轮子 RISCV。 50 | 51 | > https://github.com/riscv 52 | 53 | CPU 优化指令集,如 x86 平台:SSE/SSE2/AVX。。。AVX512 54 | 55 | > https://github.com/golang/go/wiki/AVX512 56 | 57 | 优化指令集往往能用来加速内存移动,指令运算。基本思想是用硬件方式来做 batch,减少指令数,加速计算。 58 | 59 | ### 寄存器 60 | 61 | 寄存器是中央处理器内的组成部分。寄存器是有限存贮容量的高速存贮部件,它们可用来暂存指令、数据和地址。 62 | 63 | 通用寄存器: 64 | ``` 65 | ah/al => 8 位 66 | ax/bx => 16 位 67 | eax/ebx => 32 位 68 | rax/rbx => 64 位,8 byte 69 | 70 | rax : 0x0000000000000000 71 | ``` 72 | 73 | 指令寄存器: 74 | 75 | ``` 76 | pc/rip 77 | ``` 78 | 表示当前我的代码运行到哪里了。 79 | 80 | 81 | 标志寄存器: 82 | ``` 83 | eflags 84 | ``` 85 | 86 | 记录各种运算结果(是否为 0,是否发生溢出)的标志位。 87 | 88 | 在 dlv 中读取寄存器的值: 89 | 90 | ``` 91 | regs ------------------------ Print contents of CPU registers. 92 | ``` 93 | 94 | ``` 95 | (dlv) regs 96 | rax = 0x0000000001053940 97 | rbx = 0x0000000000000000 98 | rcx = 0x000000c000000300 99 | rdx = 0x00000000010750c0 100 | rdi = 0x0000000000000000 101 | rsi = 0x0000000000000001 102 | rbp = 0x000000c0000427d0 103 | rsp = 0x000000c000042790 104 | r8 = 0x7fffffffffffffff 105 | r9 = 0xffffffffffffffff 106 | r10 = 0x00000000010c0478 107 | r11 = 0x0000000000000202 108 | r12 = 0x0000000000203000 109 | r13 = 0x0000000000000000 110 | r14 = 0x0000000000000178 111 | r15 = 0x0000000000000004 112 | rip = 0x000000000105394f 113 | rflags = 0x0000000000000202 114 | cs = 0x000000000000002b 115 | fs = 0x0000000000000000 116 | gs = 0x0000000000000000 117 | fctrl = 0x037f 118 | fstat = 0x0000 119 | ftag = 0x0000 120 | fop = 0x0000 121 | fioff = 0x00000000 122 | fiseg = 0x0000 123 | fooff = 0x00000000 124 | foseg = 0x0000 125 | mxcsr = 0x00001fa0 [RZ/RN=0 PM UM OM ZM DM IM PE] 126 | mxcsrmask = 0x0000ffff 127 | ST(0) = 0x7fff0000000000000000 -Inf 128 | ST(1) = 0x00000000000000000000 0 129 | ST(2) = 0x00000000000000000000 0 130 | ST(3) = 0x00000000000000000000 0 131 | ST(4) = 0x00000000000000000000 0 132 | ST(5) = 0x7fff00000000ff73ff84 -Inf 133 | ST(6) = 0x7fff00000000022f2d1c -Inf 134 | ST(7) = 0x7fff00000000ffffd1fe -Inf 135 | XMM0 = 0x00000000000000000000000000000000 v2_int={ 0000000000000000 0000000000000000 } v4_int={ 00000000 00000000 00000000 00000000 } v8_int={ 0000 0000 0000 0000 0000 0000 0000 0000 } v16_int={ 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 } v2_float={ 0 0 } v4_float={ 0 0 0 0 } 136 | YMM0 = 0x00000000000000000000000000000000 v2_int={ 0000000000000000 0000000000000000 } v4_int={ 00000000 00000000 00000000 00000000 } v8_int={ 0000 0000 0000 0000 0000 0000 0000 0000 } v16_int={ 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 } v2_float={ 0 0 } v4_float={ 0 0 0 0 } 137 | XMM1 = 0x00000000000000000000000000000000 v2_int={ 0000000000000000 0000000000000000 } v4_int={ 00000000 00000000 00000000 00000000 } v8_int={ 0000 0000 0000 0000 0000 0000 0000 0000 } v16_int={ 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 } v2_float={ 0 0 } v4_float={ 0 0 0 0 } 138 | YMM1 = 0x00000000000000000000000000000000 v2_int={ 0000000000000000 0000000000000000 } v4_int={ 00000000 00000000 00000000 00000000 } v8_int={ 0000 0000 0000 0000 0000 0000 0000 0000 } v16_int={ 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 } v2_float={ 0 0 } v4_float={ 0 0 0 0 } 139 | XMM2 = 0x00706565777367622e656d69746e7572 v2_int={ 2e656d69746e7572 0070656577736762 } v4_int={ 746e7572 2e656d69 77736762 00706565 } v8_int={ 7572 746e 6d69 2e65 6762 7773 6565 0070 } v16_int={ 72 75 6e 74 69 6d 65 2e 62 67 73 77 65 65 70 00 } v2_float={ 3.446835183876702e-85 1.4592995157673208e-306 } v4_float={ 7.55706e+31 5.2165747e-11 4.9368164e+33 1.0321949e-38 } 140 | YMM2 = 0x00000000000000000000000000000000 v2_int={ 0000000000000000 0000000000000000 } v4_int={ 00000000 00000000 00000000 00000000 } v8_int={ 0000 0000 0000 0000 0000 0000 0000 0000 } v16_int={ 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 } v2_float={ 0 0 } v4_float={ 0 0 0 0 } 141 | XMM3 = 0xff000000000000000000000000000000 v2_int={ 0000000000000000 ff00000000000000 } v4_int={ 00000000 00000000 00000000 ff000000 } v8_int={ 0000 0000 0000 0000 0000 0000 0000 ff00 } v16_int={ 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ff } v2_float={ 0 -5.486124068793689e+303 } v4_float={ 0 0 0 -1.7014118e+38 } 142 | YMM3 = 0x00000000000000000000000000000000 v2_int={ 0000000000000000 0000000000000000 } v4_int={ 00000000 00000000 00000000 00000000 } v8_int={ 0000 0000 0000 0000 0000 0000 0000 0000 } v16_int={ 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 } v2_float={ 0 0 } v4_float={ 0 0 0 0 } 143 | XMM4 = 0x3a31303b33333d64633a31303b36333d v2_int={ 633a31303b36333d 3a31303b33333d64 } v4_int={ 3b36333d 633a3130 33333d64 3a31303b } v8_int={ 333d 3b36 3130 633a 3d64 3333 303b 3a31 } v16_int={ 3d 33 36 3b 30 31 3a 63 64 3d 33 33 3b 30 31 3a } v2_float={ 9.884816049279249e+169 2.16948150441166e-28 } v4_float={ 0.0027801536 3.4346387e+21 4.173252e-08 0.0006759201 } 144 | YMM4 = 0x00000000000000000000000000000000 v2_int={ 0000000000000000 0000000000000000 } v4_int={ 00000000 00000000 00000000 00000000 } v8_int={ 0000 0000 0000 0000 0000 0000 0000 0000 } v16_int={ 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 } v2_float={ 0 0 } v4_float={ 0 0 0 0 } 145 | XMM5 = 0x333d67733a37303b30343b31333d7573 v2_int={ 30343b31333d7573 333d67733a37303b } v4_int={ 333d7573 30343b31 3a37303b 333d6773 } v8_int={ 7573 333d 3b31 3034 303b 3a37 6773 333d } v16_int={ 73 75 3d 33 31 3b 34 30 3b 30 37 3a 73 67 3d 33 } v2_float={ 1.747202215459339e-76 7.147741244431301e-62 } v4_float={ 4.411181e-08 6.5567735e-10 0.0006988083 4.4099078e-08 } 146 | YMM5 = 0x00000000000000000000000000000000 v2_int={ 0000000000000000 0000000000000000 } v4_int={ 00000000 00000000 00000000 00000000 } v8_int={ 0000 0000 0000 0000 0000 0000 0000 0000 } v16_int={ 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 } v2_float={ 0 0 } v4_float={ 0 0 0 0 } 147 | XMM6 = 0x30343b32333d77743a37303b30343b36 v2_int={ 3a37303b30343b36 30343b32333d7774 } v4_int={ 30343b36 3a37303b 333d7774 30343b32 } v8_int={ 3b36 3034 303b 3a37 7774 333d 3b32 3034 } v16_int={ 36 3b 34 30 3b 30 37 3a 74 77 3d 33 32 3b 34 30 } v2_float={ 2.926787950883477e-28 1.7472035332342394e-76 } v4_float={ 6.556776e-10 0.0006988083 4.4113634e-08 6.556774e-10 } 148 | YMM6 = 0x00000000000000000000000000000000 v2_int={ 0000000000000000 0000000000000000 } v4_int={ 00000000 00000000 00000000 00000000 } v8_int={ 0000 0000 0000 0000 0000 0000 0000 0000 } v16_int={ 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 } v2_float={ 0 0 } v4_float={ 0 0 0 0 } 149 | XMM7 = 0x3a37303b30343b33333d776f3a37303b v2_int={ 333d776f3a37303b 3a37303b30343b33 } v4_int={ 3a37303b 333d776f 30343b33 3a37303b } v8_int={ 303b 3a37 776f 333d 3b33 3034 303b 3a37 } v16_int={ 3b 30 37 3a 6f 77 3d 33 33 3b 34 30 3b 30 37 3a } v2_float={ 7.162919315999914e-62 2.9267879508834756e-28 } v4_float={ 0.0006988083 4.4113616e-08 6.5567746e-10 0.0006988083 } 150 | YMM7 = 0x00000000000000000000000000000000 v2_int={ 0000000000000000 0000000000000000 } v4_int={ 00000000 00000000 00000000 00000000 } v8_int={ 0000 0000 0000 0000 0000 0000 0000 0000 } v16_int={ 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 } v2_float={ 0 0 } v4_float={ 0 0 0 0 } 151 | XMM8 = 0x6e6962732f6c61636f6c2f7273752f3a v2_int={ 6f6c2f7273752f3a 6e6962732f6c6163 } v4_int={ 73752f3a 6f6c2f72 2f6c6163 6e696273 } v8_int={ 2f3a 7375 2f72 6f6c 6163 2f6c 6273 6e69 } v16_int={ 3a 2f 75 73 72 2f 6c 6f 63 61 6c 2f 73 62 69 6e } v2_float={ 5.34158331241814e+228 7.340685338891256e+223 } v4_float={ 1.9425516e+31 7.309582e+28 2.1498674e-10 1.8057256e+28 } 152 | YMM8 = 0x00000000000000000000000000000000 v2_int={ 0000000000000000 0000000000000000 } v4_int={ 00000000 00000000 00000000 00000000 } v8_int={ 0000 0000 0000 0000 0000 0000 0000 0000 } v16_int={ 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 } v2_float={ 0 0 } v4_float={ 0 0 0 0 } 153 | XMM9 = 0x2f3a6e69622f3a6e69622f7273752f3a v2_int={ 69622f7273752f3a 2f3a6e69622f3a6e } v4_int={ 73752f3a 69622f72 622f3a6e 2f3a6e69 } v8_int={ 2f3a 7375 2f72 6962 3a6e 622f 6e69 2f3a } v16_int={ 3a 2f 75 73 72 2f 62 69 6e 3a 2f 62 69 6e 3a 2f } v2_float={ 4.3499931289042936e+199 3.4830493665207283e-81 } v4_float={ 1.9425516e+31 1.7090081e+25 8.080976e+20 1.6955827e-10 } 154 | YMM9 = 0x00000000000000000000000000000000 v2_int={ 0000000000000000 0000000000000000 } v4_int={ 00000000 00000000 00000000 00000000 } v8_int={ 0000 0000 0000 0000 0000 0000 0000 0000 } v16_int={ 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 } v2_float={ 0 0 } v4_float={ 0 0 0 0 } 155 | XMM10 = 0x2f3a6e6962732f3a6e6962732f727375 v2_int={ 6e6962732f727375 2f3a6e6962732f3a } v4_int={ 2f727375 6e696273 62732f3a 2f3a6e69 } v8_int={ 7375 2f72 6273 6e69 2f3a 6273 6e69 2f3a } v16_int={ 75 73 72 2f 73 62 69 6e 3a 2f 73 62 69 6e 3a 2f } v2_float={ 7.340685339299987e+223 3.4830493686057503e-81 } v4_float={ 2.205079e-10 1.8057256e+28 1.12149046e+21 1.6955827e-10 } 156 | YMM10 = 0x00000000000000000000000000000000 v2_int={ 0000000000000000 0000000000000000 } v4_int={ 00000000 00000000 00000000 00000000 } v8_int={ 0000 0000 0000 0000 0000 0000 0000 0000 } v16_int={ 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 } v2_float={ 0 0 } v4_float={ 0 0 0 0 } 157 | XMM11 = 0x6e69622f6f672f6c61636f6c2f727375 v2_int={ 61636f6c2f727375 6e69622f6f672f6c } v4_int={ 2f727375 61636f6c 6f672f6c 6e69622f } v8_int={ 7375 2f72 6f6c 6163 2f6c 6f67 622f 6e69 } v16_int={ 75 73 72 2f 6c 6f 63 61 6c 2f 67 6f 2f 62 69 6e } v2_float={ 1.3662107766268992e+161 7.340386390188806e+223 } v4_float={ 2.205079e-10 2.6221498e+20 7.1548367e+28 1.8057176e+28 } 158 | YMM11 = 0x00000000000000000000000000000000 v2_int={ 0000000000000000 0000000000000000 } v4_int={ 00000000 00000000 00000000 00000000 } v8_int={ 0000 0000 0000 0000 0000 0000 0000 0000 } v16_int={ 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 } v2_float={ 0 0 } v4_float={ 0 0 0 0 } 159 | XMM12 = 0x572f736e6f69746163696c7070412f3a v2_int={ 63696c7070412f3a 572f736e6f697461 } v4_int={ 70412f3a 63696c70 6f697461 572f736e } v8_int={ 2f3a 7041 6c70 6369 7461 6f69 736e 572f } v16_int={ 3a 2f 41 70 70 6c 69 63 61 74 69 6f 6e 73 2f 57 } v2_float={ 7.675814073001636e+170 9.454564732925896e+111 } v4_float={ 2.391508e+29 4.305905e+21 7.22507e+28 1.929103e+14 } 160 | YMM12 = 0x00000000000000000000000000000000 v2_int={ 0000000000000000 0000000000000000 } v4_int={ 00000000 00000000 00000000 00000000 } v8_int={ 0000 0000 0000 0000 0000 0000 0000 0000 } v16_int={ 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 } v2_float={ 0 0 } v4_float={ 0 0 0 0 } 161 | XMM13 = 0x6e6f432f7070612e6b72616873657269 v2_int={ 6b72616873657269 6e6f432f7070612e } v4_int={ 73657269 6b726168 7070612e 6e6f432f } v8_int={ 7269 7365 6168 6b72 612e 7070 432f 6e6f } v16_int={ 69 72 65 73 68 61 72 6b 2e 61 70 70 2f 43 6f 6e } v2_float={ 3.776715943768552e+209 9.04044281118662e+223 } v4_float={ 1.8178657e+31 2.9302004e+26 2.9757554e+29 1.8512034e+28 } 162 | YMM13 = 0x00000000000000000000000000000000 v2_int={ 0000000000000000 0000000000000000 } v4_int={ 00000000 00000000 00000000 00000000 } v8_int={ 0000 0000 0000 0000 0000 0000 0000 0000 } v16_int={ 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 } v2_float={ 0 0 } v4_float={ 0 0 0 0 } 163 | XMM14 = 0x6573552f3a534f63614d2f73746e6574 v2_int={ 614d2f73746e6574 6573552f3a534f63 } v4_int={ 746e6574 614d2f73 3a534f63 6573552f } v8_int={ 6574 746e 2f73 614d 4f63 3a53 552f 6573 } v16_int={ 74 65 6e 74 73 2f 4d 61 63 4f 53 3a 2f 55 73 65 } v2_float={ 5.1289999310656885e+160 5.013847184688216e+180 } v4_float={ 7.5550804e+31 2.365626e+20 0.0008060841 7.181915e+22 } 164 | YMM14 = 0x00000000000000000000000000000000 v2_int={ 0000000000000000 0000000000000000 } v4_int={ 00000000 00000000 00000000 00000000 } v8_int={ 0000 0000 0000 0000 0000 0000 0000 0000 } v16_int={ 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 } v2_float={ 0 0 } v4_float={ 0 0 0 0 } 165 | XMM15 = 0x6e69622f6f672f6e69677261782f7372 v2_int={ 69677261782f7372 6e69622f6f672f6e } v4_int={ 782f7372 69677261 6f672f6e 6e69622f } v8_int={ 7372 782f 7261 6967 2f6e 6f67 622f 6e69 } v16_int={ 72 73 2f 78 61 72 67 69 6e 2f 67 6f 2f 62 69 6e } v2_float={ 5.608551565561017e+199 7.340386390188808e+223 } v4_float={ 1.4234273e+34 1.7487625e+25 7.1548376e+28 1.8057176e+28 } 166 | YMM15 = 0x00000000000000000000000000000000 v2_int={ 0000000000000000 0000000000000000 } v4_int={ 00000000 00000000 00000000 00000000 } v8_int={ 0000 0000 0000 0000 0000 0000 0000 0000 } v16_int={ 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 } v2_float={ 0 0 } v4_float={ 0 0 0 0 } 167 | trapno = 0x00000001 168 | err = 0x00000000 169 | faultvaddr = 0x00000000010ee000 170 | ``` 171 | 172 | 大部分不需要理解,知道通用寄存器和简单的扩展寄存器就好了。 173 | 174 | ### 基础指令 175 | 176 | ``` 177 | mov rax, 0x1 // rax = 1 178 | add rax, 11 // rax = rax + 11 179 | sub rax,1 // rax = rax - 1 180 | 181 | // 在寄存器之间搬数据 182 | mov rax, rbx // rax = rbx 183 | 184 | // 在寄存器和内存之间搬数据 185 | mov rax, [rbx] // 把 rbx 寄存器的值看作一个地址,把该物理地址的值取出,然后赋值给 rax,即 rax = *rbx 186 | 187 | // 在内存之间搬数据 188 | - 你想多了,不可以的 189 | 190 | push rax => 将 rax 的值存储到栈顶,并将 rsp 上移 8 个字节 191 | pop rax => 将 rsp 指向的内存位置的 8 个字节移动到 rax,并将 rsp 从栈顶下移 8 个字节 192 | 193 | jmp addr => 跳转到指定地址 194 | ``` 195 | 196 | ### 函数 or 过程 197 | 198 | 函数调用在汇编上的本质: 199 | 200 | ``` 201 | jmp 跳进去 202 | jmp 跳回来 203 | ``` 204 | 205 | cpu 很傻,跳回来的时候我必须到当前过程的下一条指令位置,难道我还需要在被调用过程里知道调用者的过程下一条指令位置? 206 | 207 | ``` 208 | call => push pc; jmp to callee addr; 209 | ret => pop pc; 210 | ``` 211 | 212 | 要点,在栈上记录下一条指令位置,即我们常说的 `return address`,理解成路标就行。在执行完被调用函数的时候,顺着路标回去继续向下走。 213 | 214 | ### 高级语言函数 215 | 216 | 函数栈: 217 | ``` 218 | +--------------+ 219 | | | 220 | + | | 221 | | +--------------+ 222 | | | | 223 | | | arg(N-1) | starts from 7'th argument for x86_64 224 | | | | 225 | | +--------------+ 226 | | | | 227 | | | argN | 228 | | | | 229 | | +--------------+ 230 | | | | 231 | | |Return address| %rbp + 8 232 | Stack grows down | | | 233 | | +--------------+ 234 | | | | 235 | | | %rbp | Frame base pointer 236 | | | | 237 | | +--------------+ 238 | | | | 239 | | | local var1 | %rbp - 8 240 | | | | 241 | | +--------------+ 242 | | | | 243 | | | local var 2 | <-- %rsp 244 | | | | 245 | v +--------------+ 246 | | | 247 | | | 248 | +--------------+ 249 | ``` 250 | 251 | 调用规约: 252 | 253 | 参考这里: 254 | > https://github.com/cch123/llp-trans/blob/master/part3/translation-details/function-calling-sequence/calling-convention.md 255 | 256 | 主要是知道哪些寄存器是需要 caller save,哪些是 callee save。 257 | 258 | ### 和系统打交道 259 | 260 | ``` 261 | section .data 262 | message: db 'hello, world!', 10 263 | 264 | section .text 265 | global _start 266 | 267 | _start: 268 | mov rax, 1 ; 'write' syscall number 269 | mov rdi, 1 ; stdout descriptor 270 | mov rsi, message ; string address 271 | mov rdx, 14 ; string length in bytes 272 | syscall ; 重点在这里 273 | ``` 274 | 275 | 更具体的可以参考 [这里](https://github.com/cch123/llp-trans/blob/d6b7f46c72e83ac9145d5534c6bc4e690da8d815/part1/assembly-language/writing-hello-world/basic-instructions.md) 276 | 277 | 和[这里](http://blog.rchapman.org/posts/Linux_System_Call_Table_for_x86_64/) 278 | 279 | ## plan9 汇编 280 | 281 | - 寄存器名差别 282 | 283 | ``` 284 | rax -> AX 285 | rbx -> BX 286 | r8 -> R8 287 | ``` 288 | 289 | 因为移动多少个字节是由指令而不是操作数决定的,所以稍微省心一些。 290 | 291 | - 指令对应关系 292 | 293 | ``` 294 | MOVB $1, DI -> mov al, 0x44 // 1 byte 295 | MOVW $0x10, BX -> mov ax, 0x10 // 2 bytes 296 | MOVD $100, DX -> mov eax, 100 // 4 bytes 297 | MOVQ $-10, AX -> mov rax, -10 // 8 bytes 298 | ``` 299 | 300 | 可以看到,指令的操作数是反着的,大部分 plan9 和 intel 的指令确实是反着的关系,学习过汇编的同学可能会认为很好啊,这样 plan9 的操作数顺序应该是和 AT&T 一致了,遗憾的是也有例外: 301 | 302 | ``` 303 | Mnemonic | Go operands | AT&T operands 304 | ================================================ 305 | BOUNDW | m16&16, r16 | r16, m16&16 306 | BOUNDL | m32&32, r32 | r32, m32&32 307 | CMPB | AL, imm8 | imm8, AL 308 | CMPW | AX, imm16 | imm16, AX 309 | CMPL | EAX, imm32 | imm32, EAX 310 | CMPQ | RAX, imm32 | imm32, RAX 311 | CMPW | r/m16, imm16 | imm16, r/m16 312 | CMPW | r/m16, imm8 | imm8, r/m16 313 | CMPW | r/m16, r16 | r16, r/m16 314 | CMPL | r/m32, imm32 | imm32, r/m32 315 | CMPL | r/m32, imm8 | imm8, r/m32 316 | CMPL | r/m32, r32 | r32, r/m32 317 | CMPQ | r/m64, imm32 | imm32, r/m64 318 | CMPQ | r/m64, imm8 | imm8, r/m64 319 | CMPQ | r/m64, r64 | r64, r/m64 320 | CMPB | r/m8, imm8 | imm8, r/m8 321 | CMPB | r/m8, imm8 | imm8, r/m8 322 | CMPB | r/m8, r8 | r8, r/m8 323 | CMPB | r/m8, r8 | r8, r/m8 324 | CMPW | r16, r/m16 | r/m16, r16 325 | CMPL | r32, r/m32 | r/m32, r32 326 | CMPQ | r64, r/m64 | r/m64, r64 327 | CMPB | r8, r/m8 | r/m8, r8 328 | CMPB | r8, r/m8 | r/m8, r8 329 | CMPPD | imm8, xmm1, xmm2/m128 | imm8, xmm2/m128, xmm1 330 | CMPPS | imm8, xmm1, xmm2/m128 | imm8, xmm2/m128, xmm1 331 | CMPSD | imm8, xmm1, xmm2/m64 | imm8, xmm2/m64, xmm1 332 | CMPSS | imm8, xmm1, xmm2/m32 | imm8, xmm2/m32, xmm1 333 | ENTER | 0, imm16 | imm16, 0 334 | ENTER | 1, imm16 | imm16, 1 335 | ``` 336 | 337 | ## 使用汇编知识 338 | 339 | ### 程序的基本分段 340 | 341 | ``` 342 | .data : 有初始化值的全局变量;定义常量。
.bss : 没有初始化值的全局变量。 343 | .text : 代码段。 344 | .rodata: 只读数据段。 345 | ``` 346 | 347 | 代码在 .text 段中,大概知道就行。Go 语言用户程序的入口为 main.main。 348 | 349 | 可以使用 objdump 来看 intel 指令的反汇编结果: 350 | 351 | ```go 352 | package main 353 | 354 | func main() { 355 | println("hello") 356 | } 357 | 358 | 000000000044c150 : 359 | 44c150: 64 48 8b 0c 25 f8 ff mov rcx,QWORD PTR fs:0xfffffffffffffff8 360 | 44c157: ff ff 361 | 44c159: 48 3b 61 10 cmp rsp,QWORD PTR [rcx+0x10] 362 | 44c15d: 76 3b jbe 44c19a 363 | 44c15f: 48 83 ec 18 sub rsp,0x18 364 | 44c163: 48 89 6c 24 10 mov QWORD PTR [rsp+0x10],rbp 365 | 44c168: 48 8d 6c 24 10 lea rbp,[rsp+0x10] 366 | 44c16d: e8 8e 59 fd ff call 421b00 367 | 44c172: 48 8d 05 ba dc 01 00 lea rax,[rip+0x1dcba] # 469e33 368 | 44c179: 48 89 04 24 mov QWORD PTR [rsp],rax 369 | 44c17d: 48 c7 44 24 08 06 00 mov QWORD PTR [rsp+0x8],0x6 370 | 44c184: 00 00 371 | 44c186: e8 b5 62 fd ff call 422440 372 | 44c18b: e8 f0 59 fd ff call 421b80 373 | 44c190: 48 8b 6c 24 10 mov rbp,QWORD PTR [rsp+0x10] 374 | 44c195: 48 83 c4 18 add rsp,0x18 375 | 44c199: c3 ret 376 | 44c19a: e8 d1 83 ff ff call 444570 377 | 44c19f: eb af jmp 44c150 378 | 44c1a1: cc int3 379 | ``` 380 | 381 | ### Go 反汇编工具 382 | 383 | ``` 384 | go tool compile -S 385 | ``` 386 | 387 | 输出的汇编代码还没有链接,呈现的地址都是偏移量。 388 | 389 | ``` 390 | go tool objdump 391 | ``` 392 | 393 | 相当于反汇编你的程序,会丢失很多信息。 394 | 395 | ```go 396 | package main 397 | 398 | func main() { 399 | println("hello") 400 | } 401 | 402 | TEXT main.main(SB) /home/vagrant/a.go 403 | a.go:3 0x44c150 64488b0c25f8ffffff MOVQ FS:0xfffffff8, CX 404 | a.go:3 0x44c159 483b6110 CMPQ 0x10(CX), SP 405 | a.go:3 0x44c15d 763b JBE 0x44c19a 406 | a.go:3 0x44c15f 4883ec18 SUBQ $0x18, SP 407 | a.go:3 0x44c163 48896c2410 MOVQ BP, 0x10(SP) 408 | a.go:3 0x44c168 488d6c2410 LEAQ 0x10(SP), BP 409 | a.go:4 0x44c16d e88e59fdff CALL runtime.printlock(SB) 410 | a.go:4 0x44c172 488d05badc0100 LEAQ 0x1dcba(IP), AX 411 | a.go:4 0x44c179 48890424 MOVQ AX, 0(SP) 412 | a.go:4 0x44c17d 48c744240806000000 MOVQ $0x6, 0x8(SP) 413 | a.go:4 0x44c186 e8b562fdff CALL runtime.printstring(SB) 414 | a.go:4 0x44c18b e8f059fdff CALL runtime.printunlock(SB) 415 | a.go:5 0x44c190 488b6c2410 MOVQ 0x10(SP), BP 416 | a.go:5 0x44c195 4883c418 ADDQ $0x18, SP 417 | a.go:5 0x44c199 c3 RET 418 | a.go:3 0x44c19a e8d183ffff CALL runtime.morestack_noctxt(SB) 419 | a.go:3 0x44c19f ebaf JMP main.main(SB) 420 | ``` 421 | 422 | 当你手写汇编时,与 `go tool compile -S` 和 `objdump` 的是不一样的,不要直接照着这个结果来写! 423 | 424 | ### 查看 Go 语言的函数调用规约 425 | 426 | ``` 427 | 428 | caller 429 | +------------------+ 430 | | | 431 | +----------------------> -------------------- 432 | | | | 433 | | | caller parent BP | 434 | | BP(pseudo SP) -------------------- 435 | | | | 436 | | | Local Var0 | 437 | | -------------------- 438 | | | | 439 | | | ....... | 440 | | -------------------- 441 | | | | 442 | | | Local VarN | 443 | -------------------- 444 | caller stack frame | | 445 | | callee arg2 | 446 | | |------------------| 447 | | | | 448 | | | callee arg1 | 449 | | |------------------| 450 | | | | 451 | | | callee arg0 | 452 | | ----------------------------------------------+ FP(virtual register) 453 | | | | | 454 | | | return addr | parent return address | 455 | +----------------------> +------------------+--------------------------- <-------------------------------+ 456 | | caller BP | | 457 | | (caller frame pointer) | | 458 | BP(pseudo SP) ---------------------------- | 459 | | | | 460 | | Local Var0 | | 461 | ---------------------------- | 462 | | | 463 | | Local Var1 | 464 | ---------------------------- callee stack frame 465 | | | 466 | | ..... | 467 | ---------------------------- | 468 | | | | 469 | | Local VarN | | 470 | SP(Real Register) ---------------------------- | 471 | | | | 472 | | | | 473 | | | | 474 | | | | 475 | | | | 476 | +--------------------------+ <-------------------------------+ 477 | 478 | callee 479 | ``` 480 | 481 | 482 | ### 确定应用层代码被翻译为哪些 runtime 函数 483 | 484 | #### 例,查看 defer 的执行过程: 485 | 486 | ```go 487 | package main 488 | 489 | func f() int { 490 | var res = 0 491 | defer func() { 492 | res++ 493 | }() 494 | return res 495 | } 496 | 497 | func main() {} 498 | ``` 499 | 500 | ``` 501 | "".f STEXT size=137 args=0x8 locals=0x28 502 | 0x0000 00000 (defer.go:3) TEXT "".f(SB), $40-8 503 | 0x0000 00000 (defer.go:3) MOVQ (TLS), CX 504 | 0x0009 00009 (defer.go:3) CMPQ SP, 16(CX) 505 | 0x000d 00013 (defer.go:3) JLS 127 506 | 0x000f 00015 (defer.go:3) SUBQ $40, SP 507 | 0x0013 00019 (defer.go:3) MOVQ BP, 32(SP) 508 | 0x0018 00024 (defer.go:3) LEAQ 32(SP), BP 509 | 0x001d 00029 (defer.go:3) FUNCDATA $0, gclocals·33cdeccccebe80329f1fdbee7f5874cb(SB) 510 | 0x001d 00029 (defer.go:3) FUNCDATA $1, gclocals·33cdeccccebe80329f1fdbee7f5874cb(SB) 511 | 0x001d 00029 (defer.go:3) FUNCDATA $3, gclocals·9fb7f0986f647f17cb53dda1484e0f7a(SB) 512 | 0x001d 00029 (defer.go:3) PCDATA $2, $0 513 | 0x001d 00029 (defer.go:3) PCDATA $0, $0 514 | 0x001d 00029 (defer.go:3) MOVQ $0, "".~r0+48(SP) 515 | 0x0026 00038 (defer.go:4) MOVQ $0, "".res+24(SP) 516 | 0x002f 00047 (defer.go:7) PCDATA $2, $1 517 | 0x002f 00047 (defer.go:7) LEAQ "".res+24(SP), AX 518 | 0x0034 00052 (defer.go:7) PCDATA $2, $0 519 | 0x0034 00052 (defer.go:7) MOVQ AX, 16(SP) 520 | 0x0039 00057 (defer.go:5) MOVL $8, (SP) 521 | 0x0040 00064 (defer.go:5) PCDATA $2, $1 522 | 0x0040 00064 (defer.go:5) LEAQ "".f.func1·f(SB), AX 523 | 0x0047 00071 (defer.go:5) PCDATA $2, $0 524 | 0x0047 00071 (defer.go:5) MOVQ AX, 8(SP) 525 | 0x004c 00076 (defer.go:5) CALL runtime.deferproc(SB) 526 | 0x0051 00081 (defer.go:5) TESTL AX, AX 527 | 0x0053 00083 (defer.go:5) JNE 111 528 | 0x0055 00085 (defer.go:8) MOVQ "".res+24(SP), AX 529 | 0x005a 00090 (defer.go:8) MOVQ AX, "".~r0+48(SP) 530 | 0x005f 00095 (defer.go:8) XCHGL AX, AX 531 | 0x0060 00096 (defer.go:8) CALL runtime.deferreturn(SB) 532 | 0x0065 00101 (defer.go:8) MOVQ 32(SP), BP 533 | 0x006a 00106 (defer.go:8) ADDQ $40, SP 534 | 0x006e 00110 (defer.go:8) RET 535 | 0x006f 00111 (defer.go:5) XCHGL AX, AX 536 | 0x0070 00112 (defer.go:5) CALL runtime.deferreturn(SB) 537 | 0x0075 00117 (defer.go:5) MOVQ 32(SP), BP 538 | 0x007a 00122 (defer.go:5) ADDQ $40, SP 539 | 0x007e 00126 (defer.go:5) RET 540 | 0x007f 00127 (defer.go:5) NOP 541 | 0x007f 00127 (defer.go:3) PCDATA $0, $-1 542 | 0x007f 00127 (defer.go:3) PCDATA $2, $-1 543 | 0x007f 00127 (defer.go:3) CALL runtime.morestack_noctxt(SB) 544 | 0x0084 00132 (defer.go:3) JMP 0 545 | ``` 546 | 547 | defer 提供给你是用来关闭各种资源,恢复 panic 程序现场的。 548 | 549 | 不是给你炫技或者面试别人的。 550 | 551 | 劝君善良。 552 | 553 | #### 例,查看 map 被翻译为的 runtime 函数 554 | 555 | ```go 556 | package main 557 | 558 | import "fmt" 559 | 560 | func main() { 561 | var a = map[int]int{} 562 | a[1] = 1 563 | fmt.Println(a) 564 | } 565 | ``` 566 | 567 | ```shell 568 | go tool compile -S map.go | grep 'map.go:6' 569 | ``` 570 | 571 | ``` 572 | 0x0021 00033 (map.go:6) PCDATA $2, $0 573 | 0x0021 00033 (map.go:6) PCDATA $0, $0 574 | 0x0021 00033 (map.go:6) CALL runtime.makemap_small(SB) 575 | 0x0026 00038 (map.go:6) PCDATA $2, $1 576 | 0x0026 00038 (map.go:6) MOVQ (SP), AX 577 | 0x002a 00042 (map.go:6) PCDATA $0, $1 578 | 0x002a 00042 (map.go:6) MOVQ AX, "".a+48(SP) 579 | ``` 580 | 581 | 只要再去 runtime 源代码中寻找 makemap_small 的定义即可。 582 | 583 | #### 例,查看内存是否在堆上分配: 584 | 585 | ```go 586 | package main 587 | 588 | import "fmt" 589 | 590 | func main() { 591 | var a = new([]int) 592 | fmt.Println(a) 593 | } 594 | ``` 595 | 596 | ``` 597 | 0x001d 00029 (alloc.go:6) PCDATA $0, $0 598 | 0x001d 00029 (alloc.go:6) LEAQ type.[]int(SB), AX 599 | 0x0024 00036 (alloc.go:6) PCDATA $2, $0 600 | 0x0024 00036 (alloc.go:6) MOVQ AX, (SP) 601 | 0x0028 00040 (alloc.go:6) CALL runtime.newobject(SB) 602 | 0x002d 00045 (alloc.go:6) PCDATA $2, $1 603 | 0x002d 00045 (alloc.go:6) MOVQ 8(SP), AX 604 | ``` 605 | 606 | #### 例,查看 Go 语言的编译器是否进行了尾递归优化: 607 | 608 | ```go 609 | package main 610 | 611 | func f(i int) int { 612 | if i == 0 || i == 1 { 613 | return i 614 | } 615 | return f(i - 1) 616 | } 617 | 618 | func main() { 619 | println(f(666665535)) 620 | } 621 | ``` 622 | 623 | ```go 624 | "".f STEXT size=99 args=0x10 locals=0x18 625 | 0x0000 00000 (recur.go:3) TEXT "".f(SB), $24-16 626 | 0x0000 00000 (recur.go:3) MOVQ (TLS), CX 627 | 0x0009 00009 (recur.go:3) CMPQ SP, 16(CX) 628 | 0x000d 00013 (recur.go:3) JLS 92 629 | 0x000f 00015 (recur.go:3) SUBQ $24, SP 630 | 0x0013 00019 (recur.go:3) MOVQ BP, 16(SP) 631 | 0x0018 00024 (recur.go:3) LEAQ 16(SP), BP 632 | 0x001d 00029 (recur.go:3) FUNCDATA $0, gclocals·33cdeccccebe80329f1fdbee7f5874cb(SB) 633 | 0x001d 00029 (recur.go:3) FUNCDATA $1, gclocals·33cdeccccebe80329f1fdbee7f5874cb(SB) 634 | 0x001d 00029 (recur.go:3) FUNCDATA $3, gclocals·33cdeccccebe80329f1fdbee7f5874cb(SB) 635 | 0x001d 00029 (recur.go:4) PCDATA $2, $0 636 | 0x001d 00029 (recur.go:4) PCDATA $0, $0 637 | 0x001d 00029 (recur.go:4) MOVQ "".i+32(SP), AX 638 | 0x0022 00034 (recur.go:4) TESTQ AX, AX 639 | 0x0025 00037 (recur.go:4) JNE 54 640 | 0x0027 00039 (recur.go:5) MOVQ AX, "".~r1+40(SP) 641 | 0x002c 00044 (recur.go:5) MOVQ 16(SP), BP 642 | 0x0031 00049 (recur.go:5) ADDQ $24, SP 643 | 0x0035 00053 (recur.go:5) RET 644 | 0x0036 00054 (recur.go:4) CMPQ AX, $1 645 | 0x003a 00058 (recur.go:4) JEQ 39 646 | 0x003c 00060 (recur.go:7) DECQ AX 647 | 0x003f 00063 (recur.go:7) MOVQ AX, (SP) 648 | 0x0043 00067 (recur.go:7) CALL "".f(SB) 649 | 0x0048 00072 (recur.go:7) MOVQ 8(SP), AX 650 | 0x004d 00077 (recur.go:7) MOVQ AX, "".~r1+40(SP) 651 | 0x0052 00082 (recur.go:7) MOVQ 16(SP), BP 652 | 0x0057 00087 (recur.go:7) ADDQ $24, SP 653 | 0x005b 00091 (recur.go:7) RET 654 | 0x005c 00092 (recur.go:7) NOP 655 | 0x005c 00092 (recur.go:3) PCDATA $0, $-1 656 | 0x005c 00092 (recur.go:3) PCDATA $2, $-1 657 | 0x005c 00092 (recur.go:3) CALL runtime.morestack_noctxt(SB) 658 | 0x0061 00097 (recur.go:3) JMP 0 659 | ``` 660 | 661 | 嗯,显然目前依然是没有优化的。 662 | 663 | 运行一下,bang! 664 | 665 | ``` 666 | ~/asmshare ❯❯❯ go run recur.go master ✖ ✱ ◼ 667 | runtime: goroutine stack exceeds 1000000000-byte limit 668 | fatal error: stack overflow 669 | 670 | runtime stack: 671 | runtime.throw(0x106b632, 0xe) 672 | /usr/local/go/src/runtime/panic.go:608 +0x72 673 | runtime.newstack() 674 | /usr/local/go/src/runtime/stack.go:1008 +0x729 675 | runtime.morestack() 676 | /usr/local/go/src/runtime/asm_amd64.s:429 +0x8f 677 | ``` 678 | 679 | 相反我们看看 C 语言的: 680 | 681 | ```c 682 | #include "stdio.h" 683 | 684 | int f(int i) { 685 | if( i == 0 || i == 1) { 686 | return i; 687 | } 688 | return f(i-1); 689 | } 690 | 691 | int main() { 692 | printf("%d\n", f(666665535)); 693 | return 1; 694 | } 695 | ``` 696 | 697 | ``` 698 | ~/asmshare ❯❯❯ clang -O2 c.c 699 | ~/asmshare ❯❯❯ ./a.out 700 | 1 701 | ``` 702 | 703 | 老哥,稳! 704 | 705 | #### 例,查看目前版本的 Go 在函数头和函数尾插入的调度相关指令: 706 | 707 | ``` 708 | "".f2 STEXT size=92 args=0x0 locals=0x10 709 | ---------------------- header startend ------------------------- 710 | 0x0000 00000 (shadow_example.go:17) TEXT "".f2(SB), $16-0 711 | 0x0000 00000 (shadow_example.go:17) MOVQ (TLS), CX 712 | 0x0009 00009 (shadow_example.go:17) CMPQ SP, 16(CX) 713 | 0x000d 00013 (shadow_example.go:17) JLS 85 714 | ---------------------- header end ------------------------- 715 | 716 | 0x000f 00015 (shadow_example.go:17) SUBQ $16, SP 717 | 0x0013 00019 (shadow_example.go:17) MOVQ BP, 8(SP) 718 | 0x0018 00024 (shadow_example.go:17) LEAQ 8(SP), BP 719 | 0x001d 00029 (shadow_example.go:17) FUNCDATA $0, gclocals·33cdeccccebe80329f1fdbee7f5874cb(SB) 720 | 0x001d 00029 (shadow_example.go:17) FUNCDATA $1, gclocals·33cdeccccebe80329f1fdbee7f5874cb(SB) 721 | 0x001d 00029 (shadow_example.go:17) FUNCDATA $3, gclocals·33cdeccccebe80329f1fdbee7f5874cb(SB) 722 | 0x001d 00029 (shadow_example.go:21) PCDATA $2, $0 723 | 0x001d 00029 (shadow_example.go:21) PCDATA $0, $0 724 | 0x001d 00029 (shadow_example.go:21) CALL runtime.printlock(SB) 725 | 0x0022 00034 (shadow_example.go:21) MOVQ $1, (SP) 726 | 0x002a 00042 (shadow_example.go:21) CALL runtime.printint(SB) 727 | 0x002f 00047 (shadow_example.go:21) CALL runtime.printsp(SB) 728 | 0x0034 00052 (shadow_example.go:21) MOVQ $2, (SP) 729 | 0x003c 00060 (shadow_example.go:21) CALL runtime.printint(SB) 730 | 0x0041 00065 (shadow_example.go:21) CALL runtime.printnl(SB) 731 | 0x0046 00070 (shadow_example.go:21) CALL runtime.printunlock(SB) 732 | 0x004b 00075 (shadow_example.go:22) MOVQ 8(SP), BP 733 | 0x0050 00080 (shadow_example.go:22) ADDQ $16, SP 734 | 0x0054 00084 (shadow_example.go:22) RET 735 | 0x0055 00085 (shadow_example.go:22) NOP 736 | 737 | --------------------- tail start ---------------------------- 738 | 0x0055 00085 (shadow_example.go:17) PCDATA $0, $-1 739 | 0x0055 00085 (shadow_example.go:17) PCDATA $2, $-1 740 | 0x0055 00085 (shadow_example.go:17) CALL runtime.morestack_noctxt(SB) 741 | 0x005a 00090 (shadow_example.go:17) JMP 0 742 | --------------------- tail end ---------------------------- 743 | ``` 744 | 745 | ## 编写汇编代码 746 | 747 | 不要怂,就是干 748 | 749 | ### 基本汇编函数结构 750 | 751 | ![](images/asm_func.png) 752 | 753 | 你们可能看英文头疼,补一个中文的 754 | 755 | ```go 756 | 757 | 参数及返回值大小 758 | | 759 | TEXT pkgname·add(SB),NOSPLIT,$32-32 760 | | | | 761 | 包名 函数名 栈帧大小(局部变量+可能需要的额外调用函数的参数空间的总大小,但不包括调用其它函数时的 ret address 的大小) 762 | 763 | ``` 764 | 765 | ### 补充知识:伪寄存器 766 | 767 | Go 的汇编还引入了 4 个伪寄存器,援引官方文档的描述: 768 | 769 | >- `FP`: Frame pointer: arguments and locals. 770 | >- `PC`: Program counter: jumps and branches. 771 | >- `SB`: Static base pointer: global symbols. 772 | >- `SP`: Stack pointer: top of stack. 773 | 774 | 官方的描述稍微有一些问题,我们对这些说明进行一点扩充: 775 | 776 | - FP: 使用形如 `symbol+offset(FP)` 的方式,引用函数的输入参数。例如 `arg0+0(FP)`,`arg1+8(FP)`,使用 FP 不加 symbol 时,无法通过编译,在汇编层面来讲,symbol 并没有什么用,加 symbol 主要是为了提升代码可读性。另外,官方文档虽然将伪寄存器 FP 称之为 frame pointer,实际上它根本不是 frame pointer,按照传统的 x86 的习惯来讲,frame pointer 是指向整个 stack frame 底部的 BP 寄存器。假如当前的 callee 函数是 add,在 add 的代码中引用 FP,该 FP 指向的位置不在 callee 的 stack frame 之内,而是在 caller 的 stack frame 上。具体可参见之后的 **栈结构** 一章。 777 | - PC: 实际上就是在体系结构的知识中常见的 pc 寄存器,在 x86 平台下对应 ip 寄存器,amd64 上则是 rip。除了个别跳转之外,手写 plan9 代码与 PC 寄存器打交道的情况较少。 778 | - SB: 全局静态基指针,一般用来声明函数或全局变量,在之后的函数知识和示例部分会看到具体用法。 779 | - SP: plan9 的这个 SP 寄存器指向当前栈帧的局部变量的开始位置,使用形如 `symbol+offset(SP)` 的方式,引用函数的局部变量。offset 的合法取值是 [-framesize, 0),注意是个左闭右开的区间。假如局部变量都是 8 字节,那么第一个局部变量就可以用 `localvar0-8(SP)` 来表示。这也是一个词不表意的寄存器。与硬件寄存器 SP 是两个不同的东西,在栈帧 size 为 0 的情况下,伪寄存器 SP 和硬件寄存器 SP 指向同一位置。手写汇编代码时,如果是 `symbol+offset(SP)` 形式,则表示伪寄存器 SP。如果是 `offset(SP)` 则表示硬件寄存器 SP。务必注意。对于编译输出(go tool compile -S / go tool objdump)的代码来讲,目前所有的 SP 都是硬件寄存器 SP,无论是否带 symbol。 780 | 781 | 我们这里对容易混淆的几点简单进行说明: 782 | 783 | 1. 伪 SP 和硬件 SP 不是一回事,在手写代码时,伪 SP 和硬件 SP 的区分方法是看该 SP 前是否有 symbol。如果有 symbol,那么即为伪寄存器,如果没有,那么说明是硬件 SP 寄存器。 784 | 2. SP 和 FP 的相对位置是会变的,所以不应该尝试用伪 SP 寄存器去找那些用 FP + offset 来引用的值,例如函数的入参和返回值。 785 | 3. 官方文档中说的伪 SP 指向 stack 的 top,是有问题的。其指向的局部变量位置实际上是整个栈的栈底(除 caller BP 之外),所以说 bottom 更合适一些。 786 | 4. 在 go tool objdump/go tool compile -S 输出的代码中,是没有伪 SP 和 FP 寄存器的,我们上面说的区分伪 SP 和硬件 SP 寄存器的方法,对于上述两个命令的输出结果是没法使用的。在编译和反汇编的结果中,只有真实的 SP 寄存器。 787 | 5. FP 和 Go 的官方源代码里的 framepointer 不是一回事,源代码里的 framepointer 指的是 caller BP 寄存器的值,在这里和 caller 的伪 SP 是值是相等的。 788 | 789 | 以上说明看不懂也没关系,在熟悉了函数的栈结构之后再反复回来查看应该就可以明白了。个人意见,这些是 Go 官方挖的坑。。 790 | 791 | ### 例1:实现一个简单的 a+b 792 | 793 | a.go 794 | 795 | ```go 796 | package main 797 | 798 | import "fmt" 799 | 800 | func add(a, b int) int // 汇编函数声明 801 | 802 | func main() { 803 | fmt.Println(add(10, 11)) 804 | } 805 | ``` 806 | 807 | ```go 808 | #include "textflag.h" 809 | 810 | // func add(a, b int) int 811 | TEXT ·add(SB), NOSPLIT, $0-24 812 | MOVQ a+0(FP), AX // 参数 a 813 | MOVQ b+8(FP), BX // 参数 b 814 | ADDQ BX, AX // AX += BX 815 | MOVQ AX, ret+16(FP) // 返回 816 | RET 817 | ``` 818 | 819 | 你能解释这里为什么是 $0-24 吗? 820 | 821 | ### 例2:进阶,实现 slice 求和 822 | 823 | ```go 824 | package main 825 | 826 | func sum([]int64) int64 827 | 828 | func main() { 829 | println(sum([]int64{1, 2, 3, 4, 5})) 830 | } 831 | ``` 832 | 833 | ```go 834 | #include "textflag.h" 835 | 836 | // func sum(sl []int64) int64 837 | TEXT ·sum(SB), NOSPLIT, $0-32 838 | MOVQ $0, SI 839 | MOVQ sl+0(FP), BX // &sl[0], addr of the first elem 840 | MOVQ sl+8(FP), CX // len(sl) 841 | INCQ CX // CX++, 因为要循环 len 次 842 | 843 | start: 844 | DECQ CX // CX-- 845 | JZ done 846 | ADDQ (BX), SI // SI += *BX 847 | ADDQ $8, BX // 指针移动 848 | JMP start 849 | 850 | done: 851 | MOVQ SI, ret+24(FP) 852 | RET 853 | ``` 854 | 855 | 问题,返回值的 ret+24(FP),这里的 24 是怎么算出来的呢? 856 | 857 | ### 突破编译器限制 858 | 859 | 访问结构体的私有变量: 860 | 861 | https://github.com/cch123/goroutineid 862 | 863 | ### 使用汇编来优化,SIMD 864 | 865 | 留给你去探索了,我不会。 866 | 867 | 实际上也不难,看你工作有多么不饱和了。 868 | 869 | ### Go 未支持的优化指令编写 870 | 871 | ```go 872 | TEXT ·_MultiplyAndAdd(SB), $0-32 873 | 874 | MOVQ vec1+0(FP), DI 875 | MOVQ vec2+8(FP), SI 876 | MOVQ vec3+16(FP), DX 877 | MOVQ result+24(FP), CX 878 | 879 | LONG $0x0710fcc5 // vmovups ymm0, yword [rdi] 880 | LONG $0x0e10fcc5 // vmovups ymm1, yword [rsi] 881 | LONG $0xa87de2c4; BYTE $0x0a // vfmadd213ps ymm1, ymm0, yword [rdx] 882 | LONG $0x0911fcc5 // vmovups yword [rcx], ymm1 883 | 884 | VZEROUPPER 885 | RET 886 | ``` 887 | 888 | 用 LONG,BYTE 来写这些未支持的指令,需要自己计算,或者用其它工具进行转换。 889 | 890 | 目前 Go 在 64 位 intel 平台上已支持 AVX512,一般情况下不需要写这种恶心的东西了。 891 | 892 | ## 一些和汇编相关的轮子 893 | 894 | c2goasm,把 C 代码转成 plan9 asm 代码 895 | 896 | ``` 897 | https://github.com/minio/c2goasm 898 | ``` 899 | 900 | avo,写 Go 代码,生成 asm 代码,防止写错 901 | 902 | ``` 903 | https://github.com/mmcloughlin/avo 904 | ``` 905 | 906 | asm2plan9s: 907 | 908 | ``` 909 | https://github.com/minio/asm2plan9s 910 | ``` 911 | 912 | 就是干我上面说的这件恶心的事情的: 913 | 914 | ``` 915 | For instance: 916 | 917 | \ // VPADDQ XMM0,XMM1,XMM8 918 | will be assembled into 919 | 920 | LONG $0xd471c1c4; BYTE $0xc0 \ // VPADDQ XMM0,XMM1,XMM8 921 | 922 | ``` 923 | 924 | 理解汇编指令和栈结构的利器,作者是我(思路是抄的): 925 | 926 | ``` 927 | https://github.com/cch123/asm-cli 928 | ``` 929 | 930 | 如果你信仰不够坚定,也可以看看我翻译的 rust 版,支持栈上下滚动哦~ 931 | 932 | ``` 933 | https://github.com/cch123/asm-cli-rust 934 | ``` 935 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | ## 汇编分享 2 | 3 | 1. 简单学习后应该能自行解答 Q.md 中的问题 4 | 2. 如果有问题,可以在本仓库开 issue 讨论 5 | 3. refs.md 里是一些参考链接 6 | -------------------------------------------------------------------------------- /refs.md: -------------------------------------------------------------------------------- 1 | x86 体系的汇编、调用规约 2 | 3 | https://github.com/0xAX/asm 4 | 5 | Go plan9 汇编官方文档(很不详细) 6 | 7 | https://golang.org/doc/asm 8 | 9 | 补充文档,作者是原来 intel 的工程师 10 | 11 | https://quasilyte.dev/blog/post/go-asm-complementary-reference/#external-resources 12 | 13 | 法国的一个系统工程师的汇编教程,之前有一些疏漏,不过已经被网友(其中包括我(笑)纠正了 14 | 15 | https://github.com/teh-cmc/go-internals/tree/master/chapter1_assembly_primer 16 | 17 | 我在学习汇编的时间总结的笔记: 18 | 19 | https://github.com/cch123/golang-notes/blob/master/assembly.md 20 | 21 | 柴总和我编写的 《Go 高级编程》中的汇编章节: 22 | 23 | https://github.com/chai2010/advanced-go-programming-book/blob/master/ch3-asm/readme.md 24 | 25 | 本书将于 19 年四月由人民邮电出版社出版,到时候希望大家支持! 26 | --------------------------------------------------------------------------------