Pindai bilangan bulat dan cetak interval (1, bilangan bulat) di NASM

Saya mencoba mempelajari bahasa assembly dari Linux Ubuntu 16.04 x64. Untuk saat ini saya memiliki masalah berikut: - memindai bilangan bulat n dan mencetak angka dari 1 hingga n.

Untuk n = 5 saya harusnya 1 2 3 4 5. Saya mencoba melakukannya dengan scanf dan printf tetapi setelah saya memasukkan nomornya, keluar.

Kodenya adalah:

;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

Saya sadar bahwa dalam loop ini saya akan mendapatkan output terbalik (5 4 3 2 1 0), tetapi saya tidak tahu cara mengatur kondisinya.

Perintah yang saya gunakan adalah sebagai berikut:

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

Bisakah Anda membantu saya menemukan kesalahan saya?


person lidia901    schedule 19.11.2017    source sumber
comment
Mengapa Anda push berargumentasi untuk printf ke tumpukan? Sumber informasi apa yang Anda gunakan untuk melakukan hal seperti ini? (Saya curiga Anda mengubah kode/tutorial 32b menjadi 64b, tetapi itu tidak akan berhasil, ini lebih rumit... untuk saat ini, jika Anda memiliki sumber daya 32b asm yang bagus untuk belajar, akan lebih mudah untuk mengajar Anda bagaimana membangun biner 32b di bawah linux 64b dan bekerja dengannya). ... baik itu, atau sumber daya 64b Anda berkualitas rendah, dan coba yang lebih baik...   -  person Ped7g    schedule 20.11.2017
comment
@ Ped7g Mungkin saya bingung 32b dengan 64b... Saya memodifikasi semuanya tetapi saya masih memiliki masalah dengan fungsi printf dalam satu lingkaran... dan saya tidak dapat menemukan dokumentasi untuk itu. Ini lebih sulit dari yang saya harapkan. :D   -  person lidia901    schedule 20.11.2017
comment
Bisakah Anda mengedit pertanyaan Anda, dan menunjukkan varian 32b dari sumber + baris perintah bagaimana Anda membuatnya? printf adalah salah satu yang lebih rumit untuk diperbaiki, karena memiliki jumlah argumen yang bervariasi, jadi Anda perlu mengetahui konvensi pemanggilan yang tepat di luar dasar. Anda juga dapat memeriksa tutorial ini (tampaknya menargetkan persis nasm+libc+32b dan tampaknya mendapat komentar yang baik): csee.umbc.edu/portal/help/nasm/sample.shtml Dan jika Anda baru memulai perakitan, saya akan melewatkan pemanggilan fungsi libc sepenuhnya, dan bermain-main dengan x86 murni instruksi (mengerjakan beberapa matematika), memeriksa nilai di debugger saja.   -  person Ped7g    schedule 20.11.2017
comment
Dan ada juga tautan sampel 64b, tetapi sekali lagi, jika Anda baru memulai perakitan, saya sarankan untuk tetap menggunakan 32b (selama panggilan libc terlibat, untuk asm x86-64 murni tanpa panggilan eksternal - 64b hanya kecil sedikit lebih rumit, konvensi pemanggilan itu sendiri yang jauh lebih rumit daripada 32b, Anda harus menjaga tumpukan tetap selaras sebelum setiap panggilan, dan ada fitur zona merah, dll.). Namun kemampuan menggunakan debugger untuk melangkahi instruksi dan memverifikasi status register/bendera/memori sangatlah penting, jauh lebih penting, daripada memanggil printf.   -  person Ped7g    schedule 20.11.2017
comment
Tentang lebih sulit dari yang saya harapkan - baiklah, dalam perakitan Anda memiliki kendali penuh atas mesin, sehingga Anda dapat memerintahkannya untuk melakukan apa pun, sesuai kemampuannya. Artinya, untuk setiap tindakan hukum+yang diinginkan Anda memiliki sekitar selusin cara valid untuk menulisnya, dan ribuan cara tidak valid, yang sepertinya merupakan ide bagus saat Anda menulisnya. Anda perlu belajar untuk benar-benar tepat dalam setiap langkah, mulai dari merumuskan apa yang ingin Anda capai, bagaimana Anda ingin mencapainya, dan mengapa setiap instruksi dalam kode ada di sana, lalu Anda perlu belajar membaca ulang dan membandingkannya dengan realitas periksa di debugger, lalu perbaiki.   -  person Ped7g    schedule 20.11.2017
comment
@ Ped7g apakah penting jenis OS saya 64bit? Saya pikir begitu dan itulah mengapa saya memulai dengan program 64b.   -  person lidia901    schedule 20.11.2017
comment
Nah.. tentang mengedit pertanyaan dan menampilkan varian sumber 32b.. Saya tidak bisa lagi karena saya memodifikasi seperti semuanya dan saya tidak menemukannya lagi. Tapi saya bisa menunjukkan keadaan sebenarnya dari program saya jika itu membantu   -  person lidia901    schedule 20.11.2017
comment
Ya, mungkin saja, tetapi linux 64b biasanya mampu menjalankan binari 32b dengan lapisan kompatibilitas 32b (WSL ubuntu 64b yang dikemas dalam windows 10 TIDAK mampu menjalankan binari 32b), jadi instalasi linux 64b biasa kemungkinan besar siap untuk diproduksi+dijalankan +debug biner 32b (atau Anda harus menginstal beberapa paket lagi untuk mendapatkan dukungan 32b di gcc/etc). Linux Ubuntu 16.04 x64 biasa pasti dapat diatur untuk bekerja dengan 32b (Saya sendiri menggunakan distribusi neon KDE berdasarkan 16.04, memverifikasi nasm Q+A untuk asm 32b dan 64b dengan mudah, menggunakan edb-debugger yang dibuat dari sumber dari github ).   -  person Ped7g    schedule 20.11.2017
comment
stackoverflow.com/a/36901649/4271923 (hmm, itu sebenarnya terlalu berorientasi pada gcc+as, tetapi menelusuri seperti ini nasm 32b linux 64b akan memberi Anda sesuatu yang lebih fokus pada nasm di beberapa tautan)   -  person Ped7g    schedule 20.11.2017
comment
Terima kasih banyak, @Ped7g! Kamu sangat baik! :) Saya akan mencoba belajar dulu di 32bits.   -  person lidia901    schedule 20.11.2017
comment
tentang mempelajari 32b terlebih dahulu -› jangan khawatir, dalam hal instruksi x86 murni, langkah dari 32b ke 64b tidak terlalu besar (beberapa register lagi, beberapa register tidak tersedia, beberapa aturan khusus tentang penggunaan reg 32b, itu hampir semuanya) . Hanya saja konvensi pemanggilan pada sistem 64b jauh lebih baik (dalam hal kinerja) dan kompleks, sedikit lebih sulit untuk diikuti oleh manusia (itu tidak penting ketika merancangnya, karena 99% kode dihasilkan oleh kompiler, sedangkan kinerja itu penting).   -  person Ped7g    schedule 20.11.2017
comment
Sekarang saya ingat saya menambahkan ke beberapa jawaban terbaru contoh yang berfungsi penuh untuk pencampuran nasm 32b dengan clib printf: stackoverflow.com/questions/47362660/ ... jangan ragu untuk bertanya di sana jika ada sesuatu yang tidak jelas atau berhasil untuk Anda. (tentang baris perintah yang panjang... terlihat mungkin membosankan, tapi saya menggunakan editor teks Kate dengan pengaturan metode build, jadi saya tidak keberatan dengan nama yang panjang itu... sekali lagi Anda juga dapat menyimpan perintah tersebut dalam skrip shell atau bahkan membuat file). Sayangnya saya tidak repot-repot menambahkan varian 64b.   -  person Ped7g    schedule 20.11.2017
comment
Dan saya mengingatnya dengan cara yang salah, sebaliknya, kode C++ memanggil assembler, dan saya bahkan mencoba konvensi panggilan cepat dengan sengaja... jadi ini bukan tentang memanggil printf dari perakitan.. maaf :D.. masih ada Ada banyak tutorial di Internet, dan saya terlalu lelah untuk menulis jawaban lengkapnya di sini.   -  person Ped7g    schedule 20.11.2017
comment
@Ped7g: Jawaban saya di stackoverflow.com/a/36901649/4271923 yang Anda tautkan sebelumnya memang memiliki bagian NASM. Tapi OP menggunakan register 64-bit dan konvensi pemanggilan, jadi mungkin kesalahan sebenarnya adalah menggunakan int 0x80 dalam mode 64-bit (stackoverflow.com /pertanyaan/46087730/). Sebenarnya masalahnya adalah tidak memutuskan apakah akan menggunakan panggilan sistem atau fungsi perpustakaan stdio. Dan parahnya, menggunakan call scanf / int 80h sehingga nomor syscall ditentukan oleh nilai kembalian scanf!!!!   -  person Peter Cordes    schedule 20.11.2017


