Для чего используются регистры R10-R15 в соглашении о вызовах Windows x64? - PullRequest
0 голосов
/ 14 ноября 2018

От введения Intel до сборки x64 в https://software.intel.com/en-us/articles/introduction-to-x64-assembly,

  • RCX, RDX, R8, R9 используются для аргументов целого числа и указателя в том порядке слева направо.
  • РегистрыRAX, RCX, RDX, R8, R9, R10 и R11 считаются энергозависимыми и должны рассматриваться как разрушенные при вызовах функций.
  • RBX, RBP, RDI, RSI, R12, R14, R14 и R15 должны бытьсохранено в любой функции, использующей их.

Хотя я понимаю, как RCX, RDX, R8, R9 используются в качестве аргументов функций, я видел функции, которые принимают более 4 аргументов, возвращающихся к использованию стека, например32-битный код.Ниже приведен пример:

sub_18000BF10   proc near 
lpDirectory     = qword ptr -638h
nShowCmd        = dword ptr -630h
Parameters      = word ptr -628h

             sub     rsp, 658h
             mov     r9, rcx
             mov     r8, rdx
             lea     rdx, someCommand ; "echo "Hello""...
             lea     rcx, [rsp+658h+Parameters] ; LPWSTR
             call    cs:wsprintfW
             xor     r11d, r11d
             lea     r9, [rsp+658h+Parameters] ; lpParameters
             mov     [rsp+658h+nShowCmd], r11d ; nShowCmd
             lea     r8, aCmdExe     ; "cmd.exe"
             lea     rdx, Operation  ; "open"
             xor     ecx, ecx        ; hwnd
             mov     [rsp+658h+lpDirectory], r11 ; lpDirectory
             call    cs:ShellExecuteW
             mov     eax, 1
             add     rsp, 658h
             retn
sub_18000BF10    endp

Это отрывок из IDA, и вы можете видеть, что аргументы nShowCmd и lpDirectory для ShellExecute находятся в стеке. Почему мы не можем использовать дополнительные регистры после R9 для поведения быстрого вызова?

Или, если мы можем сделать это в пользовательских функциях, а функции системного API этого не делают,есть причина для этого?Я полагаю, что аргументы быстрого вызова в регистрах были бы более эффективными, чем проверка смещения стека.

1 Ответ

0 голосов
/ 14 ноября 2018

Соглашение о вызовах Windows x64 разработано, чтобы упростить реализацию переменных функций (таких как printf и scanf) путем сброса 4 регистровых аргументов в теневое пространство, создавая непрерывный массив всех аргументов. Аргументы, превышающие 8 байтов, передаются по ссылке, поэтому каждый аргумент всегда занимает ровно 1 слот, передающий аргументы.

Учитывая это конструктивное ограничение, для большего количества аргументов регистра потребуется больше теневого пространства, что тратит больше места в стеке для небольших функций, у которых мало аргументов.

Да, большее количество аргументов регистра обычно будет более эффективным. Но если вызываемый объект хочет сразу же вызвать другой вызов функции с другими аргументами, он должен будет сохранить все свои аргументы регистров в стеке, поэтому существует ограничение на количество полезных аргументов регистров.

Вы хотите хорошее сочетание регистров с сохранением и с сохранением вызовов, независимо от того, сколько их используется для передачи аргументов . R10 и R11 - засоренные регистры нуля. Прозрачная функция-обертка, написанная на asm, может использовать их для создания пустого пространства, не нарушая ни одного из аргументов в RCX, RDX, R8, R9, и без необходимости сохранять / восстанавливать регистр, сохраняемый при вызове в любом месте.

R12..R15 - это регистры, сохраняемые при вызове, которые вы можете использовать для чего угодно, до тех пор, пока вы сохраните / восстановите их до возвращения.


Или, если мы можем сделать это в пользовательских функциях

