Cyro
Cyro

Reputation: 253

How to link a gas assembly program that uses the C standard library with ld without using gcc?

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

Answers (4)

Andy Turfer
Andy Turfer

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

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

Matthew Slattery
Matthew Slattery

Reputation: 46998

There are at least three things that you need to do to successfully use libc with dynamic linking:

  1. Link /usr/lib/crt1.o, which contains _start, which will be the entry point for the ELF binary;
  2. Link /usr/lib/crti.o (before libc) and /usr/lib/crtn.o (after), which provide some initialisation and finalisation code;
  3. 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!
$

Upvotes: 28

Igor Skochinsky
Igor Skochinsky

Reputation: 25288

I think something like this should work:

  1. make a simple C program
  2. gcc -S file.c
  3. edit file.s
  4. gas file.s
  5. ld file.o -lc crt1.o -o myprog

Upvotes: 2

Related Questions