1

Using this code:

section .data
    msg db "Most basic printf example in NASM", 0xA, 0xD, 0
    len equ  $-msg

section .bss

section .text
    global _start       
    extern printf

_start:                 
    mov  rsi, msg
    push rax ;; for stack alignment 
    call printf

fails with the error undefined reference to `printf'

I'm trying to create the simplest worked example of calling printf from NASM.

I had read suggestions of adding -lc to ld i.e.:

ld -o worked-example -lc worked-example.o or ld -o worked-example worked-example.o -lc both prevent the error message - but then the file worked-example - can't be found with ./worked-example - even though it can be seen with cat and ls

file worked-example indicates: worked-example: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib/ld64.so.1, with debug_info, not stripped

EDIT: Based on comments below, I found the dynamic linker and tried to add that: ld worked-example.o -o worked-example -lc -dynamic-linker /usr/lib64/ld-linux-x86-64.so.2 which works. [With a segfault and nothing printed - but it compiles and runs]

Michael Petch
  • 42,023
  • 8
  • 87
  • 158
d-cubed
  • 902
  • 5
  • 29
  • 52
  • What does `file worked-example` say? – Barmar Dec 04 '20 at 16:41
  • 1
    Name mangling is for C++, and will cause a linker error. – Barmar Dec 04 '20 at 16:53
  • What is the exact error message? – Barmar Dec 04 '20 at 16:54
  • 2
    You will get an error when running like the file itself can't be found because you didn't specify a dynamic linker to run at load time. On 64-bit Linux distros that can't usually be done like this `ld -o worked-example -lc worked-example.o -dynamic-linker /lib64/ld-linux-x86-64.so.2` . You stiull also have to link with `-lc` . If you used GCC to link you could avoid this since it will do this for you and link in the C library – Michael Petch Dec 04 '20 at 16:55
  • I thought you said that linking with `-lc` fixed that. What was the error message after you fixed that? – Barmar Dec 04 '20 at 16:59
  • 3
    My first comment should have said _On 64-bit Linux distros that can't usually be done like_ . Word of caution is that running code from the _C_ library by circumventing the C startup code could cause issues for some types of C function calls. If you use the MUSL C library you can avoid that problem as it can be used in a way that doesn't require prior initialization. – Michael Petch Dec 04 '20 at 17:02
  • 2
    As for it not running correctly it is because the first parameter is in RDI but you put it in RSI . And since `printf` is a variadic function you should specify the maximum number of vector registers (SIMD registers) used to make the call in register AL. In this case you used no vector registers so you can set AL to 0. Your code also lacks something to exit the program. You might be able to get away with `call exit` – Michael Petch Dec 04 '20 at 17:05
  • 1
    The format string does go in RDI as it is the first parameter. Your format string is `"Most basic printf example in NASM", 0xA, 0xD, 0`. It is a format string with a string that doesn't contain any C format specifiers in it. – Michael Petch Dec 04 '20 at 17:14
  • 1
    `_start` isn't a function. On entry to `_start`, RSP is already 16-byte aligned, and `push rax` misaligns it. So you should use `lea rdi, [rel msg]` / `xor eax,eax` / `call printf`. (Or for a PIE executable like `gcc -nostartfiles -pie foo.o`, [Can't call C standard library function on 64-bit Linux from assembly (yasm) code](https://stackoverflow.com/q/52126328): `call [rel printf wrt ..got]` or `call printf wrt ..plt`.) – Peter Cordes Dec 04 '20 at 19:21

1 Answers1

4

Michael helped fill in the missing pieces. The biggest issue is the linker command. Compile with:
nasm -f elf64 -F dwarf -g worked-example.asm

Then the linker needs to a call to the dynamic linker:
ld worked-example.o -o worked-example -lc -dynamic-linker /usr/lib64/ld-linux-x86-64.so.2

The exact path will vary by distro.
Use `sudo find / -iname ld-linux-x86-64.so.2" to find the correct path for your system.

In focusing on the linking problems, I forgot to end the program cleanly:

mov eax, 1 ; sys exit
mov ebx, 0 ; all OK 
int 80h    ; call the kernel

Perhaps, the easiest to understand worked example is:

section .data
    msg db "Most basic printf example in NASM", 0xA, 0
    len equ  $-msg

section .bss

section .text
    global _start       
    extern printf
   
_start:                 
    mov rdi, msg
    call printf
    
   ;end gracefully 
   mov eax, 1 
   mov ebx, 0
   int 80h 

However, there are a few problems with that. In deference to Peter, this is a more refined, basically identical, approach:

section .data
    msg:  db "Most basic printf example in NASM", 0xA, 0
    len equ  $-msg

section .text
    global _start       
    extern printf
    extern exit
   
_start:                 
    xor  eax, eax          ; 0 FP args
    lea  rdi, [rel msg]
    call printf

    xor  edi, edi
    call exit              ; exit(0)
     ; let C handle any stdio flushing before exiting
     ; see here: https://stackoverflow.com/questions/38379553/using-printf-in-assembly-leads-to-an-empty-ouput
d-cubed
  • 902
  • 5
  • 29
  • 52
  • 1
    1. int 0x80 in 64-bit code is not recommended (although it's basically fine for exiting). [What happens if you use the 32-bit int 0x80 Linux ABI in 64-bit code?](https://stackoverflow.com/q/46087730). 2. `call exit` after printf, to make sure stdio buffers are flushed even if stdout is full-buffered, e.g. from redirecting output to a file. Generally don't mix stdio output with raw exit system-calls. [Using printf in assembly leads to an empty ouput](https://stackoverflow.com/q/38379553) – Peter Cordes Dec 04 '20 at 19:25
  • 1
    `mov rdi, msg` is the worst way to put a label/symbol address into a register. The standard works-everywhere way is a RIP-relative LEA, or `mov edi, msg` if you want to optimize for Linux non-PIE. [How to load address of function or label into register in GNU Assembler](https://stackoverflow.com/q/57212012) (my answer there includes NASM examples). `mov rdi, msg` does work (and even works in PIE executables, thanks to runtime fixups), it's mostly just inefficient. – Peter Cordes Dec 04 '20 at 19:29
  • 1
    You *should* be zeroing AL to tell printf there are 0 FP args in XMM registers. Use `xor eax,eax` before `call`. In current GNU/Linux systems, gcc compiles variadic functions to just check AL!=0 and dump all 8 XMM0..7 registers or none (which is why your code happens to work), but earlier versions used AL for a computed jump and would crash with AL>8. (The ABI doesn't guarantee zeroing registers on entry to _start. That happens in a static executable because the kernel does that to avoid leaking possible kernel data, but the dynamic linker runs in your process before your _start.) – Peter Cordes Dec 04 '20 at 19:35
  • 2
    `0xA, 0xD` - We don't use DOS line endings in GNU/Linux, just `0xa` = `\n` newline. I tweaked some other things in your 2nd version (e.g. style: use `:` after labels, even in the data section) but I'll leave that line-ending up to you. – Peter Cordes Dec 04 '20 at 20:25