在Linux上将syscall write与printf混合使用

4
我可以帮您翻译成中文。这段内容是关于在汇编语言中调用C函数进行测试时,使用ANSI转义码并调用使用printf的C函数时出现了奇怪的行为。
以下是汇编部分:
section .data               

    red db 27,"[31;1m",0
    redlen equ $ - red

    cyan db 27,"[36;1m",0
    cyanlen equ $ - cyan

    colorReset db 27,"[0m",0
    colorResetLen equ $ - colorReset

section .text

extern printLetter
extern letter

global main

main:
        mov  BYTE [letter], 'H'
        call ansiSetRed
        call printLetter
        mov  BYTE [letter], 'e'
        call ansiSetCyan
        call printLetter
        mov  BYTE [letter], 'l'
        call ansiReset
        call printLetter
        mov  BYTE [letter], 'l'
        call ansiSetRed
        call printLetter
        mov  BYTE [letter], 'o'
        call ansiSetCyan
        call printLetter
        mov  BYTE [letter], '!'
        call ansiReset
        call printLetter
        mov  BYTE [letter], 10
        call printLetter

        ret

ansiSetRed:
        mov rax, 1
        mov rdi, 1
        mov rsi, red
        mov rdx, redlen
        syscall
        ret

ansiSetCyan:
        mov rax, 1
        mov rdi, 1
        mov rsi, cyan
        mov rdx, cyanlen
        syscall
        ret

ansiReset:
        mov rax, 1
        mov rdi, 1
        mov rsi, colorReset
        mov rdx, colorResetLen
        syscall
        ret    

看起来很长,但它只是在开头定义了一些带有ansi代码的字符串,一个用于设置前景颜色为红色,一个用于青色,另一个用于重置颜色。

然后我有一些函数,使用系统调用write打印这些ansi字符串。

主要函数只是打印“Hello!”交替使用每个字母的颜色,首先调用一个汇编函数打印相应的ansi字符串,然后调用一个外部c函数打印存储在全局变量中的字符。

这里是c部分:

#include <stdio.h>

char letter;

void printLetter(void) {

    printf("%c", letter);

}

当我运行它时,“Hello!”消息全白显示,就好像汇编部分没有打印ansi代码一样。

enter image description here

但是,如果我将 c 部分更改为在每个字符后只打印一个新行:

#include <stdio.h>

char letter;

void printLetter(void) {

    printf("%c\n", letter);

}

然后信封上的字母按照我一开始预期的那样,每种颜色各有一个。

enter image description here

这种行为的原因是什么?

此处的有用调试工具包括:strace 用于跟踪系统调用,ltrace用于跟踪库函数调用。 - Peter Cordes
1个回答

9
这是因为C标准I/O库stdio对于stdout输出到终端时使用了行缓冲。这意味着你写入的数据不会立即发送到终端,而是被缓存直到整行数据可用。你在第一个程序(一行hello)中观察到的是,直到调用带有换行符的printLetter时,Hello 的任何字符才被实际写入,导致stdout的缓冲区刷新到终端。
解决问题的方法有以下几种(单个方法均可解决问题,但只能使用一个方法):
  • 编辑ansiSetRed等函数,在其中调用fwrite而不是直接执行write系统调用。这应该使缓冲正常工作。
  • 在写入任何数据之前调用setbuf(stdout, NULL)关闭缓冲。
  • 将输出重定向到stderr,因为stderr是无缓冲的。
  • 在每个printf后手动刷新stdout,执行fflush(stdout)
  • 重写printLetter,使用系统调用write而不是printf

3
通常情况下,在同一设备上工作但处于软件堆栈不同“级别”的函数中,不要混合IO调用,除非你确保正确同步它们(刷新缓冲区等)。 - Matteo Italia
2
@MatteoItalia,我所提出的所有建议都会实现(如果我没记错的话,禁用缓冲在POSIX中被记录为在每次写入后执行fflush)。 - fuz
2
当然,事实上我已经为你的回答点赞了 =) 我只是更明确地阐述了一般观点。 - Matteo Italia

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