【问题标题】:bx lr not working in ARM assembly on Raspberry Pibx lr 无法在 Raspberry Pi 上的 ARM 程序集中工作
【发布时间】:2021-04-12 10:11:04
【问题描述】:

我正在编写一个使用 ARM 程序集作为气泵接口的程序。用户选择他们想要泵送的汽油等级,然后宣布他们想要花费的金额。由于无论汽油的等级如何,都会对该输入执行相同的检查,因此我想使用相同的代码块。我希望我的代码使用blbx lr,如here 所示,但我做错了什么。这是我的代码示例:

get_grade:
    cmpne r1, #'R'
    beq pump_regular
    cmpne r1, #'P'
    beq pump_premium

pump_regular:
    ldr r0, =strSelRegular
    bl printf
    bl prompt_amount
    @ do other things

pump_premium:
    ldr r0, =strSelPremium
    bl printf
    bl prompt_amount
    @ do other things

prompt_amount:
    ldr r0, =numInputPattern
    ldr r1, =intBuyAmt
    bl scanf
    @ check to make sure the input is valid
    @ if not, b prompt_amount
    bx lr
    

这个程序可以编译,但不符合我的预期。一旦到达prompt_amount 的末尾,它就会挂起。我已经在论坛帖子和 keil 文档中搜索了几个小时,但无济于事。非常感谢任何帮助。

【问题讨论】:

  • 我不太明白你的代码。入口点在哪里?为什么get_grade 没有返回指令?您需要注意的另一个问题是每个bl 指令都会覆盖lr 寄存器。因此,为了能够从您的函数返回,您需要在入口处将lr 保存在堆栈中,并在最后将其弹出。您可以使用push {lr}pop {pc} 来执行此操作,将pop 指令与函数返回相结合。
  • @fuz:为了保持堆栈对齐,您通常想要执行类似push {r4, lr} / pop {r4, pc} 之类的操作,即使您实际上不需要保存/恢复额外的调用保留寄存器。 (IIRC,ARM Linux 上的 ABI 在调用前保持 8 字节堆栈对齐)
  • 您正在用bl 覆盖lr。而且由于您通过bx lr 从函数返回,它一直分支到bx lr,唉,它挂起。

标签: assembly raspberry-pi arm


【解决方案1】:

BL指令的操作

if ConditionPassed(cond) then
  LR = address of the instruction after the branch instruction
  PC = PC + (SignExtend(signed_immed_24) << 2)

所以一个函数调用另一个函数

bl one
one_ret:

pc指向one,lr改为指向one_ret:

one:

此时 lr 指向 one_ret 所以一个简单的 bx lr 将返回

...
bl two
two_ret:

执行完这个bl lr指向two_ret后,一个bx lr会返回two_ret。这 从 lr 的角度来看,返回 one_ret 的知识丢失了

two:
bx lr

这分支到 two_ret

two_ret:
bx lr

此时这是一个无限循环 bx lr 分支到 two_ret...没有办法回到 one_ret

如果你

...
bl one
one_ret:
...
one:
 push {r4,lr}
 bl two:
two_ret:
 pop {r4,lr}
 bx lr
two:
 bx lr

push 和 pop 保存 lr

one:
 push {r4,lr}  save lr pointing at one_ret on the stack
 ...
 pop {r4,lr} restore lr to point at one_ret
 bx lr branch to one_ret

r4 在这里无关紧要,经常看到这个寄存器被使用。当前的调用约定需要一个 64 位对齐的堆栈,因此需要推送偶数个寄存器。例如,有时您会看到 r3 而不是 r4,但 r3 可能是返回的一部分(...也许?)比 r4 高得多,并且您进入一些特殊/保留寄存器...只需使用 r4,它没有魔法,但是对于当前和之前的调用约定, r4 是一个不错的选择...如果您实际上要保存其他寄存器,请使用这些寄存器,最终总共有偶数个寄存器(似乎不必一次推送/pop,但在你调用另一个函数之前)

在 thumb mov pc,lr 之前是典型的返回分支。然后用 thumb 所需的指令是 bx lr 因为 bx 可以处理混合模式(arm/thumb)而 mov pc,lr 可以/没有。您可以在文档中查找互通。然后除了 pop {pc} 现在可以用于互通,但它取决于架构 armv4t 不支持 pop {pc} 用于互通,它必须来自/到相同的模式。树莓派 aarch32 指令集(armv6 或 armv7-a 取决于您使用的内核)都支持 pop {pc},因此您可以将其更改为

one:
push {r4,lr}
...
pop {r4,pc}

并保存一条指令。

作为一般规则,每当您像在 C 函数中一样对标签进行 bl 时,您应该自动键入 { 和 } 添加 push {r4,lr} 和 pop {r4,pc} 然后填写代码中间。然后你可以像两种情况一样优化:上面那个“函数”中没有使用bl所以不需要保留lr,如果你没有其他理由使用堆栈那么你可以删除push/弹出并添加 bx lr。

请注意,bl 不支持混合模式,但某些工具链可能支持。例如,如果您正确标记了标签,则在 gnu binutils 中的汇编器和链接器之间:

.type two,%function
.globl two
two:

然后链接器可以看到bl二,知道该指令处于什么模式,两个代码是什么模式,并根据需要添加蹦床

bl two
...
two:

如果 bl 2 在 thumb 中,而 2 在 arm 中,则链接器将实质上替换 bl 2 以分支到蹦床

bl two_from_thumb
...
two:
...
two_from_thumb:
bx two
...

bx 两个是无效的,他们使用保留寄存器执行一些简单的指令以在功能上执行相同的操作。如果您的工具链没有为您添加蹦床,那么您不能盲目地 bl 到您想要的任何东西...有时您必须以伪代码的形式添加 lr、pc、#something、adr rx、label 和 bx rx .通过在函数调用后指向lr模拟一个bl,然后使用bx进行分支 到函数。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2017-07-31
    • 2015-01-20
    • 2018-04-26
    • 2018-12-08
    • 1970-01-01
    • 2019-01-26
    • 2022-08-15
    相关资源
    最近更新 更多