2015-12-05 2 views
6

Я написал простую программу для сборки:Почему значение EDX перезаписывается при вызове printf?

section .data 
str_out db "%d ",10,0 
section .text 
extern printf 
extern exit 
global main 
main: 

MOV EDX, ESP 
MOV EAX, EDX 
PUSH EAX 
PUSH str_out 
CALL printf 
SUB ESP, 8 ; cleanup stack 
MOV EAX, EDX 
PUSH EAX 
PUSH str_out 
CALL printf 
SUB ESP, 8 ; cleanup stack 
CALL exit 

Я ассемблер NASM и GCC, чтобы связать объектный файл в исполняемый файл на Linux.

По существу, эта программа сначала помещает значение указателя стека в регистр EDX, а затем дважды печатает содержимое этого регистра. Однако после второго вызова printf значение, напечатанное на stdout, не соответствует первому.

Такое поведение кажется странным. Когда я заменяю любое использование EDX в этой программе с помощью EBX, вычисленные целые числа идентичны ожидаемым. Я могу только указать, что EDX перезаписывается в какой-то момент во время вызова функции printf.

Почему это так? И как я могу убедиться, что регистры, которые я использую в будущем, не конфликтуют с функциями C lib?

+2

Этот человек получил меня впервые много лет назад. Ответ, который вы приняли, является правильным, но опускает 'ebp' и' esp' в качестве спасения. Эти двое, кажется, не говорят, но вы можете технически испортить это. Добро пожаловать в сборку! – sqykly

+0

@sqykly Спасибо. Это, безусловно, намного менее прощающий, чем языки более высокого уровня, к которым я привык. Но я не потерплю поражения! :) – Jake

+0

Ответьте на столько вопросов, сколько я делаю, и вы начнете задумываться об этом. – sqykly

ответ

11

Согласно x86 ABI, EBX, ESI, EDI и EBP являются вызываемыми сохранение регистров и EAX, ECX и EDX являются регистрами абонентов сохранения.

Это означает, что функции могут свободно использовать и уничтожать предыдущие значения EAX, ECX и EDX. По этой причине сохраните значения EAX, ECX, EDX перед вызовом функций, если вы не хотите, чтобы их значения изменялись. Это то, что означает «звонок-спасение».

Или, лучше, используйте другие регистры для значений, которые вам по-прежнему понадобятся после вызова функции. push/pop EBX в начале/конце функции намного лучше, чем push/pop EDX внутри цикла, который выполняет вызов функции. Когда это возможно, используйте регистры с затуманенными вызовами для временных, которые не нужны после вызова. Значения, которые уже хранятся в памяти, поэтому им не нужно писать до повторного чтения, также дешевле разлить.


С EBX, ESI, EDI и EBP являются вызываемая сохранение регистров, функции должны восстановить значения к исходным для любого из тех, кого они модифицировать, прежде чем вернуться.

ESP также спасен, но вы не можете испортить это, если вы не скопируете обратный адрес где-нибудь. Несоответствие вызова/ret ужасно для производительности, потому что современные процессоры используют предиктор обратного адреса.

+2

'EBP' также является спасением! –

+0

Это не * это * твердый. 'ret 8' из функции без параметров перепутала' esp'. Представьте, что какая-либо оптимизация хвостового звонка пошла не так. – sqykly

+0

Или! Misapplied cdecl или stdcall. – sqykly

5

ABI для целевой платформы (например, 32bit x86 Linux) определяет, какие регистры могут использоваться функциями без сохранения. (т. е. если вы хотите, чтобы они сохранялись во время разговора, вы должны сделать это сами).

Ссылка на ABI документы для Windows, и не-Window, 32 и 64-битного, в https://stackoverflow.com/tags/x86/info

Имея некоторые регистры, которые не сохраняются вызовы (доступны как регистры нуля) означает функция может быть меньше. Простые функции могут часто избегать выполнения каких-либо push/pop сохранения/восстановления. Это сокращает количество инструкций, что приводит к более быстрому коду.

Важно, чтобы некоторые из них: нужно было разлить все состояние в память через вызовы, раздували бы код функций, отличных от листьев, и замедляли бы работу вверх. в случаях, когда вызываемая функция не касалась всех регистров.

+0

Этот последний абзац звучит смешно. Если вам нужно было сохранить все состояние в памяти, функции листа будут точно такими, какие они раздувают. Нелистные функции по существу раздуваются в любом случае, поскольку они оба являются вызывающим и вызываемым. –

+0

@ DanielStevens: в последнем абзаце говорится о случае, когда все регистры сбиты, например, xmm regs находятся в 64-битном ABI SysV. Функции листа не должны ничего сохранять. Кроме того: функции, не связанные с листом, часто имеют достаточно регистры с сохраненными номерами, чтобы хранить несколько ключевых частей состояния в regs, и в основном использовали регистры сохранения звонящего в качестве пространства скреста для вычисления параметров функции-вызова. Вам нужно только сохранить/восстановить реестр, если вам все еще нужно после вызова функции. Как правило, вам нужна пара, например, счетчик циклов и указатель или два, но могут перезагружать другие вещи. –

+0

Но вы говорили о том, чтобы разлить все состояние в память, не позволяя ему сбиваться. –

Смежные вопросы