gcc vs clang: вставка функции с помощью-fPIC
рассмотрим этот код:
// foo.cxx
int last;
int next() {
return ++last;
}
int index(int scale) {
return next() << scale;
}
при компиляции с gcc 7.2:
$ g++ -std=c++11 -O3 -fPIC
серия:
next():
movq last@GOTPCREL(%rip), %rdx
movl (%rdx), %eax
addl , %eax
movl %eax, (%rdx)
ret
index(int):
pushq %rbx
movl %edi, %ebx
call next()@PLT ## next() not inlined, call through PLT
movl %ebx, %ecx
sall %cl, %eax
popq %rbx
ret
однако при компиляции того же кода с теми же флагами с использованием clang 3.9 вместо этого:
next(): # @next()
movq last@GOTPCREL(%rip), %rcx
movl (%rcx), %eax
incl %eax
movl %eax, (%rcx)
retq
index(int): # @index(int)
movq last@GOTPCREL(%rip), %rcx
movl (%rcx), %eax
incl %eax ## next() was inlined!
movl %eax, (%rcx)
movl %edi, %ecx
shll %cl, %eax
retq
вызовы gcc next()
через PLT, лязг inlines его. Оба по-прежнему lookup last
из GOT. Для компиляции в linux, правильно ли clang, чтобы сделать эту оптимизацию, и gcc отсутствует на простой вставке, или неправильно clang, чтобы сделать это оптимизация, или это чисто проблема QoI?
1 ответов
я не думаю, что стандарт входит в эту деталь. Он просто говорит, что если символ имеет внешнюю связь в разных единицах перевода, это один и тот же символ. Это делает версию clang правильной.
С этого момента, насколько мне известно, мы вышли из стандарта. Выбор компиляторов отличается от того, что они считают полезным -fPIC
выход.
отметим, что g++ -c -std=c++11 -O3 -fPIE
выходы:
0000000000000000 <_Z4nextv>:
0: 8b 05 00 00 00 00 mov 0x0(%rip),%eax # 6 <_Z4nextv+0x6>
6: 83 c0 01 add x1,%eax
9: 89 05 00 00 00 00 mov %eax,0x0(%rip) # f <_Z4nextv+0xf>
f: c3 retq
0000000000000010 <_Z5indexi>:
10: 8b 05 00 00 00 00 mov 0x0(%rip),%eax # 16 <_Z5indexi+0x6>
16: 89 f9 mov %edi,%ecx
18: 83 c0 01 add x1,%eax
1b: 89 05 00 00 00 00 mov %eax,0x0(%rip) # 21 <_Z5indexi+0x11>
21: d3 e0 shl %cl,%eax
23: c3 retq
так GCC тут знаю как оптимизировать это. Он просто выбирает не при использовании -fPIC
. Но почему? Я вижу только одно объяснение: сделать возможным переопределение символа во время динамической компоновки и последовательно видеть эффекты. Техника известна как символ взаиморасположение.
в общей библиотеке, если index
звонки next
, as next
глобально виден, gcc должен рассмотреть возможность того, что next
можно было бы вмешаться. Поэтому он использует PLT. При использовании , вы не разрешается вставлять символы, поэтому gcc включает оптимизацию.
так лязгать не так? Нет. Но gcc, похоже, обеспечивает лучшую поддержку интерпозиции символов, что удобно для инструментирования кода. Это происходит за счет некоторых накладных расходов, если используется -fPIC
вместо -fPIE
для создания его исполняемого файла.
дополнительно:
на эта запись в блоге от одного из разработчиков gcc, упоминает он, в конце после:
сравнивая некоторые тесты с clang, я заметил, что clang фактически игнорирует правила интерпозиции ELF. Хотя это ошибка, я решил добавить
-fno-semantic-interposition
флаг для GCC, чтобы получить подобное поведение. Если интерпозиция нежелательна, официальный ответ ELF должен использовать скрытую видимость, и если символ должен быть экспортирован, определите псевдоним. Это не всегда практично делать вручную.
следуя этой зацепке, я приземлился на x86-64 ABI spec. В разделе 3.5.5 он предписывает, что все функции, вызывающие глобально видимые символы, должны проходить через PLT (он доходит до определения точной последовательности команд для использования в зависимости от модели памяти).
таким образом, хотя он не нарушает стандарт C++, игнорирование семантического интерпозиции, похоже, нарушает ABI.
последнее слово: не знал, куда это положить, но это может вас заинтересовать. Я избавлю тебя от свалок., но мои тесты с параметрами objdump и компилятора показали, что:
на стороне gcc вещей:
-
gcc -fPIC
: обращается кlast
идет через GOT, звонки вnext()
проходит через PLT. -
gcc -fPIC -fno-semantic-interposition
:last
проходит гот,next()
встроена. -
gcc -fPIE
:last
является IP-относительным,next()
встроена. -
-fPIE
подразумевает-fno-semantic-interposition
на стороне лязга вещей:
-
clang -fPIC
:last
проходит гот,next()
встроена. -
clang -fPIE
:last
проходит гот,next()
встроена.
и модифицированная версия, которая компилируется в IP-relative, встроена в оба компилятора:
// foo.cxx
int last_ __attribute__((visibility("hidden")));
extern int last __attribute__((alias("last_")));
int __attribute__((visibility("hidden"))) next_()
{
return ++last_;
}
// This one is ugly, because alias needs the mangled name. Could extern "C" next_ instead.
extern int next() __attribute__((alias("_Z5next_v")));
int index(int scale) {
return next_() << scale;
}
в основном, это явно отмечает, что, несмотря на их доступность во всем мире, мы используем скрытая версия тех символов, которые будут игнорировать любой вид интерпозиции. Оба компилятора затем полностью оптимизируют доступ, независимо от переданных параметров.