首页 > 解决方案 > GCC跳转表初始化代码生成movsxd并添加?

问题描述

当我在 GCC 中编译一个带有优化的 switch 语句时,它会设置一个像这样的跳转表,

(fcn) sym.foo 148
  sym.foo (unsigned int arg1);
; arg unsigned int arg1 @ rdi
0x000006e0      83ff06         cmp edi, 6                              ; arg1
0x000006e3      0f87a7000000   ja case.default.0x790
0x000006e9      488d156c0100.  lea rdx, [0x0000085c]
0x000006f0      89ff           mov edi, edi
0x000006f2      4883ec08       sub rsp, 8
0x000006f6      486304ba       movsxd rax, dword [rdx + rdi*4]
0x000006fa      4801d0         add rax, rdx                            ; '('
;-- switch.0x000006fd:
0x000006fd      ffe0           jmp rax                                 ; switch table (7 cases) at 0x85c

是最好的方法吗MOVSXDADD

movsxd rax, dword [rdx + rdi*4]
add rax, rdx

LEA这和使用with不一样吗displacement

lea rax, [rdx + rdi*4 + rdx]

我突然想到我可能不明白这里发生了什么。RDX似乎是跳表的开始。RDI是 switch 语句的传入参数。为什么我们要添加RDX两次?

这是我正在编译的 switch 语句-O3

int foo (int x) {
  switch(x) {
    //case 0: puts("\nzero"); break;
    case 1: puts("\none"); break;
    case 2: puts("\ntwo"); break;
    case 3: puts("\nthree"); break;
    case 4: puts("\nfour"); break;
    case 5: puts("\nfive"); break;
    case 6: puts("\nsix"); break;
  }
  return 0;
}

标签: gccx86switch-statementx86-64jump-table

解决方案


GCC 在其跳转表中使用相对位移(相对于表的基数),而不是绝对地址。 因此跳转表本身与位置无关,并且在重新定位时不需要修复,例如作为加载 PIE 可执行文件或 PIC 共享库的一部分。

如果使用 编译-fno-pie -no-pie,gcc 可能会选择使用跳转目标表jmp [table + rdi*8]

x86-64 Linux 之类的目标确实支持运行时数据修复,因此可以使用简单的跳转表。但是有些目标根本不支持修复,这就是 gcc -fPIC/-fpie完全避免它的原因。这种潜在的优化是gcc bug 84011。有关更多信息,请参见那里的讨论。


不幸的是 gcc 使用的是跳转表,而不是意识到每种情况之间的唯一区别是数据,而不是代码。所以实际上它只需要一个字符串指针的表查找。(如果愿意,可以通过相对位移来完成。)

这是一个单独的错过优化,我将其报告为错误 85585。(这提醒了我,我有一个半写的后续,我应该完成并发布。)


推荐阅读