Сборка x86 - Почему [e]bx сохраняется в соглашениях о вызовах?

Я заметил, что многие соглашения о вызовах настаивают на сохранении [e] bx для вызываемого.

Теперь я могу понять, почему они сохранили что-то вроде [e] sp или [e] bp, так как это может испортить стек вызываемого. Я также могу понять, почему вы можете захотеть сохранить [e] si или [e] di, поскольку это может привести к нарушению строковых инструкций вызываемого, если они не будут особенно осторожны.

Но [e] bx? Что же такого важного в [e] bx? Что делает [e] bx настолько особенным, что несколько соглашений о вызовах настаивают на том, чтобы оно сохранялось при вызовах функций?

Есть ли какая-то тонкая ошибка / ошибка, которая может возникнуть из-за того, что вы возитесь с [e] bx?

Оказывает ли изменение [e] bx как-то большее влияние на вызываемого, чем, например, изменение [e] dx или [e] cx?

Я просто не понимаю, почему так много соглашений о вызовах выделяют [e] bx для сохранения.

3 ответа

Решение

Не все регистры являются хорошими кандидатами на сохранение:

no (e)ax -- Implicitly used in some instructions; Return value
no (e)dx -- edx:eax is implicity used in cdq, div, mul and in return values

   (e)bx -- generic register, usable in 16-bit addressing modes (base)
   (e)cx -- shift-counts, used in loop, rep

   (e)si -- movs operations, usable in 16-bit addressing modes (index)
   (e)di -- movs operations, usable in 16-bit addressing modes (index)

Must (e)bp -- frame pointer, usable in 16-bit addressing modes (base)
Must (e)sp -- stack pointer, not addressable in 8086 (other than push/pop)

Глядя на таблицу, у двух регистров есть веские основания для сохранения, а у двух - нет причин для сохранения. Накопитель = (e)ax, например, является наиболее часто используемым регистром из-за короткого кодирования. SI,D Я создаю пару логических регистров - на REP MOVS и других строковых операциях они оба удаляются.

В парадигме сбережений вызывающего / вызывающего абонента в полтора раза обсуждение в основном будет идти, только если bx/cx предпочтительнее si/di. В других соглашениях о вызовах могут быть выбраны только EDX,EAX и ECX.

У EBX есть несколько неясных неявных применений, которые все еще актуальны в современном коде (например, CMPXGH8B / CMPXGH16B), но это наименее специальный регистр в 32/64-битном коде.

EBX делает хороший выбор для регистра, сохраняющего вызов, потому что редко функция нуждается в сохранении / восстановлении EBX, потому что им нужен EBX, а не какой-либо энергонезависимый регистр. Как указывает ответ Бретта Хейла, это делает EBX отличным выбором для указателя глобальной таблицы смещений (GOT) в ABI, которым он нужен.

В 16-битном режиме режимы адресации были ограничены (любое подмножество) [BP|BX + DI|SI + disp8/disp16]), так что BX там определенно особенный.

Одна из главных причин, конечно же, для i386 ELF ABI, заключается в том, что ebx содержит адрес регистра таблицы глобальных смещений (GOT) для кода, независимого от позиции (PIC). См. 3-35 спецификации для деталей. Это было бы крайне вредно, если бы, скажем, код разделяемой библиотеки должен был восстанавливать GOT после каждого возврата вызова функции.

Это компромисс между сохранением ни одного из регистров и сохранением их всех. Можно было бы предложить либо сохранить ни одного, либо сохранить все, но крайняя причина - неэффективность, вызванная копированием содержимого в память (стек). Выбор, позволяющий сохранять некоторые регистры, а некоторые нет, снижает среднюю стоимость вызова функции.

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