创建常量跳跃 table; xcode;铛;汇编
creating constant jump table; xcode; clang; asm
当我尝试在我的 iphone (arm64) 的 asm 程序中创建跳转 table 时,我遇到了一个很奇怪的问题:
.globl my_func
my_func:
...
//jump (switch) table
.L.f_switch:
.short .L.case0 - .L.f_switch
.short .L.case1 - .L.f_switch
...
.L.case0:
//some case code
...
.L.case1:
//other case code
编译后此 table 由零填充,而不是实际值。通过dump编译出来的object文件可以看到
(__TEXT,__text) section
_my_func:
0000000000000000 adr x4, #16
0000000000000004 ldrh w5, [x4, x3, lsl #1]
0000000000000008 add x4, x4, w5, uxth
000000000000000c br x4
.L.f_switch:
0000000000000010 .long 0x00000000
0000000000000014 .long 0x00000000
0000000000000018 .long 0x00000000
000000000000001c nop
如何解决?
我相信您观察到的条目设置为 0 与搬迁有关。编译器可能会发出重定位信息,linker 最终会解析这些信息。为此,我创建了这个小示例程序:
test.s
.text
.align 4
.globl _main
_main:
adr x0, .L.f_switch
ldr w1, [x0, x1, LSL#2]
add x0, x0, x1
br x0
.L.f_switch:
.word .L.case0 - .L.f_switch
.word .L.case1 - .L.f_switch
.word .L.case2 - .L.f_switch
.L.case0:
nop
.L.case1:
nop
.L.case2:
nop
ret
我正在使用 XCode 7 并且 clang 报告 clang --version
的版本信息:
Apple LLVM version 7.0.0 (clang-700.0.72)
Target: x86_64-apple-darwin14.5.0
Thread model: posix
为了简化命令行操作,我设置了一个环境变量以指向我的 iPhone SDK:
export ISYSROOT="/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS.sdk/"
第一个实验是将test.s
编译为test.o
。我使用这个命令:
clang -x assembler -arch arm64 test.s -o test.o -c
现在如果我使用 otool
转储 test.o 使用:
otool -drGtv test.o
我明白了:
test.o:
Data in code table (0 entries)
offset length kind
Relocation information (__TEXT,__text) 6 entries
address pcrel length extern type scattered symbolnum/value
00000018 False long True SUB False .L.f_switch
00000018 False long True UNSIGND False .L.case2
00000014 False long True SUB False .L.f_switch
00000014 False long True UNSIGND False .L.case1
00000010 False long True SUB False .L.f_switch
00000010 False long True UNSIGND False .L.case0
(__TEXT,__text) section
_main:
0000000000000000 adr x0, #16
0000000000000004 ldr w1, [x0, x1, lsl #2]
0000000000000008 add x0, x0, x1
000000000000000c br x0
.L.f_switch:
0000000000000010 .long 0x00000000
0000000000000014 .long 0x00000000
0000000000000018 .long 0x00000000
.L.case0:
000000000000001c nop
.L.case1:
0000000000000020 nop
.L.case2:
0000000000000024 nop
0000000000000028 ret
编译器(汇编程序)已为等式的两个部分(.L.case#
和 .L.F_switch
)发出了 00000010、00000014 和 00000018 的重定位条目。 table 本身用占位符零填充。 linker 的工作是解决搬迁问题。我可以使用如下命令手动 link 上面的 test.o
:
ld -demangle -dynamic -arch arm64 -iphoneos_version_min 5.0.0 -syslibroot /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS.sdk/ -o test -L/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS.sdk//usr/lib/system test.o -lSystem /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/../lib/clang/7.0.0/lib/darwin/libclang_rt.ios.a
我现在可以使用 otool
转储最终的 executable,命令如下:
otool -drGtv test
并得到这个输出:
test:
Data in code table (0 entries)
offset length kind
(__TEXT,__text) section
_main:
0000000100007f80 adr x0, #16
0000000100007f84 ldr w1, [x0, x1, lsl #2]
0000000100007f88 add x0, x0, x1
0000000100007f8c br x0
.L.f_switch:
0000000100007f90 .long 0x0000000c
0000000100007f94 .long 0x00000010
0000000100007f98 .long 0x00000014
.L.case0:
0000000100007f9c nop
.L.case1:
0000000100007fa0 nop
.L.case2:
0000000100007fa4 nop
0000000100007fa8 ret
请注意,所有重定位都已由最终执行者linker 解决table。
或者,我可以一步编译并 linked 生成 executable test
,命令如下:
clang -x assembler -arch arm64 -L$ISYSROOT/usr/lib/system --sysroot=$ISYSROOT test.s -o test
我将其拆分以显示目标文件的样子,然后在 linking.
之后生成的 executable
首先,我要感谢 Michael Petch 对本次讨论的贡献,这非常有帮助。
其次,我想强调跳转table中数据的大小很重要。 Clang 对“.word”(4 字节)偏移没有任何问题。当使用其他“.byte”(1 字节)或“.short”/“.hword”(2 字节)偏移量时,问题就开始了。
测试 1.数据类型为“.short”(2 字节).
my_func:
...
//jump (switch) table
.L.f_switch:
.short .L.case0 - .L.f_switch
.short .L.case1 - .L.f_switch
...
.L.case0:
//some case code
...
.L.case1:
//other case code
转储是:
Relocation information (__TEXT,__text) 10 entries
address pcrel length extern type scattered symbolnum/value
00000018 False word True SUB False .L.f_switch
00000018 False word True UNSIGND False .L.case4
00000016 False word True SUB False .L.f_switch
00000016 False word True UNSIGND False .L.case3
00000014 False word True SUB False .L.f_switch
00000014 False word True UNSIGND False .L.case2
00000012 False word True SUB False .L.f_switch
00000012 False word True UNSIGND False .L.case1
00000010 False word True SUB False .L.f_switch
00000010 False word True UNSIGND False .L.case0
(__TEXT,__text) section
_my_func:
0000000000000000 adr x4, #16
0000000000000004 ldrh w5, [x4, x3, lsl #1]
0000000000000008 add x4, x4, w5, uxth
000000000000000c br x4
.L.f_switch:
0000000000000010 .long 0x00000000
0000000000000014 .long 0x00000000
0000000000000018 .long 0x00000000
000000000000001c nop
到目前为止一切都按照 Michael 在他的回答中描述的那样进行(除了 2 字节偏移实体的预留)
在那个链接器之后 returns 错误:
in section __TEXT,__text reloc 0: ARM64_RELOC_SUBTRACTOR must have r_length of 2 or 3 for architecture arm64
请注意,如果使用 4 Bytes 个实体,则不会出现任何错误。
测试 2。 可以作为解决方法。
.set case_0, .L.case0 - .L.f_switch
.set case_1, .L.case1 - .L.f_switch
.set case_2, .L.case2 - .L.f_switch
...
.L.f_switch:
.hword case_0
.hword case_1
.hword case_2
...
这种方法的转储是:
(__TEXT,__text) section
_my_func:
0000000000000000 adr x4, #16
0000000000000004 ldrh w5, [x4, x3, lsl #1]
0000000000000008 add x4, x4, w5, uxth
000000000000000c br x4
.L.f_switch:
0000000000000010 .long 0x01200020
0000000000000014 .long 0x06900240
0000000000000018 .long 0x00000cc0
000000000000001c nop
正如您所注意到的那样,编译器通过右偏移值直接填充跳转 table。因此没有重定位信息和链接器的任何问题。
我还想提请注意以下事实。
- GNU GCC 编译器为 "Test 1" 和 "Test 2" 代码生成 "Test 2" 中的结果(填充跳转 table)。
- 如果 table 中的偏移量无法适合当前数据类型,GNU GCC 编译器将生成错误。例如使用 1 字节数据类型且偏移量大于 255。在这种情况下 Clang 不会产生任何错误,因此程序员应该手动控制它。
当我尝试在我的 iphone (arm64) 的 asm 程序中创建跳转 table 时,我遇到了一个很奇怪的问题:
.globl my_func
my_func:
...
//jump (switch) table
.L.f_switch:
.short .L.case0 - .L.f_switch
.short .L.case1 - .L.f_switch
...
.L.case0:
//some case code
...
.L.case1:
//other case code
编译后此 table 由零填充,而不是实际值。通过dump编译出来的object文件可以看到
(__TEXT,__text) section
_my_func:
0000000000000000 adr x4, #16
0000000000000004 ldrh w5, [x4, x3, lsl #1]
0000000000000008 add x4, x4, w5, uxth
000000000000000c br x4
.L.f_switch:
0000000000000010 .long 0x00000000
0000000000000014 .long 0x00000000
0000000000000018 .long 0x00000000
000000000000001c nop
如何解决?
我相信您观察到的条目设置为 0 与搬迁有关。编译器可能会发出重定位信息,linker 最终会解析这些信息。为此,我创建了这个小示例程序:
test.s
.text
.align 4
.globl _main
_main:
adr x0, .L.f_switch
ldr w1, [x0, x1, LSL#2]
add x0, x0, x1
br x0
.L.f_switch:
.word .L.case0 - .L.f_switch
.word .L.case1 - .L.f_switch
.word .L.case2 - .L.f_switch
.L.case0:
nop
.L.case1:
nop
.L.case2:
nop
ret
我正在使用 XCode 7 并且 clang 报告 clang --version
的版本信息:
Apple LLVM version 7.0.0 (clang-700.0.72)
Target: x86_64-apple-darwin14.5.0
Thread model: posix
为了简化命令行操作,我设置了一个环境变量以指向我的 iPhone SDK:
export ISYSROOT="/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS.sdk/"
第一个实验是将test.s
编译为test.o
。我使用这个命令:
clang -x assembler -arch arm64 test.s -o test.o -c
现在如果我使用 otool
转储 test.o 使用:
otool -drGtv test.o
我明白了:
test.o:
Data in code table (0 entries)
offset length kind
Relocation information (__TEXT,__text) 6 entries
address pcrel length extern type scattered symbolnum/value
00000018 False long True SUB False .L.f_switch
00000018 False long True UNSIGND False .L.case2
00000014 False long True SUB False .L.f_switch
00000014 False long True UNSIGND False .L.case1
00000010 False long True SUB False .L.f_switch
00000010 False long True UNSIGND False .L.case0
(__TEXT,__text) section
_main:
0000000000000000 adr x0, #16
0000000000000004 ldr w1, [x0, x1, lsl #2]
0000000000000008 add x0, x0, x1
000000000000000c br x0
.L.f_switch:
0000000000000010 .long 0x00000000
0000000000000014 .long 0x00000000
0000000000000018 .long 0x00000000
.L.case0:
000000000000001c nop
.L.case1:
0000000000000020 nop
.L.case2:
0000000000000024 nop
0000000000000028 ret
编译器(汇编程序)已为等式的两个部分(.L.case#
和 .L.F_switch
)发出了 00000010、00000014 和 00000018 的重定位条目。 table 本身用占位符零填充。 linker 的工作是解决搬迁问题。我可以使用如下命令手动 link 上面的 test.o
:
ld -demangle -dynamic -arch arm64 -iphoneos_version_min 5.0.0 -syslibroot /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS.sdk/ -o test -L/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS.sdk//usr/lib/system test.o -lSystem /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/../lib/clang/7.0.0/lib/darwin/libclang_rt.ios.a
我现在可以使用 otool
转储最终的 executable,命令如下:
otool -drGtv test
并得到这个输出:
test:
Data in code table (0 entries)
offset length kind
(__TEXT,__text) section
_main:
0000000100007f80 adr x0, #16
0000000100007f84 ldr w1, [x0, x1, lsl #2]
0000000100007f88 add x0, x0, x1
0000000100007f8c br x0
.L.f_switch:
0000000100007f90 .long 0x0000000c
0000000100007f94 .long 0x00000010
0000000100007f98 .long 0x00000014
.L.case0:
0000000100007f9c nop
.L.case1:
0000000100007fa0 nop
.L.case2:
0000000100007fa4 nop
0000000100007fa8 ret
请注意,所有重定位都已由最终执行者linker 解决table。
或者,我可以一步编译并 linked 生成 executable test
,命令如下:
clang -x assembler -arch arm64 -L$ISYSROOT/usr/lib/system --sysroot=$ISYSROOT test.s -o test
我将其拆分以显示目标文件的样子,然后在 linking.
之后生成的 executable首先,我要感谢 Michael Petch 对本次讨论的贡献,这非常有帮助。
其次,我想强调跳转table中数据的大小很重要。 Clang 对“.word”(4 字节)偏移没有任何问题。当使用其他“.byte”(1 字节)或“.short”/“.hword”(2 字节)偏移量时,问题就开始了。
测试 1.数据类型为“.short”(2 字节).
my_func:
...
//jump (switch) table
.L.f_switch:
.short .L.case0 - .L.f_switch
.short .L.case1 - .L.f_switch
...
.L.case0:
//some case code
...
.L.case1:
//other case code
转储是:
Relocation information (__TEXT,__text) 10 entries
address pcrel length extern type scattered symbolnum/value
00000018 False word True SUB False .L.f_switch
00000018 False word True UNSIGND False .L.case4
00000016 False word True SUB False .L.f_switch
00000016 False word True UNSIGND False .L.case3
00000014 False word True SUB False .L.f_switch
00000014 False word True UNSIGND False .L.case2
00000012 False word True SUB False .L.f_switch
00000012 False word True UNSIGND False .L.case1
00000010 False word True SUB False .L.f_switch
00000010 False word True UNSIGND False .L.case0
(__TEXT,__text) section
_my_func:
0000000000000000 adr x4, #16
0000000000000004 ldrh w5, [x4, x3, lsl #1]
0000000000000008 add x4, x4, w5, uxth
000000000000000c br x4
.L.f_switch:
0000000000000010 .long 0x00000000
0000000000000014 .long 0x00000000
0000000000000018 .long 0x00000000
000000000000001c nop
到目前为止一切都按照 Michael 在他的回答中描述的那样进行(除了 2 字节偏移实体的预留)
在那个链接器之后 returns 错误:
in section __TEXT,__text reloc 0: ARM64_RELOC_SUBTRACTOR must have r_length of 2 or 3 for architecture arm64
请注意,如果使用 4 Bytes 个实体,则不会出现任何错误。
测试 2。 可以作为解决方法。
.set case_0, .L.case0 - .L.f_switch
.set case_1, .L.case1 - .L.f_switch
.set case_2, .L.case2 - .L.f_switch
...
.L.f_switch:
.hword case_0
.hword case_1
.hword case_2
...
这种方法的转储是:
(__TEXT,__text) section
_my_func:
0000000000000000 adr x4, #16
0000000000000004 ldrh w5, [x4, x3, lsl #1]
0000000000000008 add x4, x4, w5, uxth
000000000000000c br x4
.L.f_switch:
0000000000000010 .long 0x01200020
0000000000000014 .long 0x06900240
0000000000000018 .long 0x00000cc0
000000000000001c nop
正如您所注意到的那样,编译器通过右偏移值直接填充跳转 table。因此没有重定位信息和链接器的任何问题。
我还想提请注意以下事实。
- GNU GCC 编译器为 "Test 1" 和 "Test 2" 代码生成 "Test 2" 中的结果(填充跳转 table)。
- 如果 table 中的偏移量无法适合当前数据类型,GNU GCC 编译器将生成错误。例如使用 1 字节数据类型且偏移量大于 255。在这种情况下 Clang 不会产生任何错误,因此程序员应该手动控制它。