Can't Call C Standard Library Function on 64-Bit Linux from Assembly (Yasm) Code

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

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).

Failed to compile or link yasm program that call c function

Just ran your example and encountered the same issue.

The cause is my case was an incorrect dynamic linker (ELF interpreter).

To verify this is the issue, type file ./a.out you should be getting something similiar to the following output:

a.out: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib/ld64.so.1, not stripped

ld's default choice of dynamic linker (/lib/ld64.so.1) doesn't exist on most Linux systems, hence the problem. You could use patchelf (apt-get install patchelf) and the following command to correct this:

patchelf --set-interpreter [path_to_interpreter] ./a.out

You can get the correct path you should use as the ELF interpreter by typing file /bin/ls - the standard dynamic linker path will be printed. (Also, readelf can print that and more).


The best way is to use ld's -dynamic-linker flag to set the INTERP section in the elf header correctly in the first place.

See also this answer for more details on building / linking asm into static or dynamic binaries, with or without libc.

Calling libc functions from another shared library in AT&T syntax?

You only get undefined reference to `main' with -no-pie.

I think -no-pie is overriding -shared, so you're telling GCC to link a position-dependent executable, not a shared-library at all. (ELF shared libraries aka "shared objects" always have to be position-independent, there's no such thing as a non-PIC .so. Fun fact: PIE executables are another type of ELF shared object.)


Unlike when linking a non-PIE executable, ld rewrites call printf into a call through the PLT for you. But when linking a PIE or shared library, you have to do it manually. (Newer ld might do this for you, at least in a PIE, but this was a recent change.)

In 32-bit code, call printf likely assembles and links with a runtime fixup that rewrites the call rel32 instruction at runtime to be printf's actual address in libc, once it's loaded to a randomized base address. But that doesn't work for 64-bit code because it might be more than +-2GiB away. Similar to why 32-bit absolute addresses no longer allowed in x86-64 Linux? after distros made PIE executables the default GCC config.

Your options for 64-bit code are:

  • call *printf@GOTPCREL(%rip), like gcc -fno-plt would emit when compiling C.
  • call printf@plt like compilers have historically used.
  • non-PIE executable isn't an option; you're making a -shared library.

For a NASM-syntax version of this with more detail about what this means, and the machine code, see Can't call C standard library function on 64-bit Linux from assembly (yasm) code. Also has some examples at the bottom which include AT&T syntax, of how the linker "relaxes" this to a call rel32 if you do call *foobar@GOTPCREL(%rip) but then foobar is defined in the same shared library after all (with "hidden" visibility, not global, so symbol interposition isn't necessary).

See Unexpected value of a function pointer local variable for some examples of compiler output for a PIE vs. non-PIE executable (the PIE executable code will also work in a shared library.)

-fno-plt style of code-gen is probably not worth it in 32-bit mode, where you don't have efficient EIP-relative addressing to reach the GOT, unless you already needed a GOT pointer for something else in this funciton.

how to call C functions from assembly routines and link the C and assembly files using nasm and gcc

easier to just show you an example, I found this on the internet a while ago and saved it as a source on my computer, not sure where from though

; printf1.asm   print an integer from storage and from a register
; Assemble: nasm -f elf -l printf.lst printf1.asm
; Link: gcc -o printf1 printf1.o
; Run: printf1
; Output: a=5, eax=7

; Equivalent C code
; /* printf1.c print an int and an expression */
; #include
; int main()
; {
; int a=5;
; printf("a=%d, eax=%d\n", a, a+2);
; return 0;
; }

; Declare some external functions
;
extern printf ; the C function, to be called

SECTION .data ; Data section, initialized variables

a: dd 5 ; int a=5;
fmt: db "a=%d, eax=%d", 10, 0 ; The printf format, "\n",'0'


SECTION .text ; Code section.

global main ; the standard gcc entry point
main: ; the program label for the entry point
push ebp ; set up stack frame
mov ebp,esp
mov eax, [a] ; put a from store into register
add eax, 2 ; a+2
push eax ; value of a+2
push dword [a] ; value of variable a
push dword fmt ; address of ctrl string
call printf ; Call C function
add esp, 12 ; pop stack 3 push times 4 bytes

mov esp, ebp ; takedown stack frame
pop ebp ; same as "leave" op

mov eax,0 ; normal, no error, return value
ret ; return


Related Topics



Leave a reply



Submit