Отсканируйте целое число и распечатайте интервал (1, целое число) в NASM

Я пытаюсь выучить язык ассемблера из Linux Ubuntu 16.04 x64. На данный момент у меня возникла следующая проблема: - отсканировать целое число n и распечатать числа от 1 до n.

Для n = 5 у меня должно получиться 1 2 3 4 5. Я пытался сделать это с помощью scanf и printf, но после ввода числа он выходит.

Код такой:

;nasm -felf64 code.asm && gcc code.o && ./a.out

SECTION .data
    message1: db "Enter the number: ",0
    message1Len: equ $-message1
    message2: db "The numbers are:", 0
    formatin: db "%d",0
    formatout: db "%d",10,0 ; newline, nul
    integer: times 4 db 0 ; 32-bits integer = 4 bytes

SECTION .text
    global main
    extern scanf
    extern printf

main:

    mov eax, 4
    mov ebx, 1
    mov ecx, message1
    mov edx, message1Len
    int 80h

    mov rdi, formatin
    mov rsi, integer
    mov al, 0
    call scanf
    int 80h

    mov rax, integer
    loop:
        push rax
        push formatout
        call printf
        add esp, 8
        dec rax
    jnz loop

    mov rax,0

ret

Я знаю, что в этом цикле у меня будет обратный вывод (5 4 3 2 1 0), но я не знал, как установить условие.

Я использую следующую команду:

nasm -felf64 code.asm && gcc code.o && ./a.out

Не могли бы вы помочь мне найти, в чем я ошибаюсь?


person lidia901    schedule 19.11.2017    source источник
comment
Почему вы push аргументы за printf в стек? Какой источник информации вы использовали для этого? (У меня есть подозрение, что вы меняете код / ​​учебник 32b на 64b, но это не будет работать так просто, это сложнее ... на данный момент, если у вас есть хороший ресурс 32b asm для обучения, было бы намного проще научить вы, как собрать 32-битный двоичный файл под 64-битным Linux и вместо этого работать с ним). ... либо так, либо ваш ресурс 64b некачественный, попробуйте какой-нибудь получше ...   -  person Ped7g    schedule 20.11.2017
comment
@ Ped7g Наверное, я перепутал 32b с 64b ... Я все изменил, но у меня все еще есть проблемы с функцией printf в цикле ... и я не могу найти к ней документацию. Это сложнее, чем я ожидал. : D   -  person lidia901    schedule 20.11.2017
comment
Можете ли вы отредактировать свой вопрос и показать вариант 32b исходного кода + командную строку, как вы его создаете? printf - один из наиболее сложных для правильного понимания, поскольку он имеет переменное количество аргументов, поэтому вам необходимо знать правильное соглашение о вызовах, помимо основ. Вы также можете проверить это руководство (ориентировано именно на nasm + libc + 32b и хорошо прокомментировано): csee.umbc.edu/portal/help/nasm/sample.shtml И если вы только начинаете со сборки, я бы полностью пропустил вызов функций libc и поигрался с чистым x86 инструкции (выполняя некоторую математику), проверяя значения только в отладчике.   -  person Ped7g    schedule 20.11.2017
comment
И есть также ссылка на образец 64b, но опять же, если вы только начинаете со сборки, я бы предложил придерживаться 32b (пока задействован вызов libc, для чистого asm x86-64 без внешних вызовов - 64b только крошечный немного сложнее, это само соглашение о вызовах, которое намного сложнее, чем 32b, вы должны также выравнивать стек перед каждым вызовом, и есть функция красной зоны и т. д.). Но возможность использовать отладчик для пошагового выполнения инструкций и проверки состояния регистров / флагов / памяти важна, гораздо важнее, чем вызов printf.   -  person Ped7g    schedule 20.11.2017
comment
Примерно сложнее, чем я ожидал - ну, в сборке у вас есть полный контроль над машиной, поэтому вы можете указать ей делать все, на что она способна. Это означает, что для каждого законного + требуемого действия у вас есть около дюжины действительных способов его написания и тысячи недействительных способов, которые действительно выглядели хорошей идеей, когда вы их писали. Вам нужно научиться быть абсолютно точным на каждом этапе, от формулирования того, чего вы хотите достичь, как вы хотите этого достичь и почему каждая инструкция в коде принадлежит именно этому, затем вам нужно научиться перечитывать это и сравнивать с реальность проверь в отладчике, потом исправь.   -  person Ped7g    schedule 20.11.2017
comment
@ Ped7g имеет ли значение, что у меня 64-битная ОС? Я так думал и поэтому начал с программ 64b.   -  person lidia901    schedule 20.11.2017
comment
Что ж ... насчет редактирования вопроса и показа варианта 32b источника ... Я больше не могу, потому что я изменил как все, и я больше не нахожу его. Но я могу показать фактическое состояние моей программы, если это поможет   -  person lidia901    schedule 20.11.2017
comment
Да, может, но 64-битный Linux обычно способен запускать 32-битные двоичные файлы с 32-битным уровнем совместимости (64-битный ubuntu WSL, упакованный в Windows 10, НЕ способен запускать 32-битные двоичные файлы), поэтому обычная установка 64-битного Linux, скорее всего, готова для создания + запуск + отлаживать двоичные файлы 32b (или вам нужно будет установить еще несколько пакетов, чтобы иметь поддержку 32b в gcc / etc). Обычный Linux Ubuntu 16.04 x64 определенно можно настроить для работы с 32b (я использую дистрибутив KDE neon на основе 16.04, легко проверяю nasm Q + A для 32b и 64b asm, используя edb-debugger, созданный из исходников из github ).   -  person Ped7g    schedule 20.11.2017
comment
stackoverflow.com/a/36901649/4271923 (хм, на самом деле это слишком много gcc +, как ориентировано, но поиск по этим строкам nasm 32b linux 64b должен дать вам что-то более сосредоточенное на nasm в нескольких ссылках)   -  person Ped7g    schedule 20.11.2017
comment
Большое спасибо, @ Ped7g! Ты очень добр! :) Попробую сначала выучить на 32бит.   -  person lidia901    schedule 20.11.2017
comment
насчет изучения 32b в первую очередь - ›не волнуйтесь, с точки зрения чистых инструкций x86 шаг от 32b до 64b невелик (еще несколько регистров, некоторые регистры недоступны, некоторые специальные правила использования регистров 32b, это почти все) . Просто соглашение о вызовах в системе 64b намного лучше (с точки зрения производительности) и сложнее, людям немного сложнее следовать (это не было важно при его проектировании, так как 99% кода создается компиляторами, а производительность было важно).   -  person Ped7g    schedule 20.11.2017
comment
Теперь я вспоминаю, что добавил к недавнему ответу полностью рабочий пример смешивания nasm 32b с clib printf: stackoverflow.com/questions/47362660/ ... не стесняйтесь спрашивать там, если что-то не понятно или работает для вас. (насчет длинных командных строк ... выглядит, вероятно, утомительно, но я использую текстовый редактор Kate с настройкой методов сборки, поэтому я не возражаю против этих длинных имен ... опять же, вы также можете сохранить эти команды в сценарии оболочки или даже сделать файл). К сожалению, я не удосужился добавить вариант 64b.   -  person Ped7g    schedule 20.11.2017
comment
И я помню это неправильно, это было наоборот, код C ++ вызывал ассемблер, и я даже намеренно пробовал соглашение fastcall ... так что дело не в вызове printf из сборки ... извините: D ... все же есть В Интернете много руководств, и я слишком устал, чтобы писать здесь полный ответ.   -  person Ped7g    schedule 20.11.2017
comment
@ Ped7g: В моем ответе на stackoverflow.com/a/36901649/4271923, который вы связали ранее, есть раздел NASM. Но OP использует 64-битные регистры и соглашение о вызовах, поэтому, возможно, настоящая ошибка заключается в использовании int 0x80 в 64-битном режиме (stackoverflow.com / questions / 46087730 /). На самом деле проблема не в том, следует ли использовать системные вызовы или функции библиотеки stdio. И что еще хуже, использование call scanf / int 80h, поэтому номер системного вызова определяется возвращаемым значением scanf !!!!   -  person Peter Cordes    schedule 20.11.2017


