所以我正在学习使用NASM语法的x86 Linux汇编(哦,天哪,不要再这样了,你们都在想)。我正在尝试制作一个子程序,只需将EAX中的值打印到stdout。代码运行并退出时没有出现错误,但没有打印任何内容。我不明白为什么。首先,这是我正在处理的文件:
segment .bss
to_print: resd 1
segment .text
global print_eax_val
print_eax_val: ; (top)
push dword ebx ;Stack: edx
push dword ecx ; ecx
push dword edx ; ebx
; (bot)
mov ecx,eax ;ecx = eax
mov [to_print],ecx ;to_print = ecx
mov eax, 4 ;sys_write
mov ebx, 1 ;to stdout
add ecx, 47 ;add 47 for ASCII numbers
mov edx, 2 ;double word = 2 bytes
int 0x80
mov eax, [to_print] ;eax = original val
pop edx ;pop the registers back from the stack
pop ecx
pop ebx ;Stack: empty
ret
这是从我的主文件中调用的,它看起来是这样的(这可能无关紧要,除非我错过了一些激烈的东西)。
segment .data
hello db "Hello world!", 0
newline db 0xA
len equ $ - hello
len2 equ $ - newline
segment .text
extern print_nl
extern print_eax_val
global main
main:
enter 0,0
call print_nl
mov eax, 1
call print_eax_val
mov ebx, 0 ;exit code = 0 (normal)
mov eax, 1 ;exit command
int 0x80 ;ask kernel to quit
print_nl
只是定义并打印换行符的另一个子例程。此操作成功运行,并按预期打印新行。
问题是否与我的sys_write
调用的长度参数有关?我给它2,这是一个dword
的大小,这是EAX
寄存器和我的to_print
标签的大小,我用resd 1
保留了它。出于绝望,我试着把长度改成1、4、8、16和32。。。什么都没用。
编辑:对于任何想知道的人,以下是我如何修复代码的:(我会在更改的行上加上星号):
segment .bss
to_print: resd 1
segment .text
global print_eax_val
print_eax_val: ; (top)
push dword ebx ;Stack: edx
push dword ecx ; ecx
push dword edx ; ebx
; (bot)
mov ecx,eax ;ecx = eax
mov [to_print],ecx ;to_print = ecx
**** add dword [to_print], 48
mov eax, 4 ;sys_write
mov ebx, 1 ;to stdout
**** mov ecx, to_print
mov edx, 2
int 0x80
**** sub dword [to_print], 48
mov eax, [to_print] ;eax = original val
pop edx ;pop the registers back from the stack
pop ecx
pop ebx ;Stack: empty
ret
基本上,ecx
必须包含要打印的块的地址,而不是值本身。正如所选答案中所指出的,只有eax在0-9范围内时,才有效。
EDIT 2:所以我对sys_write的第二个参数(存储在edx
中的参数)有点困惑。我认为它只是指一些字节。因此,对于dword
,正如我所使用的,在那里使用4是合适的,因为双字是4个字节,即32位。我猜它之所以有效,是因为x86是小端序。因此,在内存中,to_print
的十六进制值如下所示:
90 00 00 00
在提供的长度为2的情况下,sys_write得到:
90 00
因此,幸运的是,该值没有被破坏。
后来我更改了代码,将to_print
存储为字节,使用resb 1
并使用byte
而不是dword
访问它。。。字节在这里很好,因为我知道我不会给to_print
一个大于9的值。
我对汇编程序很生疏,但看起来ECX包含值48,而它应该包含要写入的缓冲区的地址。
我想您在print_eax_val
中的意图是取EAX的二进制值,将ASCII偏移量添加到数字0
(应该是48,而不是47),然后打印该单个字符。为此,在将值存储到to_print
之前加48,将to_print
的地址放在ECX中,并将长度(EDX)设置为1,因为您只写一个字符。
现在请记住,这只适用于0x0000和0x0009之间的EAX值。当你超过9时,你会得到其他ASCII字符。
解释如何获取EAX的任意二进制值并将其转换为可以打印的十进制字符串远远超出了SO的范围。
write
系统调用需要打印一个字符缓冲区,由%ecx
指向,长度由%edx
给定。另一方面,像%eax
这样的寄存器包含一个32位整数。
因此,如果您真的想打印%eax
,您需要首先将该整数转换为一系列字符。您可以将其转换为ASCII十进制、十六进制或任何其他您喜欢的基数,但您需要将其转换成字符。