Skip to content
Advertisement

Why do write syscalls print `%` at end on linux x86_64 (nasm)?

The following hello-world program displays a % sign at the end of the printed string. Why is this and how can I remove it?

Here is my program:

section .data
    msg db  "hello, world!"

section .text
    global _start
_start:
    mov rax, 1      ; syscall 1 (write)
    mov rdi, 1      ; arg 1 = 1 (stdout)
    mov rsi, msg    ; arg 2 = msg ("hello, world!")
    mov rdx, 13     ; arg 3 = 13 (char count)
    syscall         ; call write
    mov rax, 60     ; syscall 60 (exit)
    mov rdi, 0      ; arg 1 = 0 (OK)
    syscall         ; call exit

And here is the output when I run the executable: hello, world!%

Thanks in advance.

Edit: It seems to be caused by zsh (not reproducible in bash). The question is why this happens and how to fix it.

Advertisement

Answer

This happens because your program’s output doesn’t end with a newline.


Your program isn’t printing the %. You can verify this by piping its output into hexdump -C or similar.

You can also use strace to trace what system calls its making, but that doesn’t show the output, so it doesn’t rule out the kernel magically adding a %. (But you can be sure the kernel doesn’t do that. The only munging that’s possible is for write to return early, without having written the full buffer. This is only likely with large buffer sizes, especially when writing to a pipe with a buffer larger than the pipe-buffer (maybe 64kiB).


This is a ZSH feature for partial lines: Why ZSH ends a line with a highlighted percent symbol?. You didn’t say it was a highlighted % symbol. Accurate / complete descriptions are important.

(An earlier version of this answer assumed you were using % as your prompt in ZSH. % is sometimes used as a prompt, but more often in C-shells like tcsh, not Bourne-derived shells like bash and zsh.)

You’d get exactly the same thing from echo -n "hello, world!". e.g. in bash:

peter@volta:/tmp$ echo -n 'hello world!'
hello world!peter@volta:/tmp$ 

In ZSH:

volta:/tmp$ echo -n 'hello world!' 
hello world!%
volta:/tmp$      # and the % on the previous line is in inverse-video

Bash just prints $PS1 (or runs $PROMPT_COMMAND) after a foreground command exits, regardless of cursor position. It can’t directly check that your program’s output ended with a newline. I guess ZSH uses VT100 escape codes to query the cursor position to detect cases where programs leave the cursor not at the start of a line.

You also get this when you cat a file that doesn’t end with a newline, or any number of other cases.


To fix it, include a newline (ASCII linefeed, 0xa) in your message:

section .rodata
    msg: db  "hello, world!", 0xa
    msglen equ $ - msg               ; use mov edx, msglen
    msgend:                          ; or  mov edx, msgend - msg

See How does $ work in NASM, exactly? for more details on getting the assembler to compute the size for you.

Don’t omit the : on data labels in NASM; it’s good style and avoids name conflicts with instruction mnemonics and assembler directives.

NASM (but not YASM) accepts C-style esacapes inside backquotes, so instead of the numeric ASCII code for a newline, you could write

  msg: db  `hello, world!n`
User contributions licensed under: CC BY-SA
1 People found this is helpful
Advertisement