Reputation: 253
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.
Upvotes: 25
Views: 16047
Reputation: 201
If you do use _start
instead of main
(as mentioned in some of the comments above), you'll also need to change the way the program exits, or you'll get a seg fault:
.text
.globl _start
_start:
mov $hw_str, %rdi
call puts
movl $0,%ebx # first argument: exit code.
movl $1,%eax # system call number: sys_exit.
int $0x80 # call kernel.
.data
hw_str: .asciz "Hello world!"
On Kubuntu 18.04.2 (gcc (Ubuntu 7.3.0-27ubuntu1~18.04) 7.3.0):
$ as -o hello.o hello.s
$ ld -dynamic-linker /lib64/ld-linux-x86-64.so.2 -o hello hello.o -lc
Also, one easy way to find out what the dynamic linker is on your system is to compile a small C program and then run ldd
on the binary:
test.c:
int main() { return 0; }
Compile and run ldd against executable:
$ gcc -o test test.c
$ ldd test
linux-vdso.so.1 (0x00007ffd0a182000)
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007ff24d8e6000)
/lib64/ld-linux-x86-64.so.2 (0x00007ff24ded9000)
Upvotes: 2
Reputation: 383090
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?
Upvotes: 10
Reputation: 46998
There are at least three things that you need to do to successfully use libc with dynamic linking:
/usr/lib/crt1.o
, which contains _start
, which will be the entry point for the ELF binary;/usr/lib/crti.o
(before libc) and /usr/lib/crtn.o
(after), which provide some initialisation and finalisation code;/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!
$
Upvotes: 28
Reputation: 25288
I think something like this should work:
Upvotes: 2