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

Linux 扫描整数并在NASM中打印间隔(1,整数),linux,assembly,x86-64,system-calls,Linux,Assembly,X86 64,System Calls,我正在尝试从Linux Ubuntu 16.04 x64学习汇编语言。 目前我有以下问题: -扫描整数n并打印从1到n的数字 对于n=5,我应该有12345。 我试着用scanf和printf来做,但在我输入数字后,它退出了 代码是: ;nasm -felf64 code.asm && gcc code.o && ./a.out SECTION .data message1: db "Enter the number: ",0 message1L

我正在尝试从Linux Ubuntu 16.04 x64学习汇编语言。 目前我有以下问题: -扫描整数n并打印从1到n的数字

对于n=5,我应该有12345。 我试着用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

你能帮我找出哪里出了问题吗?

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

我将重复整个函数,以便在上下文中显示必要的更改

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
注意:要使循环向上计数而不是向下计数,请按如下方式更改循环:

    mov ebx, 1
    loop:
        <call printf>
        inc ebx
        cmp ebx, [integer]
    jle loop

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

我将重复整个函数,以便在上下文中显示必要的更改

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
注意:要使循环向上计数而不是向下计数,请按如下方式更改循环:

    mov ebx, 1
    loop:
        <call printf>
        inc ebx
        cmp ebx, [integer]
    jle loop
您正在使用x86-64 System V调用约定正确调用scanf。它将其返回值保留在eax中。成功转换一个操作数%d后,它返回eax=1

然后运行int 80h,它使用eax=1作为代码进行32位遗留ABI系统调用,以确定哪个系统调用。看

eax=1/int 80h是Linux上的系统退出。unistd_32.h的uuu NR_exit=1。使用调试器;这将向您显示使程序退出的指令

在我更正之前,您的标题说您有一个分段错误,但我在x86-64桌面上进行了测试,事实并非如此。它使用int 80h退出系统调用干净地退出。但是在没有segfault的代码中,使用调试器找出哪条指令,使用unistd_64.h中的64位系统调用号码,而不是32位unistd_32.h调用号码

您的代码已接近正常工作状态:在sys_write中正确使用int 0x80 32位ABI,并且只传递32位参数。指针args适合32位,因为在x86-64上的默认代码模型中,静态代码/数据总是放在低2GiB的虚拟地址空间中。正是出于这个原因,所以您可以使用压缩指令(如mov edi、formatin)将地址放入寄存器中,或者将它们用作即时或rel32有符号置换

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

此外,将系统调用与C stdio函数混合通常是个坏主意。Stdio使用内部缓冲区,而不是总是在每次函数调用时进行系统调用,因此可能会出现问题,或者当Stdio缓冲区中已经有stdin的数据时,读取可能会等待用户输入

你的循环也在几个方面被打破了。您似乎试图使用堆栈上的32位调用约定参数调用printf

即使在32位代码中,这也是错误的,因为printf的返回值在eax中。因此循环是无限的,因为printf返回打印的字符数。这至少是%d\n格式字符串中的两个,因此dec rax/jnz将始终跳转

在x86-64 SysV ABI中,如果没有在XMM寄存器中传递任何FP参数,则需要在使用xor eax、eax调用printf之前将al归零。您还必须在rdi、rsi…中传递参数,如scanf

您还可以在推送两个8字节的值之后添加rsp,8,这样堆栈将永远增长。但是您永远不会返回,因此最终的segfault将发生在堆栈溢出上,而不是在rsp不指向返回地址的情况下尝试返回

决定是制作32位代码还是64位代码,仅复制/粘贴目标模式和操作系统的示例。但请注意,64位代码可以而且经常使用32位寄存器

另请参见其中的NASM部分,该部分包含一个方便的asm链接脚本,该脚本可汇编并链接到静态二进制文件中。但是,由于您正在编写main而不是_start,并且正在使用libc函数,如果您决定使用32位代码而不是将程序的32位部分替换为64位代码,那么您应该只使用gcc-m32链接 -位函数调用和系统调用约定

请参阅。

