在汇编x86中将一个字符打印到标准输出

8

我对使用汇编语言在屏幕上打印字符有些困惑。架构是x86(linux)。是否可以调用其中一个C函数或者有更简单的方法?要输出的字符存储在寄存器中。

谢谢!

3个回答

12

当然,您可以使用任何普通的C函数。以下是一个使用printf打印输出的NASM示例:

;
; assemble and link with:
; nasm -f elf test.asm && gcc -m32 -o test test.o
;
section .text

extern printf   ; If you need other functions, list them in a similar way

global main

main:

    mov eax, 0x21  ; The '!' character
    push eax
    push message
    call printf
    add esp, 8     ; Restore stack - 4 bytes for eax, and 4 bytes for 'message'
    ret

message db 'The character is: %c', 10, 0

如果你只想打印一个字符,你可以使用putchar函数:

push eax
call putchar

如果你想打印出一个数字,可以这样做:

mov ebx, 8
push ebx
push message
call printf
...    
message db 'The number is: %d', 10, 0

谢谢。但是如果寄存器ebx保存值8,我想打印字符'8'怎么办?如果我尝试将ebx作为参数推送,它不起作用,那么有没有解决方法?谢谢。 - user973758
1
那将等同于 printf("%d", 8);。我已将其添加到答案中。 - Martin

10
为了全面起见,这里介绍一种不需要使用 C 语言的方法。

使用 DOS 中断

AH = 02h - 将字符写入标准输出

将字符 DL 写入标准输出。 (我使用 DOS 模拟器 emu2 进行了测试)

mov dl, 21h ; char '!'
mov ah, 02h ; char output service
int 21h     ; call DOS services

x86-32 Linux write系统调用

write系统调用需要你提供字符串的地址。如果只有一个字符,你可以将其推入堆栈。

push    '!'         ; push dword
mov     eax, 4      ; write call number, __NR_write from unistd_32.h
mov     ebx, 1      ; write to stdout (fd=1)
mov     ecx, esp    ; use char on stack
mov     edx, 1      ; write 1 char
int     0x80        ; call into the kernel
add     esp, 4      ; restore sp 

x86-64 Linux write 系统调用

与上面相似,但现在的调用号是1,使用syscall代替int 0x80,并且调用规约寄存器不同。

有关寄存器顺序的信息

push    '!'         ; push qword 0x21
mov     rax, 1      ; write call number, __NR_write from unistd_64.h
mov     edi, 1      ; write to stdout (int fd=1)
mov     rsi, rsp    ; use char on stack
mov     rdx, 1      ; size_t len = 1 char to write.
syscall            ; call the kernel, it looks at registers to decide what to do
add     rsp, 8      ; restore stack pointer

mov edx, 1mov rdx, 1 的作用是完全相同的,但 NASM 将为您优化。该代码使用与 C 类型匹配的操作数大小,不会将小的非负 64 位整数进行优化移动。


1
对于x86-64 Linux,您可以使用lea -1(%rsp), %rsi / movb $0x21, (%rsi)来使用红区,而无需弹出堆栈。 - Peter Cordes
那么对于X86-64 Windows呢?(21h调用被阻止。) - BrainSlugs83
1
@BrainSlugs83 我不清楚Windows,抱歉。也许Peter Cordes可以提供一些帮助。 - qwr
1
@BrainSlugs83:Windows 理论上可以从用户空间使用系统调用 ABI,但它是未记录的,并且在不同版本之间不稳定。不过,可以参考 https://github.com/j00ru/windows-syscalls 了解逆向工程细节。我不知道你实际想要使用哪个。 - Peter Cordes
1
@BrainSlugs83:通常情况下,对于Windows系统,您不应该直接使用原始系统调用,而是应该调用DLL函数。如何在Windows下使用汇编语言编写hello world?展示了如何使用各种API和汇编器为32位和64位可执行文件编写hello world程序。 - Peter Cordes

-3

调用 putchar(3) 是最简单的方法。只需将 char 的值移动到 rdi 寄存器 (对于 x86-64,或者是 edi 对于 x86),然后调用 putchar。

例如(对于 x86-64):

asm("movl $120, %rdi\n\t"
    "call putchar\n\t");

将会在标准输出中打印一个x


错误的ABI!(猜测是x86-64 OS X,根据寄存器--尽管我认为您想要%rdi而不是%edi--以及库函数前面的_?) - Matthew Slattery
你在代码中进行了更改,但是你的描述仍然说“edi寄存器”。 - porglezomp
i386的System V调用约定在堆栈上传递参数,EDI是一个被调用保留的寄存器。此外,使用GNU C“基本”内联汇编(没有clobbers)进行此操作是不安全的:您会覆盖编译器的寄存器。对于x86-64,您还会覆盖编译器的红区。这 在x86-64 Linux上调用putchar,但那之后会发生什么是不可预测的。 - Peter Cordes
@MatthewSlattery: movl%edi 是将一个值零扩展到 RDI 的更有效的方式。就当前状态而言,这甚至无法组装 (movl双字操作数后缀与qword寄存器不匹配)。但无论如何,在没有任何破坏的情况下,这是完全错误的。请参见我的先前评论。在独立的 asm 中,mov $'x', %edicall putchar 这将是好的。 - Peter Cordes

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接