- 装逼
- 防骗:各种专家和架构师,江湖骗子,牛鬼蛇神,拿着 Go 语言以外的过往经验就忽悠上了
- 你这么写效率不高,下面可能会有逃逸,你看得按我教你的这么写
- 我们 java 赋值为 null 就能帮助 GC 判断,所以我也需要用完了 x = nil
- 这个东西 runtime 里是这样这样那样那样实现的,我这么多年编程经验,你信我的准没错
- Go 的 xxx 类型参数是引用传递的, yyy 类型参数是值传递的,哥当年研究 C艹 的研究了好久,哥的理解肯定比你权威
- Go 的调度老牛逼了,稳得很
- 按照编程语言的一般理论,这个地方肯定就是这么实现的,没错
编译 -> 汇编 -> 优化 -> 链接
汇编代码在汇编阶段生成,链接阶段会对生成的汇编代码进行修改。(eg0)
实际上优化过程是在所有过程中都有的,这里我们只说汇编优化。
链接阶段会对汇编结果做调整(如将函数地址从偏移量转换为逻辑地址)
所有汇编指令都可以转换为 0101 的序列,如:
>> mov rax,0x1
mnemonic : mov rax,0x1 => hex : 48c7c001000000
hex 是机器码的 16 进制编码。
RISC/CISC
intel? 难说。
risc 的新轮子 RISCV。
CPU 优化指令集,如 x86 平台:SSE/SSE2/AVX。。。AVX512
优化指令集往往能用来加速内存移动,指令运算。基本思想是用硬件方式来做 batch,减少指令数,加速计算。
寄存器是中央处理器内的组成部分。寄存器是有限存贮容量的高速存贮部件,它们可用来暂存指令、数据和地址。
通用寄存器:
ah/al => 8 位
ax/bx => 16 位
eax/ebx => 32 位
rax/rbx => 64 位,8 byte
rax : 0x0000000000000000
指令寄存器:
pc/rip
表示当前我的代码运行到哪里了。
标志寄存器:
eflags
记录各种运算结果(是否为 0,是否发生溢出)的标志位。
在 dlv 中读取寄存器的值:
regs ------------------------ Print contents of CPU registers.
(dlv) regs
rax = 0x0000000001053940
rbx = 0x0000000000000000
rcx = 0x000000c000000300
rdx = 0x00000000010750c0
rdi = 0x0000000000000000
rsi = 0x0000000000000001
rbp = 0x000000c0000427d0
rsp = 0x000000c000042790
r8 = 0x7fffffffffffffff
r9 = 0xffffffffffffffff
r10 = 0x00000000010c0478
r11 = 0x0000000000000202
r12 = 0x0000000000203000
r13 = 0x0000000000000000
r14 = 0x0000000000000178
r15 = 0x0000000000000004
rip = 0x000000000105394f
rflags = 0x0000000000000202
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
ST(0) = 0x7fff0000000000000000 -Inf
ST(1) = 0x00000000000000000000 0
ST(2) = 0x00000000000000000000 0
ST(3) = 0x00000000000000000000 0
ST(4) = 0x00000000000000000000 0
ST(5) = 0x7fff00000000ff73ff84 -Inf
ST(6) = 0x7fff00000000022f2d1c -Inf
ST(7) = 0x7fff00000000ffffd1fe -Inf
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 }
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 }
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 }
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 }
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 }
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 }
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 }
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 }
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 }
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 }
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 }
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 }
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 }
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 }
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 }
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 }
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 }
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 }
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 }
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 }
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 }
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 }
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 }
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 }
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 }
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 }
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 }
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 }
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 }
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 }
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 }
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 }
trapno = 0x00000001
err = 0x00000000
faultvaddr = 0x00000000010ee000
大部分不需要理解,知道通用寄存器和简单的扩展寄存器就好了。
mov rax, 0x1 // rax = 1
add rax, 11 // rax = rax + 11
sub rax,1 // rax = rax - 1
// 在寄存器之间搬数据
mov rax, rbx // rax = rbx
// 在寄存器和内存之间搬数据
mov rax, [rbx] // 把 rbx 寄存器的值看作一个地址,把该物理地址的值取出,然后赋值给 rax,即 rax = *rbx
// 在内存之间搬数据
- 你想多了,不可以的
push rax => 将 rax 的值存储到栈顶,并将 rsp 上移 8 个字节
pop rax => 将 rsp 指向的内存位置的 8 个字节移动到 rax,并将 rsp 从栈顶下移 8 个字节
jmp addr => 跳转到指定地址
函数调用在汇编上的本质:
jmp 跳进去
jmp 跳回来
cpu 很傻,跳回来的时候我必须到当前过程的下一条指令位置,难道我还需要在被调用过程里知道调用者的过程下一条指令位置?
call => push pc; jmp to callee addr;
ret => pop pc;
要点,在栈上记录下一条指令位置,即我们常说的 return address
,理解成路标就行。在执行完被调用函数的时候,顺着路标回去继续向下走。
函数栈:
+--------------+
| |
+ | |
| +--------------+
| | |
| | arg(N-1) | starts from 7'th argument for x86_64
| | |
| +--------------+
| | |
| | argN |
| | |
| +--------------+
| | |
| |Return address| %rbp + 8
Stack grows down | | |
| +--------------+
| | |
| | %rbp | Frame base pointer
| | |
| +--------------+
| | |
| | local var1 | %rbp - 8
| | |
| +--------------+
| | |
| | local var 2 | <-- %rsp
| | |
v +--------------+
| |
| |
+--------------+
调用规约:
参考这里:
主要是知道哪些寄存器是需要 caller save,哪些是 callee save。
section .data
message: db 'hello, world!', 10
section .text
global _start
_start:
mov rax, 1 ; 'write' syscall number
mov rdi, 1 ; stdout descriptor
mov rsi, message ; string address
mov rdx, 14 ; string length in bytes
syscall ; 重点在这里
更具体的可以参考 这里
和这里
- 寄存器名差别
rax -> AX
rbx -> BX
r8 -> R8
因为移动多少个字节是由指令而不是操作数决定的,所以稍微省心一些。
- 指令对应关系
MOVB $1, DI -> mov al, 0x44 // 1 byte
MOVW $0x10, BX -> mov ax, 0x10 // 2 bytes
MOVD $100, DX -> mov eax, 100 // 4 bytes
MOVQ $-10, AX -> mov rax, -10 // 8 bytes
可以看到,指令的操作数是反着的,大部分 plan9 和 intel 的指令确实是反着的关系,学习过汇编的同学可能会认为很好啊,这样 plan9 的操作数顺序应该是和 AT&T 一致了,遗憾的是也有例外:
Mnemonic | Go operands | AT&T operands
================================================
BOUNDW | m16&16, r16 | r16, m16&16
BOUNDL | m32&32, r32 | r32, m32&32
CMPB | AL, imm8 | imm8, AL
CMPW | AX, imm16 | imm16, AX
CMPL | EAX, imm32 | imm32, EAX
CMPQ | RAX, imm32 | imm32, RAX
CMPW | r/m16, imm16 | imm16, r/m16
CMPW | r/m16, imm8 | imm8, r/m16
CMPW | r/m16, r16 | r16, r/m16
CMPL | r/m32, imm32 | imm32, r/m32
CMPL | r/m32, imm8 | imm8, r/m32
CMPL | r/m32, r32 | r32, r/m32
CMPQ | r/m64, imm32 | imm32, r/m64
CMPQ | r/m64, imm8 | imm8, r/m64
CMPQ | r/m64, r64 | r64, r/m64
CMPB | r/m8, imm8 | imm8, r/m8
CMPB | r/m8, imm8 | imm8, r/m8
CMPB | r/m8, r8 | r8, r/m8
CMPB | r/m8, r8 | r8, r/m8
CMPW | r16, r/m16 | r/m16, r16
CMPL | r32, r/m32 | r/m32, r32
CMPQ | r64, r/m64 | r/m64, r64
CMPB | r8, r/m8 | r/m8, r8
CMPB | r8, r/m8 | r/m8, r8
CMPPD | imm8, xmm1, xmm2/m128 | imm8, xmm2/m128, xmm1
CMPPS | imm8, xmm1, xmm2/m128 | imm8, xmm2/m128, xmm1
CMPSD | imm8, xmm1, xmm2/m64 | imm8, xmm2/m64, xmm1
CMPSS | imm8, xmm1, xmm2/m32 | imm8, xmm2/m32, xmm1
ENTER | 0, imm16 | imm16, 0
ENTER | 1, imm16 | imm16, 1
.data : 有初始化值的全局变量;定义常量。
.bss : 没有初始化值的全局变量。
.text : 代码段。
.rodata: 只读数据段。
代码在 .text 段中,大概知道就行。Go 语言用户程序的入口为 main.main。
可以使用 objdump 来看 intel 指令的反汇编结果:
package main
func main() {
println("hello")
}
000000000044c150 <main.main>:
44c150: 64 48 8b 0c 25 f8 ff mov rcx,QWORD PTR fs:0xfffffffffffffff8
44c157: ff ff
44c159: 48 3b 61 10 cmp rsp,QWORD PTR [rcx+0x10]
44c15d: 76 3b jbe 44c19a <main.main+0x4a>
44c15f: 48 83 ec 18 sub rsp,0x18
44c163: 48 89 6c 24 10 mov QWORD PTR [rsp+0x10],rbp
44c168: 48 8d 6c 24 10 lea rbp,[rsp+0x10]
44c16d: e8 8e 59 fd ff call 421b00 <runtime.printlock>
44c172: 48 8d 05 ba dc 01 00 lea rax,[rip+0x1dcba] # 469e33 <go.string.*+0x1e3>
44c179: 48 89 04 24 mov QWORD PTR [rsp],rax
44c17d: 48 c7 44 24 08 06 00 mov QWORD PTR [rsp+0x8],0x6
44c184: 00 00
44c186: e8 b5 62 fd ff call 422440 <runtime.printstring>
44c18b: e8 f0 59 fd ff call 421b80 <runtime.printunlock>
44c190: 48 8b 6c 24 10 mov rbp,QWORD PTR [rsp+0x10]
44c195: 48 83 c4 18 add rsp,0x18
44c199: c3 ret
44c19a: e8 d1 83 ff ff call 444570 <runtime.morestack_noctxt>
44c19f: eb af jmp 44c150 <main.main>
44c1a1: cc int3
go tool compile -S
输出的汇编代码还没有链接,呈现的地址都是偏移量。
go tool objdump
相当于反汇编你的程序,会丢失很多信息。
package main
func main() {
println("hello")
}
TEXT main.main(SB) /home/vagrant/a.go
a.go:3 0x44c150 64488b0c25f8ffffff MOVQ FS:0xfffffff8, CX
a.go:3 0x44c159 483b6110 CMPQ 0x10(CX), SP
a.go:3 0x44c15d 763b JBE 0x44c19a
a.go:3 0x44c15f 4883ec18 SUBQ $0x18, SP
a.go:3 0x44c163 48896c2410 MOVQ BP, 0x10(SP)
a.go:3 0x44c168 488d6c2410 LEAQ 0x10(SP), BP
a.go:4 0x44c16d e88e59fdff CALL runtime.printlock(SB)
a.go:4 0x44c172 488d05badc0100 LEAQ 0x1dcba(IP), AX
a.go:4 0x44c179 48890424 MOVQ AX, 0(SP)
a.go:4 0x44c17d 48c744240806000000 MOVQ $0x6, 0x8(SP)
a.go:4 0x44c186 e8b562fdff CALL runtime.printstring(SB)
a.go:4 0x44c18b e8f059fdff CALL runtime.printunlock(SB)
a.go:5 0x44c190 488b6c2410 MOVQ 0x10(SP), BP
a.go:5 0x44c195 4883c418 ADDQ $0x18, SP
a.go:5 0x44c199 c3 RET
a.go:3 0x44c19a e8d183ffff CALL runtime.morestack_noctxt(SB)
a.go:3 0x44c19f ebaf JMP main.main(SB)
当你手写汇编时,与 go tool compile -S
和 objdump
的是不一样的,不要直接照着这个结果来写!
caller
+------------------+
| |
+----------------------> --------------------
| | |
| | caller parent BP |
| BP(pseudo SP) --------------------
| | |
| | Local Var0 |
| --------------------
| | |
| | ....... |
| --------------------
| | |
| | Local VarN |
--------------------
caller stack frame | |
| callee arg2 |
| |------------------|
| | |
| | callee arg1 |
| |------------------|
| | |
| | callee arg0 |
| ----------------------------------------------+ FP(virtual register)
| | | |
| | return addr | parent return address |
+----------------------> +------------------+--------------------------- <-------------------------------+
| caller BP | |
| (caller frame pointer) | |
BP(pseudo SP) ---------------------------- |
| | |
| Local Var0 | |
---------------------------- |
| |
| Local Var1 |
---------------------------- callee stack frame
| |
| ..... |
---------------------------- |
| | |
| Local VarN | |
SP(Real Register) ---------------------------- |
| | |
| | |
| | |
| | |
| | |
+--------------------------+ <-------------------------------+
callee
package main
func f() int {
var res = 0
defer func() {
res++
}()
return res
}
func main() {}
"".f STEXT size=137 args=0x8 locals=0x28
0x0000 00000 (defer.go:3) TEXT "".f(SB), $40-8
0x0000 00000 (defer.go:3) MOVQ (TLS), CX
0x0009 00009 (defer.go:3) CMPQ SP, 16(CX)
0x000d 00013 (defer.go:3) JLS 127
0x000f 00015 (defer.go:3) SUBQ $40, SP
0x0013 00019 (defer.go:3) MOVQ BP, 32(SP)
0x0018 00024 (defer.go:3) LEAQ 32(SP), BP
0x001d 00029 (defer.go:3) FUNCDATA $0, gclocals·33cdeccccebe80329f1fdbee7f5874cb(SB)
0x001d 00029 (defer.go:3) FUNCDATA $1, gclocals·33cdeccccebe80329f1fdbee7f5874cb(SB)
0x001d 00029 (defer.go:3) FUNCDATA $3, gclocals·9fb7f0986f647f17cb53dda1484e0f7a(SB)
0x001d 00029 (defer.go:3) PCDATA $2, $0
0x001d 00029 (defer.go:3) PCDATA $0, $0
0x001d 00029 (defer.go:3) MOVQ $0, "".~r0+48(SP)
0x0026 00038 (defer.go:4) MOVQ $0, "".res+24(SP)
0x002f 00047 (defer.go:7) PCDATA $2, $1
0x002f 00047 (defer.go:7) LEAQ "".res+24(SP), AX
0x0034 00052 (defer.go:7) PCDATA $2, $0
0x0034 00052 (defer.go:7) MOVQ AX, 16(SP)
0x0039 00057 (defer.go:5) MOVL $8, (SP)
0x0040 00064 (defer.go:5) PCDATA $2, $1
0x0040 00064 (defer.go:5) LEAQ "".f.func1·f(SB), AX
0x0047 00071 (defer.go:5) PCDATA $2, $0
0x0047 00071 (defer.go:5) MOVQ AX, 8(SP)
0x004c 00076 (defer.go:5) CALL runtime.deferproc(SB)
0x0051 00081 (defer.go:5) TESTL AX, AX
0x0053 00083 (defer.go:5) JNE 111
0x0055 00085 (defer.go:8) MOVQ "".res+24(SP), AX
0x005a 00090 (defer.go:8) MOVQ AX, "".~r0+48(SP)
0x005f 00095 (defer.go:8) XCHGL AX, AX
0x0060 00096 (defer.go:8) CALL runtime.deferreturn(SB)
0x0065 00101 (defer.go:8) MOVQ 32(SP), BP
0x006a 00106 (defer.go:8) ADDQ $40, SP
0x006e 00110 (defer.go:8) RET
0x006f 00111 (defer.go:5) XCHGL AX, AX
0x0070 00112 (defer.go:5) CALL runtime.deferreturn(SB)
0x0075 00117 (defer.go:5) MOVQ 32(SP), BP
0x007a 00122 (defer.go:5) ADDQ $40, SP
0x007e 00126 (defer.go:5) RET
0x007f 00127 (defer.go:5) NOP
0x007f 00127 (defer.go:3) PCDATA $0, $-1
0x007f 00127 (defer.go:3) PCDATA $2, $-1
0x007f 00127 (defer.go:3) CALL runtime.morestack_noctxt(SB)
0x0084 00132 (defer.go:3) JMP 0
defer 提供给你是用来关闭各种资源,恢复 panic 程序现场的。
不是给你炫技或者面试别人的。
劝君善良。
package main
import "fmt"
func main() {
var a = map[int]int{}
a[1] = 1
fmt.Println(a)
}
go tool compile -S map.go | grep 'map.go:6'
0x0021 00033 (map.go:6) PCDATA $2, $0
0x0021 00033 (map.go:6) PCDATA $0, $0
0x0021 00033 (map.go:6) CALL runtime.makemap_small(SB)
0x0026 00038 (map.go:6) PCDATA $2, $1
0x0026 00038 (map.go:6) MOVQ (SP), AX
0x002a 00042 (map.go:6) PCDATA $0, $1
0x002a 00042 (map.go:6) MOVQ AX, "".a+48(SP)
只要再去 runtime 源代码中寻找 makemap_small 的定义即可。
package main
import "fmt"
func main() {
var a = new([]int)
fmt.Println(a)
}
0x001d 00029 (alloc.go:6) PCDATA $0, $0
0x001d 00029 (alloc.go:6) LEAQ type.[]int(SB), AX
0x0024 00036 (alloc.go:6) PCDATA $2, $0
0x0024 00036 (alloc.go:6) MOVQ AX, (SP)
0x0028 00040 (alloc.go:6) CALL runtime.newobject(SB)
0x002d 00045 (alloc.go:6) PCDATA $2, $1
0x002d 00045 (alloc.go:6) MOVQ 8(SP), AX
package main
func f(i int) int {
if i == 0 || i == 1 {
return i
}
return f(i - 1)
}
func main() {
println(f(666665535))
}
"".f STEXT size=99 args=0x10 locals=0x18
0x0000 00000 (recur.go:3) TEXT "".f(SB), $24-16
0x0000 00000 (recur.go:3) MOVQ (TLS), CX
0x0009 00009 (recur.go:3) CMPQ SP, 16(CX)
0x000d 00013 (recur.go:3) JLS 92
0x000f 00015 (recur.go:3) SUBQ $24, SP
0x0013 00019 (recur.go:3) MOVQ BP, 16(SP)
0x0018 00024 (recur.go:3) LEAQ 16(SP), BP
0x001d 00029 (recur.go:3) FUNCDATA $0, gclocals·33cdeccccebe80329f1fdbee7f5874cb(SB)
0x001d 00029 (recur.go:3) FUNCDATA $1, gclocals·33cdeccccebe80329f1fdbee7f5874cb(SB)
0x001d 00029 (recur.go:3) FUNCDATA $3, gclocals·33cdeccccebe80329f1fdbee7f5874cb(SB)
0x001d 00029 (recur.go:4) PCDATA $2, $0
0x001d 00029 (recur.go:4) PCDATA $0, $0
0x001d 00029 (recur.go:4) MOVQ "".i+32(SP), AX
0x0022 00034 (recur.go:4) TESTQ AX, AX
0x0025 00037 (recur.go:4) JNE 54
0x0027 00039 (recur.go:5) MOVQ AX, "".~r1+40(SP)
0x002c 00044 (recur.go:5) MOVQ 16(SP), BP
0x0031 00049 (recur.go:5) ADDQ $24, SP
0x0035 00053 (recur.go:5) RET
0x0036 00054 (recur.go:4) CMPQ AX, $1
0x003a 00058 (recur.go:4) JEQ 39
0x003c 00060 (recur.go:7) DECQ AX
0x003f 00063 (recur.go:7) MOVQ AX, (SP)
0x0043 00067 (recur.go:7) CALL "".f(SB)
0x0048 00072 (recur.go:7) MOVQ 8(SP), AX
0x004d 00077 (recur.go:7) MOVQ AX, "".~r1+40(SP)
0x0052 00082 (recur.go:7) MOVQ 16(SP), BP
0x0057 00087 (recur.go:7) ADDQ $24, SP
0x005b 00091 (recur.go:7) RET
0x005c 00092 (recur.go:7) NOP
0x005c 00092 (recur.go:3) PCDATA $0, $-1
0x005c 00092 (recur.go:3) PCDATA $2, $-1
0x005c 00092 (recur.go:3) CALL runtime.morestack_noctxt(SB)
0x0061 00097 (recur.go:3) JMP 0
嗯,显然目前依然是没有优化的。
运行一下,bang!
~/asmshare ❯❯❯ go run recur.go master ✖ ✱ ◼
runtime: goroutine stack exceeds 1000000000-byte limit
fatal error: stack overflow
runtime stack:
runtime.throw(0x106b632, 0xe)
/usr/local/go/src/runtime/panic.go:608 +0x72
runtime.newstack()
/usr/local/go/src/runtime/stack.go:1008 +0x729
runtime.morestack()
/usr/local/go/src/runtime/asm_amd64.s:429 +0x8f
相反我们看看 C 语言的:
#include "stdio.h"
int f(int i) {
if( i == 0 || i == 1) {
return i;
}
return f(i-1);
}
int main() {
printf("%d\n", f(666665535));
return 1;
}
~/asmshare ❯❯❯ clang -O2 c.c
~/asmshare ❯❯❯ ./a.out
1
老哥,稳!
"".f2 STEXT size=92 args=0x0 locals=0x10
---------------------- header startend -------------------------
0x0000 00000 (shadow_example.go:17) TEXT "".f2(SB), $16-0
0x0000 00000 (shadow_example.go:17) MOVQ (TLS), CX
0x0009 00009 (shadow_example.go:17) CMPQ SP, 16(CX)
0x000d 00013 (shadow_example.go:17) JLS 85
---------------------- header end -------------------------
0x000f 00015 (shadow_example.go:17) SUBQ $16, SP
0x0013 00019 (shadow_example.go:17) MOVQ BP, 8(SP)
0x0018 00024 (shadow_example.go:17) LEAQ 8(SP), BP
0x001d 00029 (shadow_example.go:17) FUNCDATA $0, gclocals·33cdeccccebe80329f1fdbee7f5874cb(SB)
0x001d 00029 (shadow_example.go:17) FUNCDATA $1, gclocals·33cdeccccebe80329f1fdbee7f5874cb(SB)
0x001d 00029 (shadow_example.go:17) FUNCDATA $3, gclocals·33cdeccccebe80329f1fdbee7f5874cb(SB)
0x001d 00029 (shadow_example.go:21) PCDATA $2, $0
0x001d 00029 (shadow_example.go:21) PCDATA $0, $0
0x001d 00029 (shadow_example.go:21) CALL runtime.printlock(SB)
0x0022 00034 (shadow_example.go:21) MOVQ $1, (SP)
0x002a 00042 (shadow_example.go:21) CALL runtime.printint(SB)
0x002f 00047 (shadow_example.go:21) CALL runtime.printsp(SB)
0x0034 00052 (shadow_example.go:21) MOVQ $2, (SP)
0x003c 00060 (shadow_example.go:21) CALL runtime.printint(SB)
0x0041 00065 (shadow_example.go:21) CALL runtime.printnl(SB)
0x0046 00070 (shadow_example.go:21) CALL runtime.printunlock(SB)
0x004b 00075 (shadow_example.go:22) MOVQ 8(SP), BP
0x0050 00080 (shadow_example.go:22) ADDQ $16, SP
0x0054 00084 (shadow_example.go:22) RET
0x0055 00085 (shadow_example.go:22) NOP
--------------------- tail start ----------------------------
0x0055 00085 (shadow_example.go:17) PCDATA $0, $-1
0x0055 00085 (shadow_example.go:17) PCDATA $2, $-1
0x0055 00085 (shadow_example.go:17) CALL runtime.morestack_noctxt(SB)
0x005a 00090 (shadow_example.go:17) JMP 0
--------------------- tail end ----------------------------
不要怂,就是干
你们可能看英文头疼,补一个中文的
参数及返回值大小
|
TEXT pkgname·add(SB),NOSPLIT,$32-32
| | |
包名 函数名 栈帧大小(局部变量+可能需要的额外调用函数的参数空间的总大小,但不包括调用其它函数时的 ret address 的大小)
Go 的汇编还引入了 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.
官方的描述稍微有一些问题,我们对这些说明进行一点扩充:
- 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 上。具体可参见之后的 栈结构 一章。 - PC: 实际上就是在体系结构的知识中常见的 pc 寄存器,在 x86 平台下对应 ip 寄存器,amd64 上则是 rip。除了个别跳转之外,手写 plan9 代码与 PC 寄存器打交道的情况较少。
- SB: 全局静态基指针,一般用来声明函数或全局变量,在之后的函数知识和示例部分会看到具体用法。
- 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。
我们这里对容易混淆的几点简单进行说明:
- 伪 SP 和硬件 SP 不是一回事,在手写代码时,伪 SP 和硬件 SP 的区分方法是看该 SP 前是否有 symbol。如果有 symbol,那么即为伪寄存器,如果没有,那么说明是硬件 SP 寄存器。
- SP 和 FP 的相对位置是会变的,所以不应该尝试用伪 SP 寄存器去找那些用 FP + offset 来引用的值,例如函数的入参和返回值。
- 官方文档中说的伪 SP 指向 stack 的 top,是有问题的。其指向的局部变量位置实际上是整个栈的栈底(除 caller BP 之外),所以说 bottom 更合适一些。
- 在 go tool objdump/go tool compile -S 输出的代码中,是没有伪 SP 和 FP 寄存器的,我们上面说的区分伪 SP 和硬件 SP 寄存器的方法,对于上述两个命令的输出结果是没法使用的。在编译和反汇编的结果中,只有真实的 SP 寄存器。
- FP 和 Go 的官方源代码里的 framepointer 不是一回事,源代码里的 framepointer 指的是 caller BP 寄存器的值,在这里和 caller 的伪 SP 是值是相等的。
以上说明看不懂也没关系,在熟悉了函数的栈结构之后再反复回来查看应该就可以明白了。个人意见,这些是 Go 官方挖的坑。。
a.go
package main
import "fmt"
func add(a, b int) int // 汇编函数声明
func main() {
fmt.Println(add(10, 11))
}
#include "textflag.h"
// func add(a, b int) int
TEXT ·add(SB), NOSPLIT, $0-24
MOVQ a+0(FP), AX // 参数 a
MOVQ b+8(FP), BX // 参数 b
ADDQ BX, AX // AX += BX
MOVQ AX, ret+16(FP) // 返回
RET
你能解释这里为什么是 $0-24 吗?
package main
func sum([]int64) int64
func main() {
println(sum([]int64{1, 2, 3, 4, 5}))
}
#include "textflag.h"
// func sum(sl []int64) int64
TEXT ·sum(SB), NOSPLIT, $0-32
MOVQ $0, SI
MOVQ sl+0(FP), BX // &sl[0], addr of the first elem
MOVQ sl+8(FP), CX // len(sl)
INCQ CX // CX++, 因为要循环 len 次
start:
DECQ CX // CX--
JZ done
ADDQ (BX), SI // SI += *BX
ADDQ $8, BX // 指针移动
JMP start
done:
MOVQ SI, ret+24(FP)
RET
问题,返回值的 ret+24(FP),这里的 24 是怎么算出来的呢?
访问结构体的私有变量:
https://github.com/cch123/goroutineid
留给你去探索了,我不会。
实际上也不难,看你工作有多么不饱和了。
TEXT ·_MultiplyAndAdd(SB), $0-32
MOVQ vec1+0(FP), DI
MOVQ vec2+8(FP), SI
MOVQ vec3+16(FP), DX
MOVQ result+24(FP), CX
LONG $0x0710fcc5 // vmovups ymm0, yword [rdi]
LONG $0x0e10fcc5 // vmovups ymm1, yword [rsi]
LONG $0xa87de2c4; BYTE $0x0a // vfmadd213ps ymm1, ymm0, yword [rdx]
LONG $0x0911fcc5 // vmovups yword [rcx], ymm1
VZEROUPPER
RET
用 LONG,BYTE 来写这些未支持的指令,需要自己计算,或者用其它工具进行转换。
目前 Go 在 64 位 intel 平台上已支持 AVX512,一般情况下不需要写这种恶心的东西了。
c2goasm,把 C 代码转成 plan9 asm 代码
https://github.com/minio/c2goasm
avo,写 Go 代码,生成 asm 代码,防止写错
https://github.com/mmcloughlin/avo
asm2plan9s:
https://github.com/minio/asm2plan9s
就是干我上面说的这件恶心的事情的:
For instance:
\ // VPADDQ XMM0,XMM1,XMM8
will be assembled into
LONG $0xd471c1c4; BYTE $0xc0 \ // VPADDQ XMM0,XMM1,XMM8
理解汇编指令和栈结构的利器,作者是我(思路是抄的):
https://github.com/cch123/asm-cli
如果你信仰不够坚定,也可以看看我翻译的 rust 版,支持栈上下滚动哦~
https://github.com/cch123/asm-cli-rust