你看到的是结果而不是症状。
访问 arm 网站 (infocenter.arm.com) 并找到 armv5 的 arm 架构参考手册,您可能不得不放弃一个电子邮件地址,但这些很容易获得,没什么大不了的。
因此有 arm 指令集和与之配套的 arm 架构。有些寄存器是特殊的,有些则不是。显然 r15 非常特殊,它是程序计数器,并且有些指令具有仅支持 r15(pc 相对寻址)的寻址模式。同样,r14 是特殊的,它也为分支链接指令硬连线,返回是通用的,但调用不是,确保您可以在不使用 r14 的情况下进行调用,但没关系 r14 是特殊的,因为它硬连线到一个/一些指令上。
在 thumb 模式下,r13 被硬编码到 push 和 pop 指令中(在 arm 模式下,ldm 和 stm 在技术上可以使用任何寄存器,尽管 r15 可能是个坏主意)。
可能还有其他人……
所以一些寄存器被硬连线到一些指令或寻址模式中。因此,除了所有这些,可能出于性能原因,还有不同的处理器模式、主管、中断等......而且 arm 文档向您展示了其中一些寄存器具有 _svc 版本或 _und 版本等。是的, r14_svc 位与 r14_abt 位不同,如果修改 r14_abt,则无法使用 r14_svc 或 r14_irq 读取 vale。当特定指令根据处理器模式读取或写入 r14 或 r13 等时,将选择不同的存储库并使用特定模式的寄存器存储库。对于 r0-r7,所有模式都使用相同的实际寄存器/位/ram,但从 r8 开始要知道使用了哪些寄存器,您必须查看模式。根据 ARM ARM,此声明可能会有所不同,但我现在正在查看的是:
ARM 处理器共有 37 个寄存器:
他们给你看一张 r0-r15 的图片,它加起来是 37 而不是 16,因为它们是独立的寄存器。
现在您进入调用约定,它与硬件完全无关,它只是人们同意使用的一种习惯或约定。如果你跳上你的 Wayback 机器,你可能会了解到一些 x86 编译器使用基于堆栈的参数传递,而一些使用基于寄存器的编译器,它们彼此不兼容,有时你可以指定编译时使用的约定。其他一些人也是如此,但不是全部。如果您查看 mips 文档,那里的硬件人员喜欢通过根据约定重命名寄存器来定义调用约定,这不是刻在硅上的,您不必遵守它。与架构手册之外的其他地方定义的 arm 调用约定相同,因为这从来没有意义,但它是在某个地方定义的,编译器编写者无论他们为 arm 工作还是不符合这些标准,甚至在何时更改,都足够理智arm 改变了约定。
这是您开始看到使用 r0-r3 传递参数的约定的地方,然后在使用这些参数后,如果您的计算机觉得需要一个帧指针(懒惰的程序员,更容易调试编译器输出) ) 他们选择 r12 或其他。该约定定义了函数需要保留哪些寄存器以及不需要保留哪些寄存器,并定义了如何返回值。尽管在某些方面的约定是任意的,如果您正在制作自己的编译器,您可以一直发明自己的或使用堆栈而不注册等,但您可能使用的编译器符合 arm 调用约定(abi/eabi ) 在其不断发展的版本之一中。
是的,这就是为什么约定中没有保留一个或多个高位寄存器的原因,以便在您觉得需要帧指针时可以将其用作帧指针。
保留是什么意思?如果您查看以前的寄存器较少、编译器和堆栈参数传递不太成熟的日子,您基本上保留了在进入函数时要触及的每个寄存器并在退出时恢复它们,那么您修改的内容(返回值)就在堆栈上或者您将堆栈顶部用于函数中的易失性内容(局部变量、中间值等)。即使有 16 个寄存器,您也可以开始谈论使用寄存器进行参数传递,甚至制定一个不必保留所有寄存器的约定。这就是 arm 公约和其他一些公约所做的。
unsigned int fun ( unsigned int a, unsigned int b )
{
return(a+b+7);
}
a 将被传递给寄存器 r0 中的函数,b 在寄存器 r1 中,因为约定是这样说的。返回值在 r0 中,因为约定是这样的,所以代码的优化版本是通过向 r0 添加 7 来破坏 r0,因此 a 值消失了,我们可以从代码中看到我们只需要 a 值足够长的时间来用它做一次数学运算。所以我们可以
r0 = r0 + 7;
r0 = r0 + r1;
然后返回
或者我们可以
r1 = r1 + 7;
r0 = r0 + r1;
return
或主题的其他变体。在上述任何一种情况下,我们都没有保留 r0,因为约定基本上说我们必须销毁内容,因为那是我们的回报。但是我们也可以至少修改 r0,r1,r2,r3 所以我们也可以这样做:
r2 = r1+7;
r3 = r2+r0;
r0 = r3;
return
这将是合法的,因为除了我们之外没有人关心我们会弄乱 r0-r3(有时还有其他人)。
如果你这样做了
r4 = r1+7;
r0 = r0 + r4;
return
您可能会很幸运而没有崩溃,或者您可能会以正确的方式崩溃,或者可能很长时间没有崩溃但然后崩溃。为什么因为规则说我们不应该修改 r4,我们必须保留它:
push {r4}
r4 = r1+7;
r0 = r0 + r4;
pop {r4}
return
退出函数时的值必须与进入函数时的值相同。为了说明这一点:
unsigned int fun ( unsigned int a, unsigned int b )
{
return(more_fun(a,b)+a+b+7);
}
为了实现这一点,我们可能会这样做
push {r4,r14}
r4 = r0 + r1;
r4 = r4 + 7;
call more_fun();
r0 = r0 + r4;
pop {r4,r14}
return
我们需要根据 r0 和 r1 中的值记住 a+b+7 是什么,但是由于 r0 和 r1 可以通过 more_fun() 进行修改,我们需要按原样保存它们,或者先进行数学运算保存结果。无论哪种方式,我们都需要将它们放入堆栈以保存它们或将它们放入非易失性寄存器中,并且要使用非易失性寄存器,我们需要保存其先前的内容。
现在,如果 more_fun() 违反了规则并修改了 r4,那么我们的 fun() 结果将是不正确的,并且不正确的值可能会导致 fun() 的调用者做错事或崩溃或其他什么,或者有时你很幸运并且它修改了 r4 但它以相同的值或安全值的方式修改它。然后也许你改变了几行代码,现在 r4 上游被用来保存其他东西,你管它,现在你确实崩溃了。
只要每个函数的每个实现都遵循约定,那么一切都会顺利进行。 (假设约定与手臂一样)。