Почему rbp и rsp называются регистрами общего назначения?
Согласно Intel в x64 следующие регистры называются регистрами общего назначения (RAX, RBX, RCX, RDX, RBP, RSI, RDI, RSP и R8-R15) https://software.intel.com/en-us/articles/introduction-to-x64-assembly.
В следующей статье написано, что RBP и RSP являются регистрами специального назначения (RBP указывают на базу текущего кадра стека, а RSP указывают на вершину текущего кадра стека). https://www.recurse.com/blog/7-understanding-c-by-learning-assembly
Теперь у меня есть два противоречивых утверждения. Заявление Intel должно быть доверенным, но что является правильным и почему RBP и RSP вообще называют универсальными?
Спасибо за любую помощь.
2 ответа
Общее назначение означает, что все эти регистры могут использоваться с любыми инструкциями, выполняющими вычисления с регистрами общего назначения, в то время как, например, вы не можете делать что хотите с указателем инструкций (RIP) или регистром флагов (RFLAGS).
Предполагалось, что некоторые из этих регистров будут использоваться для конкретного использования, и обычно используются. Наиболее важными из них являются RSP и RBP.
Если вам нужно использовать их для своих собственных целей, вы должны сохранить их содержимое, прежде чем хранить что-то внутри, и восстановить их первоначальное значение, когда это будет сделано.
Если регистр может быть операндом для add
или используется в режиме адресации, это "общее назначение", в отличие от регистров, таких как FS
сегментный регистр или RIP. Регистры GP также называются "целочисленными регистрами", хотя регистры других типов также могут содержать целые числа.
В компьютерной архитектуре процессоры обычно обрабатывают целочисленные регистры / инструкции отдельно от регистров / инструкций FP/SIMD. Например, процессоры семейства Intel Sandybridge имеют отдельные физические файлы регистров для переименования целочисленных GP или регистров FP/vector. Они просто называются целочисленными или регистровыми файлами FP. (Там, где FP - это сокращение от всего, что ядру не нужно сохранять / восстанавливать, чтобы использовать регистры GP, оставляя нетронутым состояние FPU/SIMD пользовательского пространства.) Каждая запись в файле регистров FP имеет ширину 256 бит (для удерживайте вектор AVX ymm), но записи в целочисленном регистре должны иметь ширину только 64 бита.
На процессорах, которые переименовывают регистры сегментов ( Skylake нет), я думаю, это будет частью целочисленного состояния, как и RFLAGS + RIP. Но когда мы говорим "регистр целых чисел", мы обычно имеем в виду именно регистр общего назначения.
Каждый регистр имеет некоторые особенности для некоторых инструкций, за исключением некоторых совершенно новых регистров, добавленных с x86-64: R8-R15. Они не дисквалифицируют их как общие цели (низкие 16 из) оригинальных 8 датируются 8086 годом, и было неявное использование каждого из них даже в оригинальном 8086.
Для RSP это специально для push/pop/call/ret, поэтому большая часть кода никогда не использует его ни для чего другого. (А в режиме ядра используется асинхронно для прерываний, так что вы действительно не можете спрятать его где-нибудь, чтобы получить дополнительный регистр GP, как вы можете в коде пользовательского пространства: является ли ESP универсальным, как EAX?)
Но в управляемых условных (как нет обработчиков сигналов) вам не нужно использовать RSP для указателя стека. Например, вы можете использовать его для чтения массива в цикле с помощью pop, как в этом коде-ответе. (Я на самом деле использовал esp
в 32-битном коде, но такая же разница: pop
быстрее чем lodsd
на Skylake, в то время как оба по 1 байту.)
Неявное использование и особенность для каждого регистра:
См. Также сборку x86. Почему [e]bx сохраняется в соглашениях о вызовах? для частичного списка.
Я в основном ограничиваю это инструкциями пользовательского пространства, особенно теми, которые современный компилятор может фактически генерировать из кода C или C++. Я не пытаюсь быть исчерпывающим для регистров, которые имеют много скрытого использования.
rax
: один операнд [i]mul / [i]div / cdq / cdqe, строковые инструкции (stos),cmpxchg
и т. д. и т. д., а также специальные короткие кодировки для многих инструкций, таких какxchg
-с Eax, где0x90 nop
пришел (прежде чем это стало отдельной инструкцией в x86-64, потому чтоxchg eax,eax
ноль расширяет EAX в RAX и, следовательно, не может использовать0x90
кодирование. Ноxchg rax,rax
все еще можно собрать до REX.W = 1 0x90.rcx
: число смен,rep
-строка рассчитывает, медленныйloop
инструкцияrdx
:rdx:rax
используется для деления и умножения, и cwd / cdq / cqo для их настройки.rdtsc
, BMI2mulx
,rbx
: 8086xlatb
,cpuid
используйте все четыре из EAX..EDX. 486cmpxchg8b
x86-64cmpxchg16b
, Компиляторы будут излучатьcmpxchg8
заstd::atomic<long long>::compare_exchange_weak
и 16b дляatomic<struct_16_bytes>
, RBX имеет наименьшее неявное использование оригинальных 8, ноlock cmpxchg16b
это один из немногих компиляторов, которые будут фактически использоваться.rsi
/rdi
: string ops, в том числеrep movsb
которые некоторые компиляторы иногда встроены. (GCC также inlinesrep cmpsb
для строковых литералов в некоторых случаях, но это, вероятно, не оптимально).rbp
:leave
(только 1 моп медленнее, чемmov rsp, rbp
/pop rbp
, GCC фактически использует его в функциях с указателем кадра, когда он не может простоpop rbp
). Также ужасно медленныйenter
который никто никогда не использует.rsp
: стек ops: push/pop / call/reg иleave
, (А такжеenter
).r11
:syscall
/sysret
используйте его для сохранения / восстановления RFLAGS пространства пользователя. (Наряду с RCX для сохранения / восстановления RIP пользовательского пространства).
Особые случаи кодирования в режиме адресации:
rbp
/ r13
не может быть базовым регистром без смещения: вместо этого эта кодировка означает: (в ModRM) rel32
(Относительно RIP) или (в SIB) disp32
без базового регистра. (r13
использует те же 3 бита в ModRM/SIB, поэтому этот выбор упрощает декодирование, поскольку не позволяет декодеру длины команды смотреть на бит REX.B для получения 4-го бита базового регистра). [r13]
собирает в [r13 + disp8=0]
, [r13+rdx]
собирает в [rdx+r13]
(избежать проблемы путем замены базы / индекса, когда это возможно).
rsp
/ r12
в качестве базового регистра всегда нужен байт SIB. (Кодирование ModR/M base=RSP является escape-кодом для сигнализации байта SIB, и опять же, больше декодера должно заботиться о префиксе REX, если r12
был обработан по-другому).
rsp
не может быть индексным регистром. Это позволяет кодировать [rsp]
, что более полезно, чем [rsp + rsp]
, (Intel могла бы разработать кодировки ModRM / SIB для 32-битных режимов адресации (впервые в 386), поэтому SIB без индекса был возможен только при base=ESP. [eax + esp*4]
возможно и только исключает [esp + esp*1/2/4/8]
, Но это бесполезно, поэтому они упростили аппаратное обеспечение, сделав index=ESP кодом без индекса независимо от базы. Это позволяет использовать два избыточных способа кодирования любого режима адресации base или base+disp: с или без SIB.)
r12
может быть индексным регистром. В отличие от других случаев, это не влияет на декодирование длины команды. Кроме того, его нельзя обойти с помощью более длинной кодировки, как в других случаях. AMD хотела, чтобы регистр AMD64 был как можно более ортогональным, поэтому имеет смысл потратить несколько дополнительных транзисторов на проверку REX.X как часть декодирования индекса / без индекса. Например, [rsp + r12*4]
требует index=r12, поэтому имея r12
не вполне общее назначение сделало бы AMD64 худшей целью компилятора.
0: 41 8b 03 mov eax,DWORD PTR [r11]
3: 41 8b 04 24 mov eax,DWORD PTR [r12] # needs a SIB like RSP
7: 41 8b 45 00 mov eax,DWORD PTR [r13+0x0] # needs a disp8 like RBP
b: 41 8b 06 mov eax,DWORD PTR [r14]
e: 41 8b 07 mov eax,DWORD PTR [r15]
11: 43 8b 04 e3 mov eax,DWORD PTR [r11+r12*8] # *can* be an index
Компиляторам нравится, когда все регистры могут использоваться для чего угодно, только ограничивая распределение регистров для нескольких особых случаев. Это то, что подразумевается под ортогональностью регистра.
Разыменование rbp может привести к ошибке #SS(сегмент стека).
Недавно у меня произошел сбой ядра Linux с "ошибкой сегмента стека".
crash> dmesg
[...]
stack segment: 0000 [#1] SMP
[...]
RIP: 0010:[<ffffffff8125fa8b>] lock_get_status+0x9b/0x3b0
RSP: 0018:ffff89954a317d90 EFLAGS: 00010282
[...]
RBP: 800000fa8c251867 R08: 0000000000001000 R09: 000000000000ffff
[...]
crash> dis lock_get_status+0x9b
0xffffffff8125fa8b <lock_get_status+0x9b>: mov 0x28(%rbp),%rax
Адрес памяти в rbp - это неканонический адрес. Это причина этой аварии. Из этого сбоя я узнал, что доступ к rbp неявно обращается к регистру сегмента ss, даже если rbp не используется в качестве базового указателя кадра стека.
Согласно Intel SDMv1 3.4.1 регистры общего назначения:
EBP - указатель на данные в стеке (в сегменте SS)