【问题标题】:Assembly x86_64: Getting input integer from user and print it程序集 x86_64:从用户那里获取输入整数并打印它
【发布时间】:2017-10-07 21:42:26
【问题描述】:

我正在尝试编写一个按以下方式工作的程序:

获取用户输入的数字 -> 除以 2 -> 打印结果(商)。

divide-by-the-number-2 部分似乎并没有太大的难度,所以我初步编写了一个程序,获取用户输入的整数并打印该整数。

这意味着我试图编写一个程序,将用户的字符串整数转换为实整数,然后将其转换回字符串并打印出来。

但是在编译之后我进入了一个无限循环(点击Enter 之后没有任何反应)。

我使用以下命令进行编译:

nasm -f elf64 ascii.asm -o ascii.o

ld ascii.o -o ascii

./ascii

在下面的代码中,子程序_getInteger 用于从字符串转换为整数,子程序_appendEOL 和_loopDigit 用于从整数到字符串的整个转换。

section .bss
        ascii resb 16           ; holds user input
        intMemory resb 100      ; will hold the endline feed 
        intAddress resb 8       ; hold offset address from the intMemory

section .data
        text db "It's not an integer", 10
        len equ $-text

section .text

        global _start

_start:

        call _getText
        call _toInteger
        call _appendEOL

mov rax, 60
        mov rdi, 0
        syscall

_getText:
        mov rax, 0
        mov rdi, 0
        mov rsi, ascii
        mov rdx, 16
        syscall
        ret

_toInteger:
        mov rbx,10      ; for decimal scaling
        xor rax, rax    ; initializing result
        mov rcx, ascii  ; preparing for working with input
        movzx rdx, byte [rcx]   ; getting first byte (digit)
        inc rcx         ; for the next digit

        cmp rdx, '0'    ; if it's less than '0' is not a digit
        jb _invalid

        cmp rdx, '9'    ; if it's greater than '9' is not a digit
        ja _invalid

        sub rdx, '0'    ; getting decimal value
        mul rbx         ; rax = rax*10
        add rax, rdx    ; rax = rax + rdx
        jmp _toInteger  ; repeat
        ret

_invalid:
        mov rax, 1
        mov rdi, 1
        mov rsi, text
        mov rdx, len
        syscall
        ret

_appendEOL:
        ; getting EOL
        mov rcx, intMemory
        mov rbx, 10 ; EOL
        mov [rcx], rbx
        inc rcx
        mov [intAddress], rcx

_loopDigit:
        xor rdx, rdx
        mov rbx, 10
        div rbx
        push rax
        add rdx, '0'
        mov rcx, [intAddress]
        mov [rcx], dl
        inc rcx
        mov [intAddress], rcx
        pop rax
        cmp rax, 0
        jne _loopDigit

_printDigit:
        mov rcx, [intAddress]

        mov rax, 1
        mov rdi, 1
        mov rsi, rcx
        mov rdx, 1
        syscall
        mov rcx, [intAddress]
        dec rcx
        mov [intAddress], rcx
        cmp rcx, intMemory
        jge _printDigit

        ret

【问题讨论】:

  • 听起来是时候了解 GDB(或其他调试器)了
  • @Tommylee2k 这是什么意思(:?我在上面的代码中有什么问题吗?我已经花了大约两周的时间使用 Assembly,所以我几乎一无所知。我没有完全明白你的意思):。
  • 这意味着如果您试图证明代码的正确性,源代码是否编译几乎完全无关紧要(除非任何编译时失败立即排除)。实际上,即使运行可执行文件并收到预期的结果,距离证明正确性还有很长的路要走。为了至少更接近那个难以捉摸的乌托邦目标,您应该使用调试器逐条指令执行代码,根据您的期望/设计测试每条执行指令后的当前机器状态,并测试不同的输入,例如 null/empty /大/...

标签: assembly nasm x86-64


【解决方案1】:

_toInteger 是一个无限循环,它永远检查第一个数字。您需要更好的循环入口和中断条件。

下一期是mul rbx。该指令还更改了EDX,需要将其添加到RAX 下面的一行。如果不想使用IMUL rax,rax,10,可以使用LEA的算术能力:

add rax, rax                ; RAX = RAX * 2
lea rax, [rax + rax * 4]    ; RAX = (former RAX * 2) + (former RAX * 8)

