首页 > 解决方案 > 奇怪的只读符号显示为 nm 中的初始化数据部分 (D)

问题描述

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

这表明数据对象将被放置在可写内存中,但是,当同一个对象文件与gccor clang(但不是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 的列中(未显示))与行为有关。

标签: clinker

解决方案


很可能您的 gcc 配置有--enable-default-pie(gcc -v来检查)。

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

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

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

使用铿锵声或者gcc -fno-pie你有:

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

推荐阅读