![Влияние статического и динамического связывания на начальный адрес](https://rvso.com/image/31682/%D0%92%D0%BB%D0%B8%D1%8F%D0%BD%D0%B8%D0%B5%20%D1%81%D1%82%D0%B0%D1%82%D0%B8%D1%87%D0%B5%D1%81%D0%BA%D0%BE%D0%B3%D0%BE%20%D0%B8%20%D0%B4%D0%B8%D0%BD%D0%B0%D0%BC%D0%B8%D1%87%D0%B5%D1%81%D0%BA%D0%BE%D0%B3%D0%BE%20%D1%81%D0%B2%D1%8F%D0%B7%D1%8B%D0%B2%D0%B0%D0%BD%D0%B8%D1%8F%20%D0%BD%D0%B0%20%D0%BD%D0%B0%D1%87%D0%B0%D0%BB%D1%8C%D0%BD%D1%8B%D0%B9%20%D0%B0%D0%B4%D1%80%D0%B5%D1%81.png)
У меня есть простая программа на языке C. Я запускаю:
$ gcc Q1.c -Wall -save-temps -o Q1
Затем я проверяю сгенерированный исполняемый файл:
$ objdump -f Q1
Q1: file format elf32-i386
architecture: i386, flags 0x00000112:
EXEC_P, HAS_SYMS, D_PAGED
start address 0x080483b0
Затем я компилирую его со статической линковкой:
$ gcc Q1.c -Wall -save-temps -static -o Q1
и проверьте файл еще раз:
$ objdump -f Q1
Q1: file format elf32-i386
architecture: i386, flags 0x00000112:
EXEC_P, HAS_SYMS, D_PAGED
start address 0x08048e08
Какое влияние статическая и динамическая компоновка оказывает на начальный адрес программы? Начальный адрес — это адрес main()
, верно?
решение1
Начальный адрес — это адрес main()
, верно?
Не совсем: Начало программы на самом деле не main()
. По умолчанию GCC создаст исполняемые файлы, начальный адрес которых соответствует символу _start
. Вы можете увидеть это, выполнив objdump --disassemble Q1
. Вот вывод моей простой программы, которая делает только return 0;
в main()
:
0000000000400e30 <_start>:
400e30: 31 ed xor %ebp,%ebp
400e32: 49 89 d1 mov %rdx,%r9
400e35: 5e pop %rsi
400e36: 48 89 e2 mov %rsp,%rdx
400e39: 48 83 e4 f0 and $0xfffffffffffffff0,%rsp
400e3d: 50 push %rax
400e3e: 54 push %rsp
400e3f: 49 c7 c0 a0 15 40 00 mov $0x4015a0,%r8
400e46: 48 c7 c1 10 15 40 00 mov $0x401510,%rcx
400e4d: 48 c7 c7 40 0f 40 00 mov $0x400f40,%rdi
400e54: e8 f7 00 00 00 callq 400f50 <__libc_start_main>
400e59: f4 hlt
400e5a: 66 90 xchg %ax,%ax
400e5c: 0f 1f 40 00 nopl 0x0(%rax)
Как вы можете видеть по адресу 400e54
, _start()
в свою очередь вызывается __libc_start_main
, который инициализирует необходимые вещи (pthreads, atexit,...) и, наконец, вызывается main()
с соответствующими аргументами (argc, argv и env).
Хорошо, но какое отношение это имеет к изменению начального адреса?
Когда вы просите gcc
скомпоновать статически, это означает, что вся инициализация, о которой я упоминал выше, должна быть выполнена с использованием функций, которые находятся в исполняемом файле. И действительно, если вы посмотрите на размеры обоих исполняемых файлов, вы обнаружите, что статическая версия намного больше. В моем тесте статическая версия составляет 800 КБ, а общая версия — всего 6 КБ.
Дополнительные функции оказались размещены перед _start()
, отсюда и изменение начального адреса. Вот макет статического исполняемого файла вокруг start()
:
000000000049e960 r translit_from_tbl
0000000000400a76 t _i18n_number_rewrite
0000000000400bc0 t fini
0000000000400bd0 t init_cacheinfo
0000000000400e30 T _start
0000000000400e60 t deregister_tm_clones
0000000000400e90 t register_tm_clones
0000000000400ed0 t __do_global_dtors_aux
А вот макет общего исполняемого файла:
00000000004003c0 T _start
00000000004003f0 t deregister_tm_clones
00000000004004b0 T main
00000000004004c0 T __libc_csu_init
00000000006008a0 B _end
0000000000400370 T _init
В результате я получаю немного разные начальные адреса: 0x400e30 в статическом случае и 0x4003c0 в общем случае.