Почему 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, BMI2 mulx,
  • rbx: 8086 xlatb, cpuid используйте все четыре из EAX..EDX. 486 cmpxchg8b x86-64 cmpxchg16b, Компиляторы будут излучать cmpxchg8 за std::atomic<long long>::compare_exchange_weak и 16b для atomic<struct_16_bytes>, RBX имеет наименьшее неявное использование оригинальных 8, но lock cmpxchg16b это один из немногих компиляторов, которые будут фактически использоваться.
  • rsi / rdi: string ops, в том числе rep movsb которые некоторые компиляторы иногда встроены. (GCC также inlines rep 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)

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