macOS 下 C++ 模版的反汇编
C++ 模版代码
先来一段简单的 C++ 模版的代码,代码如下:
#include <iostream>
using namespace std;
template<typename T>
T min(T a, T b, T c)
{
if (a > b) a = b;
if (a > c) a = c;
return a;
}
int main()
{
int a = 1, b = 2, c = 3;
cout << min(a, b, c) << endl;
long long a1 = 1000000000, b1 = 2000000000, c1 = 300000000;
cout << min(a1, b1, c1) << endl;
return 0;
}
代码的功能很简单的,分别传入 int 和 long long 两种类型的实参给模版函数 min,然后进行编译,编译完成后用 ht editor 进行查看。
ht editor 查看反汇编代码
用 ht editor 挺好的,打开编译后的可执行文件以后,直接可以查看 main 函数的反汇编代码,代码如下:
│_main+0 │
│ 100000be0 ! │
│ ......... ! ;******************************************************** │
│ ......... ! ; function _main │
│ ......... ! ;******************************************************** │
│ ......... ! _main: │
│ ......... ! push rbp │
│ 100000be1 ! mov rbp, rsp │
│ 100000be4 ! sub rsp, 40h │
│ 100000be8 ! mov dword ptr [rbp-4], 0 │
│ 100000bef ! mov dword ptr [rbp-8], 1 │
│ 100000bf6 ! mov dword ptr [rbp-0ch], 2 │
│ 100000bfd ! mov dword ptr [rbp-10h], 3 │
│ 100000c04 ! mov edi, [rbp-8] │
│ 100000c07 ! mov esi, [rbp-0ch] │
│ 100000c0a ! mov edx, [rbp-10h] │
│ 100000c0d ! call wrapper_100002008_100000eb6 │
│ 100000c12 ! mov rdi, [data_100001000] │
│ 100000c19 ! mov esi, eax │
│ 100000c1b ! call wrapper_100002038_100000eda │
│ 100000c20 ! mov rdi, rax │
│ 100000c23 ! lea rsi, [__ZNSt3__1L4endlIcNS_11char_traitsIcEEEERNS_13basic_ostreamIT_T0_EES7_] │
│ 100000c2a ! call __ZNSt3__113basic_ostreamIcNS_11char_traitsIcEEElsEPFRS3_S4_E │
│ 100000c2f ! mov qword ptr [rbp-18h], 3b9aca00h │
│ 100000c37 ! mov qword ptr [rbp-20h], 77359400h │
│ 100000c3f ! mov qword ptr [rbp-28h], 11e1a300h │
│ 100000c47 ! mov rdi, [rbp-18h] │
│ 100000c4b ! mov rsi, [rbp-20h] │
│ 100000c4f ! mov rdx, [rbp-28h] │
│ 100000c53 ! mov [rbp-30h], rax │
│ 100000c57 ! call wrapper_100002010_100000ebc │
│ 100000c5c ! mov rdi, [data_100001000] │
│ 100000c63 ! mov rsi, rax │
│ 100000c66 ! call wrapper_100002040_100000ee0 │
│ 100000c6b ! mov rdi, rax │
│ 100000c6e ! lea rsi, [__ZNSt3__1L4endlIcNS_11char_traitsIcEEEERNS_13basic_ostreamIT_T0_EES7_] │
│ 100000c75 ! call __ZNSt3__113basic_ostreamIcNS_11char_traitsIcEEElsEPFRS3_S4_E │
│ 100000c7a ! xor ecx, ecx │
│ 100000c7c ! mov [rbp-38h], rax │
│ 100000c80 ! mov eax, ecx │
│ 100000c82 ! add rsp, 40h │
│ 100000c86 ! pop rbp │
│ 100000c87 ! ret
然后可以看到,其中地址 100000c0d 是传递 3 个 int 类型的参数给模版函数 min 时的反汇编代码,地址 100000c57 是传递 3 个 long long 类型的参数给模版函数 min 时的反汇编代码。他们都是两个 call 指令。
我们选择第一个 call 指令后命的 标示符,然后回车,然后会来到另外一处反汇编的位置,代码如下:
│ ......... ! ;---------------------------------------------- │
│ ......... ! ; W R A P P E R for address 100002008 │
│ ......... ! ;---------------------------------------------- │
│ ......... ! wrapper_100002008_100000eb6: ;xref c100000c0d │
│ ......... ! jmp qword ptr [data_100002008]
可以看到,这里 ht editor 还给出了提示,它是一个 wrapper,其实观察这个代码下面的代码,都是类似的形式,这里应该是一个由 jmp 构成的跳表。光标移动到 [data_100002008] 位置处按下回车,然后就反汇编代码会跳转到 100002008 的位置处,然后起反汇编代码如下:
100002008 data_100002008: ;xref J100000eb6 │
│ ......... db 90h ; '?' │
│ 100002009 db 0ch ; ' ' │
│ 10000200a db 00h ; ' ' │
│ 10000200b db 00h ; ' ' │
│ 10000200c db 01h ; ' ' │
│ 10000200d db 00h ; ' ' │
│ 10000200e db 00h ; ' ' │
│ 10000200f db 00h ; ' ' │
│ 100002010 data_100002010: ;xref J100000ebc │
│ ......... db 50h ; 'P' │
│ 100002011 db 0dh ; ' ' │
│ 100002012 db 00h ; ' ' │
│ 100002013 db 00h ; ' ' │
│ 100002014 db 01h ; ' ' │
│ 100002015 db 00h ; ' ' │
│ 100002016 db 00h ; ' ' │
│ 100002017 db 00h ; ' '
在 100002008 的位置处,都是字节定义的数据,但是这部分数据应该是真实的地址,看看 wrapper 的代码,它是一个间接寻址,那么这部分数据就是一个地址,实际的地址是 100000c90。然后去这个地址看其具体的反汇编代码,代码如下:
│ 100000c90 ! │
│ ......... ! ;******************************************************** │
│ ......... ! ; function int min<int>(int, int, int) │
│ ......... ! ;******************************************************** │
│ ......... ! __Z3minIiET_S0_S0_S0_: │
│ ......... ! push rbp │
│ 100000c91 ! mov rbp, rsp │
│ 100000c94 ! mov [rbp-4], edi │
│ 100000c97 ! mov [rbp-8], esi │
│ 100000c9a ! mov [rbp-0ch], edx │
│ 100000c9d ! mov edx, [rbp-4] │
│ 100000ca0 ! cmp edx, [rbp-8] │
│ 100000ca3 ! jng loc_100000caf │
│ 100000ca9 ! mov eax, [rbp-8] │
│ 100000cac ! mov [rbp-4], eax │
│ 100000caf ! │
│ ......... ! loc_100000caf: ;xref j100000ca3 │
│ ......... ! mov eax, [rbp-4] │
│ 100000cb2 ! cmp eax, [rbp-0ch] │
│ 100000cb5 ! jng loc_100000cc1 │
│ 100000cbb ! mov eax, [rbp-0ch] │
│ 100000cbe ! mov [rbp-4], eax │
│ 100000cc1 ! │
│ ......... ! loc_100000cc1: ;xref j100000cb5 │
│ ......... ! mov eax, [rbp-4] │
│ 100000cc4 ! pop rbp │
│ 100000cc5 ! ret
可以看到,这段代码的位置就在 main 函数的位置下方,ht editor 也给出了注释,它就是 min 函数,且形参类型是 3 个 int 类型。在代码中,除了开辟栈帧以外,其余的部分使用的都是 32 位的寄存器。
按照类似的方法找到传递 3 个 long long 类型的 min 函数,可以看到它使用的都是 64 位的寄存器。
C++ 模版反汇编总结
可以看到,C++ 的模版在编译后实际是有两份,一份是 3 个 int 形参的 min,另一份是 3 个 long long 形参的 min,C++ 在语法上为我们程序员提供了便利,但是编译器却为我们做了很多的事情。
