Почему глобальные переменные в x86-64 доступны относительно указателя инструкций? - PullRequest
2 голосов
/ 22 мая 2019

Я попытался скомпилировать код c в код сборки, используя gcc -S -fasm foo.c. Код c объявляет глобальную переменную и переменную в основной функции, как показано ниже:

int y=6;
int main()
{
        int x=4;
        x=x+y;
        return 0;
}

Теперь я посмотрел код сборки, который был сгенерирован из этого кода C, и увидел, что глобальная переменная y хранится с использованием значения указателя инструкции rip.

Я думал, что в текстовом сегменте хранится только глобальная переменная const, но, глядя на этот пример, кажется, что в текстовом сегменте также хранятся обычные глобальные переменные, что очень странно.

Я полагаю, что некоторые предположения, которые я сделал, неверны, так что, может, кто-нибудь объяснит мне это?

код сборки, сгенерированный компилятором c:

        .file   "foo.c"
        .text
        .globl  y
        .data
        .align 4
        .type   y, @object
        .size   y, 4
y:
        .long   6
        .text
        .globl  main
        .type   main, @function

main:
.LFB0:
        .cfi_startproc
        pushq   %rbp
        .cfi_def_cfa_offset 16
        .cfi_offset 6, -16
        movq    %rsp, %rbp
        .cfi_def_cfa_register 6
        movl    $4, -4(%rbp)
        movl    y(%rip), %eax
        addl    %eax, -4(%rbp)
        movl    $0, %eax
        popq    %rbp
        .cfi_def_cfa 7, 8
        ret
        .cfi_endproc
.LFE0:

Ответы [ 2 ]

6 голосов
/ 22 мая 2019

Смещения между различными разделами вашего исполняемого файла являются константами времени соединения , поэтому относительная к RIP адресация может использоваться для любого раздела (включая .data, где используются глобальные значения, отличные от const).Обратите внимание на .data в выходных данных asm.

Это применимо даже в исполняемой или разделяемой библиотеке PIE, где абсолютные адреса не известны до времени выполнения (ASLR).

Runtime ASLR для независимых от позиции исполняемых файлов (PIE) рандомизирует один базовый адрес для всей программы, а не начальные адреса отдельных сегментов относительно друг друга.

Весь доступ к статическим переменным использует относительную RIP-адресацию, потому что это наиболееэффективен даже в зависящем от позиции исполняемом файле, где абсолютная адресация является опцией (поскольку статические адреса являются константами времени соединения).

В 32-разрядной версии x86 существует 2 избыточных способа кодирования режима адресации безрегистры и абсолютный адрес disp32.(С байтом SIB и без него).x86-64 переназначил более короткую на RIP+rel32, поэтому mov foo, %eax на 1 байт длиннее mov foo(%rip), %eax.

64-битная абсолютная адресация займет еще больше места и доступна только для movв / из RAX / EAX / AX / AL, если только вы сначала не используете отдельную инструкцию для ввода адреса в регистр.

(В Linux PIE / PIC x86-64 разрешена 64-битная абсолютная адресация иобрабатываются с помощью исправлений во время загрузки, чтобы поместить правильный адрес в код или таблицу переходов или статически инициализированный указатель функции.Таким образом, технически не имеет , чтобы быть независимым от позиции, но обычно это более эффективно, чтобы быть32-разрядная абсолютная адресация не допускается, поскольку ASLR не ограничивается низкими 31 битами виртуального адресного пространства.)


Обратите внимание, что в исполняемом файле, отличном от PIE, gcc будет использовать 32-битную абсолютную адресацию для записи адреса статических данных в регистр.например, puts("hello"); обычно компилируется как

mov   $.LC0, %edi     # mov r32, imm32
call  puts

В модели памяти по умолчанию, отличной от PIE, статический код и данные связываются с низкими 32 битами виртуального адресного пространства, поэтому 32-битные абсолютные адреса работают независимо от того,они расширены до нуля или знака до 64-битЭто удобно для индексирования статических массивов , например, mov array(%rax), %edx;add $4, %eax например.

См. 32-разрядные абсолютные адреса, более не разрешенные в x86-64 Linux? для получения дополнительной информации о исполняемых файлах PIE, которые используют позиционно-независимый код для всего, включая RIP-относительный LEA, такой как 7-байтовый lea .LC0(%rip), %rdi вместо 5-байтового mov $.LC0, %edi.

Я упоминаю Linux, потому что он выглядит из директив .cfi, как будто вы компилируете для платформы, отличной от Windows.

2 голосов
/ 22 мая 2019

Хотя сегменты .data и .text не зависят друг от друга, после связывания их смещения относительно друг друга фиксированы (по крайней мере, в модели кода gcc x86-64 -mcmodel=small, которая является моделью кода по умолчанию иработает для всех программ, чей код + данные меньше 2 ГБ).

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

По этим причинам программы x86-64, скомпилированные для модели малого кода (по умолчанию), используют RIP-относительную адресацию как для кода, так и для глобальных данных.Это означает, что компилятору не нужно выделять регистр для указания того, куда система загрузила раздел .data исполняемого файла;программа уже знает свое собственное значение RIP и смещение между ним и глобальными данными, к которым оно хочет получить доступ, поэтому наиболее эффективный способ получить к нему доступ - это 32-разрядное фиксированное смещение от RIP.

(Абсолют 32Двухбитные режимы адресации занимают больше места, а 64-разрядные абсолютные режимы адресации еще менее эффективны и доступны только для RAX / EAX / AX / AL.веб-сайт: Понимание моделей кода x64

...