【问题标题】:How does the linker determine which custom sections are read-only?链接器如何确定哪些自定义节是只读的?
【发布时间】:2023-01-03 02:45:02
【问题描述】:

如果您在 C 中为全局变量定义自定义部分,并在链接描述文件中定义自定义输出部分,链接器(?)如何确定该部分的属性(W = 可写,A = 可分配,...) ?

发行和 MWE

我目前面临的问题是,我将同一部分分配给两个全局变量,其中一个是常量(在其用法中,而不是程序代码),而另一个则不是。最后,它们都进入了一个只能分配但不可写的部分,并且程序以分段错误终止。

示例程序(test.c):

static double some_thing[5] __attribute__ ((section ("my_nonconst_section"))) = {2.0, 4.0, 6.0, 8.0, 10.0};
static double another_thing[5]  __attribute__ ((section ("my_nonconst_section")));

int main(int argc, char const *argv[]) {
    another_thing[1] = some_thing[argc];
    return another_thing[argc] == 0.0;
}

我的自定义链接描述文件扩展(linkerscript.ld,请注意,自定义地址对我来说至关重要,这就是我首先使用这些部分的原因):

SECTIONS {
  . = 0x0000001b000002e0;
  my_nonconst_section : {KEEP(*(my_nonconst_section))}
  /* . = 0xAddressForThisSection;
     my_const_section : {KEEP(*(my_const_section))}
     ... */
}
INSERT AFTER .gnu.attributes;

我使用 clang(测试10.0.0-4ubuntu1 和自建 12)用我的链接器脚本编译/链接它(clang 也不是可选的,见下文):

clang -mcmodel=large -O1 test.c -Wl,-Tlinkerscript.ld -o test.tmp

然后执行它:

./test.tmp

但是,我注意到 clang -O0(它没有推导出 some_thing 是常量)和 gcc9.4.0,具有任何优化级别)没有表现出这种行为。

我使用的链接器是GNU ld (GNU Binutils for Ubuntu) 2.34,但是我可以看到与gold链接器相同的效果。我的目标是x86_64

我能否以某种方式影响自定义链接器部分具有的属性(最好在链接描述文件中定义)?我最好有一些未写在可写部分中的变量。

背景/背景:

我正在编写一个 (LLVM/clang) 编译器传递,它使用自定义链接器部分注释全局变量。我在自定义链接描述文件(通过这些部分扩展默认链接描述文件)中为这些全局变量定义了链接器输出部分,类似于上述内容。

该过程通过查看全局变量属性来区分常量和非常量全局变量。如果它是常量,则选择常量的节标签,否则选择包含可写数据的节标签。然而,添加部分注释后,另一个编译器通道能够显示其中一个变量——用(意识形态上的)非常量部分注释——确实只被读取,因此通道将其标记为常量。

结果是包含标记为const 的全局变量的部分变为只读,但它仍然包含非常量全局变量。在程序执行期间,尝试在此部分写入另一个全局变量会导致分段错误(如预期)。

我确定这两个变量都是只读的nm

0000001b00000310 r another_thing
0000001b000002e0 r some_thing

该部分如下所示(由readelf -S 确定):

[Nr] Name                    Type            Address          Off    Size   ES Flg Lk Inf Al
[..] my_nonconst_section     PROGBITS        0000001b000002e0 0032e0 000058 00   A  0   0 16

通常,我希望非常量数据部分使用Flg = WA,而常量部分使用Flg = A

笔记

目前,我不必手写一个完整的链接描述文件,并且编译器通道兼容处理已经有部分注释的 C 源代码,保留这个属性会很好。 我看到可以使用 MEMORY 指令完全定义内存布局,但据我所知,这需要为所有部分定义内存,我不想这样做。 目前,由于我正在使用“绑定”来确定这些部分的地址,我不能(据我所知)将绑定与命名内存结合使用,即属性特征> (RW) 以确保该部分是可写的一。