您使用x86-64 System V调用约定正确调用了scanf。它将其返回值保留在eax中。成功转换一个操作数%d后,它返回eax=1

然后运行int 80h,它使用eax=1作为代码进行32位遗留ABI系统调用,以确定哪个系统调用。看

eax=1/int 80h是Linux上的系统退出。unistd_32.h的uuu NR_exit=1。使用调试器;这将向您显示使程序退出的指令

在我更正之前,您的标题说您有一个分段错误,但我在x86-64桌面上进行了测试,事实并非如此。它使用int 80h退出系统调用干净地退出。但是在没有segfault的代码中,使用调试器找出哪条指令,使用unistd_64.h中的64位系统调用号码,而不是32位unistd_32.h调用号码

您的代码已接近正常工作状态:在sys_write中正确使用int 0x80 32位ABI,并且只传递32位参数。指针args适合32位,因为在x86-64上的默认代码模型中,静态代码/数据总是放在低2GiB的虚拟地址空间中。正是出于这个原因,所以您可以使用压缩指令(如mov edi、formatin)将地址放入寄存器中,或者将它们用作即时或rel32有符号置换

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

此外,将系统调用与C stdio函数混合通常是个坏主意。Stdio使用内部缓冲区,而不是总是在每次函数调用时进行系统调用,因此可能会出现问题,或者当Stdio缓冲区中已经有stdin的数据时,读取可能会等待用户输入

你的循环也在几个方面被打破了。您似乎试图使用堆栈上的32位调用约定参数调用printf

即使在32位代码中,这也是错误的,因为printf的返回值在eax中。因此循环是无限的,因为printf返回打印的字符数。这至少是%d\n格式字符串中的两个,因此dec rax/jnz将始终跳转

在x86-64 SysV ABI中,如果没有在XMM寄存器中传递任何FP参数,则需要在使用xor eax、eax调用printf之前将al归零。您还必须在rdi、rsi…中传递参数,如scanf

您还可以在推送两个8字节的值之后添加rsp,8,这样堆栈将永远增长。但是您永远不会返回,因此最终的segfault将发生在堆栈溢出上,而不是在rsp不指向返回地址的情况下尝试返回

决定是制作32位代码还是64位代码,仅复制/粘贴目标模式和操作系统的示例。但请注意,64位代码可以而且经常使用32位寄存器

另请参见其中的NASM部分,该部分包含一个方便的asm链接脚本,该脚本可汇编并链接到静态二进制文件中。但是,由于您正在编写main而不是_start,并且正在使用libc函数,如果您决定使用32位代码,而不是用64位函数调用和系统调用约定替换程序的32位部分,那么您应该只使用gcc-m32链接


请参阅。

