您遇到的主要障碍是尝试将0x12(ASCII 18)的值写入stdout。快速检查 ASCII 图表会发现该值是不可打印的。在汇编中,您只能向stdout 写入字符。这意味着当面对写一个数值时,你必须将值分成数字,将数字转换为它们的ASCII表示(通过添加'0'或0x30或48十进制)到值。
这与手动将数字转换为 C 或任何其他语言的字符串没有什么不同。您基本上将原始数字除以 10 重复,每次将 余数 作为数字保存到缓冲区,然后以相反的顺序将缓冲区写出以将数字的 ASCII 表示形式写入 @987654330 @(SO上有很多例子)。
在查看将所有数字分隔到缓冲区并将它们写入stdout 的示例之前,让我们在示例中介绍一些基础知识并让它打印一些东西(任何东西)。汇编中需要巩固的第一个概念是汇编中的所有标签(变量)都指向内存地址,而不是数值。当您在程序集中分配标签时,例如:
var1 db 0x12
您正在存储一个数据字节,其值为12-hex在var1 指向的内存位置。 var1 是指向该内存位置的指针。在 nasm 中,如果您想操作/或引用该位置的 值,则必须通过将指针包含在 [ ] 中来取消引用指向地址的指针(只需就像用 *var1 解引用 C 中的指针一样。
要巩固的下一个概念是sys_write (syscall 4) 期望存储在ecx 中的起始内存地址,而不是值。然后它将edx字节的数据写入存储在ebx (1 - stdout) 中的文件描述符。 mov将var1 的地址转换为edx 将允许我们通过对[edx] 进行操作来取消引用该地址的值。要将单个数字转换为它们的 ASCII 值,您需要向它们添加 '0'(或 0x30),但如果我们只将 '0' 添加到 [edx],结果会是什么? (现值 0x12 (18) + 0x30 (48) = 0x42 (66) -- 恰好是 ASCII 'B')
(我们也将作弊并将newline (0xa) 附加到var1 的末尾,并将其设为 2 字节,这样我们就不必搞乱单独的调用)
将此与您的示例放在一起将导致:
section .text
global _start
_start:
mov eax, 4 ; linux sys_write
mov ebx, 1 ; stdout
mov ecx, var1 ; mov address of var1 to ecx
add byte [ecx], 0x30 ; add '0' to first byte of var1
mov edx, 2 ; number of chars to print
int 0x80 ; syscall
mov eax, 0x1 ; __NR_exit 1
xor ebx, ebx ; exit code of 0
int 0x80
section .data
var1 db 0x12, 0xa ; ASCII 18 (non-printable) with newline
编译运行程序会得到:
$ ./convert
B
现在我们可以继续看一个完整的示例,它将存储在var1 的值的每个数字分开,然后将0x12(十进制18)的每个数字打印到stdout,后跟newline。 (输出到十六进制表示的转换留给您 - 您可以在网上搜索并找到几个示例,int 80h.org 浮现在脑海中。)
nasm 中的一个简短示例是:
section .text
global _start
_start:
mov edi, result ; address of buffer in destination index
xor eax, eax ; zero out eax
mov al, [var1] ; put the value of var1 in al to divide
mov bl, 10 ; base 10 divisor to find remainder
; separate remainder digits into result buffer
remloop:
div bl ; divide current value by 10
mov [edi], ah ; move the remainder to result
cmp al, 0 ; is the quotient zero?
je printchar ; if it is we are done
xor ah, ah
inc edi ; move offset in result string (note digits
jmp remloop ; of answer are stored in reverse order)
printchar:
add byte [edi], 0x30 ; add ascii '0' to digit to get printable char
mov eax, 4 ; linux sys_write
mov ebx, 1 ; stdout
mov ecx, edi ; point to current digit
mov edx, 1 ; number of chars to print
int 0x80 ; syscall
dec edi ; point to next digit
cmp edi, result ; are we past the final digit?
jge printchar ; if not, keep printing to stdout
; print newline
mov eax, 4 ; linux sys_write
mov ebx, 1 ; stdout
mov ecx, newline ; address of newline
mov edx, 1 ; number of chars to print
int 0x80 ; syscall
; exit the program gracefully
mov eax, 0x1 ; __NR_exit 1
mov ebx, 0x5 ; exit code of 5
int 0x80
section .data
var1 db 0x12 ; ASCII 18 (non-printable)
result times 8 db 0 ; 8 byte buffer for result
newline db 0xa ; newline character
如果你构建代码:
nasm -f elf -o convert.o convert.asm
ld -m elf_i386 -o convert convert.o
然后您可以显示0x12 或18 ASCII 的ASCII 输出:
$ ./convert
18
有关 Assembly 的出色网络参考,请参阅 The Art of Assembly Language Programming。阅读。所有的。真是太好了。虽然它主要是为 8086 编写的,但所有原则都 100% 适用于当前的汇编编程。唯一的区别是 x86_64 的寄存器大小、调用约定和系统调用号。