提问人:lafinur 提问时间:10/6/2023 最后编辑:Peter Cordeslafinur 更新时间:10/12/2023 访问量:62
在 ARMv8 中嵌套使用 RET 指令
Nested use of RET instruction in ARMv8
问:
设 ,是两组指令,都以指令结尾,并且以链接分支。换句话说,我们有一个看起来像这样的代码(为了清楚起见,我将在下面列举一些位置):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)
我希望的行为是,在分支到 in 后,执行从 .然而,事实并非如此。当 I 调用时,链路寄存器保存所需返回点的地址,并被执行。但是,在里面,我调用 ,将链路寄存器替换为 。这使得 after 的指令将执行 带到 ,而 after 的指令再次指向 。你可以看到问题所在。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
我是组装的新手,所以如果问题有点微不足道,请原谅我。
答:
您基本上标记了多个体系结构。所以我会选择一个架构。
简短的回答是将返回地址保存到堆栈中。
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 但不同的手臂结构,随着时间的推移,增加了更多的拇指互通支持。
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)。您最终将需要保留其他内容来进行嵌套调用,以免丢弃返回地址以外的内容。这是编译语言制定/选择调用约定的地方,该约定作为一组规则允许以通用方式构造函数,以便您可以无限期地嵌套或递归。
评论
lr
b label1
call
ret
pop {pc}
ret
ret
ret
push
pop
stp
ldp