Ответы (2)


Есть несколько проблем:
1. Параметры printf, о которых говорилось в комментариях. В x86-64 первые несколько параметров передаются в регистрах.
2. printf не сохраняет значение eax.
3. Стек не выровнен.
4. rbx используется без сохранения вызывающего значение.
5. Адрес integer загружается вместо его значения.
6. Поскольку printf является функцией varargs, для eax необходимо установить значение 0 перед вызовом.
7. Поддельный int 80h после вызов scanf.

Я повторю всю функцию, чтобы показать необходимые изменения в контексте.

main:
    push rbx           ; This fixes problems 3 and 4.

    mov eax, 4
    mov ebx, 1
    mov ecx, message1
    mov edx, message1Len
    int 80h

    mov rdi, formatin
    mov rsi, integer
    mov al, 0
    call scanf

    mov ebx, [integer] ; fix problems 2 and 5
    loop:
        mov rdi, formatout   ; fix problem 1
        mov esi, ebx
        xor eax, eax   ; fix problem 6
        call printf
        dec ebx
    jnz loop

    pop rbx            ; restore caller's value
    mov rax,0

ret

P.S. Чтобы он вел счет вверх, а не вниз, измените цикл следующим образом:

    mov ebx, 1
    loop:
        <call printf>
        inc ebx
        cmp ebx, [integer]
    jle loop
