Что такое «%rdi» в ассемблере и где оно берет свое значение?

Заинтригованный постом об UB , я решил начать читать Джонатана Бартлетта «Программирование с нуля», чтобы поиграться с C++ UB и посмотреть, как выглядит сборка.

Но пробуя разные вещи, я обнаружил кое-что странное в довольно простом случае. Рассмотрим этот код

int foo(int * p) {
    int y = 7;
    if (p)
        ++y;
    return y;
}

Его сборка

foo(int*):
        cmpq    $1, %rdi
        movl    $7, %eax
        sbbl    $-1, %eax
        ret

(Обозреватель компиляторов)

Теперь я понимаю, что movl $7, %eax помещает значение 7 в регистр eax, а затем то, которое будет возвращено вызывающей стороне ret. Поэтому я также понял, что sbbl $-1, %eax — это инструкция, которая заботится о вычитании -1 из содержимого eax и сохранении результата в самом eax, и что эта инструкция выполняется только в том случае, если p не равно нулю. Это заставляет меня предположить, что sbbl использует скрытое логическое значение, вычисленное предыдущими строками. Единственный кандидат, даже по имени, это cmpq $1, %rdi.

Но что это делает? Из вышеупомянутой книги я понял, что аргументы функций передаются от вызывающей стороны к вызываемой через стек: вызывающая сторона помещает аргументы в стек, а вызываемая сторона извлекает эти значения. Но здесь такого нет.

Так %rdi что? Регистр первого (и в данном случае единственного) аргумента функции? Почему это так? Существуют ли другие регистры, относящиеся к дополнительным аргументам? Сколько? И кроме того, что является хорошим источником информации по этой теме?

RDI содержит первый целочисленный аргумент/указатель в соглашении о вызовах x86-64 System V. В книге, которую вы читаете, используется 32-разрядный ассемблер x86, где стандартное соглашение о вызовах намного старше и менее эффективно, только с использованием аргументов стека. Если вы используете gcc -O3 -m32 -mregparm=3, вы получите 32-битный код, используя аргументы регистра. Если вы используете gcc -O3 -m32, вы получите более знакомый код.

Peter Cordes 21.11.2022 20:38

Что касается того, как GCC организует возврат 7 или 8 в соответствии с условием if (p), да, cmp $1, %rdi устанавливает CF, если RDI был равен нулю, в противном случае очищает его. Таким образом, более поздний SBB добавит 0 (EAX -= -1 + CF=1) или 1 (EAX -= -1 + CF=0).

Peter Cordes 21.11.2022 20:41
[JS за 1 час] - 9. Асинхронный
[JS за 1 час] - 9. Асинхронный
JavaScript является однопоточным, то есть он может обрабатывать только одну задачу за раз. Для обработки длительных задач, таких как сетевые запросы,...
Топ-10 компаний-разработчиков PHP
Топ-10 компаний-разработчиков PHP
Если вы ищете надежных разработчиков PHP рядом с вами, вот список лучших компаний по разработке PHP.
Скраппинг поиска Apple App Store с помощью Python
Скраппинг поиска Apple App Store с помощью Python
📌Примечание: В этой статье я покажу вам, как скрапировать поиск Apple App Store и получить точно такой же результат, как на Apple iMac, потому что...
Редкие достижения на Github ✨
Редкие достижения на Github ✨
Редкая коллекция доступна в профиле на GitHub ✨
Подъем в javascript
Подъем в javascript
Hoisting - это поведение в JavaScript, при котором переменные и объявления функций автоматически "перемещаются" в верхнюю часть соответствующих...
Улучшение генерации файлов Angular
Улучшение генерации файлов Angular
Angular - это фреймворк. Вы можете создать практически любое приложение без использования сторонних библиотек.
0
4
113
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

%rdi ссылка на реестр rdi.

В этом случае создается впечатление, что компилятор передает первый параметр в регистр, а не в стек.

Передача параметров в основном является соглашением: пока компилятор согласован в том, как он передает параметры, компилятор может переключаться с передачи параметров одним способом (например, всегда в стеке) на другой (некоторые в регистрах) практически в любое время, когда сочтет нужным. (новая версия компилятора или даже просто передача какого-либо переключателя в командной строке компилятора).

В зависимости от того, когда и где вы смотрите, один компилятор может поддерживать несколько соглашений о вызовах. Например, долгое время 32-разрядный компилятор Microsoft поддерживал четыре: cdecl, fastcall, stdcall и thiscall (последний использовался только для функций-членов C++). Из них cdecl и stdcall были основаны исключительно на стеке, а fastcall и thiscall оба использовали регистры для некоторых аргументов.

Программирование с нуля использует сборку i386 с синтаксисом AT&T в Linux; это бесплатная книга, написанная до того, как x86-64 получила широкое распространение (но она довольно хороша, судя по тому, что я просмотрел). Такие термины, как «cdecl», не существуют в ABI i386 System V или x86-64 System V. (Единственный способ получить 32-битный код, используя что-либо, кроме аргументов стека, — это GCC __attribute__((regparm(3))), по умолчанию — regparm(0), если вы не используете параметр командной строки.)

Peter Cordes 21.11.2022 20:45

Компиляторы C не могут по прихоти переключать соглашения о вызовах в новой версии, что нарушит двоичную совместимость с существующими библиотеками. (Теоретически они могли бы использоваться для функций static, но эта функция никоим образом не является частной.)

Peter Cordes 21.11.2022 20:48

@PeterCordes: Да, могут, и да. Я полагаю, что «по прихоти» немного преувеличивает, но они, безусловно, время от времени менялись от одной версии к другой.

Jerry Coffin 22.11.2022 03:45

GCC для x86-64 (который использует OP) никогда не менял свой C ABI несовместимыми с предыдущими способами, о чем я не знаю, и вряд ли это произойдет в будущем. Да, вы можете изменить его с помощью аргументов командной строки, например. -mpreferred-stack-boundary=3 чтобы поддерживать только 2**3 = 8-байтовое выравнивание стека. GCC для i386 случайно (?) начал требовать выравнивания 16-байтового стека с SSE code-gen, в конечном итоге изменив ABI, чтобы потребовать его, как только была обнаружена проблема с библиотеками, скомпилированными таким образом, существующими в дикой природе. (И вы можете изменить соглашение о вызовах с помощью -mregparm=3.)

Peter Cordes 22.11.2022 06:00

Я думаю, что некоторые другие ISA, которые чаще используются во встроенных системах, изменили ABI, возможно, потому, что они чаще используются в системах, где все можно без особых проблем перестроить из исходного кода. Но я не знаю подробностей тех. Учитывая ваше обсуждение 32-битных имен соглашений о вызовах Windows, вы говорите об изменении значений по умолчанию MSVC или что-то в этом роде?

Peter Cordes 22.11.2022 06:02

@PeterCordes: MSVC — один из примеров, но далеко не единственный.

Jerry Coffin 22.11.2022 09:54
%rdi — это 64-битный регистр (когда-то он обозначал «индекс назначения», а r означает 64-битный регистр). Функция, написанная на C, обычно предполагает, что первый параметр находится в этом регистре (или указатель на него, если он больше 64 бит). В прошлом он часто использовался для хранения указателя на некоторый массив, в который вы записывали, или назначение функций C memcpy() или memset(), поскольку аппаратное обеспечение x86 имеет специальные команды для многократной записи в память, на которую указывает %rdi.
puppydrum64 23.11.2022 16:38

Другие вопросы по теме