奇怪的只读符号显示为 nm 中的初始化数据部分 (D)

Curious readonly symbols showing up as in initialized-data-section (D) in nm

我注意到 gcc(但不是 clang),const(只读)初始化数据对象不再显示为 R 个数据对象在 nm 中,而是变成了 D(初始化部分)对象。

这表明数据对象将被放置在可写内存中,但是,当同一个目标文件与 gccclang(但不是 tcc)链接时,然后它似乎无论如何都放在只读内存中。

clang 似乎没有使用这些奇怪的只读-D 符号(相反,对象仍然是 R)。 Tinycc 确实也将此类对象制作成 D 符号,但是那些 D 符号似乎没有那种导致链接器将它们放入只读内存的奇怪 属性。

你能解释一下这是怎么回事吗?

下面的脚本演示了 gcc、clang 和 tinycc 在所有组合中用作编译器和链接器的行为:

#!/bin/sh -eu
cat > file.c <<EOF
struct obj { void (*fnptr)(void); int z; };
static void fn0(void) { }
const struct obj constInitedReadonlyObject = { 0, 42 };
const struct obj readonlyObject = { &fn0, 42 };

int main()
{
    int volatile*z = (int volatile*)&readonlyObject.z;
    *z = 1000;
}
EOF
for cc in gcc tcc clang; do
    $cc -c file.c
    echo cc=$cc type=$( nm file.o |grep readonlyObject |cut -d ' ' -f 2 )
    for ld in gcc tcc clang; do
        $ld file.o
        printf '\t%s\n' "ld=$ld $(if ./a.out 2>/dev/null; then echo NOTHING; else echo FAULT; fi)"
    done
done

我系统上的输出:

cc=gcc type=D
    ld=gcc FAULT
    ld=tcc NOTHING
    ld=clang FAULT
cc=tcc type=D
    ld=gcc NOTHING
    ld=tcc NOTHING
    ld=clang NOTHING
cc=clang type=R
    ld=gcc FAULT
    ld=tcc FAULT
    ld=clang FAULT

编辑:对目标文件执行 readelf -s 并对两个数据对象进行 grepping 产生:

clang
     4: 0000000000000000    16 OBJECT  GLOBAL DEFAULT    4 constInitedReadonlyObject
     6: 0000000000000010    16 OBJECT  GLOBAL DEFAULT    4 readonlyObject
gcc
    11: 0000000000000000    16 OBJECT  GLOBAL DEFAULT    5 constInitedReadonlyObject
    12: 0000000000000000    16 OBJECT  GLOBAL DEFAULT    6 readonlyObject
tcc
     3: 0000000000000000    16 OBJECT  GLOBAL DEFAULT    3 constInitedReadonlyObject
     4: 0000000000000010    16 OBJECT  GLOBAL DEFAULT    3 readonlyObject

我猜不同的数字(在名称为 Ndx 的列中(未显示))与行为有关。

您的 gcc 很可能配置了 --enable-default-piegcc -v 进行检查)。

PIE中,readonlyObject需要在程序启动时可写,以允许动态重定位处理代码将fn0的地址写入其第一个字段。为此,gcc 将这些对象放入带有 .data.rel.ro 前缀的部分,链接器将这些部分与其他 .data 部分分开收集。动态链接器(或者,在静态 PIE 的情况下,链接重定位处理代码)可以在写入该区域后 mprotect

因此,使用 gcc(和隐式 -fpie -pie)你有:

  • readonlyObject.data.rel.ro
  • nm分类为“全球数据”
  • 在程序启动时可写以进行重定位
  • 达到 main 时只读

使用 clang 或 gcc -fno-pie 你有:

  • readonlyObject.rodata
  • nm分类为“全局常量”
  • 即使在程序启动时也是只读的