GCC LTO 胖目标文件上的 `nm` 输出不正确
Incorrect output of `nm` on GCC LTO fat object files
如果我有 tmp.c
:
char constantFOO[0x12];
char constantBAR[0x34];
我看到 gcc -c tmp.c -o tmp.o && nm tmp.o
节目
0000000000000034 C constantBAR
0000000000000012 C constantFOO
但是如果我用 -flto -ffat-lto-objects
编译,nm
输出符号值的零:
00000000 C constantBAR
00000000 C constantFOO
我可以在两个 .o
文件的 hexdump 中得到 34
和 12
值。
我的问题是
nm
对 LTO fat 文件的行为是否符合预期?我只是给它输入了它不期望的输入,它正在输出垃圾吗?
原始输出(符号值匹配未初始化数组长度)的解释是什么? This question好像对数组的问题没有帮助,可能是我理解错了
我在 -S
模式(输出汇编语言)下使用 GCC 8.3 编译了你的 tmp.c
有无 -flto -ffat-lto-objects
。在这两种情况下,都会发出相同的常量基本定义:
.comm constantFOO,18,16
.comm constantBAR,52,32
LTO 发出的大部分额外数据进入名为 .gnu.lto_.something
的 ELF 部分。 LTO 模式添加了一个额外的标记对象:
.comm __gnu_lto_v1,1,1
出现在 LTO 编译对象中,但不出现在没有 LTO 的对象中。
从表面上看,这根本不应该影响 nm
对这些符号的输出,较低级别的工具 readelf -s
会为它们生成匹配的输出:
$ readelf -s tmp-normal.o
Symbol table '.symtab' contains 9 entries:
Num: Value Size Type Bind Vis Ndx Name
0: 0000000000000000 0 NOTYPE LOCAL DEFAULT UND
1: 0000000000000000 0 FILE LOCAL DEFAULT ABS test.c
2: 0000000000000000 0 SECTION LOCAL DEFAULT 1
3: 0000000000000000 0 SECTION LOCAL DEFAULT 2
4: 0000000000000000 0 SECTION LOCAL DEFAULT 3
5: 0000000000000000 0 SECTION LOCAL DEFAULT 5
6: 0000000000000000 0 SECTION LOCAL DEFAULT 4
7: 0000000000000010 18 OBJECT GLOBAL DEFAULT COM constantFOO
8: 0000000000000020 52 OBJECT GLOBAL DEFAULT COM constantBAR
$ readelf -s tmp-lto.o
Symbol table '.symtab' contains 17 entries:
Num: Value Size Type Bind Vis Ndx Name
0: 0000000000000000 0 NOTYPE LOCAL DEFAULT UND
1: 0000000000000000 0 FILE LOCAL DEFAULT ABS test.c
2: 0000000000000000 0 SECTION LOCAL DEFAULT 1
3: 0000000000000000 0 SECTION LOCAL DEFAULT 2
4: 0000000000000000 0 SECTION LOCAL DEFAULT 3
5: 0000000000000000 0 SECTION LOCAL DEFAULT 4
6: 0000000000000000 0 SECTION LOCAL DEFAULT 5
7: 0000000000000000 0 SECTION LOCAL DEFAULT 6
8: 0000000000000000 0 SECTION LOCAL DEFAULT 7
9: 0000000000000000 0 SECTION LOCAL DEFAULT 8
10: 0000000000000000 0 SECTION LOCAL DEFAULT 9
11: 0000000000000000 0 SECTION LOCAL DEFAULT 10
12: 0000000000000000 0 SECTION LOCAL DEFAULT 12
13: 0000000000000000 0 SECTION LOCAL DEFAULT 11
14: 0000000000000010 18 OBJECT GLOBAL DEFAULT COM constantFOO
15: 0000000000000020 52 OBJECT GLOBAL DEFAULT COM constantBAR
16: 0000000000000001 1 OBJECT GLOBAL DEFAULT COM __gnu_lto_v1
因此我认为 nm
的行为是一个错误,应该报告给 GNU binutils 的维护者(参见 https://sourceware.org/binutils/)。
至于符号值与数组长度匹配的 "original output",发生的情况通常是 nm
所示的符号值是其在目标文件部分中的偏移量。但是,公共符号不在任何部分中并且没有偏移量,因此 nm
打印符号的大小作为其值。这是 IIRC 的历史行为,可以一直追溯到 System V 的任何一次迭代,它添加了对类似 FORTRAN 的公共数据的支持。请注意 readelf -s
如何将 18 和 52 打印为对象的 大小 ,并将 .comm
的第三个参数(每个符号的所需对齐方式)作为它们的值。
如果你用-fno-common
编译你会看到不同的输出:
$ gcc -c -fno-common tmp.c -o tmp-nc.o
$ nm tmp-nc.o
0000000000000020 B constantBAR
0000000000000000 B constantFOO
$ readelf -s tmp-nc.o | grep constant
7: 0000000000000000 18 OBJECT GLOBAL DEFAULT 3 constantFOO
8: 0000000000000020 52 OBJECT GLOBAL DEFAULT 3 constantBAR
因为现在您的数组位于 .bss
部分,并且在该部分中有定义的偏移量。
注意 char constantFOO[0x12];
定义了一个 可写 0x12 char
数组。如果你想让它实际上保持不变,你需要说 const char
。 (然后它将被放入目标文件的 .rodata
部分, nm
和 readelf
的输出将再次不同。)
如果我有 tmp.c
:
char constantFOO[0x12];
char constantBAR[0x34];
我看到 gcc -c tmp.c -o tmp.o && nm tmp.o
节目
0000000000000034 C constantBAR
0000000000000012 C constantFOO
但是如果我用 -flto -ffat-lto-objects
编译,nm
输出符号值的零:
00000000 C constantBAR
00000000 C constantFOO
我可以在两个 .o
文件的 hexdump 中得到 34
和 12
值。
我的问题是
nm
对 LTO fat 文件的行为是否符合预期?我只是给它输入了它不期望的输入,它正在输出垃圾吗?原始输出(符号值匹配未初始化数组长度)的解释是什么? This question好像对数组的问题没有帮助,可能是我理解错了
我在 -S
模式(输出汇编语言)下使用 GCC 8.3 编译了你的 tmp.c
有无 -flto -ffat-lto-objects
。在这两种情况下,都会发出相同的常量基本定义:
.comm constantFOO,18,16
.comm constantBAR,52,32
LTO 发出的大部分额外数据进入名为 .gnu.lto_.something
的 ELF 部分。 LTO 模式添加了一个额外的标记对象:
.comm __gnu_lto_v1,1,1
出现在 LTO 编译对象中,但不出现在没有 LTO 的对象中。
从表面上看,这根本不应该影响 nm
对这些符号的输出,较低级别的工具 readelf -s
会为它们生成匹配的输出:
$ readelf -s tmp-normal.o
Symbol table '.symtab' contains 9 entries:
Num: Value Size Type Bind Vis Ndx Name
0: 0000000000000000 0 NOTYPE LOCAL DEFAULT UND
1: 0000000000000000 0 FILE LOCAL DEFAULT ABS test.c
2: 0000000000000000 0 SECTION LOCAL DEFAULT 1
3: 0000000000000000 0 SECTION LOCAL DEFAULT 2
4: 0000000000000000 0 SECTION LOCAL DEFAULT 3
5: 0000000000000000 0 SECTION LOCAL DEFAULT 5
6: 0000000000000000 0 SECTION LOCAL DEFAULT 4
7: 0000000000000010 18 OBJECT GLOBAL DEFAULT COM constantFOO
8: 0000000000000020 52 OBJECT GLOBAL DEFAULT COM constantBAR
$ readelf -s tmp-lto.o
Symbol table '.symtab' contains 17 entries:
Num: Value Size Type Bind Vis Ndx Name
0: 0000000000000000 0 NOTYPE LOCAL DEFAULT UND
1: 0000000000000000 0 FILE LOCAL DEFAULT ABS test.c
2: 0000000000000000 0 SECTION LOCAL DEFAULT 1
3: 0000000000000000 0 SECTION LOCAL DEFAULT 2
4: 0000000000000000 0 SECTION LOCAL DEFAULT 3
5: 0000000000000000 0 SECTION LOCAL DEFAULT 4
6: 0000000000000000 0 SECTION LOCAL DEFAULT 5
7: 0000000000000000 0 SECTION LOCAL DEFAULT 6
8: 0000000000000000 0 SECTION LOCAL DEFAULT 7
9: 0000000000000000 0 SECTION LOCAL DEFAULT 8
10: 0000000000000000 0 SECTION LOCAL DEFAULT 9
11: 0000000000000000 0 SECTION LOCAL DEFAULT 10
12: 0000000000000000 0 SECTION LOCAL DEFAULT 12
13: 0000000000000000 0 SECTION LOCAL DEFAULT 11
14: 0000000000000010 18 OBJECT GLOBAL DEFAULT COM constantFOO
15: 0000000000000020 52 OBJECT GLOBAL DEFAULT COM constantBAR
16: 0000000000000001 1 OBJECT GLOBAL DEFAULT COM __gnu_lto_v1
因此我认为 nm
的行为是一个错误,应该报告给 GNU binutils 的维护者(参见 https://sourceware.org/binutils/)。
至于符号值与数组长度匹配的 "original output",发生的情况通常是 nm
所示的符号值是其在目标文件部分中的偏移量。但是,公共符号不在任何部分中并且没有偏移量,因此 nm
打印符号的大小作为其值。这是 IIRC 的历史行为,可以一直追溯到 System V 的任何一次迭代,它添加了对类似 FORTRAN 的公共数据的支持。请注意 readelf -s
如何将 18 和 52 打印为对象的 大小 ,并将 .comm
的第三个参数(每个符号的所需对齐方式)作为它们的值。
如果你用-fno-common
编译你会看到不同的输出:
$ gcc -c -fno-common tmp.c -o tmp-nc.o
$ nm tmp-nc.o
0000000000000020 B constantBAR
0000000000000000 B constantFOO
$ readelf -s tmp-nc.o | grep constant
7: 0000000000000000 18 OBJECT GLOBAL DEFAULT 3 constantFOO
8: 0000000000000020 52 OBJECT GLOBAL DEFAULT 3 constantBAR
因为现在您的数组位于 .bss
部分,并且在该部分中有定义的偏移量。
注意 char constantFOO[0x12];
定义了一个 可写 0x12 char
数组。如果你想让它实际上保持不变,你需要说 const char
。 (然后它将被放入目标文件的 .rodata
部分, nm
和 readelf
的输出将再次不同。)