为什么要将printf的参数推送到堆栈中?你用什么信息来源来做这件事?我怀疑您正在将32b代码/教程更改为64b,但这不会如此简单,它更复杂。。。目前,如果您有很好的32b asm学习资源,那么教您如何在64b linux下构建32b二进制文件并使用它将更容易。。。要么是这样,要么你的64b资源质量很低,试试更好的…@Ped7g可能我把32b和64b搞混了。。。我修改了所有内容,但在循环中printf函数仍然存在问题。。。我找不到它的文档。这比我想象的要难DCA您可以编辑您的问题,并显示32b版本的source+命令行如何构建它吗?printf是比较复杂的一种,因为它的参数数量可变,所以除了基本的调用约定外,您还需要知道正确的调用约定。您也可以查看本教程,它的目标正是nasm+libc+32b,并且似乎得到了很好的评论:如果您刚刚开始使用汇编,我将完全跳过调用libc函数,并使用纯x86指令进行一些数学运算,仅在调试器中检查值。还有64b示例链接,但同样,如果您只是从汇编开始,我建议只要涉及到调用libc,就坚持使用32b,对于没有外部调用的纯x86-64 asm,64b只是稍微复杂一点,调用约定本身要比32b复杂得多,您还必须在每次调用之前保持堆栈对齐,还有红色区域的特征等等。。。但是能够使用调试器单步跳过指令并验证寄存器/标志/mem的状态
ory是必不可少的,比调用printf重要得多。关于先学习32b->别担心,就纯x86指令而言,从32b到64b的步骤并不多一些寄存器,一些寄存器不可用,关于32b寄存器用法的一些特殊规则,几乎就这些。只是64b系统上的调用约定在性能和复杂度方面要好得多,对于设计它时并不重要的人来说,遵循它有点困难,因为99%的代码是由编译器生成的,而性能很重要。为什么要将printf的参数推到堆栈中?你用什么信息来源来做这件事?我怀疑您正在将32b代码/教程更改为64b,但这不会如此简单,它更复杂。。。目前,如果您有很好的32b asm学习资源,那么教您如何在64b linux下构建32b二进制文件并使用它将更容易。。。要么是这样,要么你的64b资源质量很低,试试更好的…@Ped7g可能我把32b和64b搞混了。。。我修改了所有内容,但在循环中printf函数仍然存在问题。。。我找不到它的文档。这比我想象的要难DCA您可以编辑您的问题,并显示32b版本的source+命令行如何构建它吗?printf是比较复杂的一种,因为它的参数数量可变,所以除了基本的调用约定外,您还需要知道正确的调用约定。您也可以查看本教程,它的目标正是nasm+libc+32b,并且似乎得到了很好的评论:如果您刚刚开始使用汇编,我将完全跳过调用libc函数,并使用纯x86指令进行一些数学运算,仅在调试器中检查值。还有64b示例链接,但同样,如果您只是从汇编开始,我建议只要涉及到调用libc,就坚持使用32b,对于没有外部调用的纯x86-64 asm,64b只是稍微复杂一点,调用约定本身要比32b复杂得多,您还必须在每次调用之前保持堆栈对齐,还有红色区域的特征等等。。。但是,能够使用调试器单步执行指令并验证寄存器/标志/内存的状态比调用printf更重要。关于先学习32b->别担心,对于纯x86指令,从32b到64b的步骤并不多一些寄存器,一些寄存器不可用,关于32b注册表使用的一些特殊规则,几乎就这些。只是64b系统上的调用约定在性能和复杂度方面要好得多,这对于人类来说有点困难,这在设计时并不重要,因为99%的代码是由编译器生成的,而性能很重要。32位int 80h ABI for sys_write in 64位代码在技术上并没有错,但是64位系统调用会更好。另外,您没有提到实际使程序退出的问题0:int 80h,eax=scanf的返回值=1=\uu NR\u exit。请看我的答案。@PeterCordes另一个问题是将clib IO函数与sys_write混合在一起。。。我的意思是,我太累了,无法制定一个完整的修复方案,因为出现了太多错误,所以我宁愿尝试在开始时提出更小的步骤。事实上,我担心修复中有太多更改,如果不采取这些较小的步骤,将很难理解first@Ped7g:如果您在使用任何可能缓冲I/O的stdio库函数之前进行系统写入,而不是在返回之前进行写入,那么实际上是安全的。但是,是的,这绝对是值得警惕的。@prl我认为它应该在最后是jge循环,对吗?还有,非常感谢!是的,我把比较写反了。被迫频繁阅读AT&T语法的危险。我将通过反转比较而不是条件分支来修复它。32位int 80h ABI for sys_write in 64位代码在技术上并没有错,但64位syscall会更好。另外,您没有提到实际使程序退出的问题0:int 80h,eax=scanf的返回值=1=\uu NR\u exit。请看我的答案。@PeterCordes另一个问题是将clib IO函数与sys_write混合在一起。。。我的意思是,我太累了,无法制定一个完整的修复方案,因为出现了太多错误,所以我宁愿尝试在开始时提出更小的步骤。事实上,我担心修复中有太多更改,如果不采取这些较小的步骤,将很难理解first@Ped7g:如果您在使用任何可能缓冲I/O的stdio库函数之前进行系统写入,而不是在返回之前进行写入,那么实际上是安全的。但是,是的,这绝对是值得警惕的。@prl我认为它应该在最后是jge循环,对吗?还有,非常感谢!是的,我把比较写反了。被迫频繁阅读AT&T语法的危险。我将通过反转比较而不是c来修复它 条件分支。