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

Assembly 在linux控制台(NASM程序集)中显示所有ascii字符,assembly,nasm,Assembly,Nasm,我读了一篇关于nasm的教程,其中有一个代码示例,显示了整个ascii字符集。除了我们为什么要推ecx和弹出ecx之外,我几乎什么都懂,因为我不知道它与代码的其余部分有什么关系。Ecx的值为256,因为我们需要所有字符,但不知道在哪里以及如何使用它。当我们按下并弹出ecx时到底发生了什么?为什么我们要把achar的地址移到dx?我没有看到我们用dx做任何事情。我知道我们需要增加achar的地址,但我不清楚这个增量与ecx和dx的关系。我希望能有一些见解 section .text

我读了一篇关于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和弹出ecx,因为我不知道它与代码的其余部分有什么关系。Ecx的值为256,因为我们需要所有字符,但不知道在哪里以及如何使用它

它由指令使用(这不是一个好主意:),它将递减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位值从零到255,每个值都可以在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
全球启动;必须声明使用gcc
_开始:;告诉链接器入口点
呼叫显示
mov-eax,1;系统呼叫号码(系统退出)
int 0x80;调用内核
; 显示所有有效的可打印ASCII字符(32-126)和后面的新行。
显示:
mov字节[achar],'';第一个有效的可打印ASCII码
下一步:
mov-eax,4
mov-ebx,1
乙酰胆碱
mov edx,1
int 0x80
inc字节[achar]
cmp字节[achar],126
jbe-next;重复此操作,直到打印完所有字符
; 将输出所有32..126个可打印ASCII字符
; 再显示一个字符,新行(重复使用寄存器)
mov字节[achar],`\n`;NASM对类C元字符使用反勾号
mov-eax,4;ebx、ecx和edx已从上面的循环中设置
int 0x80
ret
第2节bss
achar:resb 1;为字符输出保留一个字节
但是,首先在内存中准备整个输出,然后一次性输出,这样做更有意义:

section.text
全球启动;使符号“_start”全局(链接器可见)
_开始:;链接器的默认入口点
呼叫显示
mov-eax,1;系统呼叫号码(系统退出)
int 0x80;调用内核
; 显示所有有效的可打印ASCII字符(32-126)和后面的新行。
显示:
; 准备包含所有ASCII字符和新行的内存字符串
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{|}~