Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Mixing syscall write with printf on linux

I was doing some test in assembly calling c functions and I get what I think es a strange behavior when using ansi escape codes and calling a c function that uses printf.

This is the assembly part:

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    

Looks long but all that i does is define some strings with ansi codes at the beginning, one to set foreground color to red, one for cyan and one to reset the colors.

Then I have functions that print this ansi strings using syscall write.

The main function is just supposed to print "Hello!" alternating the color of each letter, by first calling an assembly function that prints the corresponding ansi string, and then calling an extern c function that prints a character that is stored in a global variable.

Here the c part:

#include <stdio.h>

char letter;

void printLetter(void) {

    printf("%c", letter);

}

When I run it, the Message "Hello!" is displayed all white like if the assembly part was not printing the ansi codes

enter image description here

but if I change the c part to just print a new line after each character:

#include <stdio.h>

char letter;

void printLetter(void) {

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

}

Then the letters show one of each color as I expected at the beginning.

enter image description here

What can be the cause of this behavior?

like image 815
wallek876 Avatar asked Jan 05 '23 22:01

wallek876


1 Answers

That's because stdio (the C standard I/O package) uses line buffering for stdout if stdout goes to a terminal. That means that data you write is not immediately sent to the terminal but rather buffered until a whole line is available. What you observe in your first program (hello on a line) is that no character of Hello is actually written until you call printLetter with a line feed, causing the buffer for stdout to be flushed to the terminal.

I see the following approaches to solve your problem (any single of them does the trick, but use only one approach):

  • edit ansiSetRed etc. to call fwrite instead of directly executing a write system call. This should make buffering work as expected.
  • call setbuf(stdout, NULL) to turn off buffering before writing any data.
  • write to stderr instead of stdout as stderr is unbuffered
  • execute fflush(stdout) after each printf to manually flush stdout.
  • Rewrite printLetter to use the system call write instead of printf.
like image 72
fuz Avatar answered Jan 08 '23 08:01

fuz