Search code examples
cgccarmsystem-callsgem5

Why would a simple C program need syscalls?


Related to this other question. I am trying to run this simple C program in gem5:

int main() {
    int a=1, b=2;
    int c=a+b;
    return c;
}

And it fails because gem5 doesn't have some syscalls implemented.

My question is, why would a simple program like this require syscalls? This should run bare-metal without trouble. Is there a way to compile this to avoid syscalls? I am using arm-linux-gnueabi-gcc -static -DUNIX to compile it.


Solution

  • Without syscalls the program cannot exit. The way it works is typically something like this:

    // Not how it's actually implemented... just a sketch.
    void _start() {
        char **argv = ...;
        int argc = ...;
        // ... other initialization code ...
        int retcode = main(argc, argv);
        exit(retcode);
    }
    

    The exact details depend on the operating system, but exit(), which terminates the process, typically has to be a system call or is implemented with system calls.

    Note that this is true for "hosted" C implementations, not for "freestanding" C implementations, and is highly operating-system specific. There are freestanding C implementations can run on bare metal, but hosted C implementations usually need an operating system.

    You can compile without standard libraries and without the runtime but your entry point cannot return... there is nothing to return to, without a runtime.

    Creating a baremetal program

    It is generally possible to compile programs capable of running baremetal.

    • Use -ffreestanding. This makes GCC generate code that does not assume that the standard library is available (and has other effects).

    • Use -nostdlib. This will prevent GCC from linking with the standard library. Note that memcmp, memset, memcpy, and memmove calls may be generated anyway, so you may have to provide these yourself.

    At this point you can write your program, but you typically have to use _start instead of main:

    void _start(void) {
        while (1) { }
    }
    

    Note that you can't return from _start! Think about it... there is nowhere to return to. When you compile a program like this you can see that it doesn't use any system calls and doesn't have a loader.

    $ gcc -ffreestanding -nostdlib test.c
    

    We can verify that it loads no libraries:

    $ ldd a.out                              
        statically linked
    $ readelf -d a.out 
    
    Dynamic section at offset 0xf30 contains 8 entries:
      Tag        Type                         Name/Value
     0x000000006ffffef5 (GNU_HASH)           0x278
     0x0000000000000005 (STRTAB)             0x2b0
     0x0000000000000006 (SYMTAB)             0x298
     0x000000000000000a (STRSZ)              1 (bytes)
     0x000000000000000b (SYMENT)             24 (bytes)
     0x0000000000000015 (DEBUG)              0x0
     0x000000006ffffffb (FLAGS_1)            Flags: PIE
     0x0000000000000000 (NULL)               0x0
    

    We can also see that it doesn't contain any code that makes system calls:

    $ objdump -d a.out
    
    a.out:     file format elf64-x86-64
    
    
    Disassembly of section .text:
    
    00000000000002c0 <_start>:
     2c0:   eb fe                   jmp    2c0 <_start>