【问题标题】:printing numbers in nasm在 nasm 中打印数字
【发布时间】:2017-10-01 10:24:03
【问题描述】:

我已经编写了一个汇编代码来打印从 1 到 9 的数字,但是代码只打印 1,并且没有打印除 1 以外的其他元素,并且只接收到一个输出。这意味着循环也没有运行。我无法弄清楚我的代码有什么问题。

section .bss

        lena equ 1024
        outbuff resb lena

section .data

section .text

        global _start
        _start:
                nop
                mov cx,0

                incre:
                inc cx
                add cx,30h
                mov [outbuff],cx

                cmp cx,39h
                jg done

                cmp cx,39h
                jl print


                print:
                mov rax,1           ;sys_write
                mov rdi,1
                mov rsi,outbuff
                mov rdx,lena
                syscall
                jmp incre

                done:
                mov rax,60          ;sys_exit
                mov rdi,0
                syscall

我的操作系统是 64 位 Linux。此代码是使用 nasm 和以下命令构建的:nasm -f elf64 -g -o num.o num.asm 和 ld -o num num.asm

【问题讨论】:

  • 您可以在此处获取此信息stackoverflow.com/questions/31143237/…
  • 学习使用调试器(gdb 应该可用,然后你可以尝试一些 GUI 扩展,或者我个人更喜欢edb-debugger,但你必须从源代码构建它,可能是如果你不习惯那会很棘手)。
  • 您在收到答案后不应更改问题中的代码。这使问题变得不可见并使答案无效!

标签: linux assembly nasm


【解决方案1】:

经过一些实验后重写了答案。

您的代码中有两个错误,还有一些效率低下。

首先,将 0x30 添加到数字(将其从数字 1 转换为 ASCII 1)。但是,您在循环内执行该增量。因此,您的第一次迭代 cx 是 0x31,第二次是 0x62(“b”),第三次是 0x93(无效的 UTf-8 序列)等等。

只需将 cx 初始化为 0x30 并从循环内删除添加。

但是还有另一个问题。 RCX 在系统调用期间被破坏。将cx 替换为r12 会导致程序运行。

除此之外,您将缓冲区的长度传递给write,但它只有一个字符。到目前为止的程序:

section .bss

        lena equ 1024
        outbuff resb lena

section .data

section .text

        global _start
        _start:
                nop
                mov r12,30h

                incre:
                inc r12
                mov [outbuff],r12

                cmp r12,39h
                jg done

                cmp r12,39h
                jl print


                print:
                mov rax,1           ;sys_write
                mov rdi,1
                mov rsi,outbuff
                mov rdx,1
                syscall
                jmp incre

                done:
                mov rax,60          ;sys_exit
                mov rdi,0
                syscall

除了现在,代码效率极低。您在相同条件下进行了两次比较,其中一个分支到下一条指令。

此外,如果您将中断条件移到代码末尾,您的代码会更快更小。此外,cx 是一个 16 位寄存器。 r12 是一个 64 位寄存器。我们实际上只需要 8 位。使用比需要更大的寄存器意味着我们所有的立即数都会浪费内存和缓存中的空间。因此,我们切换到r12 的 8 位变体。经过这些更改,我们得到:

section .bss

        lena equ 1024
        outbuff resb lena

section .data

section .text

        global _start
        _start:
                nop
                mov r12b,30h

                incre:
                inc r12b
                mov [outbuff],r12b

                mov rax,1           ;sys_write
                mov rdi,1
                mov rsi,outbuff
                mov rdx,1
                syscall

                cmp r12b,39h
                jl incre

                mov rax,60          ;sys_exit
                mov rdi,0
                syscall

您还可以做很多事情。例如,您调用write 系统调用 9 次,而不是填充缓冲区然后调用一次(尽管您已经分配了一个 1024 字节的缓冲区)。将 r12 初始化为零 (xor r12, r12) 然后添加 0x30 可能会更快。 (与 8 位版本的寄存器无关)。

【讨论】:

  • 我已经从循环内部删除了 add cx,30h 但输出仍然相同
  • 我不认为对单个字符使用整个 64b 寄存器非常准确,每次覆盖 8 个字节的内存。 r12w 模拟原始 16 位怎么样,或者可能更好 r12b 因为输出将只使用单个字节?
  • @Ped7g 所以你基本上同意我的看法,这是一团糟,你只是喜欢你的东西乱七八糟:-)
  • @Ped7g: ah/bh/ch/dh 不能使用 REX 前缀进行编码,因为 REX 前缀会将该编码的含义更改为 dil/sil/bpl/spl。这使得 ISA 更规则,因此您始终可以存储或 movzx 任何寄存器的低字节,使其成为更好的编译器目标(尽管一个好的编译器会尝试分配寄存器以最小化代码大小的 REX 前缀数量原因。)
  • xor r12d, r12d 优于 xor r12, r12。 Silvermont/KNL 不将 64 位异或识别为归零习惯用法,仅 32 位。 (我对my xor-zeroing answer 进行了半成品编辑,我真的应该发布...)或者更好的是,您可以使用ebxebp。或者,如果您最后只进行一次系统调用,您可以使用ecx,因为它不必在syscall 中继续存在。此外,OP 可以使用堆栈而不是静态缓冲区。
猜你喜欢
  • 2021-12-20
  • 2017-04-01
  • 2016-01-07
  • 1970-01-01
  • 2014-02-21
  • 2016-01-21
  • 2015-09-17
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多