危险的迁移错误是什么意思?

14
我遇到了一个链接错误:
危险的重定位:l32r:在使用之后放置字面量:
我还在尝试调试。然而,我想更好地理解这个错误。我知道什么是重定位,但不确定它为什么会有危险,并且正在寻求一些澄清。此外,一个能够生成这种类型错误的小代码片段将会很有帮助。
简而言之,什么是“危险的重定位”?

你能发布一下代码吗? - Bijaya Bidari
不行。首先,它是一个非常庞大的代码库,其次,它是专有的。 - Fred Thomsen
你可以通过提取代码的小片段来重新创建问题,并发布该片段。 - Bijaya Bidari
2
@FredThomsen:这是什么架构?重定位的名称表明您正在处理Xtensa,但由于重定位是特定于架构的,最好确定一下。 - LThode
1
@LThode 正确。它是Xtensa。 - Fred Thomsen
显示剩余2条评论
2个回答

28
这是一个由两部分组成的回答,因为这里有两个问题,一个是一般性的(“什么是危险的重定位?”),另一个与Xtensa有关(“为什么不能在代码中使用文字量后面放置它?”)。
首先了解什么是“危险的重定位”,需要理解什么是“重定位”。编译器在从某段代码生成目标文件时,需要引用其他地方定义的符号,可能是链接中的另一个目标文件,也可能是共享库。然而,在编译给定的目标文件时,编译器不知道外部符号的地址。它必须发出重定位作为命名占位符,告诉链接器:“好的,请把foobar的地址塞到这个位置上,噢,你必须将它做成X、Y和Z以适应指令的要求。”
大多数情况下,这样做没有问题,最终会得到一个二进制文件。当这个过程失败,链接器无法使编译器提供的符号地址适应重定位处的指令时,它就会放弃并输出“危险的重定位”信息(其中还有其他信息——常见的“重定位被截断以适应”的错误也会弹出来),以通知程序员出现了严重错误。
现在我们知道什么是通用的“危险的重定位”,可以继续讨论错误信息的后半部分,即“l32r:文字放置在使用后”。Xtensa使用一种称为L32R的指令从内存中加载常量值,这些常量值不适合于Xtensa的MOVI立即加载指令,因为它具有12位有符号立即字段。L32R指令在Xtensa ISA参考中描述如下:
“L32R是从内存中相对于PC(程序计数器)读取32位数据的指令。当不能使用MOVI指令来编码常量时,通常会使用它将常量值加载到寄存器中。
L32R通过将指令字中编码的16位符号扩展常量值左移两位并添加到L32R地址加三的地址上,并清除最低的两个比特位,形成一个虚拟地址。因此,偏移量总是能够指定从L32R指令的地址开始-262141到-4个字节对齐的地址。32位(四个字节)被从物理地址中读出。然后,该数据被写入地址寄存器at。”

考虑到上述关于L32R的限制,错误信息可以很好地分解: 编译器生成了一个L32R来加载代码中某个位置的常量(可以是值也可以是地址),但无论是常量值不可用于编译器(考虑使用extern const),还是需要链接器填充地址(这是最有可能的情况),都导致常量无法加载。因此,它发出了这个L32R重定位指令,告诉链接器在程序中填充常量值或常量地址的地址,并将其嵌入在L32R指令中。然而,链接器在前256KB的代码区域或常量池中找不到任何地方来存储常量,所以报错了。

如何解决这个问题?

不幸的是,这种类似‘危险重定位’的错误取决于代码大小,所以除非你手头真的遇到编译器或链接器的bug,否则很难通过小片段的代码复现这个错误。不过,你可以尝试解决其中的两个可能原因。

没有空间来存储我的常量池!

如果你使用的是-mno-text-section-literals编译选项(这是默认选项),那么链接器会将常量池作为单独的节传递,然后将其与代码节交错排列。如果你的某个目标文件在链接时特别大,在它的.text节中可能有超过256KB的代码,这将导致链接器无法为与之关联的常量池节分配一个L32R指令的范围。使用-mtext-section-literals编译选项可以消除此错误;如果不起作用,则说明你已经开启该选项,或者你正在使用-ffunction-sections(每个函数都放进自己的节中,有时在嵌入式开发中使用以允许链接器丢弃未使用的代码),请继续阅读下面的内容。

链接器(或汇编器)仍然找不到放置我的常量的地方!

当编译器和汇编器被要求将文字面量放入文本段时,它们会限制将文字面量池放置在使用它们的函数之前(即在函数的ENTRY指令之前),以最小化文字面量池作为代码执行的风险,这显然会导致糟糕的结果。如果您的代码中有一个极长的函数 - 我不敢想象什么样的函数会生成超过256KB的代码 - “默认”的文字面量池放置在ENTRY指令之前,可能会超出函数末尾处的L32R指令的范围。通常,编译器会发出已知为.literal_position的汇编指令,以及跳过中间函数文字面量池的指令,为汇编器和链接器提供额外的位置来存放文字面量。您可以告诉编译器使用-save-temps输出汇编清单,然后搜索其中的.literal_position指令;如果在具有超过256KB标记的L32R指令的函数中不存在该指令,则恭喜!您刚刚发现了一个编译器错误!
其他可能导致此问题的情况是,编译器或链接器无法在ENTRY指令之前找到放置文字面量池的位置,并且编译器无法自行解决这个问题 - 这可能会发生在中断处理程序或被链接脚本明确放置在物理内存边界开头的函数中。在这种情况下,您需要在罪魁祸首函数的顶部插入.literal_position指令及其相关的跳转和标签,以便为汇编器提供放置罪魁祸首函数文字面量的位置。正如GAS手册所述:汇编器会自动将文本区段文字池放置在“ENTRY”指令之前,因此“.literal_position”指令只需要指定某个文字池的其他位置。您可能需要添加显式跳转指令以跳过内联文字池。
例如,中断向量不以“ENTRY”指令开头,因此汇编器将无法自动找到一个好的位置来放置文字池。此外,中断向量的代码必须从特定的起始地址开始,因此文字池不能出现在代码的开头。向量的文字池必须显式地定位在向量的中间(在任何使用文字之前,由于PC相对的“L32R”指令使用了负偏移量)。
等等,我正在使用绝对文字选项!
如果您在Xtensa核心中启用了“LITBASE”选项,并且收到此错误,则表明您的文字池已经溢出。编译器应在这种情况下生成必要的“胶水”以切换文字池。如果没有,请恭喜!您刚刚发现了一个编译器错误!

0

一个有趣的例子,但它并没有帮助我理解。 - Fred Thomsen

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接