#loops #assembly #x86 #nasm
#циклы #сборка #x86 #nasm
Вопрос:
Ладно, короче говоря, я изучаю сборку и пытаюсь заставить цикл выводить символы ascii «0» — «9». Итак, я выполнил все основы, которые я видел в примерах, например, сохранил состояния регистров с помощью pushad и popad, выделил место в стеке и убедился, что я оставляю все так, как они начинались. Итак, я справился с этим небольшим примером:
;
; Hello_World.asm
; (NASM Syntax, Windows)
extern _printf
section .text
_main:
pushad ; save register states
push ebp ; save old stack
mov ebp, esp ; prepare new stack
sub esp, 1*4 ; allocate 4 bytes
mov byte [esp 0], 48 ; add ascii '0' to stack
mov byte [esp 1], 0 ; add ascii NULL terminator to stack
push esp; ; push the string in the stacks refrence
call _printf ; call printf()
add esp, 4 ; pop string refrence
add esp, 1*4 ; deallocate 4 bytes
mov esp, ebp ; close this stack
pop ebp ; restore old stack
popad ; restore register states
ret ; leave this function
Это работает, выводится ‘0’, но это немного на всякий случай. Я попытался добавить в нее цикл, но там все просто разваливается. Я читал, что код операции ‘loop’ должен уменьшать регистр ECX и возвращаться к параметру label, если ECX > 0, однако, я не думаю, что у меня это получилось.
Итак, я добавляю несколько строк и получаю это:
;
; Hello_World.asm
;
extern _printf
global _main
section .text
_main:
pushad ; save register states
push ebp ; save old stack
mov ebp, esp ; prepare new stack
sub esp, 1*4 ; allocate 4 bytes
mov byte [esp 0], 48 ; add ascii '0' to stack
mov byte [esp 1], 0 ; add ascii NULL terminator to stack
mov ecx, 9 ; set loop counter to 9
aLoop:
inc byte [esp 0] ; increment ascii character
push esp; ; push the string in the stacks refrence
call _printf ; call printf()
add esp, 4 ; pop string refrence
loop aLoop ; loop back to aLoop if ecx > 0
add esp, 1*4 ; deallocate 4 bytes
mov esp, ebp ; close this stack
pop ebp ; restore old stack
popad ; restore register states
ret ; leave this function
Ну, теперь все идет как по маслу. Я запускаю его в командной строке и слышу этот звуковой сигнал в своих наушниках, и он перебирает каждый символ ascii, выводя их все. Итак, примерно через 5 секунд летающих символов, я предполагаю, что что-то переполняется, и это просто вылетает.
Я довольно новичок в сборке (сегодня мой первый день реального кодирования), и я не вижу, что происходит не так. Не мог бы кто-нибудь, пожалуйста, объяснить, как я мог бы лучше реализовать цикл?
Заранее благодарю! -Джейсон
Ответ №1:
Сохраняет ли подпрограмма «_printf» содержимое ECX? Если нет, это может быть вашей проблемой. Попробуйте сохранить его во время вызова.
Комментарии:
1. После быстрой проверки это исправлено! Итак, теперь, после быстрого ввода ecx в стек и небольших манипуляций для сохранения моей ссылки на строку, все работает идеально! Большое вам спасибо, это проблема, на которую мне придется обратить внимание в будущем.
2. @Jason тогда отметьте этот ответ как решение вашего вопроса.
3.
loop
происходит медленно. Кроме того, вам следует избегать использования push / pop в этом случае. В качестве счетчика просто используйте ebx, который сохраняется при вызове. Условием цикла было бы:DEC EBX; JNZ aLoop
Ответ №2:
хорошо, хорошо, то, что вы описываете, это то, что цикл по какой-то причине не завершается. Это означает, что проблема в значительной степени должна быть здесь:
add esp, 4 ; pop string refrence
loop aLoop ; loop back to aLoop if ecx > 0
Это звучит как задание для отладчика: что на самом деле происходит с ecx
?
Что ж, я отмечаю, что вы устанавливаете ecx
на 9
. Затем вы добавляете 4
в esp
. Когда ты меняешься ecx
? (Да, я знаю, что это должно происходить в loop
инструкции, но если бы это работало, вы бы не спрашивали. Что на самом деле происходит с ecx
?)
Кстати, звуковой сигнал подается просто: когда вы перебираете все символы ASCII, вы нажимаете ASCII 0x07, символ BEL.
Комментарии:
1. Да, мне, вероятно, следует найти хороший отладчик следующим (есть предложения?). Но я понимаю, что инструкция ‘loop’ должна уменьшать регистр ECX, а затем выполнять сравнение, но, похоже, этого не происходит. Может быть, мне было бы лучше переключиться на обычные сравнения и переходы…
2. у вас там есть вызов _printf (кстати, вы уверены, что последовательность вызова правильная?) таким образом, вы могли бы попробовать напечатать значение ecx. Что касается отладчиков, я не знаю вашей среды. У вас есть nasm, поэтому я предполагаю, что это, вероятно, Windows; у вас есть Visual Studio?
Ответ №3:
aLoop:
inc byte [esp 0] ; increment ascii character
push ecx; ; save ecx
push esp; ; push the string in the stacks refrence
call printf ; call printf()
add esp, 4 ; pop string refrence
pop ecx
loop aLoop ; loop back to aLoop if ecx > 0
Регистры, сохраняемые вызывающим абонентом, являются eax, ecx, edx. Вызываемой подпрограмме разрешено изменять эти регистры. Ищите сохраненные регистры вызывающего абонента и сохраненные регистры вызываемого абонента с помощью поисковой системы. Должен предоставить вам более подробную информацию.
Комментарии:
1. Ах, но вы видите, что, нажимая ecx перед ссылкой на строку, esp теперь указывает на что-то другое! Несмотря на небольшие манипуляции, все это работает. Хотя спасибо!
2. Последний раз я возился со сборкой x86 20 лет назад, и подобные вещи напоминают мне, что я не скучаю по этому.
3. Aph, спасибо, что сообщили мне, какие регистры следует сохранить, это окажет большую помощь в будущем. Майк ДеСимоне, я думаю, что такие мелочи, как это, делают это увлекательным, я делаю это ради вызова, но да, это утомительно.
4. О, не поймите меня неправильно, какое-то время это весело. У меня это было на занятиях продолжительностью в семестр, и мы только что выполнили 8086 (без E__ регистров), а 386 только что вышел, так что да, ближний и дальний указатели. Но в то же время я написал 68000 assembly на своем Mac для развлечения и предпочитаю регистровые файлы сверхспециализированным регистрам, которые требуют много перемещений данных для более сложных алгоритмов.