【问题讨论】:

  • 我从来不记得这些链接描述文件的确切语法,但 iirc 之类的 MEMORY { cant_touch_this (r) : ... } ... SECTIONS { my_nonconst_section : ... } > cant_touch_this 可能有用。
  • 我认为 my_nonconst_section (aw) : {KEEP(*(my_nonconst_section))} 设置了可分配和可写的属性,但不确定是否能解决这里的问题
  • @Lundin 你知道是否有办法扩展已经存在的 MEMORY 定义(例如,用于放置 .text 等部分)而不必手动重新定义所有这些定义?我宁愿自己不完全定义MEMORY,因为这似乎比我需要的更具侵入性(一些全局变量分配给一些自定义部分)。我想从长远来看,它也会更加脆弱。目前,我只添加了几个部分,并且使用MEMORY,我必须定义整个链接描述文件(如果我正确理解它的用法)。
  • 关于节定义中的属性(@nos):据我了解,这些属性不能直接与节相关联(语法不允许)。据我了解,这是因为,例如,可写是输出部分分配给的内存的一个属性,而不是部分本身。如果我尝试建议的解决方案,我会收到错误消息:/usr/bin/ld:linkerscript.ld:3: non constant or forward reference address expression for section my_nonconst_section

标签: c linker


【解决方案1】:

成就已解锁:事实上,它是编译器——触发编译器中的错误。

这不是链接器做的,而是编译器做的。

链接器只是合并它被告知要合并的部分,连同它们的属性(在不匹配的情况下获得最大的权限),然后将它们放置到可执行段中;或者在可重定位链接的情况下,只需合并同名部分(属性具有相同的行为)。

编译器的工作是将程序的变量放入部分并为这些部分分配适当的属性。不幸的是,当用户请求将需要不同部分属性的变量放入同一部分时,Clang 13 和更早版本似乎会感到困惑。如果在链接之前查看目标文件会更清楚:

$ nix shell 'nixpkgs#binutils' 'nixpkgs#clang_13'
$ clang -mcmodel=large -O1 -c test.c
$ readelf -S test.o
There are 11 section headers, starting at offset 0x2a8:

Section Headers:
  [Nr] Name              Type             Address           Offset
       Size              EntSize          Flags  Link  Info  Align
[...]
  [ 3] .rela.text        RELA             0000000000000000  000001d0
       0000000000000030  0000000000000018          10     2     8
  [ 4] my_nonconst_[...] PROGBITS         0000000000000000  00000080
       0000000000000058  0000000000000000   A       0     0     16
  [ 5] .comment          PROGBITS         0000000000000000  000000d8
       0000000000000016  0000000000000001  MS       0     0     1
[...]
$ ^D

(我试图看看你是否可以通过声明 another_thing 来解决这个问题,这首先需要一个 WA 部分。你不能,而且 some_thing 甚至以一个较低的地址结束。)

Clang 14 及更高版本通过生成两个具有不同属性的同名部分正确地处理了这个问题,即使令人困惑:

$ nix shell 'nixpkgs#binutils' 'nixpkgs#clang_14'
$ clang -mcmodel=large -O1 -c test.c
$ readelf -S test.o
There are 12 section headers, starting at offset 0x2c0:

Section Headers:
  [Nr] Name              Type             Address           Offset
       Size              EntSize          Flags  Link  Info  Align
[...]
  [ 3] .rela.text        RELA             0000000000000000  000001e8
       0000000000000030  0000000000000018   I      11     2     8
  [ 4] my_nonconst_[...] PROGBITS         0000000000000000  00000080
       0000000000000028  0000000000000000   A       0     0     16
  [ 5] my_nonconst_[...] PROGBITS         0000000000000000  000000b0
       0000000000000028  0000000000000000  WA       0     0     16
  [ 6] .comment          PROGBITS         0000000000000000  000000d8
       0000000000000016  0000000000000001  MS       0     0     1
[...]
$ ^D

如果您随后将此目标文件链接到可执行文件中,那么在前者和有问题的情况下,my_nonconst_section 以属性 A (duh) 结尾,因此您会遇到段错误,而在后者和正确的情况下,它以 @ 结尾987654328@(由于合并),因此可执行文件可以正常工作。

P. S.有趣的是,由于 GNU 汇编器无法支持单个对象中的同名部分,而 LLVM-MC 也紧随其后,来自 clang -c 的无错误目标文件现在与 clang -S 打印的汇编程序源不匹配,因为后者在单个代码块中声明两个变量之前使用.section my_nonconst_section,"aw",@progbits

【讨论】:

    猜你喜欢
    • 2012-11-24
    • 2023-03-27
    • 2015-01-07
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-08-06
    • 2020-08-09
    相关资源
    最近更新 更多