为什么这个简单的汇编程序没有返回数字?
我正试图让它打印12
十六进制,但它打印乱码。
global _start
section .text
_start:
mov eax, 0x4
mov ebx, 0x1
mov ecx, var1
mov edx, 2
int 0x80
;exit the program gracefully
mov eax, 0x1
mov ebx, 0x5
int 0x80
section .data
var1: db 0x12
答案 0 :(得分:2)
您遇到的主要障碍是尝试将0x12
(ASCII 18)的值写入stdout
。快速检查ASCII图表将显示该值不可打印。在汇编中,您只能将字符写入stdout
。这意味着当面对写入数值时,您必须将值分成数字,将数字转换为ASCII表示(通过添加'0'
或0x30
或48
十进制)到该值。
与手动将数字转换为C或任何其他语言的字符串没有什么不同。你基本上将原始数字除以10
,每次将余数保存为缓冲区,然后以相反的顺序写出缓冲区,将数字的ASCII表示写入stdout
(SO上有很多例子)。
在查看将所有数字分成缓冲区并将其写入stdout
的示例之前,让我们先介绍一下您示例中的一些基础知识,然后让它打印一些内容(任何内容)。你需要在大脑中巩固的第一个装配概念是装配中的所有标签(变量)指向内存地址而不是数值。在程序集中分配标签时,例如:
var1 db 0x12
您存储单个数据字节,其值12-hex
位于var1
指向的内存位置。 var1
是指向该内存位置的指针。在nasm中,如果您想在该位置操作/或引用值,则必须通过将指针括在[ ]
中来取消引用指向该地址的指针(就像用C *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:
$ ./convert
18
有关装配的优秀网络参考,请参阅The Art of Assembly Language Programming。阅读。所有的。这很好。虽然它主要是针对8086编写的,但所有原则都100%适用于当前的汇编编程。唯一的区别是x86_64的寄存器大小,调用约定和系统调用号。