LINUX.ORG.RU

«Hello world!» под Linux x86_64

 ,


2

3

Всем привет. Пытаюсь сгенерировать исполнимый файл, выводящий в терминал «Hello World» под Linux x86_64 путем вызова функции printf и с использованием типа релокации R_X86_64_PC32. Но при попытке запуска выкидывает «Symbol `printf’ causes overflow in R_X86_64_PC32 relocation». Аналогичная программа под Linux x86_32 с релокацией R_386_PC32 работает без проблем.

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

Может кто-нибудь скинуть аналогичный исполнимый файл с выводом строки функцией printf и использованием релокации R_X86_64_PC32 для анализа?


Вангую, что это лабораторная работа. Из гугла, может поможет:

subq    $8, %rsp             ; allocate 8 bytes of stack
xorl    %edx, %edx           ; i = 0 ; put it in the 3rd parameter for __printf_chk
leaq    .LC0(%rip), %rsi     ; 2nd parameter for __printf_chk.  The: "%d\n"
xorl    %eax, %eax           ; 0 variadic fp params
movl    $1, %edi             ; 1st parameter for __printf_chk
call    __printf_chk@PLT     ; call the runtime loader wrapper for __printf_chk
xorl    %eax, %eax           ; return 0 from main
addq    $8, %rsp             ; deallocate 8 bytes of stack.
ret
serg002 ★★★
()
Последнее исправление: serg002 (всего исправлений: 1)
Ответ на: комментарий от X512

Вы точно уверены, что релокацию R_X86_64_PC32 нельзя применить для printf?

Просто я поискал в интернете и нашел такую вот презентацию: https://www.cs.princeton.edu/courses/archive/spr18/cos217/lectures/16_MachineLang.pdf

в которой даже скриншоты есть об использовании R_X86_64_PC32 для printf…

D_V_L
() автор топика
Ответ на: комментарий от D_V_L

Вы точно уверены, что релокацию R_X86_64_PC32 нельзя применить для printf?

Если повезёт и адрес на printf будет не слишком далеко от вызова с 32 битной релокацией, то заработает. Но на современных ОС включена рандомизация загрузки динамических библиотек, так что работать это не будет.

X512 ★★★★★
()
Ответ на: комментарий от X512

Понял вас. Печально. ( Хорошо, тогда немного скорректирую вопрос. Моя задача в том, чтобы при релокации использовать относительный адрес, не абсолютный. Поэтому я пытался использовать тип R_X86_64_PC32, а не R_X86_64_64 например. Логично было бы использовать R_X86_64_PC64, что я и пытался делать изначально, однако в прежних версиях Linux такой тип не поддерживался (не знаю как уж в последних версиях). И вот смотрю на оставшиеся варианты типов релокации и не вижу ничего подходящего для использования относительного адреса… Такая задача вообще прежде как-то решалась, чтобы относительный адрес при релокации использовать под Linux x86_64?

D_V_L
() автор топика

Релокация должна быть динамической (исполняемый ELF) или статической (.o)? Исполняемый файл - DYN (PIE) или EXEC?

Какой линкер используется? Самописный или binutils? binutils следует Linux x86_64 psABI (https://raw.githubusercontent.com/wiki/hjl-tools/x86-psABI/x86-64-psABI-1.0.pdf). В нем крайне нежелательны релокации вне GOT или PLT. Иначе настройка динамической R_X86_64_PC32 релокации потребует записи в .text на стадии загрузки (что делать с переролнением 32-битного имещения - вообще не понятно).

С помощью -fno-plt можно использовать GOTPCREL (но это всё равно лишнее разыменование в памяти):

$ cat a.c
#include <stdio.h>
int main() {
  puts("hello\n");
}
$ gcc -c a.c -o a.o
$ objdump -dr a.o

0000000000000000 <main>:
   0:   55                      push   %rbp
   1:   48 89 e5                mov    %rsp,%rbp
   4:   bf 00 00 00 00          mov    $0x0,%edi
                        5: R_X86_64_32  .rodata
   9:   e8 00 00 00 00          call   e <main+0xe>
                        a: R_X86_64_PLT32       puts-0x4
   e:   b8 00 00 00 00          mov    $0x0,%eax
  13:   5d                      pop    %rbp
  14:   c3                      ret

$ gcc -c a.c -o a.o -fno-plt
$ objdump -dr a.o

0000000000000000 <main>:
   0:   55                      push   %rbp
   1:   48 89 e5                mov    %rsp,%rbp
   4:   bf 00 00 00 00          mov    $0x0,%edi
                        5: R_X86_64_32  .rodata
   9:   ff 15 00 00 00 00       call   *0x0(%rip)        # f <main+0xf>
                        b: R_X86_64_GOTPCRELX   puts-0x4
   f:   b8 00 00 00 00          mov    $0x0,%eax
  14:   5d                      pop    %rbp
  15:   c3                      ret

Как минимум .o файл можно заставить иметь любые релокации :)

$ cat a.c
#include <stdio.h>

int main() {
  asm volatile (
      "mov %0, %%rdi\n"
      ".byte 0xe8\n"
      ".Lacall:\n"
      ".long 0\n"
      ".reloc .Lacall, R_X86_64_PC32, puts\n"
  :
  : "r"("hello!\n")
  : "memory");
}
$ gcc -c a.c -o a.o
$ objdump -dr a.o

0000000000000000 <main>:
   0:   55                      push   %rbp
   1:   48 89 e5                mov    %rsp,%rbp
   4:   b8 00 00 00 00          mov    $0x0,%eax
                        5: R_X86_64_32  .rodata
   9:   48 89 c7                mov    %rax,%rdi
   c:   e8 00 00 00 00          call   11 <main+0x11>
                        d: R_X86_64_PC32        puts
  11:   b8 00 00 00 00          mov    $0x0,%eax
  16:   5d                      pop    %rbp
  17:   c3                      ret

Я не уверен, что линкер можно заставить сгенерить TEXTREL просто так. Не все libc поддерживают TEXTREL. Например, musl не поддерживает.

Но всё это выглядит как X/Y problem. Откуда ограниечение не использовать GOT или PLT?

sf ★★★
()
Ответ на: комментарий от sf

Спасибо большое за столь развернутый ответ.

Такая задача встала передо мной, т.к. я играюсь с написанием компилятора, который генерирует исполнимые файлы. Под R_386_PC32 реализовать удалось, а вот на Linux x86_64 споткнулся с этой релокацией. Для переноса просто хотелось поправить заголовки в генерируемом файле и схему релокации на Elf64_Rela, не меняя все остальное. Но получается, что все не так просто. (

А что вы имеете в виду под GOT и PLT? Просто я в них пока вообще не разбирался. R_X86_64_GOT32 и R_X86_64_PLT32 похоже тоже 32-х битные, т.е. возникнут те же самые проблемы, да и R_X86_64_GOT32 не похоже что для динамической релокации (формула расчета адреса: G + A)

D_V_L
() автор топика
Ответ на: комментарий от D_V_L

https://en.wikipedia.org/wiki/Global_Offset_Table - Таблица, которая обычно определяется psABI для потенциально нелокальных символов. Позволяет дедуплицировать внешние адреса символов в одном месте, позволяет не патчить исполняемый код на стадии загрузки бинарника (что ускоряет загрузку бинарника и снижает требования к памяти).

Например, при вызове функции puts из libc.so ее адрес заранее неизвестен. Вместо того, чтобы хранить адрес в puts прямо в коде его кладут в GOT.

Сгенеренный код вместо ‘call $puts’ предполагает ‘call *GOT[puts_addr_slot]’. gcc / binutils использует синтаксис типа call *puts@GOTPCREL(%rip). Так ld сам выберет слот для puts. Сама GOT автоматически создается линкером из релокаций .o файлов, которые ссылаются на символы через GOT и не могут быть разрешены статически (вне модуля). В данном случае статическая релокация в .o файле будет R_X86_64_GOTPCRELX puts-0x4 (или R_X86_64_PLT32 puts-0x4 в случае -fplt, но пока можно проигнорировать PLT).

В нашем примере в GOT может находиться единственный элемент puts. тогда вызов после релокации будет ‘call GOT[0]’. В самой этой инструкции релокацию можно вобще не задавать. Но сама таблица GOT появится в адресном пространстве и потребует еще одной релокации:

$ cat a.c
#include <stdio.h>
int main() {
  puts("hello\n");
}

$ gcc a.c -o a -fno-plt

$ objdump -DR a
...

0000000000401106 <main>:
  401106:       55                      push   %rbp
  401107:       48 89 e5                mov    %rsp,%rbp
  40110a:       bf 04 20 40 00          mov    $0x402004,%edi
  40110f:       ff 15 c3 2e 00 00       call   *0x2ec3(%rip)        # 403fd8 <puts@GLIBC_2.2.5>
  401115:       b8 00 00 00 00          mov    $0x0,%eax
  40111a:       5d                      pop    %rbp
  40111b:       c3                      ret
...
Дизассемблирование раздела .got:

0000000000403fd0 <.got>:
        ...
                        403fd0: R_X86_64_GLOB_DAT       __libc_start_main@GLIBC_2.34
                        403fd8: R_X86_64_GLOB_DAT       puts@GLIBC_2.2.5
                        403fe0: R_X86_64_GLOB_DAT       __gmon_start__@Base

Здесь - один тип динамический релокаций синтезированный линкером: R_X86_64_GLOB_DAT.

GOT обычно используется для глобальных переменных.

PLT (Procedure Linkage table, https://maskray.me/blog/2021-09-19-all-about-procedure-linkage-table) - похожий на GOT (и использующий GOT) механизм для отложенной загрузки адреса функции. Тоже генерится ld из релокаций типа R_X86_64_PLT32 (на x86_64 обычный call puts создает такие релокации).

Вместо того, чтобы сразу загрузить адрес puts в GOT при старте программы можно вычислять адрес puts при первой попытке вызова puts (lazy PLT). Для этого libc.so кладет адрес функции резолвера во все PLT слоты.

sf ★★★
()
Ответ на: комментарий от D_V_L

Уважаемый D_V_L, Вы напрямую генереруете исполняемый динамический ELF, без сторонних компоновщиков?

я в них пока вообще не разбирался.

До генерации исполнимого файла полезно разобраться в ABI целевой платформы (и в платформе в целом).

vM ★★
()