Search code examples
linuxassemblyx86-64nasmyasm

Can't call C standard library function on 64-bit Linux from assembly (yasm) code


I have a function foo written in assembly and compiled with yasm and GCC on Linux (Ubuntu) 64-bit. It simply prints a message to stdout using puts(), here is how it looks:

bits 64

extern puts
global foo

section .data

message:
  db 'foo() called', 0

section .text

foo:
  push rbp
  mov rbp, rsp
  lea rdi, [rel message]
  call puts
  pop rbp
  ret

It is called by a C program compiled with GCC:

extern void foo();

int main() {
    foo();
    return 0;
}

Build commands:

yasm -f elf64 foo_64_unix.asm
gcc -c foo_main.c -o foo_main.o
gcc foo_64_unix.o foo_main.o -o foo
./foo

Here is the problem:

When running the program it prints an error message and immediately segfaults during the call to puts:

./foo: Symbol `puts' causes overflow in R_X86_64_PC32 relocation
Segmentation fault

After disassembling with objdump I see that the call is made with the wrong address:

0000000000000660 <foo>:
 660:   90                      nop
 661:   55                      push   %rbp
 662:   48 89 e5                mov    %rsp,%rbp
 665:   48 8d 3d a4 09 20 00    lea    0x2009a4(%rip),%rdi
 66c:   e8 00 00 00 00          callq  671 <foo+0x11>      <-- here
 671:   5d                      pop    %rbp
 672:   c3                      retq

(671 is the address of the next instruction, not address of puts)

However, if I rewrite the same code in C the call is done differently:

645:   e8 c6 fe ff ff          callq  510 <puts@plt>

i.e. it references puts from the PLT.

Is it possible to tell yasm to generate similar code?


Solution

  • The 0xe8 opcode is followed by a signed offset to be applied to the PC (which has advanced to the next instruction by that time) to compute the branch target. Hence objdump is interpreting the branch target as 0x671.

    YASM is rendering zeros because it has likely put a relocation on that offset, which is how it asks the loader to populate the correct offset for puts during loading. The loader is encountering an overflow when computing the relocation, which may indicate that puts is at a further offset from your call than can be represented in a 32-bit signed offset. Hence the loader fails to fix this instruction, and you get a crash.

    66c: e8 00 00 00 00 shows the unpopulated address. If you look in your relocation table, you should see a relocation on 0x66d. It is not uncommon for the assembler to populate addresses/offsets with relocations as all zeros.

    This page suggests that YASM has a WRT directive that can control use of .got, .plt, etc.

    Per S9.2.5 on the NASM documentation, it looks like you can use CALL puts WRT ..plt (presuming YASM has the same syntax).