thumb汇编在ldr pc时容易忽略的一个细节
最近在修花指令,脚本修复 ldr 的时候,如果是从 PC 开始计算,发现ida的表现和keystone 的表现是不一致的,查了很久没找到原因。直到后面翻arm手册,才发现一个小细节。
一、重现方式
1 | __attribute__((target("thumb-mode"))) |
1 | ➜ /tmp $ANDROID_NDK/toolchains/llvm/prebuilt/darwin-x86_64/bin/armv7a-linux-androideabi16-clang -c test.c |
二、错误理解
看看这段程序,主要也就把 [PC+4]
的内容放到 r0和r1里,因为pc在变,所以访问的内存地址也在变,ok。
在thumb模式下,pc永远等于当前地址+4。
在执行第一句的时候,pc=5(因为是thumb),加上4等于9,访问时候去掉最后一个bit,所以r0是0x11111111。
在执行第二句的时候,pc=7(因为是thumb),加上4等于11,访问时候去掉最后一个bit,所以r1是0x22221111。
但是,实际却不是这样的,请看下面的部分。
2019年09月29日:ADR 同理。
三、正确理解
看看objdump,它认为r0是+8位置,r1也是+8位置。
1 | ➜ /tmp $ANDROID_NDK/toolchains/llvm/prebuilt/darwin-x86_64/arm-linux-androideabi/bin/objdump -d test.o |
看看ida,它直接告诉我,r0=0x11111111,r1=0x11111111。
那么问题在哪呢?我错误的理解是为什么?于是我翻官方手册,翻到了下面这段话。
A8.8.64 LDR (literal) LDR{<c>}{<q>} <Rt>, <label> Normal form LDR{<c>}{<q>} <Rt>, [PC, #+/-<imm>] Alternative form The label of the literal data item that is to be loaded into <Rt>. The assembler calculates the required value of the offset from the Align(PC, 4) value of the instruction to this label.
注意,这里说访问地址时,将 PC 向4对齐后,再进行访问。而我之前错误的理解是,将最后一个bit去掉,其实是向2对齐了。所以,这里报道存在了偏差,对thumb的 PC 理解是没问题的,是 LDR 对这种情况加了特殊处理。
2019年09月29日:ADR 也会收到受到影响,因为ADR也是和 PC 相关的,
1 | if ConditionPassed() then |
四、工具对它的处理
既然ldr语句自身的位置会影响到即将访问到的位置,那么各个工具是如何正确处理的呢? 这里看 pwntools 和 keystone的表现。
1 | In [145]: asm('ldr r0,[pc,#4];ldr r0,[pc,#4]', arch='thumb').encode('hex') |
汇编时,它们是按照需求去翻译的,没有问题;反汇编时,会根据实际情况稍微算一下。
五、结论
thumb的ldr在处理pc相对位置时,要先让pc向4对齐,然后再访问相对偏移。
2019年09月29日更新:ADR 指令同样需要让pc向 4 对齐。