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

时间:2018-01-01 01:50:20

标签: assembly nasm

我阅读了关于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'  

1 个答案:

答案 0 :(得分: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文本模式视频内存的代码,完整的8位值从0到255,每个都有distinct graphical representation,您可以在VGA自定义字体中指定或已知特定字符的代码页,有时也被称为“扩展ASCII”,但常见的DOS安装与评论中的链接不同,有更多的盒子绘图字符。这包括\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上使用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{|}~