在linux控制台中显示所有ascii字符(NASM程序集)

问题描述 投票:3回答:1

我阅读了关于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'  
assembly nasm
1个回答
3
投票

我几乎了解一切

好吧,那么你有点领先于我...(虽然从你的进一步评论中你会发现那些代码中的其他一些非感觉的东西:))。

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

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

由于int 0x80服务调用需要ecx作为内存地址值,计数器由push / pop保存/恢复。更高效的方法是将计数器值放入一些备用寄存器,例如esi,并执行dec esi jnz 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文本模式视频内存,从0到255的全8位值,每个都有distinct graphical representation,您可以在VGA自定义字体或特定字符的已知代码页中指定,这有时也称为“扩展ASCII”,但常见的DOS安装与评论中的链接不同,有更多的盒子绘图字符。这包括\r\n控制字符,这对于VGA只是另一种字体字形,而不是换行和换行控制字符(这意味着由BIOS / DOS服务调用创建,而不是输出\n字符将移动内部游标到下一行并从输出中丢弃char)。

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

结论是,该示例以值'0'48)开始,直到值126它输出正确的可打印ASCII字符,在126之后输出“something”,并且由于这些字节有时会形成无效的UTF8编码,我在技术上称之为“bogus”输出具有未定义的行为,您可以获得不同的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上使用nasm 2.11.08(基于Ubuntu 16.04的“KDE neon”发行版)进行了尝试,并通过命令构建:

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{|}~
© www.soinside.com 2019 - 2024. All rights reserved.