В таблице символов объектного файла C ELF функция указана дважды

#c #linux #linker #g #elf

#c #linux #компоновщик #g #elf

Вопрос:

У меня есть исходный файл, который определяет конструктор перемещения для очень большого класса. Я компилирую ее с помощью g 4.9.2 в системе Linux. Когда я сбрасываю таблицу символов результирующего объектного файла ELF, я вижу 2 списка для конструктора перемещения. Оба списка имеют один и тот же адрес, одинаковый размер, один и тот же тип, и компоновщик связывает его просто отлично, без нарушений ODR. Когда я разбираю объектный файл, я вижу только одну функцию конструктора перемещения. Я пришел к выводу, что в таблице символов есть две записи, которые указывают на одно и то же местоположение.

Такое же поведение также происходит для конструктора этого конкретного класса, который определен в этом же исходном файле.

Единственный флаг компиляции, который я вижу, который я не до конца понимаю, это ‘-m64’, но я не знаю, как это повлияет на таблицу символов.

Я также пробовал это с g 9.2.0, и теперь у меня есть 3 записи в таблице символов! Две из которых указывают на один и тот же адрес, а третья указывает на адрес 0x0, находится в разделе .text.unlikely и помечена как [clone .cold] .

Почему это так?


редактировать: на самом деле я могу воспроизвести это дома с очень маленьким классом.

 // class.h
class VeryLargeClass
{
    int data;

    public:
    VeryLargeClass(VeryLargeClassamp;amp;);
};

// class.cpp
#include "class.h"

VeryLargeClass::VeryLargeClass(VeryLargeClassamp;amp; other)
{
    data = other.data;
    other.data = 0;
}
  

Если я скомпилирую это с g -c -O3 class.cpp -o class.o помощью, а затем дамп таблицы символов с objdump -t class.o | c filt помощью, я получаю следующее:
class.o: формат файла elf64-x86-64

 SYMBOL TABLE:
0000000000000000 l    df *ABS*  0000000000000000 main.cc
0000000000000000 l    d  .text  0000000000000000 .text
0000000000000000 l    d  .data  0000000000000000 .data
0000000000000000 l    d  .bss   0000000000000000 .bss
0000000000000000 l    d  .note.GNU-stack    0000000000000000 .note.GNU-stack
0000000000000000 l    d  .eh_frame  0000000000000000 .eh_frame
0000000000000000 l    d  .comment   0000000000000000 .comment
0000000000000000 g     F .text  000000000000000b VeryLargeClass::VeryLargeClass(VeryLargeClassamp;amp;)
0000000000000000 g     F .text  000000000000000b VeryLargeClass::VeryLargeClass(VeryLargeClassamp;amp;)
  

Обратите внимание, как конструктор перемещения дважды отображается в таблице символов? Я предполагаю, что я просто чего-то не понимаю в формате ELF. Это было сделано с использованием g 10.2.

Комментарии:

1. Запрашивают ли эти списки символ или предоставляют символ? Определен ли конструктор перемещения в этом исходном файле? Для этого имело бы смысл запрашивать в нескольких местах.

2. вероятно, это конструктор перемещения или копирования, но если это не так, это может быть ошибка компилятора

3. Людям будет трудно воспроизвести это и, следовательно, трудно ответить, если вы не дадите нам минимально воспроизводимый пример (минимальный объем кода, который показывает поведение, которое вы описываете).

4. @Anonymous1847 Мне удалось воспроизвести его дома, я отредактировал свой вопрос.

Ответ №1:

TL; DR: Если вы хотите понять, что происходит

  1. не используйте objdump для проверки файлов ELF, используйте readelf вместо этого (см. Ниже)
  2. не разбирайте имена с c filt помощью — разные символы могут создавать одно и то же разбираемое имя (т. Е. Это не преобразование один к одному).

Подробные сведения:

 cat foo.cc

struct Foo {
  Foo(Fooamp;amp; f);
  void *p;
};

Foo::Foo(Foo amp;amp;f) {
  p = f.p;
  f.p = nullptr;
}
  
 g   -c foo.cc
objdump -t foo.o | grep Foo | c  filt

0000000000000000 g     F .text  0000000000000028 Foo::Foo(Fooamp;amp;)
0000000000000000 g     F .text  0000000000000028 Foo::Foo(Fooamp;amp;)

objdump -t foo.o | grep Foo 
0000000000000000 g     F .text  0000000000000028 _ZN3FooC2EOS_
0000000000000000 g     F .text  0000000000000028 _ZN3FooC1EOS_
  

Обратите внимание, что символы разные. Вы можете прочитать о конструкторах C1 and C2 здесь .

PS Почему вы никогда не должны использовать objdump для просмотра ELF файлов?

objdump является частью binutils . Хотя binutils они все еще активно поддерживаются, они были написаны задолго до ELF того, как существовали и использовались libbfd . Последняя имеет внутреннюю модель данных, которая не может адекватно описать ELF формат файла.

Поэтому, когда вы используете objdump для ELF файла, сначала libbfd разбирает файл на эту внутреннюю / неадекватную модель данных, а затем objdump представляет эту модель в удобочитаемой форме. При переводе многое теряется.

Комментарии:

1. Вдохновленный этим ответом, я попытался использовать readelf , но потерпел неудачу. У меня есть символ — метод, который я могу исследовать, objdump но нет readelf . Первая показывает это (с --syms ), вторая — нет (я даже пробовал --all ). Не говоря уже о том, что readelf , похоже, у --disassemble нее нет опций…

2. @AdamBadura Ошибка, readelf безусловно, возможна, но то, что вы описали, кажется очень маловероятным. Пожалуйста, отправьте сообщение об ошибке со всеми подробностями.