Адресация указателя сборочной инструкции X86

Обычно я не трачу много времени на сборку, поэтому следующий вывод компилятора меня немного смутил.

Скажем, я скомпилирую этот фрагмент кода на моем Intel Core 2 Duo под управлением OSX 10.6:

while (var != 69) // var is a global variable
{
    printf("Looping!\n");
}

Сборка для сравнения "var!= 69" выглядит следующим образом:

cmpl    $69, _var(%rip)

Я понимаю, что это эффективно означает сравнение значения "69" с содержимым глобальной переменной "var", но мне сложно понять часть "_var (% rip)". Обычно я ожидаю, что будет значение смещения, например, для обращения к локальным переменным в стеке (например: -4 ($ ebp)). Однако я не совсем понимаю, как смещение указателя инструкции с объявлением "_var" даст мне содержимое глобальной переменной "var".

Что именно означает эта линия?

Спасибо.

Ответ 1

Это работает почти так же, как обращение к локальным переменным в стеке с помощью offset(%ebp). В этом случае компоновщик установит поле смещения этой команды на разницу между адресом var и значением, которое %rip будет иметь при выполнении этой команды. (Если я правильно помню, это значение является адресом следующей команды, потому что %rip всегда указывает на команду после исполняемого в настоящее время.) Таким образом, добавление дает адрес var.

Почему так? Это признак независимого от позиции кода. Если компилятор сгенерировал

cmpl $69, _var

и компоновщик заполнил абсолютный адрес var, тогда при запуске программы исполняемый образ всегда должен быть загружен в память по одному конкретному адресу, так что все переменные имели абсолютные адреса, код ожидает. Делая это таким образом, единственное, что должно быть исправлено, это расстояние между кодом и данными; код плюс данные (т.е. полное исполняемое изображение) могут быть загружены по любому адресу, и он все равно будет работать.

... Зачем беспокоиться? Почему плохо загружать исполняемый файл по одному конкретному адресу? Это не обязательно. Общие библиотеки должны быть независимы от позиции, потому что в противном случае у вас могут быть две библиотеки, которые хотят быть загружены на перекрывающиеся адреса, и вы не можете использовать их оба в одной программе. (Некоторые системы справились с этим, сохранив глобальный реестр всех библиотек и требуемое пространство, но, очевидно, это не масштабируется.) Создание исполняемых файлов, не зависящих от положения, в основном делается в качестве меры безопасности: несколько сложнее использовать переполнение буфера если вы не знаете, где находится программный код в памяти (это называется рандомизация размещения адресного пространства).