Это способ получения исправлений кода (корректировка адресов в зависимости от места расположения кода в виртуальной памяти, которые могут различаться в разных процессах) без необходимости сохранять отдельную копию кода для каждого процесса. 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
загружается во время выполнения.