gcc - 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
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;
}
解决方案
GCC 在其跳转表中使用相对位移(相对于表的基数),而不是绝对地址。 因此跳转表本身与位置无关,并且在重新定位时不需要修复,例如作为加载 PIE 可执行文件或 PIC 共享库的一部分。
如果使用 编译-fno-pie -no-pie
,gcc 可能会选择使用跳转目标表jmp [table + rdi*8]
x86-64 Linux 之类的目标确实支持运行时数据修复,因此可以使用简单的跳转表。但是有些目标根本不支持修复,这就是 gcc -fPIC
/-fpie
完全避免它的原因。这种潜在的优化是gcc bug 84011。有关更多信息,请参见那里的讨论。
不幸的是 gcc 使用的是跳转表,而不是意识到每种情况之间的唯一区别是数据,而不是代码。所以实际上它只需要一个字符串指针的表查找。(如果愿意,可以通过相对位移来完成。)
这是一个单独的错过优化,我将其报告为错误 85585。(这提醒了我,我有一个半写的后续,我应该完成并发布。)
推荐阅读
- ruby-on-rails - 如何永久解锁 Google 帐户验证码以在我的 heroku 应用中发送电子邮件?
- java - 是否可以让 nd4j 在 linux-arm 系统上工作?
- java - 使用 AOP 的动态 Kafka 消费者
- git - 使用 Git 搜索已删除的文件
- error-handling - 如何获得 KieSession 构建的结果(即规则编译器错误)?
- linux - 使用 posix_spawnp 给出“错误打开终端:”错误消息
- excel - 在 Excel 中将分钟和秒转换为小时、分钟和秒
- javascript - 从 webView 中删除元素
- html - 带有显示每个打印页面的页脚的 HTML 文件
- javascript - 每个键必须是多个字符串;得到功能