GCC LTO 胖目标文件上 `nm` 命令输出错误

3
如果我有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文件的十六进制转储中找到3412的值。

我的问题是:

  1. nm在LTO fat文件上的行为是否符合预期?我只是给它输入它不希望看到的内容,它就会输出垃圾吗?

  2. 是什么导致原始输出(符号值匹配未初始化数组长度)?这个问题似乎对于数组的问题没有帮助,但也许我误解了。

1个回答

2

我使用GCC 8.3编译了您的tmp.c文件,并分别使用-flto -ffat-lto-objects和不使用这些选项,以-S模式(输出汇编语言)。在两种情况下,都会发出相同的常量基本定义:

其中,tmp.c是文件名,-flto -ffat-lto-objects是编译选项,-S是输出汇编代码。

    .comm   constantFOO,18,16
    .comm   constantBAR,52,32

大部分由LTO发出的额外数据都会进入名为.gnu.lto_.something的ELF部分。 LTO模式会添加一个额外的标记对象:最初的回答。
   .comm   __gnu_lto_v1,1,1

在经过LTO编译的对象中出现,但在未经编译的对象中未出现。

从表面上看,这不应该对这些符号的nm输出产生任何影响,而较低级别的工具readelf -s为它们生成匹配的输出:

最初的回答:

该符号出现在经过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的行为是一个bug,应该向GNU binutils的维护者报告(请参见 https://sourceware.org/binutils/ )。
至于符号值与数组长度匹配的“原始输出”,其背后的原因是通常情况下,nm显示的符号值是它在目标文件中所在节的偏移量。然而,普通符号不属于任何节,并且没有偏移量,因此nm将符号的大小打印为其值。如果我没记错的话,这是历史行为,可以追溯到添加类似FORTRAN的公共数据支持的System V的哪个版本。请注意,readelf -s将18和52打印为对象的大小,并将每个符号的第三个参数(每个符号的期望对齐方式)作为它们的值。
如果您使用-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部分中,nmreadelf的输出将再次不同。)

非常感谢您提供的全面答案!我在这里提交了一个错误报告:https://sourceware.org/bugzilla/show_bug.cgi?id=24326 - jberryman

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接