另一个问题是SYS_READ 系统调用在_getText 中的棘手行为。您不会得到带有空终止符的 C 样式字符串。 SYS_READ 用\n 填充缓冲区,如果根据RDX 有足够的位置。有时是\n,有时不是——这对_toInteger 来说不是一个有用的中断条件。一个解决方法是取消SYS_READ 的最后一个字节,无论它是\n 还是数字。这会将可用缓冲区缩短 1。

_getText:
    mov rax, 0
    mov rdi, 0
    mov rsi, ascii
    mov rdx, 16
    syscall
    mov byte [ascii-1+rax], 0
    ret

准备好迎接SYS_READ 给您的更多惊喜。中断条件现在为空。让我们开始吧:

_toInteger:
    mov rbx,10      ; for decimal scaling
    xor rax, rax    ; initializing result
    mov rcx, ascii  ; preparing for working with input

    .LL1:           ; loops the bytes
    movzx rdx, byte [rcx]   ; getting current byte (digit)

    test rdx, rdx   ; RDX == 0?
    jz .done        ; Yes: break

    inc rcx         ; for the next digit

    cmp rdx, '0'    ; if it's less than '0' is not a digit
    jb _invalid

    cmp rdx, '9'    ; if it's greater than '9' is not a digit
    ja _invalid

    sub rdx, '0'    ; getting decimal value

    ; mul rbx         ; rax = rax*10
    add rax, rax
    lea rax, [rax + rax * 4]

    add rax, rdx    ; rax = rax + rdx

    ;jmp _toInteger  ; repeat
    jmp .LL1  ; repeat

    .done:
    ret

请注意:_toInteger 返回 RAX 中的整数,但您不保存此值。对EAX 的下一次写入操作将销毁它。

【讨论】:

  • 很抱歉回复晚了,但我对GDB 有点熟悉了(真的很漂亮)。我有 4 个问题,非常感谢您的指导:1) 在这个预期的程序中,mul rbx 可以通过哪种方式更改edx 寄存器,而不是由于乘法后的溢出? (也许有一些关于我在这个问题中没有考虑的字节)。 2) 我知道lea rax, [rax + rax * 4] 在每个循环之后将rax 2 的值向左移动,以便为下一个数字腾出一些空间。对吗?
  • 3) 您指出的SYS_READ 的问题是由于我任意设置了用户输入的字符串的长度,对吧? 4) 我读到 imul 清除溢出(如果我没记错的话,这意味着 edx 不会被改变),但是为什么应该使用 imul 的三操作数形式而不仅仅是一操作数(或二操作数)。
  • @Jazz:这是一个全球站点。即使是24小时的延迟也不是不礼貌的。 1) mul rbx 在EDX:EAX 中产生 64 位结果,而不仅仅是溢出。 2) LEA 行和前一行只是执行 EAX = EAX * 10 的一种方式。它不会更改 EDX,也不会更改标志。 3) SYS_READ 的问题是 SYS_READ 的问题。你绝对是无辜的。我将关于 SYS_READ 的三个问题推到了顶部。单击nasm 标签,然后单击“活动”,您会在您的问题下方看到这些问题(由我修改)..
  • @Jazz: 4) IMUL 的单操作数形式也改变了EDX。当然,您也可以使用双操作数形式。你在装配的世界里。这里不存在标准、意识形态或宗教——如果它有效,那很好。您需要详细的参考资料,Intel Manual。
  • 哇。有很多很酷的东西要学。感谢您回答问题。我正在仔细阅读有关 SYS_READ 的内容。我希望在这几天解决这些问题。如果没有,我将创建另一个问题 xD(但希望有更好的基础)。再次感谢您抽出宝贵时间。
【解决方案2】:

您的“无限循环”在您的 _toInteger 函数中。

RDX 将为 0 或您的第一个元素或 ASCII 输入的值,因为您通过跳回标签 _toInteger 将指针重置为第一个元素。因此,您永远不能离开或跳出循环。

我们怎么强调都不为过;您应该始终使用调试器。

 mov rcx, ascii  ; preparing for working with input

但即使你解决了这个问题,_toInteger 函数似乎还有其他问题。

$ ./jazz_001
12
It's not an integer
20

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2014-01-10
    • 2013-01-18
    • 1970-01-01
    • 1970-01-01
    • 2019-04-11
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多