Элегантный способ изменения объектного файла 'функция xref в Linux - PullRequest
0 голосов
/ 07 ноября 2018

Я задавался вопросом, есть ли способ сделать "действительно независимый от позиции код".

Представьте себе такую ​​ситуацию (реализация здесь действительно не важна.): У вас есть файл с именем «a.c», который содержит две простые функции: код c - functin a и функцию b, когда fnction a вызывает функцию `b '..

При компиляции этой простой ситуации выполните: gcc -c -o a.out a.c -fPIC И затем, наблюдая за секцией text, мы увидим, что функция a внутри сборки находится в том месте, где она вызывает функцию b - нули места хранения. Все эти нули, которые мы все знаем, будут просто заменены во время выполнения согласно значению в таблице перемещений.

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

Итак, есть ли какой-нибудь элегантный способ сделать то же самое, что я делаю вручную, с помощью флага gcc / objdump или чего-то еще?

1 Ответ

0 голосов
/ 08 ноября 2018

Рассмотрим эту автономную hello.c для архитектуры x86-64 / AMD64 в Linux:

/* Freestanding Hello World example in Linux on x86_64/x86.
 * Compile using
 *      gcc -Wall -O2 -fPIC -pie -march=x86-64 -mtune=generic -m64 -ffreestanding -nostdlib -nostartfiles hello.c -o hello
*/
#define STDOUT_FILENO 1
#define EXIT_SUCCESS  0

#ifndef __x86_64__
#error  This program only works on x86_64 architecture!
#endif

#define SYS_write    1
#define SYS_exit    60

#define SYSCALL1_NORET(nr, arg1) \
    __asm__ volatile ( "syscall\n\t" \
                     : \
                     : "a" (nr), "D" (arg1) \
                     : "rcx", "r11" )

#define SYSCALL3(retval, nr, arg1, arg2, arg3) \
    __asm__ volatile ( "syscall\n\t" \
                     : "=a" (retval) \
                     : "a" (nr), "D" (arg1), "S" (arg2), "d" (arg3) \
                     : "rcx", "r11" )

static void my_exit(int retval)
{
    SYSCALL1_NORET(SYS_exit, retval);
}

static int my_write(int fd, const void *data, int len)
{
    int retval;

    if (fd == -1 || !data || len < 0)
        return -1;

    SYSCALL3(retval, SYS_write, fd, data, len);

    if (retval < 0)
        return -1;

    return retval;
}

static int my_strlen(const char *str)
{
    int len = 0L;

    if (!str)
        return -1;

    while (*str++)
        len++;

    return len;
}

static int wrout(const char *str)
{
    if (str && *str)
        return my_write(STDOUT_FILENO, str, my_strlen(str));
    else
        return 0;
}

void _start(void)
{
    const char *msg = "Hello, world!\n";
    wrout(msg);
    my_exit(EXIT_SUCCESS);
}

Обратите внимание, что он соответствует сценарию OP: _start() вызывает wrout(), что вызывает my_strlen() и my_write().

(Почему автономный, без всех тонкостей стандартной библиотеки C? Поскольку стандартная библиотека не компилируется с -fPIC и -pie, поэтому она должна быть динамически связана со стандартной библиотекой; и эти вызовы будет иметь перемещения, омрачая мою точку зрения. Будучи автономным, мы получаем минимальный, полный, проверяемый пример, который дает четкие, однозначные результаты.)

Скомпилируйте его, используя

gcc -Wall -O2 -fPIC -pie -march=x86-64 -mtune=generic -m64 -ffreestanding -nostdlib -nostartfiles hello.c -o hello

и запустить с помощью ./hello. Он распечатывает "Привет, мир!" как следует.

Далее, проверьте его, используя objdump -x hello:

hello:     file format elf64-x86-64
hello
architecture: i386:x86-64, flags 0x00000150:
HAS_SYMS, DYNAMIC, D_PAGED
start address 0x0000000000000340

Program Header:
    PHDR off    0x0000000000000040 vaddr 0x0000000000000040 paddr 0x0000000000000040 align 2**3
         filesz 0x00000000000001f8 memsz 0x00000000000001f8 flags r-x
  INTERP off    0x0000000000000238 vaddr 0x0000000000000238 paddr 0x0000000000000238 align 2**0
         filesz 0x000000000000001c memsz 0x000000000000001c flags r--
    LOAD off    0x0000000000000000 vaddr 0x0000000000000000 paddr 0x0000000000000000 align 2**21
         filesz 0x00000000000003d0 memsz 0x00000000000003d0 flags r-x
    LOAD off    0x0000000000000f30 vaddr 0x0000000000200f30 paddr 0x0000000000200f30 align 2**21
         filesz 0x00000000000000d0 memsz 0x00000000000000d0 flags rw-
 DYNAMIC off    0x0000000000000f30 vaddr 0x0000000000200f30 paddr 0x0000000000200f30 align 2**3
         filesz 0x00000000000000d0 memsz 0x00000000000000d0 flags rw-
    NOTE off    0x0000000000000254 vaddr 0x0000000000000254 paddr 0x0000000000000254 align 2**2
         filesz 0x0000000000000024 memsz 0x0000000000000024 flags r--
EH_FRAME off    0x0000000000000388 vaddr 0x0000000000000388 paddr 0x0000000000000388 align 2**2
         filesz 0x0000000000000014 memsz 0x0000000000000014 flags r--
   STACK off    0x0000000000000000 vaddr 0x0000000000000000 paddr 0x0000000000000000 align 2**4
         filesz 0x0000000000000000 memsz 0x0000000000000000 flags rw-
   RELRO off    0x0000000000000f30 vaddr 0x0000000000200f30 paddr 0x0000000000200f30 align 2**0
         filesz 0x00000000000000d0 memsz 0x00000000000000d0 flags r--

Dynamic Section:
  GNU_HASH             0x0000000000000278
  STRTAB               0x0000000000000320
  SYMTAB               0x00000000000002a8
  STRSZ                0x0000000000000019
  SYMENT               0x0000000000000018
  DEBUG                0x0000000000000000
  FLAGS_1              0x0000000008000000

Sections:
Idx Name          Size      VMA               LMA               File off  Algn
  0 .interp       0000001c  0000000000000238  0000000000000238  00000238  2**0
                  CONTENTS, ALLOC, LOAD, READONLY, DATA
  1 .note.gnu.build-id 00000024  0000000000000254  0000000000000254  00000254  2**2
                  CONTENTS, ALLOC, LOAD, READONLY, DATA
  2 .gnu.hash     00000030  0000000000000278  0000000000000278  00000278  2**3
                  CONTENTS, ALLOC, LOAD, READONLY, DATA
  3 .dynsym       00000078  00000000000002a8  00000000000002a8  000002a8  2**3
                  CONTENTS, ALLOC, LOAD, READONLY, DATA
  4 .dynstr       00000019  0000000000000320  0000000000000320  00000320  2**0
                  CONTENTS, ALLOC, LOAD, READONLY, DATA
  5 .text         00000037  0000000000000340  0000000000000340  00000340  2**4
                  CONTENTS, ALLOC, LOAD, READONLY, CODE
  6 .rodata       0000000f  0000000000000377  0000000000000377  00000377  2**0
                  CONTENTS, ALLOC, LOAD, READONLY, DATA
  7 .eh_frame_hdr 00000014  0000000000000388  0000000000000388  00000388  2**2
                  CONTENTS, ALLOC, LOAD, READONLY, DATA
  8 .eh_frame     00000030  00000000000003a0  00000000000003a0  000003a0  2**3
                  CONTENTS, ALLOC, LOAD, READONLY, DATA
  9 .dynamic      000000d0  0000000000200f30  0000000000200f30  00000f30  2**3
                  CONTENTS, ALLOC, LOAD, DATA
 10 .comment      00000035  0000000000000000  0000000000000000  00001000  2**0
                  CONTENTS, READONLY
SYMBOL TABLE:
0000000000000238 l    d  .interp            0000000000000000 .interp
0000000000000254 l    d  .note.gnu.build-id 0000000000000000 .note.gnu.build-id
0000000000000278 l    d  .gnu.hash          0000000000000000 .gnu.hash
00000000000002a8 l    d  .dynsym            0000000000000000 .dynsym
0000000000000320 l    d  .dynstr            0000000000000000 .dynstr
0000000000000340 l    d  .text              0000000000000000 .text
0000000000000377 l    d  .rodata            0000000000000000 .rodata
0000000000000388 l    d  .eh_frame_hdr      0000000000000000 .eh_frame_hdr
00000000000003a0 l    d  .eh_frame          0000000000000000 .eh_frame
0000000000200f30 l    d  .dynamic           0000000000000000 .dynamic
0000000000000000 l    d  .comment           0000000000000000 .comment
0000000000000000 l    df *ABS*              0000000000000000 hello.c
0000000000000000 l    df *ABS*              0000000000000000 
0000000000200f30 l     O .dynamic           0000000000000000 _DYNAMIC
0000000000000388 l       .eh_frame_hdr      0000000000000000 __GNU_EH_FRAME_HDR
0000000000201000 l     O .dynamic           0000000000000000 _GLOBAL_OFFSET_TABLE_
0000000000000340 g     F .text              0000000000000037 _start
0000000000201000 g       .dynamic           0000000000000000 __bss_start
0000000000201000 g       .dynamic           0000000000000000 _edata
0000000000201000 g       .dynamic           0000000000000000 _end

и имеет только основные символы. Вы можете даже лишить его, strip --strip-unneeded hello, после чего у него вообще нет символов. (Начальный адрес не обязательно должен быть символом в файлах ELF.) Глядя на сборку, objdump -d hello,

hello:     file format elf64-x86-64

Disassembly of section .text:

0000000000000340 <_start>:
 340:  48 8d 0d 31 00 00 00   lea    0x31(%rip),%rcx        # 378 <_start+0x38>
 347:  31 d2                  xor    %edx,%edx
 349:  0f 1f 80 00 00 00 00   nopl   0x0(%rax)
 350:  48 83 c1 01            add    $0x1,%rcx
 354:  83 c2 01               add    $0x1,%edx
 357:  80 79 ff 00            cmpb   $0x0,-0x1(%rcx)
 35b:  75 f3                  jne    350 <_start+0x10>
 35d:  b8 01 00 00 00         mov    $0x1,%eax
 362:  48 8d 35 0e 00 00 00   lea    0xe(%rip),%rsi        # 377 <_start+0x37>
 369:  89 c7                  mov    %eax,%edi
 36b:  0f 05                  syscall 
 36d:  31 ff                  xor    %edi,%edi
 36f:  b8 3c 00 00 00         mov    $0x3c,%eax
 374:  0f 05                  syscall 
 376:  c3                     retq   

и вы увидите, что все адреса относятся к %rip, включая условные переходы. Например, 75 f3 кодирует переход на 13 байт до начала следующего кода операции (0xF3 = -13).

Если вы опускаете оптимизацию (-O2), GCC пытается помочь и включает символы local в файл ELF; Вы можете удалить их, используя strip --strip-unneeded hello.

Итак, когда вы компилируете в объектный файл без оптимизации gcc -Wall -fPIC -pie -march=x86-64 -mtune=generic -m64 -ffreestanding -nostdlib -nostartfiles -c hello.c и изучите полученный hello.o, используя objdump -x hello.o, вы увидите оба локальных символа (l во втором столбце),

SYMBOL TABLE:
0000000000000000 l    df *ABS*              0000000000000000 hello.c
0000000000000000 l    d  .text              0000000000000000 .text
0000000000000000 l    d  .data              0000000000000000 .data
0000000000000000 l    d  .bss               0000000000000000 .bss
0000000000000000 l     F .text              0000000000000016 my_exit
0000000000000016 l     F .text              000000000000004e my_write
0000000000000064 l     F .text              0000000000000039 my_strlen
000000000000009d l     F .text              0000000000000046 wrout
0000000000000000 l    d  .rodata            0000000000000000 .rodata
0000000000000000 l    d  .note.GNU-stack    0000000000000000 .note.GNU-stack
0000000000000000 l    d  .eh_frame          0000000000000000 .eh_frame
0000000000000000 l    d  .comment           0000000000000000 .comment
00000000000000e3 g     F .text              000000000000002c _start

и наличие одной записи о перемещении для .text,

RELOCATION RECORDS FOR [.text]:
OFFSET           TYPE              VALUE 
00000000000000ee R_X86_64_PC32     .rodata-0x0000000000000004

типа R_X86_64_PC32 является указателем инструкции относительно 32-битной константы. При связывании в двоичный файл (исполняемый файл или библиотека) эти перемещения будут применены, и окончательный двоичный файл будет не зависящим от позиции.

В файле требуются только те символы, которые должны быть доступны извне, за исключением _start, адрес которого хранится в качестве начального адреса в файле ELF. Если функция или глобальная переменная не нужны за пределами модуля компиляции, вы отмечаете их static. Затем мы сообщаем компилятору генерировать независимый от позиции код (-fPIC) и независимый от позиции исполняемый файл (-pie). Лично я всегда включаю предупреждения и оптимизацию (-Wall -O2), но это зависит от вас.

Итак, ответ на вопрос ОП относительно «как это сделать» :

  1. Используйте static для всех функций и глобальных переменных, которые не должны быть доступны вне текущей единицы компиляции.

  2. Скомпилируйте объект и / или двоичный файл с помощью -fPIC -pie. Это должно исключить необходимость перемещения во время выполнения и использовать относительную адресацию %rip или аналогичную для всех архитектур, которые ее поддерживают.

  3. При желании можно удалить ненужные символы из двоичного файла, используя strip --strip-unneeded. Это не влияет на перемещение, но делает двоичный файл меньше, удаляя ненужную информацию о символах.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...