Я не уверен, что это хорошая тема для этого вопроса, но здесь мы идем ...
Чтобы обеспечить локальность/компактность кода для критической части кода, я ищем способ вызвать функцию во внешней (динамически загружаемой) библиотеке через «слот перехода» (перемещение ELF R_X86_64_JUMP_SLOT
) непосредственно на месте вызова — то, что компоновщик обычно помещает в PLT/GOT , но встраивайте их прямо в место вызова.
Если я эмулирую вызов типа:
#include
int main(int argc, char **argv)
{
asm ("push $1f\n\t"
"jmp *0f\n\t"
"0: .quad %P0\n"
"1:\n\t"
: : "i"(printf), "D"("Hello, World!\n"));
return 0;
}
чтобы получить место для 64-битного слова, сам вызов работает (пожалуйста, без комментариев о том, что это удачное совпадение, так как это нарушает определенные правила ABI - все это не является предметом этого вопрос ... и можно ли в моем случае обойти / решить другими способами, я пытаюсь сделать этот пример кратким).
Он создает следующую сборку:
0000000000000000: 0: bf 00 00 00 00 mov $0x0,%edi 1: R_X86_64_32 .rodata.str1.1 5: 68 00 00 00 00 pushq $0x0 6: R_X86_64_32 .text+0x19 a: ff 24 25 00 00 00 00 jmpq *0x0 d: R_X86_64_32S .text+0x11 ... 11: R_X86_64_64 printf 19: 31 c0 xor %eax,%eax 1b: c3 retq
Но (из-за использования printf
в качестве непосредственного, я полагаю... ?) целевой адрес здесь по-прежнему совпадает с адресом хука PLT — тот же R_X86_64_64
reloc. Связывание объектного файла с libc в реальный исполняемый файл приводит к:
0000000000400428: 400428: ff 25 92 04 10 00 jmpq *1049746(%rip) # 5008c0 <_GLOBAL_OFFSET_TABLE_+0x20> [ ... ] 0000000000400500 : 400500: bf 0c 06 40 00 mov $0x40060c,%edi 400505: 68 19 05 40 00 pushq $0x400519 40050a: ff 24 25 11 05 40 00 jmpq *0x400511 400511: [ .quad 400428 ] 400519: 31 c0 xorl %eax, %eax 40051b: c3 retq [ ... ] DYNAMIC RELOCATION RECORDS OFFSET TYPE VALUE [ ... ] 00000000005008c0 R_X86_64_JUMP_SLOT printf
Т.е. это по-прежнему дает двухэтапное перенаправление: сначала передать выполнение на крючок PLT, а затем перейти к точке входа в библиотеку.
Есть ли способ, как я могу указать компилятору/ассемблеру/компоновщику - в этом примере - "встроить" целевой слот перехода по адресу 0x400511
? т.е. замените «локальный» (разрешается во время компоновки программы с помощью ld
) R_X86_64_64
reloc на «удаленный» (разрешается во время загрузки программы с помощью ld.so
) R_X86_64_JUMP_SLOT
один (и принудительная неленивая загрузка для этого раздела кода) ? Возможно, файлы карты компоновщика могут сделать это возможным - если да, то как?
Редактировать:
Чтобы было ясно, вопрос в том, как добиться этого в динамически связанном исполняемом файле/для внешней функции, которая доступна только в динамической библиотеке. Да, статическое связывание действительно решает эту проблему более простым способом, но:
Следовательно, статическое связывание здесь бесполезно :(
Edit2:
Я обнаружил, что в некоторых архитектурах (SPARC, в частности, см. раздел о перемещениях SPARC в GNU как руководство] ), GNU as может создавать определенные типы ссылок перемещения для компоновщика на месте, используя модификаторы. Цитируемый SPARC использовал бы %gdop(symbolname)
, чтобы сделать ассемблер передать компоновщику инструкции, в которых говорится: «Создайте это перемещение прямо здесь». Ассемблер Intel на Itanium знает @fptr(symbol)
оператор перемещения ссылкидля того же рода вещей (см. также раздел 4 в Itanium psABI).Но существует ли для x86_64 эквивалентный механизм — что-то, что дает указание ассемблеру выдать определенный тип перемещения компоновщика в определенной позиции в коде?
Я также обнаружил, что в ассемблере GNU есть директива.reloc
, которая предположительно должна использоваться для этой цели; тем не менее, если я попытаюсь:
#include
int main(int argc, char **argv)
{
asm ("push %%rax\n\t"
"lea 1f(%%rip), %%rax\n\t"
"xchg %%rax, (%rsp)\n\t"
"jmp *0f\n\t"
".reloc 0f, R_X86_64_JUMP_SLOT, printf\n\t"
"0: .quad 0\n"
"1:\n\t"
: : "D"("Hello, World!\n"));
return 0;
}
Я получаю сообщение об ошибке от компоновщика (обратите внимание, что 7 == R_X86_64_JUMP_SLOT
):
error: /tmp/cc6BUEZh.o: unexpected reloc 7 in object file
Ассемблер создает объектный файл, для которого readelf
говорит:
Relocation section '.rela.text.startup' at offset 0x5e8 contains 2 entries: Offset Info Type Symbol's Value Symbol's Name + Addend 0000000000000001 000000050000000a R_X86_64_32 0000000000000000 .rodata.str1.1 + 0 0000000000000017 0000000b00000007 R_X86_64_JUMP_SLOT 0000000000000000 printf + 0
Это то, что я хочу, но компоновщик этого не принимает.
Компоновщик допускает простое использование R_X86_64_64
вместо указанного выше; при этом создается тот же двоичный файл, что и в первом случае... перенаправление на printf@plt
, а не на "разрешенный"...