Jawaban (2)


Ada beberapa masalah:
1. Parameter untuk printf, seperti yang dibahas di komentar. Di x86-64, beberapa parameter pertama diteruskan dalam register.
2. printf tidak mempertahankan nilai eax.
3. Tumpukan tidak selaras.
4. rbx digunakan tanpa menyimpan pemanggil nilai.
5. Alamat integer sedang dimuat alih-alih nilainya.
6. Karena printf adalah fungsi varargs, eax perlu disetel ke 0 sebelum panggilan.
7. Palsu int 80h setelah panggilan ke scanf.

Saya akan mengulangi seluruh fungsi untuk menunjukkan perubahan yang diperlukan dalam konteks.

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. Untuk membuatnya menghitung ke atas dan bukan ke bawah, ubah perulangan seperti ini:

    mov ebx, 1
    loop:
        <call printf>
        inc ebx
        cmp ebx, [integer]
    jle loop
person prl    schedule 20.11.2017
comment
int 80h ABI 32-bit untuk sys_write dalam kode 64-bit secara teknis tidak salah, tetapi syscall 64-bit akan jauh lebih baik. (Juga, Anda tidak menyebutkan masalah 0 yang sebenarnya membuat program keluar: int 80h dengan eax = nilai kembalian scanf = 1 = __NR_exit. (lihat jawaban saya). - person Peter Cordes; 20.11.2017
comment
@PeterCordes masalah lainnya adalah mencampurkan fungsi clib IO dengan sys_write ... Maksud saya, saya terlalu lelah untuk menghasilkan perbaikan yang lengkap, karena ada banyak kesalahan, jadi saya mencoba mengusulkan langkah-langkah kecil untuk permulaan. (sebenarnya saya khawatir ada banyak perubahan dalam perbaikan, sehingga akan sulit untuk dipahami sama sekali, tanpa mengambil langkah-langkah kecil terlebih dahulu) - person Ped7g; 20.11.2017
comment
@Ped7g: sebenarnya aman jika Anda sys_write sebelum menggunakan fungsi perpustakaan stdio apa pun yang mungkin menyangga I/O daripada melakukannya sebelum kembali. Tapi ya, itu jelas merupakan sesuatu yang perlu diperingatkan. - person Peter Cordes; 20.11.2017
comment
@prl Saya pikir seharusnya jge loop di akhir, kan? Juga, terima kasih banyak! - person lidia901; 20.11.2017
comment
Ya, saya menulis perbandingannya terbalik. Bahaya karena terlalu sering dipaksa membaca sintaksis AT&T. Saya akan memperbaikinya dengan membalikkan perbandingan, bukan cabang bersyarat. - person prl; 20.11.2017

Anda memanggil scanf dengan benar, menggunakan konvensi panggilan Sistem V x86-64. Ia meninggalkan nilai kembaliannya di eax. Setelah konversi satu operan (%d) berhasil, ia kembali dengan eax = 1.

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

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

Kemudian Anda menjalankan int 80h, yang membuat panggilan sistem ABI lama 32-bit menggunakan eax=1 sebagai kode untuk menentukan yang panggilan sistem. (lihat Apa yang terjadi jika Anda menggunakan ABI Linux 32-bit int 0x80 dalam kode 64-bit?).

eax=1 / int 80h adalah sys_exit di Linux. (unistd_32.h memiliki __NR_exit = 1). Gunakan debugger; itu akan menunjukkan kepada Anda instruksi mana yang membuat program Anda keluar.

Judul Anda (sebelum saya memperbaikinya) mengatakan Anda mengalami kesalahan segmentasi, tetapi saya mengujinya pada desktop x86-64 saya dan bukan itu masalahnya. Itu keluar dengan bersih menggunakan panggilan sistem keluar int 80h. (Tetapi dalam kode yang melakukan segfault, gunakan debugger untuk mengetahui instruksi mana.) strace mendekode int 0x80 panggilan sistem yang salah dalam proses 64-bit< /a>, menggunakan nomor panggilan syscall 64-bit dari unistd_64.h, bukan nomor panggilan unistd_32.h 32-bit.


Kode Anda hampir berfungsi: Anda menggunakan int 0x80 ABI 32-bit dengan benar untuk sys_write, dan hanya meneruskan argumen 32-bit. (Argumen penunjuk cocok dalam 32 bit karena kode/data statis selalu ditempatkan di ruang alamat virtual 2GiB rendah dalam model kode default pada x86-64. Tepat karena alasan ini, sehingga Anda dapat menggunakan instruksi ringkas seperti mov edi, formatin untuk memasukkan alamat dalam register, atau gunakan sebagai perpindahan langsung atau perpindahan yang ditandatangani rel32.)

OTOH Saya pikir Anda melakukan itu karena alasan yang salah. Dan seperti yang ditunjukkan oleh @prl, Anda lupa mempertahankan penyelarasan tumpukan 16-byte.

Selain itu, menggabungkan panggilan sistem dengan fungsi C stdio biasanya merupakan ide yang buruk. Stdio menggunakan buffer internal daripada selalu membuat panggilan sistem pada setiap pemanggilan fungsi, sehingga segala sesuatunya bisa tampak tidak beres, atau read dapat menunggu masukan pengguna ketika sudah ada data di buffer stdio untuk stdin.


Lingkaran Anda juga rusak dalam beberapa hal. Anda sepertinya mencoba memanggil printf dengan konvensi panggilan 32-bit (args di stack).

Bahkan dalam kode 32-bit, ini rusak, karena nilai pengembalian printf ada di eax. Jadi perulangan Anda tidak terbatas, karena printf mengembalikan jumlah karakter yang dicetak. Itu setidaknya dua dari string format %d\n, jadi dec rax / jnz akan selalu melompat.

Di SysV ABI x86-64, Anda harus nol al sebelum memanggil printf (dengan xor eax,eax), jika Anda tidak meneruskan argumen FP apa pun di register XMM. Anda juga harus memasukkan argumen di rdi, rsi, ..., seperti untuk scanf.

Anda juga add rsp, 8 setelah memasukkan dua nilai 8-byte, sehingga tumpukan bertambah selamanya. (Tetapi Anda tidak pernah kembali, jadi segfault akhirnya akan berada pada stack overflow, bukan saat mencoba kembali dengan rsp tidak menunjuk ke alamat pengirim.)


Putuskan apakah Anda membuat kode 32-bit atau 64-bit, dan hanya salin/tempel dari contoh untuk mode dan OS yang Anda targetkan. (Perhatikan bahwa kode 64-bit dapat dan sering kali menggunakan sebagian besar register 32-bit.)

Lihat juga Merakit 32-bit binari pada sistem 64-bit (rantai alat GNU) (yang menyertakan bagian NASM dengan skrip asm-link praktis yang merakit dan menghubungkan ke biner statis). Namun karena Anda menulis main alih-alih _start dan menggunakan fungsi libc, Anda sebaiknya menautkannya saja dengan gcc -m32 (jika Anda memutuskan untuk menggunakan kode 32-bit daripada mengganti bagian 32-bit dari program Anda dengan pemanggilan fungsi 64-bit dan konvensi panggilan sistem).

Lihat Apa saja konvensi pemanggilan untuk panggilan sistem UNIX & Linux di i386 dan x86-64.

person Peter Cordes    schedule 20.11.2017