什么都懂
好吧,那么你有点领先于我......(尽管从你进一步的 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{|}~