我阅读了关于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'
答案 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'
将保存您。{} p>
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{|}~