Зачем нужно перемещение, если вызывается функция в том же модуле перевода - PullRequest
6 голосов
/ 12 марта 2019

Итак, у меня есть два файла: один - моя библиотека, а второй - основной исполняемый файл программы.Библиотека:

static int internal1(int a, int b){
  return a + b;
}

namespace {
  int internal2(int a, int b){
    return a + b;
  }
}

void external2(int qq, int zz){

}

void external(int a, int b){
  external2(a, b);
  internal1(a, b);
  internal2(a, b);
}

Скомпилировано с g++ -c -O0 -fPIC -o libtest.o libtest.cpp и g++ -shared -o libtest.so libtest.o

Основная программа:

extern void external(int a, int b);

int main(){
  external(1, 2);
  return 0;
}

Скомпилировано с g++ -O0 -L. -ltest -o tester tester.cpp

Сейчасесли я сбрасываю информацию о перемещении для tester, я получаю то, что ожидаю:

Relocation section '.rela.dyn' at offset 0x4d0 contains 1 entries:
  Offset          Info           Type           Sym. Value    Sym. Name + Addend
000000600a48  000100000006 R_X86_64_GLOB_DAT 0000000000000000 __gmon_start__ + 0

Relocation section '.rela.plt' at offset 0x4e8 contains 3 entries:
  Offset          Info           Type           Sym. Value    Sym. Name + Addend
000000600a68  000300000007 R_X86_64_JUMP_SLO 0000000000000000 __libc_start_main + 0
000000600a70  000400000007 R_X86_64_JUMP_SLO 0000000000000000 _Z8externalii + 0
000000600a78  000a00000007 R_X86_64_JUMP_SLO 0000000000400578 __gxx_personality_v0 + 0

, и external находится в списке перемещений, так как он должен найти адрес и вставить его.

Однако чего я не понимаю, так это когда я выбрасываю список перемещений общего объекта, почему я вижу external2 в списке перемещений общего объекта.Почему он не просто автоматически вводит адрес, как это было для функций с внутренней связью.

Relocation section '.rela.dyn' at offset 0x460 contains 5 entries:
  Offset          Info           Type           Sym. Value    Sym. Name + Addend
0000002007d8  000000000008 R_X86_64_RELATIVE                    00000000002007d8
000000200990  000200000006 R_X86_64_GLOB_DAT 0000000000000000 __gmon_start__ + 0
000000200998  000300000006 R_X86_64_GLOB_DAT 0000000000000000 _Jv_RegisterClasses + 0
0000002009a0  000400000006 R_X86_64_GLOB_DAT 0000000000000000 __cxa_finalize + 0
0000002009d0  000500000001 R_X86_64_64       0000000000000000 __gxx_personality_v0 + 0

Relocation section '.rela.plt' at offset 0x4d8 contains 2 entries:
  Offset          Info           Type           Sym. Value    Sym. Name + Addend
0000002009c0  000400000007 R_X86_64_JUMP_SLO 0000000000000000 __cxa_finalize + 0
0000002009c8  000600000007 R_X86_64_JUMP_SLO 0000000000000646 _Z9external2ii + 0

Вызовы internal1 и internal2 не требуют перемещения, почему external2быть внешним символом означает, что теперь он должен искать через ПОЛУЧЕННЫЙ ПЛТ, даже если символ находится в той же единице перевода?Почему он не может просто сделать обычный вызов смещения, как это делается для internal s

1 Ответ

5 голосов
/ 12 марта 2019

Из любопытства давайте посмотрим, что делает компилятор, когда вы пытаетесь просто скомпилировать (даже не ссылаться) один и тот же код с большинством включенных оптимизаций. Это часто выявляет ситуации, когда руки компилятора связаны. Кроме того, компиляторы могут позволить себе быть очень неряшливыми / ленивыми в O0, поэтому я избегаю слишком многого в него читать.

Компиляция libtest.cpp с -O3 -fPIC -c выходами:

external2(int, int):
        ret
external(int, int):
        jmp     external2(int, int)@PLT

см. На кресте: https://gcc.godbolt.org/z/CnRVEX

Это очень интересно: GCC, очевидно, может сказать, что external2() не работает, но он все еще называет при O3.

Что мы можем из этого сделать? Этот вызов external2() не обязательно будет выполнять код в версии TU external2(). Но как это возможно? ODR должен позволить нам предположить, что любой external2() в том же двоичном файле хуже, чем тот, что в этом TU.

Это верно для уровня C ++, но Linux не загружает код C ++. Он загружает эльфов, которые играют по другому набору правил. Одно из этих правил заключается в том, что вы можете использовать LD_PRELOAD для загрузки символов перед исполняемым файлом для их перехвата. И делая символ внешним в коде PIC, компоновщик интерпретирует его как перегружаемый символ, который предотвращает встраивание (в моем примере), а также локальные переходы (в вашем).

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...