Сборка x86: циклы!

#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 для развлечения и предпочитаю регистровые файлы сверхспециализированным регистрам, которые требуют много перемещений данных для более сложных алгоритмов.