Ленивая процедура загрузки
Я хотел бы знать, как это работает точно. Допустим, у нас есть следующие фрагменты кода:
0000000000400400 <printf@plt-0x10>:
400400: ff 35 02 0c 20 00 pushq 0x200c02(%rip) # 601008 <_GLOBAL_OFFSET_TABLE_+0x8>
400406: ff 25 04 0c 20 00 jmpq *0x200c04(%rip) # 601010 <_GLOBAL_OFFSET_TABLE_+0x10>
40040c: 0f 1f 40 00 nopl 0x0(%rax)
0000000000400410 <printf@plt>:
400410: ff 25 02 0c 20 00 jmpq *0x200c02(%rip) # 601018 <_GLOBAL_OFFSET_TABLE_+0x18>
400416: 68 00 00 00 00 pushq $0x0
40041b: e9 e0 ff ff ff jmpq 400400 <_init+0x20>
....
40053b: e8 d0 fe ff ff callq 400410 <printf@plt>
Сначала вызывается заглушка printf (printf @ plt), затем берется адрес, расположенный по адресу 0x601018 (в GOT), чтобы перейти в него.
Скажем, это первый раз, когда вызывается printf: найденное нами значение будет 0x400416, то есть следующая инструкция, верно?
Следуя коду, значение 0 помещается в стек, а затем мы переходим к 0x400400. Здесь вводится адрес GOT (0x601008), а затем происходит переход к следующему (0x601010): почему? Что именно там?
Более того: когда и как именно вызывается динамический компоновщик?
1 ответ
Вы перестали отслеживать прямо на ответ;) Если вы посмотрите на последний указатель (0x601010
) вы должны увидеть это идет к _dl_runtime_resolve
, Первый толчок в plt
entry хранит индекс перемещения в стеке (это идентифицирует запись, с которой нужно работать), а второй толчок от got - это карта ссылок для модуля. _dl_runtime_resolve
обычно это компоновочная функция в компоновщике (для x86-64 он находится в glibc / sysdeps / x86_64 / dl-trampoline.S), которая после некоторого сохранения регистра вызывает _dl_fixup
и это выполняет всю работу по разрешению (включая обновление указателя в GOT, поэтому последующие вызовы идут непосредственно к разрешенной функции). в заключение _dl_runtime_resolve
переходит к разрешенной функции, поэтому она тоже выполняется:)