As an exercise to learn more precisely how c programs work and what minimum level of content must exist for a program to be able to use libc, I've taken it upon myself to attempt to program primarily in x86 assembly using gas and ld.
As a fun little challenge, I've successfully assembled and linked several programs linked to different self-made dynamic libraries, but I have failed to be able to code a program from scratch to use libc function calls without directly using gcc.
I understand the calling conventions of individual c library functions, and have thoroughly inspected programs compiled out of gcc through use of objdump and readelf, but haven't gotten anywhere as far as what information to include in a gas assembly file and what parameters to invoke in ld to successfully link to libc. Anyone have any insight to this?
I'm running Linux, on an x86 machine.
There are at least three things that you need to do to successfully use libc with dynamic linking:
- Link
/usr/lib/crt1.o
, which contains _start
, which will be the entry point for the ELF binary;
- Link
/usr/lib/crti.o
(before libc) and /usr/lib/crtn.o
(after), which provide some initialisation and finalisation code;
- Tell the linker that the binary will use the dynamic linker,
/lib/ld-linux.so
.
For example:
$ cat hello.s
.text
.globl main
main:
push %ebp
mov %esp, %ebp
pushl $hw_str
call puts
add $4, %esp
xor %eax, %eax
leave
ret
.data
hw_str:
.asciz "Hello world!"
$ as -o hello.o hello.s
$ ld -o hello -dynamic-linker /lib/ld-linux.so.2 /usr/lib/crt1.o /usr/lib/crti.o -lc hello.o /usr/lib/crtn.o
$ ./hello
Hello world!
$
If you define main
in assembly
Matthew's answer does a great job of telling you the minimum requirements.
Let me show you how how to find those paths in your system. Run:
gcc -v hello_world.c |& grep 'collect2' | tr ' ' '\n'
and then pick up the files Matthew mentioned.
gcc -v
gives you the exact linker command GCC uses.
collect2 is the internal executable GCC uses as a linker front-end, which has a similar interface to ld
.
In Ubuntu 14.04 64-bit (GCC 4.8), I ended up with:
ld -dynamic-linker /lib64/ld-linux-x86-64.so.2 \
/usr/lib/x86_64-linux-gnu/crt1.o \
/usr/lib/x86_64-linux-gnu/crti.o \
-lc hello_world.o \
/usr/lib/x86_64-linux-gnu/crtn.o
You might also need -lgcc
and -lgcc_s
. See also: Do I really need libgcc?
If you define _start
in assembly
If I defined the _start
, the hello world from glibc worked with just:
ld -dynamic-linker /lib64/ld-linux-x86-64.so.2 -lc hello_world.o
I'm not sure if this is robust, i.e. if the crt
initializations can be safely skipped to invoke glibc functions. See also: Why does an assembly program only work when linked with crt1.o crti.o and crtn.o?
I think something like this should work:
- make a simple C program
- gcc -S file.c
- edit file.s
- gas file.s
- ld file.o -lc crt1.o -o myprog