NASM сборка в то время как счетчик циклов


Я пишу цикл while в сборке для компиляции в терминале Linux с nasm и gcc. Программа сравнивает x и y до тех пор, пока y >= x и сообщает количество циклов в конце. Вот код:

segment .data

out1    db "It took ", 10, 0
out2    db "iterations to complete loop. That seems like a lot.", 10, 0
x       db 10
y       db 2
count   db 0

segment .bss

segment .text

global main
extern printf

main:
    mov    eax, x
    mov    ebx, y
    mov    ecx, count
    jmp    lp         ;jump to loop lp

lp:
    cmp    ebx, eax   ;compare x and y
    jge    end        ;jump to end if y >= x
    inc    eax        ;add 1 to x
    inc    ebx        ;add 2 to y
    inc    ebx
    inc    ecx        ;add 1 to count
    jp     lp         ;repeat loop

end:

    push    out1      ;print message part 1
    call    printf

    push    count     ;print count
    call    printf

    push    out2      ;print message part 2
    call    printf

    ;mov    edx, out1               ;
    ;call   print_string            ;
                                    ;
    ;mov    edx, ecx                ;these were other attempts to print
    ;call   print_int               ;using an included file
                                    ;
    ;mov    edx, out2               ;
    ;call   print_string            ;

Это компилируется и запускается в терминале с помощью:

nasm -f elf test.asm
gcc -o test test.o
./test

Вывод терминала выглядит следующим образом:

It took
iterations to complete loop. That seems like a lot.
Segmentation fault (core dumped)
Я не вижу ничего плохого в этой логике. Я думаю, что это синтаксический, но мы только начали изучать ассемблер, и я пробовал все виды различных синтаксисов, таких как скобки вокруг переменных и использование ret в конце сегмента, но ничего не работает. Я также искал ошибки сегментации, но не нашел ничего действительно полезного. Любая помощь будет оценена, потому что я абсолютный новичок.
2 4

2 ответа:

Причина сбоя, вероятно, в том, что ваша функция main не имеет инструкции ret. Также обязательно установите eax в 0, чтобы сигнализировать об успехе:

xor     eax, eax ; or `mov eax, 0` if you're more comfortable with that
ret

Кроме того, глобальные переменные обозначают указатели, а не значения. mov eax, x задает eax адрес x. Если вы хотите, чтобы что-то произошло (или не использовать глобальные переменные), вам нужно написать туда ответ.

Наконец, вы вызываете printf с единственным нестроковым аргументом:

push    count     ;print count
call    printf

Первым аргументом должен быть форматируйте строку, например "%i". Здесь count является указателем на нулевой байт,поэтому вы ничего не получите. С моей головы, вы должны попробовать это:

out3    db "%i ", 0

; snip

push    ecx
push    out3
call    printf

Я думаю, что ваша проблема может заключаться только в том, что вы ссылаетесь на адреса ваших констант, а не на их внутреннюю ценность. Нужно думать о метке в nasm как о указателе, а не как о значении. Для доступа к нему вам просто нужно использовать [label]:

 segment .data
     x      dw 42
 segment .text
     global main
     extern printf
 main:
     mov    eax, x
     push   eax
     call   printf   ; will print address of x (like doing cout<<&x in C++)
     mov    eax, [x]
     push   eax
     call   printf   ; will print 42
     sub    esp, 8
     xor    eax, eax
     ret

PS: Я не думаю, что кто-то упоминал об этом, но летучие регистры очень часто изменяются при вызове внешнего кода (C или C++ или другого), так как при компиляции те функции, которые вы используете, "переводятся" в сборку и затем связываются с вашим asm файл. ПК не является человеком, поэтому он не различает, что было написано на высоком или низком уровне, процессор просто читает опкоды и операнды, хранящиеся в регистрах и памяти, следовательно, почему внешняя функция при использовании языка низкого уровня (call printf) собирается изменить (или нет! всегда зависит от компилятора и архитектуры) регистры, которые вы также используете. Для решения этой проблемы существуют различные решения:

  1. Вы проверяете, какие регистры не изменяются, используя gcc your_c_file.c -S и тогда в файле your_c_file.sбудет находиться заранее подготовленный ассемблерный код, созданный вашим компилятором из вашего файла C. (Как правило, довольно трудно понять, что есть что, и если вы собираетесь использовать этот метод, проверьте искажение имен, чтобы увидеть, как имена функций будут изменены.)

  2. Поместите все регистры, которые вы хотите сохранить, в стек, а затем после вызова поместите их обратно в свои регистры, имея в виду метод LIFO.

  3. используйте инструкции PUSHA и POPA, которые нажимают или хлопают все регистры соответственно.

Это руководство NASM Глава 3, которая объясняет основы языка для использования: http://www.csie.ntu.edu.tw/~comp03/nasm/nasmdoc3.html

Надеюсь, вам удалось ее решить.