【问题标题】:Displaying all ascii characters in linux console (NASM assembly)在 linux 控制台中显示所有 ascii 字符(NASM 程序集)
【发布时间】:2018-01-01 01:50:20
【问题描述】:

我阅读了有关 nasm 的教程,其中有一个显示整个 ascii 字符集的代码示例。我几乎了解所有内容,除了我们为什么要推送 ecx 和弹出 ecx,因为我看不出它与其余代码有何关系。 Ecx 的值为 256,因为我们想要所有字符,但不知道它在哪里以及如何使用。当我们推送和弹出 ecx 时到底发生了什么?为什么要把 achar 的地址移动到 dx 中呢?我没有看到我们使用 dx 做任何事情。我知道我们需要增加 achar 的地址,但我很困惑增量与 ecx 和 dx 的关系。我将不胜感激。

   section  .text
       global _start        ;must be declared for using gcc

    _start:                 ;tell linker entry point
       call    display
       mov  eax,1           ;system call number (sys_exit)
       int  0x80            ;call kernel

    display:
       mov    ecx, 256

    next:
       push    ecx
       mov     eax, 4
       mov     ebx, 1
       mov     ecx, achar
       mov     edx, 1
       int     80h

       pop     ecx  
       mov  dx, [achar]
       cmp  byte [achar], 0dh
       inc  byte [achar]
       loop    next
       ret

    section .data
    achar db '0'  

【问题讨论】:

  • ecx 被用作循环计数器,并且由于它为系统调用加载了achar,因此需要保留其值。 push/pop 是一种方法。至于mov dx, [achar],确实似乎不需要。
  • 那条dx 指令完全没有必要,而且很不合适。那是一个 16 位的寄存器。其他一切都是 32 位寄存器。嘿,我找到了你正在使用的教程,它位于tutorialspoint.com/assembly_programming/assembly_procedures.htm。教程点教程是由志愿者编写的,这样的怪事并不少见。
  • 倒计时了。请参阅有关 loop 指令作用的参考资料。 TL;DR:基本上是dec ecx; jnz
  • 不仅如此,ASCII只有128个字符,不是256个,别让我开始缩进.....
  • 从未听说过有人编造的“扩展 ASCII”。或者它是一些供应商特定的东西。无论如何,我同意你的观点,cmp 毫无意义。自己编写这个程序可能是一个很好的练习;我认为作者正在研究一种不同的方法,并在那里留下了一些东西。

标签: assembly nasm


【解决方案1】:

什么都懂

好吧,那么你有点领先于我......(尽管从你进一步的 cmets 中你会意识到该代码中的一些其他无意义的东西 :))。

为什么我们要推送 ecx 和弹出 ecx,因为我看不出它与其余代码有何关系。 Ecx 的值为 256,因为我们想要所有字符,但不知道它在哪里以及如何使用。

LOOP指令使用(这不是一个好主意:Why is the loop instruction slow?),它会递减ecx,当值大于零时跳转,即它是一个倒计时循环机制。

由于int 0x80 服务调用需要ecx 作为内存地址值,因此计数器由push/pop 保存/恢复。一种更高效的方法是将计数器值放入一些备用寄存器中,例如esi,然后执行dec esijnz next。更高效的方法是重用字符值本身,如果输出从零值开始,而不是零位,则inc byte [achar] 之后的零标志可用于检测循环条件。

achar db '0'

我不清楚,为什么“显示所有 ASCII 字符”从数字零(值 48)开始,我觉得很奇怪,我会从零开始。但这有另一个警告,linux 控制台 I/O 编码是由环境设置的,在任何常见的 linux 安装上,它现在都是 UTF8,所以有效的可打印单字节字符只有 32-126 的值(与普通的 7位 ASCII 编码,使这部分示例运行良好),值 0-31 和 127 是不可打印的控制字符,也与常见的 7b ASCII 编码相同。值 128-255 表示 UTF8 编码的多字节字符(例如:ř 是两个字节 0xC5 0x99),作为单字节,它们是无效的字节序列,因为缺少 UTF8“代码点”字节的剩余部分.

