Что значит @plt здесь? - PullRequest
       30

Что значит @plt здесь?

66 голосов
/ 29 марта 2011
0x00000000004004b6 <main+30>:   callq  0x400398 <printf@plt>

Кто-нибудь знает?

UPDATE

Почему два disas printf дают мне другой результат?

(gdb) disas printf
Dump of assembler code for function printf@plt:
0x0000000000400398 <printf@plt+0>:  jmpq   *0x2004c2(%rip)        # 0x600860 <_GLOBAL_OFFSET_TABLE_+24>
0x000000000040039e <printf@plt+6>:  pushq  $0x0
0x00000000004003a3 <printf@plt+11>: jmpq   0x400388

(gdb) disas printf
Dump of assembler code for function printf:
0x00000037aa44d360 <printf+0>:  sub    $0xd8,%rsp
0x00000037aa44d367 <printf+7>:  mov    %rdx,0x30(%rsp)
0x00000037aa44d36c <printf+12>: movzbl %al,%edx
0x00000037aa44d36f <printf+15>: mov    %rsi,0x28(%rsp)
0x00000037aa44d374 <printf+20>: lea    0x0(,%rdx,4),%rax
0x00000037aa44d37c <printf+28>: lea    0x3f(%rip),%rdx        # 0x37aa44d3c2 <printf+98>

Ответы [ 2 ]

106 голосов
/ 29 марта 2011

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

printf@plt на самом деле представляет собой небольшую заглушку, которая (в конечном итоге) вызывает настоящую функцию printf, изменяя способы ускорения последующих вызовов.

Функция real printf может быть отображена в любое местоположение в данном процессе (виртуальное адресное пространство), как и код, который пытается его вызвать.

Таким образом, чтобы разрешить правильное совместное использование кода вызывающего кода (слева внизу) и вызываемого кода (справа внизу), вы не хотите применять какие-либо исправления непосредственно к вызывающему коду, поскольку это ограничит возможности его использования. находиться в других процессах.

Таким образом, PLT - это меньшая область , специфичная для процесса, с надежно вычисляемым во время выполнения адресом, который не является общим для процессов, поэтому любой данный процесс можете изменить его, как пожелаете, без негативных последствий.


Изучите следующую диаграмму, которая показывает ваш код и код библиотеки, сопоставленные с разными виртуальными адресами в двух разных процессах, ProcA и ProcB:

Address: 0x1234          0x9000      0x8888
        +-------------+ +---------+ +---------+
        |             | | Private | |         |
ProcA   |             | | PLT/GOT | |         |
        | Shared      | +---------+ | Shared  |
========| application |=============| library |==
        | code        | +---------+ | code    |
        |             | | Private | |         |
ProcB   |             | | PLT/GOT | |         |
        +-------------+ +---------+ +---------+
Address: 0x2020          0x9000      0x6666

Этот конкретный пример показывает простой случай, когда PLT сопоставляется с фиксированным местоположением. В вашем сценарии он расположен относительно текущего счетчика программы, о чем свидетельствует ваш поиск относительно счетчика программ:

<printf@plt+0>: jmpq  *0x2004c2(%rip)  ; 0x600860 <_GOT_+24>

Я только что использовал фиксированную адресацию, чтобы упростить пример.

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

Используя код, независимый от позиции, вместе с PLT и глобальной таблицей смещений (GOT), первый вызов функции printf@plt (в PLT) является многоступенчатой ​​операцией, в какие следующие действия имеют место:

  • Вы звоните printf@plt на PLT.
  • Он вызывает версию GOT (через указатель), которая первоначально указывает на некоторый код настройки в PLT.
  • Этот код настройки загружает соответствующую разделяемую библиотеку, если это еще не сделано, тогда изменяет указатель GOT так, чтобы последующие вызовы непосредственно действительного printf, а не кода настройки PLT.
  • Затем он вызывает загруженный код printf по правильному адресу для этого процесса.

При последующих вызовах, поскольку указатель GOT был изменен, многоэтапный подход упрощен:

  • Вы звоните printf@plt на PLT.
  • Он вызывает GOT-версию (через указатель), которая теперь указывает на real printf.

Хорошую статью можно найти здесь , подробно описывая, как glibc загружается во время выполнения.

4 голосов
/ 04 сентября 2012

Не уверен, но, вероятно, то, что вы видели, имеет смысл.При первом запуске команды disas printf еще не вызывается, поэтому она не разрешена.Как только ваша программа вызывает метод printf при первом обновлении GOT, и теперь printf разрешается и GOT указывает на реальную функцию.Таким образом, следующий вызов команды disas показывает реальную сборку printf.

...