谢谢你指出这个问题。我知道我曾经尝试过在http://github.com/dwelch67/yagbat中的qemu代码,认为XPUT32调用了PUT32并按照你描述的方式工作。但它似乎并没有工作。我进行了许多实验,感到非常惊讶,这不是我预期的结果。现在我明白了gnu链接器为什么会这样做。很抱歉我的回应有点长,但我认为它非常有价值。这是一个令人困惑的话题,我知道我多年来一直错误地认为pc会带着模式位四处走动,但它实际上并不会。
在我开始以下实验之前,如果你要这样做:
LDR R6, =0x24000
ADD R6, #1 @ (set lsb to 1)
BX R6
因为你知道0x24000是Thumb代码,所以只需这样做:
LDR R6, =0x24001
BX R6
是的,如果你知道硬编码地址0x24000是一个Thumb指令,那么这就是从ARM或Thumb跳转到Thumb代码的方法,只需要使用一个寄存器存储地址加上1,然后使用bx
指令。
如果你不知道地址但知道地址的名称:
ldr r6,=something
bx r6
非常好的一点是,某个东西可以是一个 arm 或者 thumb 地址,上述代码就可以正常工作。但是需要注意的是,只有当链接器正确知道标签类型(arm 或 thumb)时,它才能正常运行。如果出现问题,就像你在这里看到的那样,它将无法正常工作。
.thumb
ping:
ldr r0,=pong
bx r0
.code 32
pong:
ldr r0,=ping
bx r0
d6008148 <ping>:
d6008148: 4803 ldr r0, [pc, #12]
d600814a: 4700 bx r0
d600814c <pong>:
d600814c: e59f0008 ldr r0, [pc, #8]
d6008150: e12fff10 bx r0
d6008158: d600814c strle r8, [r0], -ip, asr #2
d600815c: d6008148 strle r8, [r0], -r8, asr #2
操作失败了,pong
想从0xD600815C获取一个拇指地址,但却得到了一个ARM地址。
顺便提一下,这都是 GNU 汇编器的东西,对于其他工具,你可能需要做出不同的操作。 对于 gas,您需要在要声明为拇指标签的标签之前放置.thumb_func
(术语 func 意味着函数有点误导,不必担心 .thumb_func
的含义,它只是汇编器/链接器的一个技巧)。
.thumb
.thumb_func
ping:
ldr r0,=pong
bx r0
.code 32
pong:
ldr r0,=ping
bx r0
现在我们得到了想要的东西:
d6008148 <ping>:
d6008148: 4803 ldr r0, [pc, #12] ; (d6008158 <pong+0xc>)
d600814a: 4700 bx r0
d600814c <pong>:
d600814c: e59f0008 ldr r0, [pc, #8] ; d600815c <pong+0x10>
d6008150: e12fff10 bx r0
d6008158: d600814c strle r8, [r0], -ip, asr #2
d600815c: d6008149 strle r8, [r0], -r9, asr #2
0xD600815C拥有lsbit
位设置,因此您不必进行任何工作。 例如,在调用C函数时编译器会自动处理所有这些内容。但对于汇编语言,您需要使用.thumb_func
(或其他指令如果有的话)来让gas知道这是一个缩略标签,并为您设置lsbit
。
因此下面的实验是在mpcore上进行的,它是ARM11,但我还尝试了在ARM7TDMI和qemu上测试testthumb
函数1到4,并获得相同的结果。
.globl testarm
testarm:
mov r0,pc
bx lr
armbounce:
mov r0,lr
bx lr
.thumb
.thumb_func
.globl testthumb1
testthumb1:
mov r0,pc
bx lr
nop
nop
nop
bounce:
bx lr
.thumb_func
.globl testthumb2
testthumb2:
mov r2,lr
mov r0,pc
bl bounce
bx r2
nop
nop
nop
.thumb_func
.globl testthumb3
testthumb3:
mov r2,lr
mov lr,pc
mov r0,lr
bx r2
nop
nop
nop
.thumb_func
.globl testthumb4
testthumb4:
push {lr}
ldr r2,=armbounce
mov r1,pc
add r1,#5
mov lr,r1
bx r2
pop {r2}
bx r2
.thumb_func
.globl testthumb5
testthumb5:
push {lr}
ldr r2,=armbounce
mov lr,pc
bx r2
pop {r2}
bx r2
.thumb_func
.globl testthumb6
testthumb6:
push {lr}
bl testthumb6a
.thumb_func
testthumb6a:
mov r0,lr
pop {r2}
bx r2
.thumb_func
.globl testthumb7
testthumb7:
push {lr}
bl armbounce_thumb
pop {r2}
bx r2
.thumb_func
.globl testthumb8
testthumb8:
push {lr}
bl armbounce_thumb_two
pop {r2}
bx r2
.align 4
armbounce_thumb:
ldr r1,[pc]
bx r1
.word armbounce
nop
.align 4
armbounce_thumb_two:
bx pc
nop
.code 32
b armbounce
转换为:
d60080b4 <testarm>:
d60080b4: e1a0000f mov r0, pc
d60080b8: e12fff1e bx lr
d60080bc <armbounce>:
d60080bc: e1a0000e mov r0, lr
d60080c0: e12fff1e bx lr
d60080c4 <testthumb1>:
d60080c4: 4678 mov r0, pc
d60080c6: 4770 bx lr
d60080c8: 46c0 nop
d60080ca: 46c0 nop
d60080cc: 46c0 nop
d60080ce <bounce>:
d60080ce: 4770 bx lr
d60080d0 <testthumb2>:
d60080d0: 4672 mov r2, lr
d60080d2: 4678 mov r0, pc
d60080d4: f7ff fffb bl d60080ce <bounce>
d60080d8: 4710 bx r2
d60080da: 46c0 nop
d60080dc: 46c0 nop
d60080de: 46c0 nop
d60080e0 <testthumb3>:
d60080e0: 4672 mov r2, lr
d60080e2: 46fe mov lr, pc
d60080e4: 4670 mov r0, lr
d60080e6: 4710 bx r2
d60080e8: 46c0 nop
d60080ea: 46c0 nop
d60080ec: 46c0 nop
d60080ee <testthumb4>:
d60080ee: b500 push {lr}
d60080f0: 4a15 ldr r2, [pc, #84]
d60080f2: 4679 mov r1, pc
d60080f4: 3105 adds r1, #5
d60080f6: 468e mov lr, r1
d60080f8: 4710 bx r2
d60080fa: bc04 pop {r2}
d60080fc: 4710 bx r2
d60080fe <testthumb5>:
d60080fe: b500 push {lr}
d6008100: 4a11 ldr r2, [pc, #68]
d6008102: 46fe mov lr, pc
d6008104: 4710 bx r2
d6008106: bc04 pop {r2}
d6008108: 4710 bx r2
d600810a <testthumb6>:
d600810a: b500 push {lr}
d600810c: f000 f800 bl d6008110 <testthumb6a>
d6008110 <testthumb6a>:
d6008110: 4670 mov r0, lr
d6008112: bc04 pop {r2}
d6008114: 4710 bx r2
d6008116 <testthumb7>:
d6008116: b500 push {lr}
d6008118: f000 f80a bl d6008130 <armbounce_thumb>
d600811c: bc04 pop {r2}
d600811e: 4710 bx r2
d6008120 <testthumb8>:
d6008120: b500 push {lr}
d6008122: f000 f80d bl d6008140 <armbounce_thumb_two>
d6008126: bc04 pop {r2}
d6008128: 4710 bx r2
d600812a: 46c0 nop
d600812c: 46c0 nop
d600812e: 46c0 nop
d6008130 <armbounce_thumb>:
d6008130: 4900 ldr r1, [pc, #0]
d6008132: 4708 bx r1
d6008134: d60080bc
d6008138: 46c0 nop
d600813a: 46c0 nop
d600813c: 46c0 nop
d600813e: 46c0 nop
d6008140 <armbounce_thumb_two>:
d6008140: 4778 bx pc
d6008142: 46c0 nop
d6008144: eaffffdc b d60080bc <armbounce>
d6008148: d60080bc
d600814c: e1a00000 nop
调用并打印所有这些函数的结果:
D60080BC testarm
D60080C8 testthumb1
D60080D6 testthumb2
D60080E6 testthumb3
D60080FB testthumb4
testthumb5 crashes
D6008111 testthumb6
D600811D testthumb7
D6008127 testthumb8
那么这一切都是在做什么,它与您的问题有什么关系。这与从Thumb模式进行混合模式调用有关(也与更简单的Arm调用相关)。
我已经在这个级别上编写ARM和Thumb模式的程序多年了,但某种程度上一直以来都弄错了。我认为程序计数器始终保持那个lsbit中的模式,就像你知道的那样,在执行bx指令时,你要么将其设置,要么不设置。
在 ARM 处理器的 CPU 描述中非常早期,在 ARM 架构参考手册中(如果您正在编写汇编程序,则应该已经有此手册,如果没有,也许大部分问题都会得到解答)。
Program counter Register 15 is the Program Counter (PC). It can be used in most
instructions as a pointer to the instruction which is two instructions after
the instruction being executed...
那么让我们来检查一下这究竟意味着什么,这是否意味着在ARM模式下,两个指令之间相隔8个字节?而在Thumb模式下,是两个指令之间相隔4个字节吗?
所以testarm
验证程序计数器提前了8个字节,也就是两个指令。
testthumb1
验证程序提前了4个字节,在这种情况下也是两个指令。
testthumb2
:
d60080d2: 4678 mov r0, pc
d60080d4: f7ff fffb bl d60080ce <bounce>
d60080d8: 4710 bx r2
如果程序计数器提前两个“指令”,我们将得到0xD60080D8,但实际上我们得到的是比这多四个字节的0xD60080D6,这更有意义。Arm模式提前8个字节,Thumb模式提前4个字节,不要干扰解码代码执行之前的指令(或数据),只需加上4或8即可。
“testthumb3”希望“mov lr,pc”是特殊的,但实际上并不是。
如果你还没有看出模式,程序计数器的最低位(lsbit)没有被设置,我猜这对于分支表等情况是有意义的。因此,在Thumb模式下,“mov lr,pc”不能正确地设置链接寄存器以进行返回。
非常痛苦地,“testthumb4”接受程序计数器到达的任何位置,并根据小心放置的指令计算返回地址,如果你在“mov r1,pc”和“bx r2”之间更改该指令序列,则必须返回add。现在为什么我们不能做这样的事情:
add r1,pc,#1
bx r2
使用拇指指令无法实现,但使用拇指2可能可以。还有一些处理器(armv7)支持arm指令和拇指/拇指2,因此您可能会处于需要这样做的情况。但是,如果有一个允许使用上部寄存器并具有三个操作数的Thumb2 add指令,则不会添加#1,因为它将是4字节的Thumb2指令(您需要添加#3)。
所以testthumb5
直接来自我向您展示的代码,导致了这个问题的一部分,并且会导致崩溃。很抱歉我误导了大家,我将尝试返回并修补我在SO上使用此代码的问题。
testthumb6
是一个实验,用于确保我们都没有疯掉。 一切正常,链接寄存器确实设置了lsbit
,因此当您稍后执行bx lr
时,它知道从那位开始的模式。
testthumb7
是从ARM侧传输线派生的,您在从ARM模式转换为Thumb模式时看到链接器正在执行该操作,在这种情况下,我正在从Thumb模式转换为ARM模式。为什么链接器不能以这种方式执行?因为在Thumb模式下,您至少必须使用低寄存器,并且在游戏的这个阶段之后,在代码编译之后,链接器无法知道它可以损坏哪个寄存器。不过,在ARM模式下,ip寄存器(不确定是什么,可能是r12)可能会受到破坏,我想它是保留给编译器使用的。在这种情况下,我知道r1
可能会被破坏并使用它,这样就可以按预期工作了。调用armbounce代码,该代码获取要返回到的链接寄存器,该指令为Thumb指令(lsbit set
),在testthumb7
函数中的bl armbounce_thumb
之后,正好位于我们想要的位置。
testthumb8
是gnu链接器在需要从Thumb模式到ARM模式转换时执行的方式。将bl
指令设置为转到一个跳板。然后他们做了一些非常非常棘手和疯狂的事情:
d6008140 <armbounce_thumb_two>:
d6008140: 4778 bx pc
d6008142: 46c0 nop ; (mov r8, r8)
d6008144: eaffffdc b d60080bc <armbounce>
bx pc
的意思是跳转到当前指令后面四个字节对齐的ARM代码处。在此之前我们已经知道pc
(程序计数器)与当前指令相差四个字节,同时我们也知道lsbit
未被设置。接下来的nop
是两个字节的占位符,然后我们需要生成一个四个字节对齐的ARM指令,并将其作为无条件分支指向我们要前往的位置,这可能是 b something 或ldr pc
,=something,具体取决于您需要前往的距离。非常棘手。
最初的bl arm_bounce_thumb_two
会设置链接寄存器,以便返回到bl
之后的指令。跳板不会修改链接寄存器,只执行分支操作。
如果你想从ARM模式进入Thumb模式,则应该像链接器一样操作:
...
bl myfun_from_arm
...
myfun_from_arm:
ldr ip,[pc]
bx ip
.word myfun
当他们这样做时,它看起来像这样(从一个不在0xD6008xxx而是在0x0001xxxx处抓取的不同二进制文件)。
101f8: eb00003a bl 102e8 <__testthumb1_from_arm>
000102e8 <__testthumb1_from_arm>:
102e8: e59fc000 ldr ip, [pc]
102ec: e12fff1c bx ip
102f0: 00010147 andeq r0, r1, r7, asr #2
那么无论这个IP寄存器是什么(r12
?),他们都不介意将其销毁,我想你也可以自行销毁。
所以无论这个IP寄存器是什么(r12?),他们都不介意破坏它,我假设你也可以自行破坏它。