int *q
будет идти в .bss
, а не раздел .data
, так как он только инициализируется во время выполнения с помощью непостоянного инициализатора (так это законно только в C++, а не в C). Для него нет необходимости иметь 8 байтов в сегменте данных исполняемого файла.
Компилятор организует запуск функции инициализатора путем помещения ее адреса в массив инициализаторов, вызывающий код запуска CRT (C Run-Time) перед вызовом main
.
В проводнике компилятора Godbolt вы можете увидеть asm функции init без всякого шума директив. Обратите внимание, что режим адресации - это просто RIP-относительный доступ к q
. В этот момент компоновщик заполняет правое смещение от RIP, так как это постоянная времени соединения, даже если секции .text
и находятся в отдельных сегментах.
Godbolt's compiler-noise filtering не подходит для нас. Некоторые из директив актуальны, но многие из них не являются. Ниже представлена комбинация, выбранная вручную от gcc6.2 -O3
asm output with Godbolt's "filter directives" option unchecked, всего за int* q = new int(13);
. (Нет необходимости компилировать main
одновременно, мы не связываем исполняемый файл).
# gcc6.2 -O3 output
_GLOBAL__sub_I_q: # presumably stands for subroutine
sub rsp, 8 # align the stack for calling another function
mov edi, 4 # 4 bytes
call operator new(unsigned long) # this is the demangled name, like from objdump -dC
mov DWORD PTR [rax], 13
mov QWORD PTR q[rip], rax # clang uses the equivalent `[rip + q]`
add rsp, 8
ret
.globl q
.bss
q:
.zero 8 # reserve 8 bytes in the BSS
Нет ссылки на базу данных ELF (или любого другого).
Также определенно не переопределяется регистр сегментов. Сегменты ELF не имеют ничего общего с сегментами x86. (И регистр сегмента по умолчанию для этого - DS
в любом случае, поэтому компилятору не нужно выделять [ds:rip+q]
или что-то в этом роде. Некоторые дизассемблеры могут быть явными и показывать DS, несмотря на то, что в инструкции не было префикса переопределения сегмента).
Это, как компилятор организует для того, чтобы быть вызван до main()
:
# the "aw" sets options/flags for this section to tell the linker about it.
.section .init_array,"aw"
.align 8
.quad _GLOBAL__sub_I_q # this assembles to the absolute address of the function.
код запуска CRT имеет цикл, который знает размер секции .init_array
и использует память косвенной call
инструкции на каждой функциональной клавише r в свою очередь.
Раздел .init_array
помечен как записываемый, поэтому он переходит в сегмент данных. Я не уверен, что это пишет. Может быть, CRT-код отмечает это как уже сделанное путем обнуления указателей после их вызова?
Там есть аналогичный механизм в Linux для запуска инициализаторов в динамических библиотеках, которые делаются интерпретатором ELF при этом динамическом связывании. Вот почему вы можете вызвать printf()
или другие функции glibc stdio от _start
в динамически связанном двоичном файле, созданном из рукописного asm, и почему это не удается в статически связанном двоичном файле, если вы не вызываете правильные функции init. (См. this Q&A для получения дополнительной информации о создании статических или динамических двоичных файлов, которые определяют их собственные _start
или только main()
, с или без libc).
При связывании с 'gcc' начальной точкой по умолчанию является' _start'. В этом случае эта инициализация будет иметь код, а затем он будет называться 'main'. Поэтому люди, выполняющие программирование в сборке без клиффа, но связывая их с 'gcc', должны поставить в начале своего кода метку' _start: ', в то время как люди, ссылающиеся на клип по умолчанию, начинаются с' main: '(в их источнике начинается двоичный код в '_start:' из lib). :) – Ped7g
Чтобы уточнить это, '_start' не является функцией, поэтому вы не можете писать' _start' в C или C++. В сборке вам не нужно писать функции, вы можете написать произвольный код, чтобы вы могли самостоятельно писать '_start'. –
Понял, спасибо Ped7g & Dietrich Epp. –