ARMv8中RET指令的嵌套使用

问题描述 投票:0回答:1

label1
label2
为两组指令,均以
RET
指令结尾,并且
label2
通过链接分支到
label1
。换句话说,我们有一个如下所示的代码(为了清楚起见,我将在下文中列举一些位置):


label1:
    # Some operations...
    # (1)
    RET

label2:
    #Some operations
    BL label1
    #(2)
    RET

我想给

label2
打电话
main
:

main:
    # Some operations...
    BL label2
    #(3)

我希望的行为是,在

label2
中分支到
main
后,从
#(3)
继续执行。然而,这种情况并非如此。当在
main
中时,我调用
BL label2
,链接寄存器保存所需返回点
#(3)
的地址,并且
label2
被执行。然而,在
label2
内部,我调用
BL label1
,用
#(2)#
替换链接寄存器。这使得
RET
之后的
#(1)
指令将执行到
#(2)
,并且
RET
之后的
#(2)
指令再次指向
#(2)
。你可以看到问题所在。

高级编程语言允许嵌套使用函数。我可以定义一个函数

f
,在其中调用一个函数
g
,每个函数都有
return
语句。所以我想要的功能必须以某种方式实现。如何顺序或嵌套调用
BL some_label
RET
并返回到调用
BL
的第一个位置?

我对组装很陌生,所以如果问题有点微不足道,请原谅我。

function assembly x86 arm function-call
1个回答
1
投票

假设我理解这个问题。

您基本上标记了多个架构。所以我会选择一种架构。

简短的答案是将返回地址保存到堆栈中。

unsigned int more_fun ( unsigned int x );

unsigned int fun0 ( unsigned int x )
{
    return(x+1);
}

unsigned int fun1 ( unsigned int x )
{
    return(more_fun(x));
}

unsigned int fun2 ( unsigned int x )
{
    return(more_fun(x)+1);
}

Disassembly of section .text:

00000000 <fun0>:
   0:   e2800001    add r0, r0, #1
   4:   e12fff1e    bx  lr

00000008 <fun1>:
   8:   e92d4010    push    {r4, lr}
   c:   ebfffffe    bl  0 <more_fun>
  10:   e8bd4010    pop {r4, lr}
  14:   e12fff1e    bx  lr

00000018 <fun2>:
  18:   e92d4010    push    {r4, lr}
  1c:   ebfffffe    bl  0 <more_fun>
  20:   e8bd4010    pop {r4, lr}
  24:   e2800001    add r0, r0, #1
  28:   e12fff1e    bx  lr

如果没有嵌套调用,则不需要保存返回地址(在该汇编语言中显示为 lr 或链接寄存器)。额外的寄存器 r4 并不是因为 r4 在这里很特殊,而是因为编译器使用的调用约定规定了 64 位对齐的堆栈,因此它们会放入其他寄存器中,这不会影响代码/约定以使其对齐。

第二个调用一个函数,所以两件事之一,在这种情况下它可以做一个尾巴?优化并完成了这个

fun1:
b more_fun

但事实并非如此。也许只有少数人知道,但我使用的是较旧的 gcc(不是最先进的),并保留了默认值,显然是 armv4t。因此,也许出于这个原因,工具链不愿意处理手臂/拇指模式切换,但使用 BL,他们会在链接器中为您放置一个单板/蹦床。

第三个我强迫它无法进行尾部优化,这类似于你通常看到的那种事情,如果手工完成,大多数人都会写。

实际上这就是您期望编译器产生的结果:

fun2:
push {r4, lr}
bl  more_fun
add r0, r0, #1
pop {r4, lr}
bx  lr

此堆栈帧位于函数的开头和结尾

fun2:
push {r4, lr}
...
pop {r4, lr}
bx  lr

然后填写函数的内容。

相同的 gcc,但不同的 Arm 架构,随着时间的推移,添加了更多的拇指交互支持。

Disassembly of section .text:

00000000 <fun0>:
   0:   e2800001    add r0, r0, #1
   4:   e12fff1e    bx  lr

00000008 <fun1>:
   8:   eafffffe    b   0 <more_fun>

0000000c <fun2>:
   c:   e92d4010    push    {r4, lr}
  10:   ebfffffe    bl  0 <more_fun>
  14:   e2800001    add r0, r0, #1
  18:   e8bd8010    pop {r4, pc}

哦,所以如果用手写就可以在没有堆栈帧的情况下完成,就在嵌套分支之前保留您需要保留的内容,然后在恢复您需要恢复的内容之后,所以不要在函数边缘使用堆栈帧当您浏览代码时保存和恢复。这没有什么问题,这是一种非常手工的汇编方式,而不是高级语言编译的方式。

其他架构,例如arm,具有不同的...架构...调用和返回可以例如使用堆栈而不是返回寄存器。因此,在这种情况下,就返回地址而言,您只需继续拨打电话(例如 8088/86)。您最终将需要保留其他内容以进行嵌套调用,以免除返回地址之外的内容被丢弃。这是编译语言制定/选择调用约定的地方,该调用约定作为一组规则允许以通用方式构造函数,以便您可以无限地嵌套或递归。

© www.soinside.com 2019 - 2024. All rights reserved.