在Thumb代码中使用BX调用Thumb函数,或跳转到另一个函数中的Thumb指令。

19

我正在尝试学习固件修改方面的技能(其中我没有源代码),以下问题涉及使用BX指令从Thumb代码跳转或调用已存在的Thumb代码。

  1. 如何使用BX指令从我的Thumb代码跳转到现有的固件Thumb代码?
  2. 如何使用BX指令在我的Thumb代码中调用现有的Thumb函数(必须先设置LR)?

我的理解是CPU查看最低有效位(位0),我必须确保将其设置为1以保持CPU状态处于“Thumb状态”。因此,我想我必须加1将最低有效位设置为1。

所以,假设我想要跳转到0x24000(在某些现有的Thumb代码中间)......

LDR R6, =0x24000
ADD R6, #1       @ (set lsb to 1)
BX R6

我认为这是正确的吗?

现在假设我想要使用BX调用现有的thumb函数,并且我希望该函数返回给我,因此我需要将LR设置为我想要它返回的位置。

假设我想要调用的函数位于0x24000处, 有人建议我使用:

ldr r2, =0x24000
mov lr, pc
bx r2

我不理解的是:

  1. R2寄存器中的地址没有设置最低有效位(LSB)...那么bx r2指令会切换到ARM模式吗?

  2. LR寄存器... PC寄存器具有“当前指令开始+4”的地址,我被告知。 在Thumb和Arm中,任何指令地址都必须对齐(16位或32位),因此它不会设置LSB位为1。只有奇数才会将LSB位设置为1。

因此,在上面的代码中,我将LR设置为(PC),这个地址也没有设置LSB位为1。因此,当被调用的函数到达其后记时,并执行BX LR时...嗯,如何返回到我的THUMB代码?我肯定漏掉了某些东西...

通常使用BL来调用函数。手册中说,BL指令将LR设置为下一行代码... 这是否意味着(通常使用的)BL THUMB指令会自动将LR设置为return addr + 1

1个回答

24

谢谢你指出这个问题。我知道我曾经尝试过在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]   ; (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:   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  ;@ -4
    add r1,#5  ;@ -2
    mov lr,r1  ;@ +0
    bx r2      ;@ +2
    pop {r2}   ;@ +4
    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         ; (mov r8, r8)
d60080ca:   46c0        nop         ; (mov r8, r8)
d60080cc:   46c0        nop         ; (mov r8, r8)

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         ; (mov r8, r8)
d60080dc:   46c0        nop         ; (mov r8, r8)
d60080de:   46c0        nop         ; (mov r8, r8)

d60080e0 <testthumb3>:
d60080e0:   4672        mov r2, lr
d60080e2:   46fe        mov lr, pc
d60080e4:   4670        mov r0, lr
d60080e6:   4710        bx  r2
d60080e8:   46c0        nop         ; (mov r8, r8)
d60080ea:   46c0        nop         ; (mov r8, r8)
d60080ec:   46c0        nop         ; (mov r8, r8)

d60080ee <testthumb4>:
d60080ee:   b500        push    {lr}
d60080f0:   4a15        ldr r2, [pc, #84]   ; (d6008148 <armbounce_thumb_two+0x8>)
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]   ; (d6008148 <armbounce_thumb_two+0x8>)
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         ; (mov r8, r8)
d600812c:   46c0        nop         ; (mov r8, r8)
d600812e:   46c0        nop         ; (mov r8, r8)

d6008130 <armbounce_thumb>:
d6008130:   4900        ldr r1, [pc, #0]    ; (d6008134 <armbounce_thumb+0x4>)
d6008132:   4708        bx  r1
d6008134:   d60080bc            ; <UNDEFINED> instruction: 0xd60080bc
d6008138:   46c0        nop         ; (mov r8, r8)
d600813a:   46c0        nop         ; (mov r8, r8)
d600813c:   46c0        nop         ; (mov r8, r8)
d600813e:   46c0        nop         ; (mov r8, r8)

d6008140 <armbounce_thumb_two>:
d6008140:   4778        bx  pc
d6008142:   46c0        nop         ; (mov r8, r8)
d6008144:   eaffffdc    b   d60080bc <armbounce>
d6008148:   d60080bc            ; <UNDEFINED> instruction: 0xd60080bc
d600814c:   e1a00000    nop         ; (mov r0, r0)

调用并打印所有这些函数的结果:

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]    ; 102f0 <__testthumb1_from_arm+0x8>
   102ec:   e12fff1c    bx  ip
   102f0:   00010147    andeq   r0, r1, r7, asr #2

那么无论这个IP寄存器是什么(r12?),他们都不介意将其销毁,我想你也可以自行销毁。

所以无论这个IP寄存器是什么(r12?),他们都不介意破坏它,我假设你也可以自行破坏它。

非常喜欢你的回答和努力。这也是我在处理的这个反汇编固件中注意到的问题。它解释了我的测试中的崩溃(没有使用模拟器...只是直接修改固件并加载闪存芯片..哈哈)。固件具有ARM模式代码,在下部分,主要应用程序部分仅使用Thumb代码。但是,是的,这就是它如何进行Thumb到Arm和Arm到Thumb的转换。顺便说一句,此固件是使用ADS v 1.2编译的。 - vmanta
另外要补充的是,据我所知和阅读的资料,在拇指模式下(如果有拇指1的话),你可以破坏R0-R3,但必须Push/Pop R4-R7。我正在查看的固件也是这样做的,而且我注意到gnu gcc(arm-elf-gcc)的代码也是这样做的。好吧...你做了所有这些测试真是太棒了,因为我在我的固件修改中也在做同样的事情,但有时候我会变得很困惑(特别是当由于其他错误导致崩溃时),我就会使用BL(非常麻烦...需要计算偏移量,就像我解释的那样)。 - vmanta
我正准备编写一个程序作为 "as" 的预处理器,并为我设置偏移量...也许使用一些特殊符号来表示,比如 BL ADR:0x24000,然后我的程序会为我计算这个偏移量...啊哈...那将是太棒了...为什么没有人想到呢,哈哈...也许有一种方法,只是我还不知道...如果有的话,应该是链接器中某种指令...不确定但我已经看过很多资料,花费了太多时间。 嘿,非常感谢你...并且很高兴你发现了真相..呵呵 - vmanta

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接