我想了解arm链接寄存器的工作原理以及它在调试中的作用。 我从写一个简单的函数开始。
#define MACRO_TEST() (event_log__add_args(MACRO_TEST,__return_address()))
static void do_print_r14(void) {
printf_all("return address 0x%08X \n",__return_address()); //prints 0x823194BB
MACRO_TEST();
printf_all("return address 0x%08X \n",__return_address()); //prints 0x823194BB
}
事件日志打印以下内容: 返回地址:0x0000ABAB
我的问题是为什么do_print_r14函数中的打印件打印相同的值。 如果我只是登录行号和功能名称,那会不会更有帮助 将指向代码的确切位置。为什么开发人员在调试中使用r14?
这个问题对你们所有人来说听起来都很基本,但我完全不确定为什么我们需要r14注册。
答案 0 :(得分:3)
行号和函数名称告诉您您的位置,但返回地址告诉您如何到达 - 这在更复杂的代码中非常有用。在调试纯汇编,高度优化的代码或其他可能没有可理解堆栈帧的情况下,有时检查链接寄存器是了解该地址的唯一方法。
当然,这总是相对于当前的函数,因此将“打印返回地址”包含在自己的小函数中是弄巧成拙的,因为它只会告诉你在哪里从<假设编译器还没有决定内联它来调用 函数,在这种情况下你确实可以使用__LINE__
代替调用。
现在是微妙但重要的一点:__return_address()
内在的意图是“当前函数的返回地址”,这与“R14的当前内容”绝不相同 - 如果编译器在输入时保存了返回地址,然后可以随意使用R14。 do_print_r14()
中的两行都打印0x823194BB,因为这是调用do_print_r14()
的地方,并且在该调用期间没有任何内容会改变它。如果你想要查看C环境的抽象级别,看看在执行期间R14发生了什么实际,你需要使用内联汇编技巧,或者使用调试器逐步完成。
答案 1 :(得分:0)
r14
是CPU从函数返回时将跳转到的地址。因此,对__return_address
的调用将产生相同的值。
或许可以更好地证明这一点:
static void do_print_r14(void) {
printf_all("return address 0x%08X \n",__return_address());
}
static void test_r14(void) {
printf_all("first call...\n");
do_print_r14();
printf_all("second call...\n");
do_print_r14();
}
此处将打印两个不同的值(对应于调用的两个不同位置do_print_r14
)。
答案 2 :(得分:0)
LR
的用法由ARM体系结构和ARM AAPCS定义。链接寄存器的帮助仅仅是副作用而不是功能。
来自AAPCS:
5.3子程序调用
ARM和Thumb指令集都包含一个原始子程序调用指令BL,它执行分支链接操作。执行BL的效果是将程序计数器的顺序下一个值 - 返回地址 - 传送到链接寄存器(LR),将目标地址传送到程序计数器(PC)。