Assembly: printf not printing new line
When you use quotes or double quotes around a string in NASM, it doesn't accept C style escape sequences. On Linux you can encode \n
as ASCII 10 like this:
fmt db "Number of parameters: %d", 10, 0
There is an alternative. NASM supports backquotes (backticks) which will allow NASM to process the characters between them as C style escape sequences. This should work as well:
fmt db `Number of parameters: %d \n`, 0
Please note: Those are not single quotes, but backticks. This is described in the NASM documentation:
3.4.2 Character Strings
A character string consists of up to eight characters enclosed in either single quotes ('...'), double quotes ("...") or backquotes (
...
). Single or double quotes are equivalent to NASM (except of course that surrounding the constant with single quotes allows double quotes to appear within it and vice versa); the contents of those are represented verbatim. Strings enclosed in backquotes support C-style -escapes for special characters.
Printf without newline in assembly
fflush() flushes buffered output in line or full-buffered stdio streams:
extern fflush
...
xor edi, edi ; RDI = 0
call fflush ; fflush(NULL) flushes all streams
...
Alternatively, mov rdi, [stdout]
/ call fflush
also works to flush only that stream. (Use default rel
for efficient RIP-relative addressing, and you'll need extern stdout
as well.)
printing new lines with printf assembly
NASM accepts strings in single quotes ('...'
) or double quotes ("..."
), which are equivalent, and do not provide any escapes; or in backquotes (`...`
), which provide support for C-style escapes, which is what you want.
(See section 3.4.2, "Character Strings", in the documentation.)
To get actual ASCII newlines in your data in memory, rather than literal backslash n:
sentence0: db `Hello\nWorld\n`, 0
Or do it manually:
sentence0: db 'Hello', 10, 'World`, 10, 0
YASM (another NASM-syntax assembler) doesn't accept backticks, so the manual option is your only choice there.
And BTW, you can call puts
instead of printf
if you don't have any actual formatting in your format string (leave out the trailing newline).
x86 Assembly - printf doesn't print without \n
The problem is that printf
by default just prints stuff into the stdout buffer. Things won't actually be printed until the buffer is flushed. The depends on the buffering mode of stdout, but, by default, it is line-buffered, which means it gets flushed every time you print a newline character.
To flush explicitly in C, you call fflush; you can do that in asm code with
pushl stdout
call fflush
addl $4, %esp
Alternately, you can call the stdlib exit
function (which flushes all I/O buffers before actually exiting), instead of using the _exit
system call, which does not.
Printf call from assembly do not print to stdout
If you want to use C library functions you shouldn't use _start
, but good old main
. If you steal _start
from libc, it won't be able to perform several setup and cleanup operations, such as flushing the open C files.
This is visible in this case only when writing to file, as when writing to a tty stdout is line-buffered by default, so it's flushed immediately. When redirecting to file, instead, the data is just copied in a temporary buffer, to be flushed when it's full enough, or at termination - but the C runtime cleanup functions aren't ever called in your case, so the data is never actually passed to the OS.
Another possibility is to exit using the exit
libc function (extern exit
at the beginning and call exit
at the end of the execution), which should handle the cleanup, but I'm not sure if stuff from libc is even required to work fine if it doesn't get the chance to be initialized in first place should be safe on Linux, see @PeterCordes comment and thanks to @Kirill_Zaitsev for trying it.
Using printf in assembly leads to empty output when piping, but works on the terminal
Use call exit
instead of a raw _exit
syscall after using stdio functions like printf. This flushes stdio buffers (write
system call) before making an exit_group
system call).
(Or if your program defines a main
instead of _start
, returning from main
is equivalent to calling exit
. You can't ret
from _start
.) Calling fflush(NULL)
should also work.
As Michael explained, it is OK to link the C-library dynamically. This is also how it is introduced in the "Programming bottom up" book (see chapter 8).
However it is important to call exit
from the C-library in order to end the program and not to bypass it, which was what I wrongly did by calling exit-syscall
. As hinted by Michael, exit does a lot of clean up like flushing streams.
That is what happened: As explained here, the C-library buffers the the standard streams as follows:
- No buffering for standard error.
- If standard out/in is a terminal, it is line-buffered.
- If standard out/in is a not a terminal, it is fully-buffered and thus flush is needed before a raw exit system call.
Which case applies is decided when printf
is called for the first time for a stream.
So if printf_try
is called directly in the terminal, the output of the program can be seen because hello
has \n
at the end (which triggers the flush in the line-buffered mode) and it is a terminal, also the 2. case.
Calling printf_try
via $(./printf_try)
means that the stdout is no longer a terminal (actually I don't know whether is is a temp file or a memory file) and thus the 3. case is in effect - there is need for an explicit flush i.e. call to C-exit
.
MIPS printing new line methods
We can print one character at a time — one per syscall, using syscall #11. The character to print goes in $a0
. To print a newline character without using a data declaration:
li $a0, 10 # $a0 == character to print: newline '\n' (linefeed, 0xA/10)
li $v0, 11 # syscall function code #11 -- print one character
syscall
Also, often we can include the newline character, '\n' at the beginning or end of another string that we already need to printed, so the newline doesn't necessarily have to be printed separately in its own syscall.
For example, let's do the equivalent of printf("sum: %d\n, avg: %d\n", sum, avg);
This printf
prints 2 lines of output, i.e. uses two newline characters:
Decomposing this we get:
la $a0, sum # "sum: "
li $v0, 4 # syscall function code #4 for print string
syscall # prints "sum: "
move $a0, $s0 # assume sum in $s0, move to $a0 for syscall
li $v0, 1 # syscall function code #1 for print integer
syscall
li $a0, avg # "\n, avg: "
li $v0, 4 # syscall function code #4 for print string
syscall
move $a0, $s1 # assume avg in $s1
li $v0, 1 # syscall function code #1 for print integer
syscall
li $a0, 10
li $v0, 11
syscall
.data
sum: .asciz "sum: "
avg: .asciz "\n, avg: "
This sequence prints 2 lines of output. The first '\n' newline is appended to the beginning of the prompt/introducing string "avg: ". The second newline is printed using a syscall #11, so the whole sequence doesn't require a separate "\n" data declaration.
The printf format string is decomposed into string literals as follows:
printf("sum: %d\n, avg: %d\n", sum, avg);
^---^ ^-------^ ^
lit1 lit2 lit3
lit1 is "sum: "; lit2 is "\n, avg: "; lit3 is one character so doesn't need data. You can see that lit2 contains the end of one line and the beginnig of another, together printed in one syscall. Between lit1 and lit2 and lit3 are syscall #1's to print integers.
Related Topics
Default Field Separator for Awk
Perf_Event_Open - How to Monitoring Multiple Events
What Are the Real Rules for Linux Usernames on Centos 6 and Rhel 6
How Does Pthread Implemented in Linux Kernel 3.2
Explaining the 'Find -Mtime' Command
How to Fix Conda Update Conda Permission Error
What Is an Anonymous Inode in Linux
Bash Read/Write File Descriptors -- Seek to Start of File
Virtualenv: Workon Command Not Found
When to Check for Eintr and Repeat the Function Call
Udp-Broadcast on All Interfaces
Using Mono-Service to Wrap a Windows Service on Linux
How to Compile Glut + Opengl Project with Cmake and Kdevelop in Linux
Variable in Bash Script That Keeps It Value from the Last Time Running
Get String Length in Inline Gnu Assembler
Bash Script to Remove 'X' Amount of Characters the End of Multiple Filenames in a Directory