奇怪的只读符号显示为 nm 中的初始化数据部分 (D)
Curious readonly symbols showing up as in initialized-data-section (D) in nm
我注意到 gcc
(但不是 clang
),const
(只读)初始化数据对象不再显示为
R
个数据对象在 nm
中,而是变成了 D
(初始化部分)对象。
这表明数据对象将被放置在可写内存中,但是,当同一个目标文件与 gcc
或 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 的列中(未显示))与行为有关。
您的 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
时只读
使用 clang 或 gcc -fno-pie
你有:
readonlyObject
在 .rodata
- 被
nm
分类为“全局常量”
- 即使在程序启动时也是只读的
我注意到 gcc
(但不是 clang
),const
(只读)初始化数据对象不再显示为
R
个数据对象在 nm
中,而是变成了 D
(初始化部分)对象。
这表明数据对象将被放置在可写内存中,但是,当同一个目标文件与 gcc
或 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 的列中(未显示))与行为有关。
您的 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
时只读
使用 clang 或 gcc -fno-pie
你有:
readonlyObject
在.rodata
- 被
nm
分类为“全局常量” - 即使在程序启动时也是只读的