Это немного странно, но сегодня я возился с ассемблером GNU (я хочу хотя бы прочитать синтаксис) и пытался заставить этот небольшой надуманный пример работать. А именно, я просто хочу перейти от 0 до 100, печатая числа все время. Итак, через несколько минут я придумаю это:
# count.s: print the numbers from 0 to 100.
.text
string: .asciz "%d\n"
.globl _main
_main:
movl $0, %eax # The starting point/current value.
movl $100, %ebx # The ending point.
_loop:
# Display the current value.
pushl %eax
pushl $string
call _printf
addl $8, %esp
# Check against the ending value.
cmpl %eax, %ebx
je _end
# Increment the current value.
incl %eax
jmp _loop
_end:
Все, что я получаю от этого, это 3 печатных снова и снова. Как я уже сказал, просто небольшой надуманный пример, так что не волнуйтесь об этом, это не проблема жизни или смерти.
(Форматирование немного запутано, но ничего особенного).
Вы не можете доверять тому, что любая вызванная процедура делает с любым из регистров. Либо поместите регистры в стек и вытащите их обратно после вызова printf, либо сохраните значения инкремента и конечной точки в памяти и считывайте / записывайте в регистры по мере необходимости.
Я надеюсь, что следующие работы. Я предполагаю, что у pushl есть эквивалентный popl, и вы можете поместить дополнительную пару чисел в стек.
# count.s: print the numbers from 0 to 100.
.text
string: .asciz "%d\n"
.globl _main
_main:
movl $0, %eax # The starting point/current value.
movl $100, %ebx # The ending point.
_loop:
# Remember your registers.
pushl %eax
pushl %ebx
# Display the current value.
pushl %eax
pushl $string
call _printf
addl $8, %esp
# reinstate registers.
popl %ebx
popl %eax
# Check against the ending value.
cmpl %eax, %ebx
je _end
# Increment the current value.
incl %eax
jmp _loop
_end:
pusha
и popa
не поддерживаются в 64-битном режиме. Я не слишком знаком с _printf, но может ли быть так, что он модифицирует eax? Printf должен вернуть количество напечатанных символов, которое в данном случае равно двум: '0' и '\ n'. Я думаю, что он возвращает это в eax, и когда вы увеличиваете его, вы получаете 3, то есть то, что вы продолжаете печатать. Возможно, вам лучше использовать другой регистр для счетчика.
Натан на правильном пути. Вы не можете предполагать, что значения регистра не будут изменены после вызова подпрограммы. На самом деле, лучше предположить, что они будут изменены, иначе подпрограмма не сможет выполнить свою работу (по крайней мере, для архитектур с малым количеством регистров, таких как x86). Если вы хотите сохранить значение, вы должны сохранить его в памяти (например, поместить его в стек и отследить его местоположение).
Вам нужно будет сделать то же самое для любой другой переменной, которая у вас есть. Использование регистров для хранения локальных переменных в значительной степени зарезервировано для архитектур с достаточным количеством регистров для их поддержки (например, EPIC, amd64 и т. Д.).
Хорошо написанные функции обычно помещают все регистры в стек, а затем извлекают их, когда они сделаны, чтобы они оставались неизменными во время функции. Исключением будет eax, который содержит возвращаемое значение. Библиотечные функции, такие как printf, скорее всего написаны таким образом, поэтому я бы не стал делать то, что предлагает Клин:
Вам нужно будет сделать то же самое для любой другой переменной, которая у вас есть. Использование регистров для хранения локальных переменных в значительной степени зарезервировано для архитектур с достаточным количеством регистров для их поддержки (например, EPIC, amd64 и т. Д.).
На самом деле, насколько я знаю, компиляторы обычно компилируют функции таким образом, чтобы точно решить эту проблему.
@seanyboy, ваше решение излишне. Все, что нужно, это заменить eax другим регистром, например ecx.
Вы можете переписать его, например, чтобы использовать регистры, которые не должны изменяться %ebp
. Просто убедитесь, что вы положили их в стек в начале, и вытолкните их в конце вашей программы.
# count.s: print the numbers from 0 to 100.
.text
string: .asciz "%d\n"
.globl _main
_main:
push %ecx
push %ebp
movl $0, %ecx # The starting point/current value.
movl $100, %ebp # The ending point.
_loop:
# Display the current value.
pushl %ecx
pushl $string
call _printf
addl $8, %esp
# Check against the ending value.
cmpl %ecx, %ebp
je _end
# Increment the current value.
incl %ecx
jmp _loop
_end:
pop %ebp
pop %ecx
Вы можете безопасно использовать регистры, которые «сохраняются вызываемыми» без необходимости сохранять их самостоятельно. На x86 это edi, esi и ebx; другие архитектуры имеют больше.
Они описаны в ссылках ABI: http://math-atlas.sourceforge.net/devel/assembly/
xorl %eax, %eax
полностью эквивалентноmovl $0, %eax
и занимает на 3 байта меньше. Просто говорю. :)