在 DOS 时代,您可以将代码直接写入 VGA 文本模式视频内存,全 8 位值从 0 到 255,每个值都有 distinct graphical representation,您可以在 VGA 自定义字体或已知代码中指定 -特定字符的页面,这有时也称为“扩展 ASCII”,但常见的 DOS 安装与 cmets 中的链接不同,具有更多的方框图字符。这包括\r\n 控制字符,它们对于VGA 只是另一种字体字形,而不是换行和换行控制字符(该含义是由BIOS/DOS 服务调用创建的,而不是输出\n字符会将内部光标移动到下一行并从输出中丢弃字符。

用 linux 控制台 I/O 重新创建它是不可能的(除非 UTF8 字体包含所有奇怪的 DOS 字形,并且您将输出它们正确的 UTF8 编码而不是单字节值)。

结论是,该示例以值 '0' (48) 开始,直到值 126 它输出正确的可打印 ASCII 字符,在 126 之后它输出“某物”,并且这些字节将有时会形成无效的 UTF8 编码,从技术上讲,我会称其为具有未定义行为的“虚假”输出,对于不同的 linux 版本和控制台设置,您可能会得到不同的结果。

还有 NASM 风格的通知:在标签后面加上冒号,即achar: db '0',这样会在你不小心将指令助记符用作标签时节省你的时间,比如loop:dec: db 'd'

   mov  dx, [achar]

dx 不再使用,所以这是无用的指令。

   cmp  byte [achar], 0dh

这个比较的标志也不再被使用,所以这也是无用的。


所以调整后的示例可以如下所示:

section  .text
    global _start       ;must be declared for using gcc

_start:                 ;tell linker entry point
    call    display
    mov     eax,1       ;system call number (sys_exit)
    int     0x80        ;call kernel

; displays all valid printable ASCII characters (32-126), and new-line after.
display:
    mov     byte [achar], ' '   ; first valid printable ASCII
next:
    mov     eax, 4
    mov     ebx, 1
    mov     ecx, achar
    mov     edx, 1
    int     0x80
    inc     byte [achar]
    cmp     byte [achar], 126
    jbe     next        ; repeat until all chars are printed
    ; that will output all 32..126 printable ASCII characters

    ; display one more character, new line (reuse of registers)
    mov     byte [achar], `\n`  ; NASM uses backticks for C-like meta chars
    mov     eax, 4      ; ebx, ecx and edx are already set from loop above
    int     0x80
    ret

section .bss
achar: resb 1           ; reserve one byte for character output

但最好先在内存中准备整个输出,然后一次性输出,像这样:

section  .text
    global _start       ;makes symbol "_start" global (visible for linker)

_start:                 ;linker's default entry point
    call    display
    mov     eax,1       ;system call number (sys_exit)
    int     0x80        ;call kernel

; displays all valid printable ASCII characters (32-126), and new-line after.
display:
    ; prepare in memory string with all ASCII chars and new-line
    mov     al,' '      ; first valid printable ASCII
    mov     edi, allAsciiChars
    mov     ecx, edi    ; this address will be used also for "write" int 0x80
nextChar:
    mov     [edi], al
    inc     edi
    inc     al
    cmp     al, 126
    jbe     nextChar
    ; add one more new line at end
    mov     byte [edi], `\n`
    ; display the prepared "string" in one "write" call
    mov     eax, 4      ; sys_write, ecx is already set
    mov     ebx, 1      ; file descriptor STDOUT
    lea     edx, [edi+1]; edx = edi+1 (memory address beyond last char)
    sub     edx, ecx    ; edx = length of generated string
    int     0x80
    ret

section .bss
allAsciiChars: resb 126-' '+1+1 ; reserve space for ASCII characters and \n

所有示例均在 64b linux(基于 Ubuntu 16.04 的“KDE neon”发行版)上使用 nasm 2.11.08 进行了尝试,并通过命令构建:

nasm -f elf32 -F dwarf -g test.asm -l test.lst -w+all
ld -m elf_i386 -o test test.o

带输出:

$ ./test
 !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~

【讨论】:

  • 请问为什么您选择的一些寄存器是 16 位的,而另一些是 32 位的寄存器版本?这是某种内存或性能优化吗?
  • 哦,另一个问题。您将 edi 移动到 ecx 寄存器中。由于 edi 不在方括号内,因此您正在正确传递值?参考第二个代码,你首先传递内存中的所有内容
  • @Asperger 我没有在任何地方使用 16 位寄存器。 alrax 的最低 8 位部分(eaxaxah:al)。我正在使用al 处理单字节元素(8 位),因为 7 位 ASCII 就是这样使用的,最高 8 位设置为零(即值0x00..0x7F,如果你可以在头部读取十六进制并转换为位,您可以很容易地看到前 8 位0x80 是清晰的)。它由数据结构(ASCII 编码的“字符串”)决定,如果我要准备 16 位或 32 位元素的数组,我会使用 16b ax 或 32b eax 等。虽然你可以从字节更复杂。
  • @Asperger mov edi, allAsciiChars 用 32 位值加载寄存器 edi,表示 .bss 部分中保留缓冲区的内存地址。我在那里保留96 字节,其中第一个位于地址allAsciiChars。 32 位模式下的内存地址是 32 位整数值,如1234。然后通过mov ecx, edi 我将此地址值复制到ecx 寄存器中。如果我会做mov ecx, [edi],我将从该缓冲区加载前 4 个字节的内存内容,当时该缓冲区包含零,因为在启动代码之前,Linux OS 在加载+初始化二进制文件时将bss 部分归零。
  • 你从哪里得到这 96 个字节?保留缓冲区是 126 位还是 15 字节?不知道我是否理解。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多