Рассмотрим эту автономную 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
), но это зависит от вас.
Итак, ответ на вопрос ОП относительно «как это сделать» :
Используйте static
для всех функций и глобальных переменных, которые не должны быть доступны вне текущей единицы компиляции.
Скомпилируйте объект и / или двоичный файл с помощью -fPIC
-pie
. Это должно исключить необходимость перемещения во время выполнения и использовать относительную адресацию %rip
или аналогичную для всех архитектур, которые ее поддерживают.
При желании можно удалить ненужные символы из двоичного файла, используя strip --strip-unneeded
. Это не влияет на перемещение, но делает двоичный файл меньше, удаляя ненужную информацию о символах.