Да, вы можете свободно составлять свои собственные соглашения о вызовах при вызове из asm в asm с учетом ограничений, налагаемых ОС. Но если вы хотите, чтобы исключения могли разматывать стек от до такого вызова (например, если одна из дочерних функций перезванивает в некоторый C ++, который может генерировать), вы должны следовать большему количеству ограничений, таких как создание раскрутить метаданные. Если нет, вы можете сделать почти все.

См. Мое Выберите соглашение о вызовах, чтобы расположить аргументы там, где вы хотите. ответ на вопросы и ответы CodeGolf "Советы по игре в гольф в машинном коде x86 / x64".

Вы также можете вернуться в любой регистр (ы) и вернуть несколько значений. (Например, функция asm strcmp или memcmp может возвращать разницу - / 0 / + в несоответствии в EAX, и возвращают позицию несоответствия в RDI, поэтому вызывающая сторона может использовать один или оба. )


Полезным упражнением при оценке дизайна является сравнение его с другими фактическими или возможными проектами

Для сравнения, x86-64 System V ABI передает первые 6 целочисленных аргументов в регистрах, и первые 8 аргументов FP в XMM0..7 . (Windows x64 передает 5-й аргумент в стеке, даже если это FP и первые 4 аргумента были целыми числами.)

Таким образом, другое основное соглашение о вызовах x86-64 использует больше регистров передачи аргументов. Он не использует теневое пространство; он определяет красную зону под RSP, которая защищена от асинхронного замыкания. Маленькие конечные функции могут по-прежнему избегать манипулирования RSP для резервирования пространства.

Забавный факт: R10 и R11 также являются не проходящими через аргументы регистрами с перекрытыми вызовами в x86-64 SysV. Интересный факт # 2: syscall уничтожает R11 (и RCX), поэтому Linux использует R10 вместо RCX для передачи аргументов системным вызовам, но в остальном использует то же соглашение о передаче регистра-аргумента, что и при вызовах функций пользовательского пространства.

См. Также Почему Windows64 использует соглашение о вызовах, отличное от всех других операционных систем на x86-64? для дополнительной информации и предположений о том, почему Microsoft сделала выбор дизайна, который они сделали с их соглашением о вызовах.

x86-64 System V усложняет реализацию переменных функций (больше кода для индексирования аргументов), но они, как правило, встречаются редко. Большая часть кода не является узким местом при пропускной способности sscanf. Теневое пространство обычно хуже, чем красная зона. Исходное соглашение Windows x64 не передает векторные аргументы (__m128) по значению, поэтому существует второе 64-разрядное соглашение о вызовах в Windows, называемое vectorcall, которое позволяет использовать эффективные векторные аргументы. (Обычно это не имеет большого значения, потому что большинство функций, которые принимают векторные аргументы, являются встроенными, но функции математической библиотеки SIMD выиграют.)

Передача большего количества аргументов в нижние 8 (оригинальные регистры rax..rdi, для которых не требуется префикс REX), и наличие большего количества регистров с закрытыми вызовами, которые не требуют префикса REX, вероятно, хорошо для кода размер в коде, который достаточно встроен, чтобы не вызывать огромное количество вызовов функций. Вы могли бы сказать, что выбор Window для сохранения большего количества регистров не-REX для сохранения вызовов лучше для кода с циклами, содержащими вызовы функций, но если вы делаете много вызовов функций для коротких вызываемых, то они выиграют от большего незаполненные регистры, не требующие префиксов REX. Интересно, сколько мыслей MS вложило в это, или они просто сохраняли сходство с 32-битными соглашениями о вызовах при выборе того, какой из регистров с низким 8 будет сохранен для вызова.

Одна из слабостей x86-64 System V заключается в отсутствии сохраняемых вызовов регистров XMM. Таким образом, любой вызов функции требует разливания / перезагрузки любых переменных FP. Иметь пару, например, младшие 128 или 64 бита xmm6 и xmm7, было бы неплохо.

...