扫描整数并在NASM中打印间隔(1,整数)

时间:2017-11-19 21:30:07

标签: linux assembly x86-64 system-calls

我正在尝试从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

你能帮我找到我错的地方吗?

2 个答案:

答案 0 :(得分:3)

有几个问题:
1. printf的参数,如评论中所述。在x86-64中,前几个参数在寄存器中传递 2. printf不保留eax的值 3.堆栈未对齐 4.使用rbx时不保存来电者的值 5.正在加载integer的地址而不是其值 6.由于printf是一个varargs函数,因此在调用之前需要将eax设置为0 7.调用scanf后出现假int 80h

我会重复整个功能,以便在上下文中显示必要的更改。

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

答案 1 :(得分:1)

您正在使用x86-64 System V调用约定正确调用scanf。它将返回值保留在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,它使用eax=1作为代码来进行32位传统ABI系统调用,以确定哪个系统调用。 (见What happens if you use the 32-bit int 0x80 Linux ABI in 64-bit code?)。

Linux上的

eax=1 / int 80hsys_exit。 (unistd_32.h__NR_exit = 1)。使用调试器;那本可以告诉你哪个指令让你的程序退出。

你的标题(在我更正之前)说你有分段错误,但我在我的x86-64桌面上进行了测试,情况并非如此。它使用int 80h退出系统调用完全退出。 (但是在执行段错误的代码中,使用调试器找出哪条指令。)strace decodes int 0x80 system calls incorrectly in 64-bit processes,使用来自syscall的64位unistd_64.h个调用号,而不是32位Assembling 32-bit binaries on a 64-bit system (GNU toolchain) 1}}电话号码。

您的代码已接近工作:您对unistd_32.h正确使用int 0x80 32位ABI,并且只传递32位args。 (指针args符合32位,因为静态代码/数据总是放在x86-64的默认代码模型中的低2GiB虚拟地址空间中。正是因为这个原因,所以你可以使用像{{1}这样的紧凑指令将地址放在寄存器中,或将它们用作immediates或rel32 signed displacements。)

OTOH我认为你是出于错误的原因这样做的。正如@prl指出的那样,你忘了保持16字节的堆栈对齐。

此外,将系统调用与C stdio函数混合通常是一个坏主意。 Stdio使用内部缓冲区而不是总是在每个函数调用上进行系统调用,因此事情可能出现乱序,或者当{stdio缓冲区中的数据已经存在时sys_write可以等待用户输入mov edi, formatin

你的循环在几个方面也被打破了。您似乎试图使用32位调用约定(堆栈中的args)调用read

即使在32位代码中也是如此,因为stdin的返回值在printf中。所以你的循环是无限的,因为printf返回打印的字符数。 eax格式字符串中至少有两个字符串,因此printf / %d\n将始终跳转。

在x86-64 SysV ABI中,如果您没有在XMM寄存器中传递任何FP参数,则在调用dec rax(使用jnz)之前需要将al归零。您还必须在printfxor eax,eax,...中传递参数,与scanf一样。

在推送两个8字节值后,您还rdi,因此堆栈会永远增长。 (但是你永远不会返回,所以最终的段错误将是堆栈溢出,而不是试图返回时rsi没有指向返回地址。)

决定您是否制作32位或64位代码,并仅针对您要定位的模式和操作系统的示例进行复制/粘贴。 (注意,64位代码可以并且经常使用大多数32位寄存器。)

另请参阅What are the calling conventions for UNIX & Linux system calls on i386 and x86-64(其中包含一个带有便捷add rsp, 8脚本的NASM部分,该脚本可汇编并链接到静态二进制文件中。但是,由于您正在编写rsp而不是asm-link并使用libc函数,因此您应该只使用main(如果您决定使用32位代码而不是替换具有64位函数调用和系统调用约定的程序的32位部分。

请参阅https://bl.ocks.org/anonymous/dbda1acf2f76012ab557d7a24b1b7ecb/34b1273419327ce258309f9bf4522ea2aa186d92