Can't call C standard library function on 64-bit Linux from assembly (yasm) code
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
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
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
-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 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,
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-pltwould emit when compiling C.
call printf@pltlike compilers have historically used.
- non-PIE executable isn't an option; you're making a
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 */
; 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 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