person prl    schedule 20.11.2017
comment
32-битный int 80h ABI для sys_write в 64-битном коде технически не ошибочен, но 64-битный syscall был бы намного лучше. (Кроме того, вы не упомянули проблему 0, которая фактически приводила к завершению программы: int 80h с eax = возвращаемое значение scanf = 1 = __NR_exit. (См. Мой ответ). - person Peter Cordes; 20.11.2017
comment
@PeterCordes еще одна проблема - это смешивание функций ввода-вывода clib вместе с sys_write ... Я имею в виду, я слишком устал, чтобы произвести полное исправление, так как все пошло не так, поэтому я скорее попытался просто предложить более мелкие шаги для начала. (на самом деле, я боюсь, что в исправлении так много изменилось, что его будет вообще трудно понять, не предприняв сначала эти более мелкие шаги) - person Ped7g; 20.11.2017
comment
@ Ped7g: это действительно безопасно, если вы sys_write до используете какие-либо функции библиотеки stdio, которые могут буферизовать ввод-вывод вместо того, чтобы делать это перед возвратом. Но да, этого определенно стоит предостеречь. - person Peter Cordes; 20.11.2017
comment
@prl Думаю, в конце должен был быть цикл jge, верно? Кроме того, большое вам спасибо! - person lidia901; 20.11.2017
comment
Да, я написал сравнение задом наперед. Опасность, что вас заставят так часто читать синтаксис AT&T. Я исправлю это, перевернув сравнение, а не условную ветвь. - person prl; 20.11.2017

Вы правильно вызываете scanf, используя соглашение о вызовах x86-64 System V. Возвращаемое значение остается в eax. После успешного преобразования одного операнда (%d) он возвращается с eax = 1.

... correct setup for scanf, including zeroing AL.

call scanf    ; correct
int 80h       ; insane: system call with eax = scanf return value

Затем вы запускаете int 80h, который выполняет 32-разрядный системный вызов устаревшего ABI с использованием eax=1 в качестве кода для определения какой системного вызова. (см. Что произойдет, если вы используете 32-битный int 0x80 Linux ABI в 64-битном коде?).

eax=1 / int 80h - это sys_exit в Linux. (unistd_32.h имеет __NR_exit = 1). Используйте отладчик; это показало бы вам, какая инструкция вызвала выход из вашей программы.

В вашем заголовке (до того, как я его исправил) говорилось, что у вас ошибка сегментации, но я тестировал на своем рабочем столе x86-64, и это не так. Он завершается чисто с помощью системного вызова int 80h exit. (Но в коде, который выполняет segfault, используйте отладчик, чтобы узнать, какая инструкция.) strace неправильно декодирует int 0x80 системные вызовы в 64-битных процессах < / a>, используя 64-битные syscall номера вызовов из unistd_64.h, а не 32-битные unistd_32.h номера вызовов.


Ваш код был близок к работе: вы правильно используете int 0x80 32-битный ABI для sys_write и передаете ему только 32-битные аргументы. (Аргументы указателя умещаются в 32 бита, потому что статический код / ​​данные всегда помещаются в 2 ГБ виртуального адресного пространства в модели кода по умолчанию на x86-64. Именно по этой причине вы можете использовать компактные инструкции, такие как mov edi, formatin, для размещения адресов в регистрах, или использовать их как непосредственные или подписанные смещения rel32.)

OTOH Я думаю, вы сделали это не по той причине. И, как указывает @prl, вы забыли поддерживать выравнивание стека по 16 байт.

Кроме того, смешивание системных вызовов с функциями C stdio обычно является плохой идеей. Stdio использует внутренние буферы вместо того, чтобы всегда выполнять системный вызов при каждом вызове функции, поэтому что-то может отображаться не по порядку, или read может ждать ввода пользователя, когда в буфере stdio уже есть данные для stdin.


Ваша петля также разорвана несколькими способами. Кажется, вы пытаетесь вызвать printf с 32-битным соглашением о вызовах (аргументы в стеке).

Даже в 32-битном коде это не работает, потому что значение возврата printf находится в eax. Итак, ваш цикл бесконечен, потому что printf возвращает количество напечатанных символов. Это как минимум два из строки формата %d\n, поэтому dec rax / jnz всегда будет прыгать.

В x86-64 SysV ABI вам нужно обнулить al перед вызовом printfxor eax,eax), если вы не передали никаких аргументов FP в регистры XMM. Вы также должны передавать аргументы в rdi, rsi, ..., как для scanf.

Вы также add rsp, 8 после вставки двух 8-байтовых значений, поэтому стек постоянно увеличивается. (Но вы никогда не вернетесь, поэтому возможный segfault будет при переполнении стека, а не при попытке возврата, если rsp не указывает на адрес возврата.)


Решите, создаете ли вы 32-битный или 64-битный код, и копируйте / вставляйте только примеры для режима и ОС, на которые вы нацеливаетесь. (Обратите внимание, что 64-битный код может и часто использует в основном 32-битные регистры.)

См. Также Сборка 32-разрядной версии двоичные файлы в 64-разрядной системе (набор инструментов GNU) (который включает раздел NASM с удобным asm-link скриптом, который собирает и связывает в статический двоичный файл). Но поскольку вы пишете main вместо _start и используете функции libc, вам следует просто связать с gcc -m32 (если вы решите использовать 32-битный код вместо замены 32-битных частей вашей программы 64-битным вызовом функций и соглашения о системных вызовах).

См. Каковы соглашения о вызовах для системных вызовов UNIX и Linux на i386 и x86-64.

person Peter Cordes    schedule 20.11.2017