CS 134

Object Files, Libraries, and Linking

  • Duck speaking

    Didn't we do all this in CS 70? We had to write code, and link it together, and then run it. What's different here?

  • PinkRobot speaking

    The difference is that we're giving you a deeper understanding of what's really going on… You knew you “linked your code”, but you may have been fuzzy on what that actually entailed. Here, we'll go into more detail about what's in an object file, how libraries work, and how the linker puts it all together.

From Source to Assembler

Let's look at a simple example program that makes just one system call.

#include <unistd.h>

int main() {
    write(1, "Hello, world!\n", 13);
    return 0;
}

When you compile this program, the compiler generates assembly code. Here's the assembly code for the main function:

    .set noreorder
.text
    .globl main
.rdata
LC0:
        .asciiz  "Hello World!\n"

.text
main:
        addiu   $sp,$sp,-24     # Set up stack frame for main.
        la      $a1,LC0         # Parameters for write, a0 = 1, a1 = address of
        li      $a0,1           # the `Hello World' string, and a2 = 13.
        sw      $ra,16($sp)     # Save our return address (jal overwrites it).
        jal     write           # Call write.
        li      $a2,13          # Delay Slot! Executed *before* line above!
        lw      $ra,16($sp)     # Restore our return address.
        move    $v0,$0          # Our return value is zero.
        jr      $ra             # Adjust stack and return to our caller.
        addiu   $sp,$sp,24      # Delay Slot! Executed *before* line above!

Some things to notice:

  1. The main function is marked as a global symbol (.globl main).
  2. There are different sections for different things.
    • The string "Hello World\n" is stored in the read-only data section (as indicated by the .rdata directive), which is an area of memory for data that is read-only.
    • The code for the main function is in the “program text” section (as indicated by the .text directive), which is an area of memory for code.
  3. We call an external function, write, which is not defined in this file. We'll need to link in the implementation of write from somewhere else.
  • Duck speaking

    What's a “delay slot”?

  • PinkRobot speaking

    The MIPS CPU is a bit odd. The CPU has a “pipeline” of instructions to be executed, and it gets ready to execute the next instruction before the current instruction has finished. To simplify the design, whenever control flow changes (e.g., with a jump or a return), rather than undo the work it had done to set up the next instruction in the pipeline, the CPU just executes that next instruction and then jumps or returns. It's called a delay slot because there is a delay before the jump or return takes effect.

  • Duck speaking

    I bet the actual compiler's output is more complicated than this.

  • PinkRobot speaking

    It's basically the same, but it is a bit different in ways that don't really matter to us here.

The output actually looks like this:

    .file   1 "hello1.c"
    .section .mdebug.abi32
    .previous
    .gnu_attribute 4, 1
    .section    .rodata.str1.4,"aMS",@progbits,1
    .align  2
$LC0:
    .ascii  "Hello World!\012\000"
    .section    .text.startup,"ax",@progbits
    .align  2
    .globl  main
    .set    nomips16
    .ent    main
    .type   main, @function
main:
    .frame  $sp,24,$31      # vars= 0, regs= 1/0, args= 16, gp= 0
    .mask   0x80000000,-4
    .fmask  0x00000000,0
    .set    noreorder
    .set    nomacro
    lui $5,%hi($LC0)
    addiu   $sp,$sp,-24
    li  $4,1            # 0x1
    addiu   $5,$5,%lo($LC0)
    sw  $31,20($sp)
    jal write
    li  $6,13           # 0xd

    lw  $31,20($sp)
    move    $2,$0
    j   $31
    addiu   $sp,$sp,24

    .set    macro
    .set    reorder
    .end    main
    .size   main, .-main
    .ident  "GCC: (GNU) 4.8.3"

To produce this output, you need to run something like

SYSPATH=/home/myuseride/cs134/hw5-my-group/src/build/install
os161-gcc -static -S -O3 -g0 -Wall -W -std=gnu99 -mno-abicalls -fno-pic -nostdinc  -fno-unwind-tables -fno-asynchronous-unwind-tables -fno-dwarf2-cfi-asm -fno-exceptions -fomit-frame-pointer  hello.c -I$SYSPATH/include


If we compile this assembly, we get an object file, hello.o. Let's take a look at the contents of this object file:

$ os161-objdump -s -r -t hello.o

hello.o:     file format elf32-tradbigmips

SYMBOL TABLE:
00000000 l    d  .text  00000000 .text
00000000 l    d  .data  00000000 .data
00000000 l    d  .bss   00000000 .bss
00000000 l       .rodata    00000000 LC0
00000000 l    d  .rodata    00000000 .rodata
00000000 l    d  .reginfo   00000000 .reginfo
00000000 l    d  .pdr   00000000 .pdr
00000000 g     O .text  00000000 main
00000000         *UND*  00000000 write


RELOCATION RECORDS FOR [.text]:
OFFSET   TYPE              VALUE
00000004 R_MIPS_HI16       .rodata
00000008 R_MIPS_LO16       .rodata
00000014 R_MIPS_26         write


Contents of section .text:
 0000 27bdffe8 3c050000 24a50000 24040001  '...<...$...$...
 0010 afbf0010 0c000000 2406000d 8fbf0010  ........$.......
 0020 00001021 03e00008 27bd0018 00000000  ...!....'.......
Contents of section .rodata:
 0000 48656c6c 6f20576f 726c6421 0a000000  Hello World!....
  • Horse speaking

    Hay! It has a symbol table! I think I have a sense of what that might be, but why is everything zero?

At the moment, we don't yet know where everything is going to be in memory. So all the sections are addressed starting at zero. It happens that both LC0 and main are at the start of their respective sections, so they are both at offset zero within their section.

On the other hand, write is an external symbol, so it's marked as undefined (*UND*). It's zero right now because we don't know where it is yet.

  • Duck speaking

    But we call write from the assembly code. It has to be there in the code somewhere, right?

  • PinkRobot speaking

    Because we don't know where write is located yet, we have to just leave it blank. But that takes us to another important part of the object file: the relocation records.

Relocation Records

The relocation records tell the linker how to fix up the object file when it's linked with other object files. The relocation records tell the linker where to put the actual address of the write function.

$ os161-objdump -j .text -D -r hello.o

hello.o:     file format elf32-tradbigmips


Disassembly of section .text:

00000000 <main>:
   0:   27bdffe8    addiu   sp,sp,-24
   4:   3c050000    lui a1,0x0
            4: R_MIPS_HI16  .rodata
   8:   24a50000    addiu   a1,a1,0
            8: R_MIPS_LO16  .rodata
   c:   24040001    li  a0,1
  10:   afbf0010    sw  ra,16(sp)
  14:   0c000000    jal 0 <main>
            14: R_MIPS_26   write
  18:   2406000d    li  a2,13
  1c:   8fbf0010    lw  ra,16(sp)
  20:   00001021    move    v0,zero
  24:   03e00008    jr  ra
  28:   27bd0018    addiu   sp,sp,24
  2c:   00000000    nop

What the relocation records for the .text say essentially is:

  • At location 4, there is a MIPS instruction that we need to patch in the high 16 bits of the address of the LC0 symbol (which is the same as the start of the .rodata section).
  • At location 8, there is a MIPS instruction that we need to patch in the low 16 bits of the address of the LC0 symbol.
  • At location 14, there is a MIPS instruction that we need to patch in the address of the write function. Here it's a 26-bit relative address, which means it's the difference between the address of the write function and the address of the instruction, divided by 4.

What's the maximum amount of other code we could have between the jal write instruction and the write function itself?

  • Hedgehog speaking

    So when do we fix up the relocation records?

  • PinkRobot speaking

    That's the job of the linker.

The Linker

The linker is the program that takes all the object files and libraries and combines them into a single executable. The linker is responsible for

  1. Resolving external symbols. When an object file references a symbol that is defined in another object file, the linker needs to find the address of that symbol and patch it into the object file.
  2. Combining sections. The linker combines all the sections from all the object files. So, for example, if two object files have a .rodata section, the linker combines both of them into a single .rodata section in the executable.
  3. Setting up the program's memory layout. The linker decides where in memory the different sections of the program will be loaded and creates the load map.

The Libraries and Startup Code

When you compile a program, the compiler automatically links in the C library and some startup code. The libraries provide the implementation of functions like write and exit. The startup code sets things up so that we're ready to run main, calls it, and then when main returns, calls exit with the return value.

In OS/161…

In OS/161, the C library is in build/install/lib/libc.a; the .a filename extension tells us that it is an archive of object files—several .o files bundled together. The startup code is in build/install/lib/crt0.o.

The index of the C library archive is relatively small, so we can list the entire contents here (but we'll put it in a scrollable box so it doesn't take up too much space on the page).

$ os161-nm -sg $SYSPATH/lib/libc.a | fgrep -v ' in '

Archive index:

__printf.o:
         U __bad_assert
         U strchr
         U strlen
         U __udivdi3
         U __umoddi3
0000024c T __vprintf

snprintf.o:
000000d0 T snprintf
         U __vprintf
00000054 T vsnprintf

__puts.o:
00000000 T __puts
         U strlen
         U write

getchar.o:
00000000 T getchar
         U read

printf.o:
         U errno
000000a8 T printf
         U __vprintf
00000058 T vprintf
         U write

putchar.o:
00000000 T putchar
         U write

puts.o:
         U putchar
         U __puts
00000000 T puts

abort.o:
00000000 T abort
         U _exit

atoi.o:
00000000 T atoi
         U strchr

exit.o:
         U _exit
00000000 T exit

getenv.o:
00000000 d __default_environ
         U __environ
00000000 T getenv
         U memcmp
         U strchr
         U strlen

malloc.o:
         U __bad_assert
         U err
         U errx
0000051c T free
00000004 s __heapbase
00000000 s __heaptop
00000180 T malloc
         U sbrk
         U warnx

qsort.o:
         U __bad_assert
         U memcpy
00000000 T qsort

random.o:
         U __bad_assert
00000000 d end_ptr
00000018 d fptr
00000194 T initstate
00000008 d rand_deg
0000052c T random
00000004 d rand_sep
0000001c d randtbl
0000000c d rand_type
00000014 d rptr
000003a8 T setstate
0000016c T srandom
00000010 d state

system.o:
         U errno
         U execv
         U _exit
         U fork
         U memcpy
         U strlen
         U strtok
00000000 T system
         U waitpid

bzero.o:
00000000 T bzero

memcmp.o:
00000000 T memcmp

memcpy.o:
00000000 T memcpy

memmove.o:
         U memcpy
00000000 T memmove

memset.o:
00000000 T memset

strcat.o:
00000000 T strcat
         U strcpy
         U strlen

strchr.o:
00000000 T strchr

strcmp.o:
00000000 T strcmp

strcpy.o:
00000000 T strcpy

strerror.o:
00000000 T strerror
00000004 R sys_errlist
00000000 R sys_nerr

strlen.o:
00000000 T strlen

strrchr.o:
         U strlen
00000000 T strrchr

strtok.o:
00000000 T strtok
00000000 s __strtok_context
         U strtok_r

strtok_r.o:
         U strchr
00000000 T strtok_r

time.o:
         U __time
00000000 T time

syscalls.o:
0000028c T accept
000001cc T access
00000274 T bind
000001d4 T chdir
0000023c T chmod
00000244 T chown
0000012c T close
0000027c T connect
0000011c T dup
00000124 T dup2
         U errno
00000034 T execv
0000003c T _exit
000001dc T fchdir
0000024c T fchmod
00000254 T fchown
0000017c T fcntl
00000164 T flock
00000024 T fork
00000214 T fstat
00000174 T fsync
0000016c T ftruncate
0000022c T futimes
000001e4 T __getcwd
00000144 T getdirentry
000000ac T getgroups
000000bc T __getlogin
000002a4 T getpeername
0000004c T getpid
00000054 T getppid
0000009c T getresgid
0000008c T getresuid
00000104 T getrusage
0000029c T getsockname
000002ac T getsockopt
00000184 T ioctl
00000084 T issetugid
000000cc T kill
0000025c T lchmod
00000264 T lchown
0000019c T link
00000284 T listen
0000015c T lseek
0000021c T lstat
00000234 T lutimes
000001ac T mkdir
000001bc T mkfifo
00000064 T mmap
000001fc T mount
00000074 T mprotect
0000006c T munmap
000002cc T nanosleep
0000010c T open
00000114 T pipe
00000194 T poll
0000013c T pread
00000154 T pwrite
00000134 T read
000001f4 T readlink
000002dc T reboot
000001a4 T remove
000001c4 T rename
000001b4 T rmdir
0000005c T sbrk
0000018c T select
000000b4 T setgroups
000000c4 T __setlogin
000000a4 T setresgid
00000094 T setresuid
000002b4 T setsockopt
000002c4 T __settime
00000294 T shutdown
000000d4 T sigaction
000000dc T sigpending
000000e4 T sigprocmask
000000f4 T sigreturn
000000ec T sigsuspend
0000026c T socket
0000020c T stat
000001ec T symlink
000002d4 T sync
000002bc T __time
0000007c T umask
00000204 T unmount
00000224 T utimes
0000002c T vfork
000000fc T wait4
00000044 T waitpid
0000014c T write

adddi3.o:
00000000 T __adddi3

anddi3.o:
00000000 T __anddi3

ashldi3.o:
00000000 T __ashldi3

ashrdi3.o:
00000000 T __ashrdi3

cmpdi2.o:
00000000 T __cmpdi2

divdi3.o:
00000000 T __divdi3
         U __qdivrem

iordi3.o:
00000000 T __iordi3

lshldi3.o:
00000000 T __lshldi3

lshrdi3.o:
00000000 T __lshrdi3

moddi3.o:
00000000 T __moddi3
         U __qdivrem

muldi3.o:
00000104 T __muldi3

negdi2.o:
00000000 T __negdi2

notdi2.o:
00000000 T __one_cmpldi2

qdivrem.o:
00000064 T __qdivrem
00000000 d zero.1532

subdi3.o:
00000000 T __subdi3

ucmpdi2.o:
00000000 T __ucmpdi2

udivdi3.o:
         U __qdivrem
00000000 T __udivdi3

umoddi3.o:
         U __qdivrem
00000000 T __umoddi3

xordi3.o:
00000000 T __xordi3

__assert.o:
         U abort
00000000 T __bad_assert
         U snprintf
         U strlen
         U write

err.o:
         U __argv
000001ec T err
         U errno
0000020c T errx
         U exit
         U strerror
         U strlen
0000012c T verr
0000014c T verrx
         U __vprintf
00000104 T vwarn
00000118 T vwarnx
0000016c T warn
000001ac T warnx
         U write

errno.o:
00000004 C __argv
00000004 C __environ
00000004 C errno

execvp.o:
         U errno
         U execv
00000000 T execvp
         U getenv
         U memcpy
         U snprintf
         U strchr
         U strlen

getcwd.o:
         U errno
         U __getcwd
00000000 T getcwd

setjmp.o:
00000034 T longjmp
00000000 T setjmp

For each object file, the list shows both the symbols it defines (with a T or D), and the external symbols it uses (with a U), which are currently undefined in that file and will need to be resolved by the linker.

The start-up code is in build/install/lib/crt0.o, and we can look at the symbols in this file as well:

$ os161-nm -g $SYSPATH/lib/crt0.o
         U __argv
         U __environ
         U exit
         U _gp
         U main
00000000 T __start
  • Cat speaking

    So crt0.o defines the symbol __start, which is where the program starts running. It also uses the symbols __argv, __environ, exit, _gp, and main. It doesn't contain those, but it needs to know where they are.

  • PinkRobot speaking

    Right.

We can also look at our hello.o file to see what symbols it defines and uses:

$ nm -g hello.o
00000000 T main
         U write

We can build the executable by running

$ os161-ld -o hello $SYSPATH/lib/crt0.o hello.o $SYSPATH/lib/libc.a

Here we're telling the linker that

  • crt0.o comes first (because it's the start-up code, it needs to be at the beginning of the program).
  • hello.o comes next (because it's our program).
  • Optionally, it should load any .o files from libc.a that it needs to resolve symbols in what we've already loaded (and repeat as necessary).

It's up to the linker to figure out which object files from libc.a are needed to resolve the symbols in hello.o and crt0.o.

Imagine you're the linker. Which object files from libc.a do you need and why?

(You can ignore _gp as you won't find it anywhere.)

  • Dog speaking

    So the linker is like a detective, figuring out which object files are needed to resolve all the symbols.

  • PinkRobot speaking

    That's a good way to think about it. It's trying to solve a big puzzle, and it has to figure out how to put all the pieces together.

The Executable

When linking is done, our hello program is laid out in memory as

$ nm -n hello
004000b0 T _ftext
004000b0 T __start
004000f0 T main
00400130 T exit
00400150 t __syscall
00400174 T fork
0040017c T vfork
00400184 T execv
0040018c T _exit
00400194 T waitpid
0040019c T getpid
004001a4 T getppid
004001ac T sbrk
004001b4 T mmap
004001bc T munmap
004001c4 T mprotect
004001cc T umask
004001d4 T issetugid
004001dc T getresuid
004001e4 T setresuid
004001ec T getresgid
004001f4 T setresgid
004001fc T getgroups
00400204 T setgroups
0040020c T __getlogin
00400214 T __setlogin
0040021c T kill
00400224 T sigaction
0040022c T sigpending
00400234 T sigprocmask
0040023c T sigsuspend
00400244 T sigreturn
0040024c T wait4
00400254 T getrusage
0040025c T open
00400264 T pipe
0040026c T dup
00400274 T dup2
0040027c T close
00400284 T read
0040028c T pread
00400294 T getdirentry
0040029c T write
004002a4 T pwrite
004002ac T lseek
004002b4 T flock
004002bc T ftruncate
004002c4 T fsync
004002cc T fcntl
004002d4 T ioctl
004002dc T select
004002e4 T poll
004002ec T link
004002f4 T remove
004002fc T mkdir
00400304 T rmdir
0040030c T mkfifo
00400314 T rename
0040031c T access
00400324 T chdir
0040032c T fchdir
00400334 T __getcwd
0040033c T symlink
00400344 T readlink
0040034c T mount
00400354 T unmount
0040035c T stat
00400364 T fstat
0040036c T lstat
00400374 T utimes
0040037c T futimes
00400384 T lutimes
0040038c T chmod
00400394 T chown
0040039c T fchmod
004003a4 T fchown
004003ac T lchmod
004003b4 T lchown
004003bc T socket
004003c4 T bind
004003cc T connect
004003d4 T listen
004003dc T accept
004003e4 T shutdown
004003ec T getsockname
004003f4 T getpeername
004003fc T getsockopt
00400404 T setsockopt
0040040c T __time
00400414 T __settime
0040041c T nanosleep
00400424 T sync
0040042c T reboot
00400440 r LC0
00410450 B __bss_start
00410450 B _edata
00410450 B errno
00410450 B _fbss
00410450 R _fdata
00410454 B __environ
00410458 B __argv
0041045c B _end
00418440 a _gp

Because we now know where everything is, we no longer need relocation records. The addresses are all fixed up.

If we don't want to debug the code, we can strip the executable, throwing away all the information about where each symbol is (and deleting some other detritus as well) to make the executable smaller.

$ os161-strip -s -R .pdr -R .reginfo -R .gnu.attributes -R .comment  hello -o hello_clean
$ os161-objdump -s -r -t /tmp/hello_clean
/tmp/hello_clean:     file format elf32-tradbigmips

SYMBOL TABLE:
no symbols


Contents of section .text:
 4000b0 3c1c0042 279c8430 2408fff8 03a8e824  <..B'..0$......$
 4000c0 27bdfff0 3c010041 ac250448 3c010041  '...<..A.%.H<..A
 4000d0 ac260444 0c10003c 00000000 00408021  .&.D...<.....@.!
 4000e0 0c100048 02002021 1000fffd 00000000  ...H.. !........
 4000f0 27bdffe8 3c050040 24a50430 24040001  '...<..@$..0$...
 400100 afbf0010 0c1000a3 2406000d 8fbf0010  ........$.......
 400110 00001021 03e00008 27bd0018 00000000  ...!....'.......
 400120 0c10005f 00000000 00802021 3c02eeee  ..._...... !<...
 400130 3442e00f 8c420000 0810004e 00000000  4B...B.....N....
 400140 0000000c 10e00005 00000000 3c010041  ............<..A
 400150 ac220440 2403ffff 2402ffff 03e00008  .".@$...$.......
 400160 00000000 08100050 24020000 08100050  .......P$......P
 400170 24020001 08100050 24020002 08100050  $......P$......P
 400180 24020003 08100050 24020004 08100050  $......P$......P
 400190 24020005 08100050 24020006 08100050  $......P$......P
 4001a0 24020007 08100050 24020008 08100050  $......P$......P
 4001b0 24020009 08100050 2402000a 08100050  $......P$......P
 4001c0 24020011 08100050 24020012 08100050  $......P$......P
 4001d0 24020013 08100050 24020014 08100050  $......P$......P
 4001e0 24020015 08100050 24020016 08100050  $......P$......P
 4001f0 24020017 08100050 24020018 08100050  $......P$......P
 400200 24020019 08100050 2402001a 08100050  $......P$......P
 400210 2402001b 08100050 2402001c 08100050  $......P$......P
 400220 2402001d 08100050 2402001e 08100050  $......P$......P
 400230 2402001f 08100050 24020020 08100050  $......P$.. ...P
 400240 24020022 08100050 24020023 08100050  $.."...P$..#...P
 400250 2402002d 08100050 2402002e 08100050  $..-...P$......P
 400260 2402002f 08100050 24020030 08100050  $../...P$..0...P
 400270 24020031 08100050 24020032 08100050  $..1...P$..2...P
 400280 24020033 08100050 24020036 08100050  $..3...P$..6...P
 400290 24020037 08100050 24020038 08100050  $..7...P$..8...P
 4002a0 2402003b 08100050 2402003c 08100050  $..;...P$..<...P
 4002b0 2402003d 08100050 2402003e 08100050  $..=...P$..>...P
 4002c0 2402003f 08100050 24020040 08100050  $..?...P$..@...P
 4002d0 24020041 08100050 24020042 08100050  $..A...P$..B...P
 4002e0 24020043 08100050 24020044 08100050  $..C...P$..D...P
 4002f0 24020045 08100050 24020046 08100050  $..E...P$..F...P
 400300 24020047 08100050 24020048 08100050  $..G...P$..H...P
 400310 24020049 08100050 2402004a 08100050  $..I...P$..J...P
 400320 2402004b 08100050 2402004c 08100050  $..K...P$..L...P
 400330 2402004d 08100050 2402004e 08100050  $..M...P$..N...P
 400340 2402004f 08100050 24020050 08100050  $..O...P$..P...P
 400350 24020051 08100050 24020052 08100050  $..Q...P$..R...P
 400360 24020053 08100050 24020054 08100050  $..S...P$..T...P
 400370 24020055 08100050 24020056 08100050  $..U...P$..V...P
 400380 24020057 08100050 24020058 08100050  $..W...P$..X...P
 400390 24020059 08100050 2402005a 08100050  $..Y...P$..Z...P
 4003a0 2402005b 08100050 2402005c 08100050  $..[...P$..\...P
 4003b0 24020062 08100050 24020063 08100050  $..b...P$..c...P
 4003c0 24020064 08100050 24020065 08100050  $..d...P$..e...P
 4003d0 24020066 08100050 24020068 08100050  $..f...P$..h...P
 4003e0 24020069 08100050 2402006a 08100050  $..i...P$..j...P
 4003f0 2402006b 08100050 2402006c 08100050  $..k...P$..l...P
 400400 24020071 08100050 24020072 08100050  $..q...P$..r...P
 400410 24020073 08100050 24020076 08100050  $..s...P$..v...P
 400420 24020077 00000000 00000000 00000000  $..w............
Contents of section .rodata:
 400430 48656c6c 6f20576f 726c6421 0a000000  Hello World!....

$ ls -l hello_clean
-rwxrwxr-x 1 melissa melissa 1376 Nov  3 16:19 hello_clean
  • Dog speaking

    Wow, only 1376 bytes! That's pretty svelte!

  • Goat speaking

    Meh, it'd be even smaller if we hadn't included a bunch of code for syscalls we didn't use.

  • PinkRobot speaking

    It's only 8 bytes per syscall to load the syscall number and jump to the main syscall routine. That's not too bad, so we can see why they left it as just one big block of code. But yeah, it could be smaller.

After copying the file to the root of the OS/161 filesystem, we can run it:

OS/161 kernel [? for menu]: s
/bin/sh: Timing enabled.
OS/161$ ./hello
Hello World!
/bin/sh: subprocess user time: 0.000251 seconds, system time: 0.010856 seconds, real time: 0.102188 seconds
OS/161$
  • Cat speaking

    So can we do the same on Linux…?

On Linux…

The commands are a bit more annoying, but it's the same deal.

$ gcc -static -O3 -g0 -S -fno-pic -fno-unwind-tables -fno-asynchronous-unwind-tables -fno-dwarf2-cfi-asm -fno-exceptions -fomit-frame-pointer  hello1.c
$ as -v --64 -o hello1.o hello1.s
$ ld -v --build-id -m elf_x86_64 --hash-style=gnu --as-needed -static -z relro -o hello1 /usr/lib/x86_64-linux-gnu/crt1.o /usr/lib/x86_64-linux-gnu/crti.o /usr/lib/gcc/x86_64-linux-gnu/13/crtbeginT.o -L/usr/lib/gcc/x86_64-linux-gnu/13 -L/usr/lib/x86_64-linux-gnu -L/usr/lib -L/lib/x86_64-linux-gnu -L/lib -L/usr/lib/x86_64-linux-gnu -L/usr/lib -L/usr/lib/ hello1.o --start-group -lgcc -lgcc_eh -lc --end-group /usr/lib/gcc/x86_64-linux-gnu/13/crtend.o /usr/lib/x86_64-linux-gnu/crtn.o
$ strip -s -R .gnu.attributes -R .comment -R .note.gnu.property -R .note.gnu.build-id -R .note.ABI-tag  hello1
$ ls -lh hello1
-rwxrwxr-x 1 melissa melissa 690K Nov  3 17:15 hello1
$ ./hello1
Hello World!
  • Horse speaking

    Whoa! Look at the size of that executable! 690 KB just to print “Hello World!” using a system call!

  • Dog speaking

    I guess that's the price of having a full-featured C library…?

  • PinkRobot speaking

    That's exactly it.

On Linux, the linker does the same thing as we saw for OS/161, it pulls in all the dependencies. But the C library is much larger (about 6 MB total on the CS 134 server), and there are quite a lot of interdependencies between the functions, so a significant subset (nearly 12%) of the library gets pulled in just to print “Hello World!”.

FWIW, here's why everything that ended up in the executable needed to be there:

Required Object File Needed By Object File To Define
libc.a(libc-start.o) crt1.o __libc_start_main
libc.a(check_fds.o) libc.a(libc-start.o) __libc_check_standard_fds
libc.a(libc-tls.o) libc.a(libc-start.o) __libc_setup_tls
libc.a(errno.o) libc.a(check_fds.o) __libc_errno
libc.a(__libc_assert_fail.o) libc.a(libc-start.o) __libc_assert_fail
libc.a(bsd-_setjmp.o) libc.a(libc-start.o) _setjmp
libc.a(cxa_atexit.o) libc.a(libc-start.o) __cxa_atexit
libc.a(exit.o) libc.a(cxa_atexit.o) __exit_funcs_done
libc.a(_itoa.o) libc.a(__libc_assert_fail.o) _itoa_word
libc.a(itoa-digits.o) libc.a(_itoa.o) _itoa_lower_digits
libc.a(itoa-udigits.o) libc.a(_itoa.o) _itoa_upper_digits
libc.a(libc_fatal.o) libc.a(__libc_assert_fail.o) __libc_message_impl
libc.a(lowlevellock.o) libc.a(cxa_atexit.o) __lll_lock_wait_private
libc.a(nptl_deallocate_tsd.o) libc.a(libc-start.o) __nptl_deallocate_tsd
libc.a(nptl_nthreads.o) libc.a(libc-start.o) __nptl_nthreads
libc.a(pthread_keys.o) libc.a(nptl_deallocate_tsd.o) __pthread_keys
libc.a(malloc.o) libc.a(exit.o) free
libc.a(memcpy.o) libc.a(libc-tls.o) memcpy
libc.a(mempcpy.o) libc.a(libc_fatal.o) __mempcpy
libc.a(memset.o) libc.a(malloc.o) memset
libc.a(strchrnul.o) libc.a(libc_fatal.o) __strchrnul
libc.a(strlen.o) libc.a(libc_fatal.o) strlen
libc.a(memmove-avx-unaligned-erms.o) libc.a(mempcpy.o) __mempcpy_avx_unaligned
libc.a(memmove-avx-unaligned-erms-rtm.o) libc.a(mempcpy.o) __mempcpy_avx_unaligned_rtm
libc.a(memmove-avx512-no-vzeroupper.o) libc.a(mempcpy.o) __mempcpy_avx512_no_vzeroupper
libc.a(memmove-avx512-unaligned-erms.o) libc.a(mempcpy.o) __mempcpy_avx512_unaligned
libc.a(memmove-erms.o) libc.a(mempcpy.o) __mempcpy_erms
libc.a(memmove-evex-unaligned-erms.o) libc.a(mempcpy.o) __mempcpy_evex_unaligned
libc.a(memmove-sse2-unaligned-erms.o) libc.a(mempcpy.o) __mempcpy_sse2_unaligned
libc.a(memmove-ssse3.o) libc.a(mempcpy.o) __mempcpy_ssse3
libc.a(memset-avx2-unaligned-erms.o) libc.a(memset.o) __memset_avx2_unaligned
libc.a(memset-avx2-unaligned-erms-rtm.o) libc.a(memset.o) __memset_avx2_unaligned_rtm
libc.a(memset-avx512-no-vzeroupper.o) libc.a(memset.o) __memset_avx512_no_vzeroupper
libc.a(memset-avx512-unaligned-erms.o) libc.a(memset.o) __memset_avx512_unaligned
libc.a(memset-erms.o) libc.a(memset.o) __memset_erms
libc.a(memset-evex-unaligned-erms.o) libc.a(memset.o) __memset_evex_unaligned
libc.a(memset-sse2-unaligned-erms.o) libc.a(memset.o) __memset_sse2_unaligned
libc.a(strchrnul-avx2.o) libc.a(strchrnul.o) __strchrnul_avx2
libc.a(strchrnul-avx2-rtm.o) libc.a(strchrnul.o) __strchrnul_avx2_rtm
libc.a(strchrnul-evex.o) libc.a(strchrnul.o) __strchrnul_evex
libc.a(strchrnul-sse2.o) libc.a(strchrnul.o) __strchrnul_sse2
libc.a(strlen-avx2.o) libc.a(strlen.o) __strlen_avx2
libc.a(strlen-avx2-rtm.o) libc.a(strlen.o) __strlen_avx2_rtm
libc.a(strlen-evex.o) libc.a(strlen.o) __strlen_evex
libc.a(strlen-sse2.o) libc.a(strlen.o) __strlen_sse2
libc.a(clock_gettime.o) libc.a(malloc.o) __clock_gettime
libc.a(_exit.o) libc.a(exit.o) _exit
libc.a(environ.o) libc.a(libc-start.o) __environ
libc.a(fstat64.o) libc.a(check_fds.o) __fstat64
libc.a(write.o) hello1.o write
libc.a(close_nocancel.o) libc.a(malloc.o) __close_nocancel
libc.a(fcntl_nocancel.o) libc.a(check_fds.o) __fcntl64_nocancel
libc.a(open64_nocancel.o) libc.a(check_fds.o) __open_nocancel
libc.a(read_nocancel.o) libc.a(malloc.o) __read_nocancel
libc.a(brk.o) libc.a(malloc.o) __curbrk
libc.a(getsysstats.o) libc.a(malloc.o) __get_nprocs
libc.a(madvise.o) libc.a(malloc.o) __madvise
libc.a(mmap64.o) libc.a(libc_fatal.o) __mmap
libc.a(mprotect.o) libc.a(malloc.o) __mprotect
libc.a(munmap.o) libc.a(libc_fatal.o) __munmap
libc.a(sbrk.o) libc.a(malloc.o) __sbrk
libc.a(single_threaded.o) libc.a(malloc.o) __libc_single_threaded
libc.a(mremap.o) libc.a(malloc.o) __mremap
libc.a(setvmaname.o) libc.a(libc_fatal.o) __set_vma_name
libc.a(sysinfo.o) libc.a(getsysstats.o) __sysinfo
libc.a(malloc-hugepages.o) libc.a(malloc.o) __malloc_default_thp_pagesize
libc.a(chk_fail.o) libc.a(memmove-avx512-no-vzeroupper.o) __chk_fail
libc.a(fortify_fail.o) libc.a(chk_fail.o) __fortify_fail
libc.a(fprintf_chk.o) libc.a(malloc.o) __fprintf_chk
libc.a(stack_chk_fail.o) libc.a(malloc.o) __stack_chk_fail
libc.a(memmove_chk-nonshared.o) libc.a(getsysstats.o) __memmove_chk
libc.a(dl-debug.o) libc.a(libc-start.o) _dl_debug_initialize
libc.a(dl-debug-symbols.o) libc.a(dl-debug.o) _r_debug_extended
libc.a(dl-load.o) libc.a(libc-start.o) _dl_process_pt_gnu_property
libc.a(dl-misc.o) libc.a(dl-load.o) _dl_name_match_p
libc.a(dl-object.o) libc.a(dl-load.o) _dl_add_to_namespace_list
libc.a(dl-origin.o) libc.a(dl-load.o) _dl_get_origin
libc.a(dl-printf.o) libc.a(dl-load.o) _dl_debug_printf
libc.a(dl-setup_hash.o) libc.a(dl-load.o) _dl_setup_hash
libc.a(dl-tls.o) libc.a(libc-tls.o) _dl_tls_static_surplus_init
libc.a(dl-tls_init_tp.o) libc.a(libc-tls.o) __tls_pre_init_tp
libc.a(dl-tunables.o) libc.a(malloc.o) __tunable_is_initialized
libc.a(dl-cache.o) libc.a(dl-load.o) _dl_load_cache_lookup
libc.a(dl-cet.o) libc.a(libc-start.o) _dl_cet_setup_features
libc.a(dl-early_allocate.o) libc.a(libc-tls.o) _dl_early_allocate
libc.a(dl-support.o) libc.a(libc-start.o) _dl_aux_init
libc.a(dl-sysdep.o) libc.a(libc-start.o) _dl_tunable_set_hwcaps
libc.a(enbl-secure.o) libc.a(libc-start.o) __libc_enable_secure
libc.a(libc_early_init.o) libc.a(libc-start.o) __libc_early_init
libc.a(init-first.o) libc.a(libc-start.o) __libc_init_first
libc.a(ctype-info.o) libc.a(libc_early_init.o) __ctype_init
libc.a(setjmp.o) libc.a(bsd-_setjmp.o) __sigsetjmp
libc.a(sigjmp.o) libc.a(setjmp.o) __sigjmp_save
libc.a(sigprocmask.o) libc.a(sigjmp.o) __sigprocmask
libc.a(abort.o) libc.a(libc_fatal.o) abort
libc.a(getenv.o) libc.a(dl-support.o) getenv
libc.a(setenv.o) libc.a(dl-support.o) __unsetenv
libc.a(strtoul.o) libc.a(getsysstats.o) __isoc23_strtoul
libc.a(strtoul_l.o) libc.a(strtoul.o) ____strtoul_l_internal
libc.a(grouping.o) libc.a(strtoul_l.o) __correctly_grouped_prefixmb
libc.a(vfprintf-internal.o) libc.a(fprintf_chk.o) __vfprintf_internal
libc.a(errname.o) libc.a(vfprintf-internal.o) __get_errname
libc.a(printf-parsemb.o) libc.a(vfprintf-internal.o) __parse_one_specmb
libc.a(iofputs.o) libc.a(malloc.o) _IO_fputs
libc.a(iofwrite.o) libc.a(malloc.o) fwrite
libc.a(vtables.o) libc.a(iofputs.o) __io_vtables
libc.a(fileops.o) libc.a(vtables.o) _IO_new_file_setbuf
libc.a(genops.o) libc.a(fileops.o) _IO_un_link
libc.a(stdfiles.o) libc.a(genops.o) _IO_list_all
libc.a(stdio.o) libc.a(malloc.o) stderr
libc.a(strops.o) libc.a(vtables.o) _IO_str_overflow
libc.a(cancellation.o) libc.a(write.o) __pthread_enable_asynccancel
libc.a(elision-conf.o) libc.a(libc_early_init.o) __lll_elision_init
libc.a(libc-cleanup.o) libc.a(vfprintf-internal.o) __libc_cleanup_push_defer
libc.a(pthread_mutex_conf.o) libc.a(libc_early_init.o) __pthread_tunables_init
libc.a(pthread_mutex_lock.o) libc.a(dl-object.o) __pthread_mutex_lock
libc.a(pthread_mutex_unlock.o) libc.a(dl-object.o) __pthread_mutex_unlock
libc.a(pthread_sigmask.o) libc.a(sigprocmask.o) __pthread_sigmask
libc.a(tpp.o) libc.a(pthread_mutex_lock.o) __pthread_tpp_change_priority
libc.a(vars.o) libc.a(libc_early_init.o) __default_pthread_attr
libc.a(scratch_buffer_grow_preserve.o) libc.a(vfprintf-internal.o) __libc_scratch_buffer_grow_preserve
libc.a(scratch_buffer_set_array_size.o) libc.a(vfprintf-internal.o) __libc_scratch_buffer_set_array_size
libc.a(_strerror.o) libc.a(vfprintf-internal.o) __strerror_r
libc.a(memchr.o) libc.a(getsysstats.o) memchr
libc.a(memcmp.o) libc.a(dl-load.o) memcmp
libc.a(memmove.o) libc.a(memmove_chk-nonshared.o) memmove
libc.a(stpcpy.o) libc.a(dl-load.o) __stpcpy
libc.a(strchr.o) libc.a(dl-load.o) strchr
libc.a(strcmp.o) libc.a(malloc-hugepages.o) strcmp
libc.a(strdup.o) libc.a(dl-load.o) __strdup
libc.a(strncmp.o) libc.a(malloc-hugepages.o) strncmp
libc.a(strnlen.o) libc.a(vfprintf-internal.o) __strnlen
libc.a(strrchr.o) libc.a(malloc-hugepages.o) strrchr
libc.a(strsep.o) libc.a(dl-load.o) __strsep
libc.a(strstr.o) libc.a(malloc-hugepages.o) strstr
libc.a(memchr-avx2.o) libc.a(memchr.o) __memchr_avx2
libc.a(memchr-avx2-rtm.o) libc.a(memchr.o) __memchr_avx2_rtm
libc.a(memchr-evex.o) libc.a(memchr.o) __memchr_evex
libc.a(memchr-evex-rtm.o) libc.a(memchr.o) __memchr_evex_rtm
libc.a(memchr-sse2.o) libc.a(memchr.o) __memchr_sse2
libc.a(memcmp-avx2-movbe.o) libc.a(memcmp.o) __memcmp_avx2_movbe
libc.a(memcmp-avx2-movbe-rtm.o) libc.a(memcmp.o) __memcmp_avx2_movbe_rtm
libc.a(memcmp-evex-movbe.o) libc.a(memcmp.o) __memcmp_evex_movbe
libc.a(memcmp-sse2.o) libc.a(dl-support.o) __memcmp_sse2
libc.a(stpcpy-avx2.o) libc.a(stpcpy.o) __stpcpy_avx2
libc.a(stpcpy-avx2-rtm.o) libc.a(stpcpy.o) __stpcpy_avx2_rtm
libc.a(stpcpy-evex.o) libc.a(stpcpy.o) __stpcpy_evex
libc.a(stpcpy-sse2.o) libc.a(stpcpy.o) __stpcpy_sse2
libc.a(stpcpy-sse2-unaligned.o) libc.a(stpcpy.o) __stpcpy_sse2_unaligned
libc.a(strchr-avx2.o) libc.a(strchr.o) __strchr_avx2
libc.a(strchr-avx2-rtm.o) libc.a(strchr.o) __strchr_avx2_rtm
libc.a(strchr-evex.o) libc.a(strchr.o) __strchr_evex
libc.a(strchr-sse2.o) libc.a(strchr.o) __strchr_sse2
libc.a(strchr-sse2-no-bsf.o) libc.a(strchr.o) __strchr_sse2_no_bsf
libc.a(strcmp-avx2.o) libc.a(strcmp.o) __strcmp_avx2
libc.a(strcmp-avx2-rtm.o) libc.a(strcmp.o) __strcmp_avx2_rtm
libc.a(strcmp-evex.o) libc.a(strcmp.o) __strcmp_evex
libc.a(strcmp-sse2.o) libc.a(strcmp.o) __strcmp_sse2
libc.a(strcmp-sse2-unaligned.o) libc.a(strcmp.o) __strcmp_sse2_unaligned
libc.a(strcmp-sse4_2.o) libc.a(strcmp.o) __strcmp_sse42
libc.a(strncmp-avx2.o) libc.a(strncmp.o) __strncmp_avx2
libc.a(strncmp-avx2-rtm.o) libc.a(strncmp.o) __strncmp_avx2_rtm
libc.a(strncmp-evex.o) libc.a(strncmp.o) __strncmp_evex
libc.a(strncmp-sse2.o) libc.a(strncmp.o) __strncmp_sse2
libc.a(strncmp-sse4_2.o) libc.a(strncmp.o) __strncmp_sse42
libc.a(strnlen-avx2.o) libc.a(strnlen.o) __strnlen_avx2
libc.a(strnlen-avx2-rtm.o) libc.a(strnlen.o) __strnlen_avx2_rtm
libc.a(strnlen-evex.o) libc.a(strnlen.o) __strnlen_evex
libc.a(strnlen-sse2.o) libc.a(strnlen.o) __strnlen_sse2
libc.a(strrchr-avx2.o) libc.a(strrchr.o) __strrchr_avx2
libc.a(strrchr-avx2-rtm.o) libc.a(strrchr.o) __strrchr_avx2_rtm
libc.a(strrchr-evex.o) libc.a(strrchr.o) __strrchr_evex
libc.a(strrchr-sse2.o) libc.a(strrchr.o) __strrchr_sse2
libc.a(strstr-avx512.o) libc.a(strstr.o) __strstr_avx512
libc.a(strstr-sse2-unaligned.o) libc.a(strstr.o) __strstr_sse2_unaligned
libc.a(wcrtomb.o) libc.a(vfprintf-internal.o) __wcrtomb
libc.a(wcsmbsload.o) libc.a(wcrtomb.o) __wcsmbs_load_conv
libc.a(wcsrtombs.o) libc.a(vfprintf-internal.o) __wcsrtombs
libc.a(getdents64.o) libc.a(malloc-hugepages.o) __getdents64
libc.a(getpid.o) libc.a(dl-printf.o) __getpid
libc.a(sched_cpucount.o) libc.a(getsysstats.o) __sched_cpucount
libc.a(sched_getp.o) libc.a(tpp.o) __sched_getparam
libc.a(sched_gets.o) libc.a(tpp.o) __sched_getscheduler
libc.a(sched_primax.o) libc.a(tpp.o) __sched_get_priority_max
libc.a(sched_primin.o) libc.a(tpp.o) __sched_get_priority_min
libc.a(sched_sets.o) libc.a(tpp.o) __sched_setscheduler
libc.a(getcwd.o) libc.a(dl-object.o) __getcwd
libc.a(lseek64.o) libc.a(fileops.o) __lseek64
libc.a(lstat64.o) libc.a(getcwd.o) __lstat64
libc.a(open64.o) libc.a(fileops.o) __open
libc.a(openat64.o) libc.a(getcwd.o) __openat64
libc.a(read.o) libc.a(fileops.o) __read
libc.a(stat64.o) libc.a(dl-load.o) __stat64
libc.a(pread64_nocancel.o) libc.a(malloc-hugepages.o) __pread64_nocancel
libc.a(write_nocancel.o) libc.a(fileops.o) __write_nocancel
libc.a(getrlimit64.o) libc.a(libc_early_init.o) __getrlimit
libc.a(getpagesize.o) libc.a(getsysstats.o) __getpagesize
libc.a(tsearch.o) libc.a(setenv.o) __tsearch
libc.a(init-misc.o) libc.a(init-first.o) __init_misc
libc.a(readonly-area.o) libc.a(vfprintf-internal.o) __readonly_area
libc.a(memcpy_chk-nonshared.o) libc.a(setenv.o) __memcpy_chk
libc.a(memset_chk-nonshared.o) libc.a(strops.o) __memset_chk
libc.a(dl-catch.o) libc.a(dl-load.o) _dl_signal_error
libc.a(dl-exception.o) libc.a(dl-catch.o) _dl_exception_create
libc.a(dl-execstack.o) libc.a(dl-load.o) _dl_make_stacks_executable
libc.a(dl-lookup.o) libc.a(dl-support.o) _dl_lookup_symbol_x
libc.a(dl-reloc.o) libc.a(dl-support.o) _dl_protect_relro
libc.a(dl-scope.o) libc.a(dl-lookup.o) _dl_scope_free
libc.a(dl-thread_gscope_wait.o) libc.a(dl-scope.o) __thread_gscope_wait
libc.a(dl-trampoline.o) libc.a(dl-reloc.o) _dl_runtime_resolve_fxsave
libc.a(dl-tlsdesc.o) libc.a(dl-reloc.o) _dl_tlsdesc_return
libc.a(gconv_db.o) libc.a(wcsmbsload.o) __gconv_find_transform
libc.a(gconv_conf.o) libc.a(gconv_db.o) __gconv_load_conf
libc.a(gconv_builtin.o) libc.a(gconv_db.o) __gconv_get_builtin_trans
libc.a(gconv_simple.o) libc.a(wcsmbsload.o) __gconv_btwoc_ascii
libc.a(gconv_trans.o) libc.a(gconv_simple.o) __gconv_transliterate
libc.a(gconv_cache.o) libc.a(gconv_conf.o) __gconv_load_cache
libc.a(gconv_dl.o) libc.a(gconv_db.o) __gconv_find_shlib
libc.a(setlocale.o) libc.a(wcsmbsload.o) __libc_setlocale_lock
libc.a(lc-ctype.o) libc.a(ctype-info.o) _nl_current_LC_CTYPE
libc.a(lc-numeric.o) libc.a(vfprintf-internal.o) _nl_current_LC_NUMERIC
libc.a(C_name.o) libc.a(setlocale.o) _nl_POSIX_name
libc.a(SYS_libc.o) libc.a(_strerror.o) _libc_intl_domainname
libc.a(global-locale.o) libc.a(setlocale.o) _nl_global_locale
libc.a(xlocale.o) libc.a(strtoul_l.o) _nl_C_locobj
libc.a(dcgettext.o) libc.a(_strerror.o) __dcgettext
libc.a(dcigettext.o) libc.a(dcgettext.o) __dcigettext
libc.a(finddomain.o) libc.a(dcigettext.o) _nl_find_domain
libc.a(loadmsgcat.o) libc.a(dcigettext.o) _nl_load_domain
libc.a(localealias.o) libc.a(finddomain.o) _nl_expand_alias
libc.a(l10nflist.o) libc.a(finddomain.o) _nl_make_l10nflist
libc.a(explodename.o) libc.a(finddomain.o) _nl_explode_name
libc.a(plural.o) libc.a(loadmsgcat.o) __gettext_free_exp
libc.a(plural-exp.o) libc.a(loadmsgcat.o) __gettext_extract_plural
libc.a(hash-string.o) libc.a(gconv_cache.o) __hash_string
libc.a(__longjmp.o) libc.a(dl-catch.o) __longjmp
libc.a(raise.o) libc.a(abort.o) raise
libc.a(sigaction.o) libc.a(abort.o) __sigaction
libc.a(libc_sigaction.o) libc.a(sigaction.o) __libc_sigaction
libc.a(qsort.o) libc.a(localealias.o) qsort
libc.a(strtol.o) libc.a(gconv_conf.o) __isoc23_strtol
libc.a(strtol_l.o) libc.a(strtol.o) ____strtol_l_internal
libc.a(funlockfile.o) libc.a(vfprintf-internal.o) _IO_funlockfile
libc.a(grouping_iterator.o) libc.a(vfprintf-internal.o) __grouping_iterator_init_none
libc.a(printf_buffer_done.o) libc.a(vfprintf-internal.o) __printf_buffer_done
libc.a(printf_buffer_pad_1.o) libc.a(vfprintf-internal.o) __printf_buffer_pad_1
libc.a(printf_buffer_putc_1.o) libc.a(vfprintf-internal.o) __printf_buffer_putc_1
libc.a(printf_buffer_puts_1.o) libc.a(vfprintf-internal.o) __printf_buffer_puts_1
libc.a(printf_buffer_to_file.o) libc.a(vfprintf-internal.o) __printf_buffer_to_file_init
libc.a(printf_buffer_write.o) libc.a(vfprintf-internal.o) __printf_buffer_write
libc.a(printf_fp.o) libc.a(vfprintf-internal.o) __printf_fp_l_buffer
libc.a(printf_fphex.o) libc.a(vfprintf-internal.o) __printf_fphex_l_buffer
libc.a(printf_function_invoke.o) libc.a(vfprintf-internal.o) __printf_function_invoke
libc.a(reg-modifier.o) libc.a(vfprintf-internal.o) __printf_modifier_table
libc.a(reg-printf.o) libc.a(vfprintf-internal.o) __printf_function_table
libc.a(reg-type.o) libc.a(vfprintf-internal.o) __printf_va_arg_table
libc.a(snprintf.o) libc.a(_strerror.o) __snprintf
libc.a(translated_number_width.o) libc.a(vfprintf-internal.o) __translated_number_width
libc.a(wprintf_buffer_putc_1.o) libc.a(printf_fp.o) __wprintf_buffer_putc_1
libc.a(wprintf_buffer_to_file.o) libc.a(printf_fp.o) __wprintf_buffer_to_file_init
libc.a(errlist.o) libc.a(_strerror.o) __get_errlist
libc.a(errlist-data.o) libc.a(errlist.o) _sys_errlist_internal_len
libc.a(filedoalloc.o) libc.a(stdio.o) _IO_file_doallocate
libc.a(iofclose.o) libc.a(readonly-area.o) _IO_new_fclose
libc.a(iofopen.o) libc.a(readonly-area.o) _IO_new_fopen
libc.a(iogetdelim.o) libc.a(readonly-area.o) __getdelim
libc.a(wgenops.o) libc.a(fileops.o) _IO_wsetb
libc.a(wfileops.o) libc.a(fileops.o) _IO_wdo_write
libc.a(iofwide.o) libc.a(wgenops.o) _IO_fwide
libc.a(vsnprintf.o) libc.a(snprintf.o) __vsnprintf_internal
libc.a(iofgets_u.o) libc.a(localealias.o) __fgets_unlocked
libc.a(alloca_cutoff.o) libc.a(printf_fp.o) __libc_alloca_cutoff
libc.a(elision-lock.o) libc.a(pthread_mutex_lock.o) __lll_lock_elision
libc.a(elision-unlock.o) libc.a(pthread_mutex_unlock.o) __lll_unlock_elision
libc.a(futex-internal.o) libc.a(pthread_mutex_lock.o) __futex_abstimed_wait64
libc.a(nptl-stack.o) libc.a(pthread_mutex_conf.o) __nptl_stack_cache_maxsize
libc.a(pthread_kill.o) libc.a(raise.o) __pthread_kill
libc.a(pthread_once.o) libc.a(gconv_conf.o) __pthread_once
libc.a(pthread_rwlock_init.o) libc.a(loadmsgcat.o) __pthread_rwlock_init
libc.a(pthread_rwlock_rdlock.o) libc.a(dcigettext.o) __pthread_rwlock_rdlock
libc.a(pthread_rwlock_unlock.o) libc.a(wcsmbsload.o) __pthread_rwlock_unlock
libc.a(pthread_rwlock_wrlock.o) libc.a(wcsmbsload.o) __pthread_rwlock_wrlock
libc.a(pthread_self.o) libc.a(raise.o) __pthread_self
libc.a(argz-addsep.o) libc.a(setlocale.o) __argz_add_sep
libc.a(argz-ctsep.o) libc.a(setlocale.o) __argz_create_sep
libc.a(strcasecmp_l.o) libc.a(localealias.o) __strcasecmp_l
libc.a(strcpy.o) libc.a(dcigettext.o) strcpy
libc.a(strcspn.o) libc.a(strsep.o) strcspn
libc.a(strtok_r.o) libc.a(gconv_conf.o) __strtok_r
libc.a(strcasecmp_l-avx2.o) libc.a(strcasecmp_l.o) __strcasecmp_l_avx2
libc.a(strcasecmp_l-avx2-rtm.o) libc.a(strcasecmp_l.o) __strcasecmp_l_avx2_rtm
libc.a(strcasecmp_l-evex.o) libc.a(strcasecmp_l.o) __strcasecmp_l_evex
libc.a(strcasecmp_l-sse2.o) libc.a(strcasecmp_l.o) __strcasecmp_l_sse2
libc.a(strcasecmp_l-sse4_2.o) libc.a(strcasecmp_l.o) __strcasecmp_l_sse42
libc.a(strcpy-avx2.o) libc.a(strcpy.o) __strcpy_avx2
libc.a(strcpy-avx2-rtm.o) libc.a(strcpy.o) __strcpy_avx2_rtm
libc.a(strcpy-evex.o) libc.a(strcpy.o) __strcpy_evex
libc.a(strcpy-sse2.o) libc.a(strcpy.o) __strcpy_sse2
libc.a(strcpy-sse2-unaligned.o) libc.a(strcpy.o) __strcpy_sse2_unaligned
libc.a(strcspn-sse4.o) libc.a(strcspn.o) __strcspn_sse42
libc.a(varshift.o) libc.a(strcspn-sse4.o) ___m128i_shift_right
libc.a(strcasecmp_l-nonascii.o) libc.a(strcasecmp_l-avx2.o) __strcasecmp_l_nonascii
libc.a(strcspn-generic.o) libc.a(strcspn.o) __strcspn_generic
libc.a(wcslen.o) libc.a(wcsrtombs.o) __wcslen
libc.a(wcsnlen.o) libc.a(wcsrtombs.o) __wcsnlen
libc.a(wmemcpy.o) libc.a(reg-modifier.o) __wmemcpy
libc.a(wmemmove.o) libc.a(wgenops.o) __wmemmove
libc.a(wmempcpy.o) libc.a(wgenops.o) __wmempcpy
libc.a(wcslen-avx2.o) libc.a(wcslen.o) __wcslen_avx2
libc.a(wcslen-avx2-rtm.o) libc.a(wcslen.o) __wcslen_avx2_rtm
libc.a(wcslen-evex.o) libc.a(wcslen.o) __wcslen_evex
libc.a(wcslen-sse2.o) libc.a(wcslen.o) __wcslen_sse2
libc.a(wcslen-sse4_1.o) libc.a(wcslen.o) __wcslen_sse4_1
libc.a(wcsnlen-avx2.o) libc.a(wcsnlen.o) __wcsnlen_avx2
libc.a(wcsnlen-avx2-rtm.o) libc.a(wcsnlen.o) __wcsnlen_avx2_rtm
libc.a(wcsnlen-evex.o) libc.a(wcsnlen.o) __wcsnlen_evex
libc.a(wcsnlen-sse4_1.o) libc.a(wcsnlen.o) __wcsnlen_sse4_1
libc.a(wcsnlen-generic.o) libc.a(wcsnlen.o) __wcsnlen_generic
libc.a(opendir.o) libc.a(gconv_conf.o) __opendir
libc.a(closedir.o) libc.a(getcwd.o) __closedir
libc.a(rewinddir.o) libc.a(getcwd.o) __rewinddir
libc.a(readdir64.o) libc.a(getcwd.o) __readdir64
libc.a(fdopendir.o) libc.a(getcwd.o) __fdopendir
libc.a(fstatat64.o) libc.a(getcwd.o) __fstatat64
libc.a(isatty.o) libc.a(filedoalloc.o) __isatty
libc.a(openat64_nocancel.o) libc.a(opendir.o) __openat_nocancel
libc.a(tcgetattr.o) libc.a(isatty.o) __tcgetattr
libc.a(asprintf_chk.o) libc.a(gconv_conf.o) __asprintf_chk
libc.a(mempcpy_chk-nonshared.o) libc.a(dcigettext.o) __mempcpy_chk
libc.a(dl-runtime.o) libc.a(dl-trampoline.o) _dl_fixup
libc.a(dl-libc.o) libc.a(gconv_dl.o) __libc_dlopen_mode
libc.a(gconv_open.o) libc.a(dcigettext.o) __gconv_open
libc.a(gconv.o) libc.a(dcigettext.o) __gconv
libc.a(gconv_close.o) libc.a(loadmsgcat.o) __gconv_close
libc.a(gconv_charset.o) libc.a(dcigettext.o) __gconv_create_spec
libc.a(findlocale.o) libc.a(setlocale.o) _nl_find_locale
libc.a(loadarchive.o) libc.a(findlocale.o) _nl_load_locale_from_archive
libc.a(loadlocale.o) libc.a(loadarchive.o) _nl_intern_locale_data
libc.a(C-address.o) libc.a(xlocale.o) _nl_C_LC_ADDRESS
libc.a(C-collate.o) libc.a(xlocale.o) _nl_C_LC_COLLATE
libc.a(C-ctype.o) libc.a(xlocale.o) _nl_C_LC_CTYPE
libc.a(C-identification.o) libc.a(xlocale.o) _nl_C_LC_IDENTIFICATION
libc.a(C-measurement.o) libc.a(xlocale.o) _nl_C_LC_MEASUREMENT
libc.a(C-messages.o) libc.a(xlocale.o) _nl_C_LC_MESSAGES
libc.a(C-monetary.o) libc.a(xlocale.o) _nl_C_LC_MONETARY
libc.a(C-name.o) libc.a(xlocale.o) _nl_C_LC_NAME
libc.a(C-numeric.o) libc.a(xlocale.o) _nl_C_LC_NUMERIC
libc.a(C-paper.o) libc.a(xlocale.o) _nl_C_LC_PAPER
libc.a(C-telephone.o) libc.a(xlocale.o) _nl_C_LC_TELEPHONE
libc.a(C-time.o) libc.a(xlocale.o) _nl_C_LC_TIME
libc.a(localename.o) libc.a(dcigettext.o) __current_locale_name
libc.a(cmp.o) libc.a(printf_fp.o) __mpn_cmp
libc.a(divrem.o) libc.a(printf_fp.o) __mpn_divrem
libc.a(lshift.o) libc.a(printf_fp.o) __mpn_lshift
libc.a(mul.o) libc.a(printf_fp.o) __mpn_mul
libc.a(mul_1.o) libc.a(printf_fp.o) __mpn_mul_1
libc.a(mul_n.o) libc.a(mul.o) __mpn_impn_mul_n
libc.a(rshift.o) libc.a(printf_fp.o) __mpn_rshift
libc.a(sub_n.o) libc.a(divrem.o) __mpn_sub_n
libc.a(submul_1.o) libc.a(divrem.o) __mpn_submul_1
libc.a(dbl2mpn.o) libc.a(printf_fp.o) __mpn_extract_double
libc.a(ldbl2mpn.o) libc.a(printf_fp.o) __mpn_extract_long_double
libc.a(float1282mpn.o) libc.a(printf_fp.o) __mpn_extract_float128
libc.a(fpioconst.o) libc.a(printf_fp.o) _fpioconst_pow10
libc.a(printf_buffer_as_file.o) libc.a(printf_function_invoke.o) __printf_buffer_as_file_init
libc.a(printf_buffer_flush.o) libc.a(printf_buffer_pad_1.o) __printf_buffer_flush
libc.a(wprintf_buffer_done.o) libc.a(wprintf_buffer_to_file.o) __wprintf_buffer_done
libc.a(wprintf_buffer_flush.o) libc.a(wprintf_buffer_putc_1.o) __wprintf_buffer_flush
libc.a(iogetline.o) libc.a(iofgets_u.o) _IO_getline
libc.a(vasprintf.o) libc.a(asprintf_chk.o) __vasprintf_internal
libc.a(cleanup_compat.o) libc.a(pthread_once.o) __pthread_cleanup_push
libc.a(memmem.o) libc.a(findlocale.o) __memmem
libc.a(strndup.o) libc.a(findlocale.o) __strndup
libc.a(strspn.o) libc.a(strtok_r.o) strspn
libc.a(strspn-sse4.o) libc.a(strspn.o) __strspn_sse42
libc.a(strspn-generic.o) libc.a(strspn.o) __strspn_generic
libc.a(wmemchr.o) libc.a(wcsnlen-generic.o) __wmemchr
libc.a(wmemchr-avx2.o) libc.a(wmemchr.o) __wmemchr_avx2
libc.a(wmemchr-avx2-rtm.o) libc.a(wmemchr.o) __wmemchr_avx2_rtm
libc.a(wmemchr-evex.o) libc.a(wmemchr.o) __wmemchr_evex
libc.a(wmemchr-evex-rtm.o) libc.a(wmemchr.o) __wmemchr_evex_rtm
libc.a(wmemchr-sse2.o) libc.a(wmemchr.o) __wmemchr_sse2
libc.a(lc-time-cleanup.o) libc.a(loadlocale.o) _nl_cleanup_time
libc.a(sysconf.o) libc.a(loadarchive.o) __sysconf
libc.a(getclktck.o) libc.a(sysconf.o) __getclktck
libc.a(getdtsz.o) libc.a(sysconf.o) __getdtablesize
libc.a(dl-close.o) libc.a(dl-libc.o) _dl_close
libc.a(dl-find_object.o) libc.a(dl-close.o) _dl_find_object_dlclose
libc.a(dl-open.o) libc.a(dl-libc.o) _dl_open
libc.a(dl-sort-maps.o) libc.a(dl-close.o) _dl_sort_maps
libc.a(dl-version.o) libc.a(dl-open.o) _dl_check_map_versions
libc.a(tlsdesc.o) libc.a(dl-close.o) _dl_unmap
libc.a(dl-addr-obj.o) libc.a(dl-find_object.o) _dl_addr_inside_object
libc.a(rtld_static_init.o) libc.a(dl-open.o) __rtld_static_init
libc.a(secure-getenv.o) libc.a(sysconf.o) __libc_secure_getenv
libc.a(add_n.o) libc.a(divrem.o) __mpn_add_n
libc.a(addmul_1.o) libc.a(mul.o) __mpn_addmul_1
libc.a(dladdr.o) libc.a(rtld_static_init.o) __dladdr
libc.a(dladdr1.o) libc.a(rtld_static_init.o) __dladdr1
libc.a(dlclose.o) libc.a(rtld_static_init.o) __dlclose
libc.a(dlerror.o) libc.a(rtld_static_init.o) __dlerror
libc.a(dlinfo.o) libc.a(rtld_static_init.o) __dlinfo
libc.a(dlmopen.o) libc.a(rtld_static_init.o) __dlmopen
libc.a(dlopen.o) libc.a(rtld_static_init.o) __dlopen
libc.a(dlsym.o) libc.a(rtld_static_init.o) __dlsym
libc.a(dlvsym.o) libc.a(rtld_static_init.o) __dlvsym
libc.a(libc_dlerror_result.o) libc.a(dlerror.o) __libc_dlerror_result
libc.a(cacheinfo.o) libc.a(sysconf.o) __cache_sysconf
libc.a(get_child_max.o) libc.a(sysconf.o) __get_child_max
libc.a(dl-call-libc-early-init.o) libc.a(dl-open.o) _dl_call_libc_early_init
libc.a(dl-call_fini.o) libc.a(dl-close.o) _dl_call_fini
libc.a(dl-deps.o) libc.a(dl-open.o) _dl_map_object_deps
libc.a(dl-init.o) libc.a(dl-open.o) _dl_init
libc.a(dl-lookup-direct.o) libc.a(rtld_static_init.o) _dl_lookup_direct
libc.a(dl-addr.o) libc.a(dladdr.o) _dl_addr
libc.a(dl-sym.o) libc.a(dlvsym.o) _dl_vsym
libc.a(asprintf.o) libc.a(dlerror.o) __asprintf
libgcc.a(unordtf2.o) libc.a(printf_fp.o) __unordtf2
libgcc.a(letf2.o) libc.a(printf_fp.o) __letf2
libgcc.a(sfp-exceptions.o) libgcc.a(unordtf2.o) __sfp_handle_exceptions
libgcc_eh.a(unwind-dw2.o) libc.a(iofputs.o) _Unwind_Resume
libgcc_eh.a(unwind-dw2-fde-dip.o) libgcc_eh.a(unwind-dw2.o) _Unwind_Find_FDE
libgcc_eh.a(unwind-c.o) libc.a(iofputs.o) __gcc_personality_v0
  • Goat speaking

    Meh. Talk about bloat.

  • PinkRobot speaking

    And that's why we need to find a way that the C library can be shared by all the programs that use it. Then it won't matter nearly as much how big it is. But making it happen requires a new kind of linking—dynamic linking—where the library is loaded at runtime. (Our current approach is static linking, where the library is included in the program's executable file.)

  • Rabbit speaking

    There is another way sticking with classic non-shared libraries. We could just make the library smaller with fewer interdependencies. That's the approach taken by musl for Linux, for example.

Deeper Dive: musl

musl is a C library for Linux that is designed to be small and efficient. It is a full implementation of the C standard library, but it's designed to be statically linked into programs, rather than dynamically linked (which is the usual method with glibc).

On Ubuntu, we can install musl with

sudo apt install musl-tools

and then compile our code with

$ musl-gcc -g0 -O3 -o hello1 hello1.c
$ strip -s hello1
$ ls -lh hello1
-rwxrwxr-x 1 melissa melissa 14K Nov  3 18:01 hello1
$ ./hello1
Hello World!
Required Object File Needed By Object File To Define
libc.a(__libc_start_main.lo) Scrt1.o __libc_start_main
libc.a(exit.lo) libc.a(__libc_start_main.lo) exit
libc.a(defsysinfo.lo) libc.a(__libc_start_main.lo) __sysinfo
libc.a(libc.lo) libc.a(__libc_start_main.lo) __progname_full
libc.a(write.lo) hello1.o write
libc.a(__environ.lo) libc.a(__libc_start_main.lo) __environ
libc.a(__init_tls.lo) libc.a(__libc_start_main.lo) __init_tls
libc.a(_Exit.lo) libc.a(exit.lo) _Exit
libc.a(syscall_ret.lo) libc.a(write.lo) __syscall_ret
libc.a(memcpy.lo) libc.a(__init_tls.lo) memcpy
libc.a(__syscall_cp.lo) libc.a(write.lo) __syscall_cp
libc.a(default_attr.lo) libc.a(__init_tls.lo) __default_stacksize
libc.a(__set_thread_area.lo) libc.a(__init_tls.lo) __set_thread_area
libc.a(__errno_location.lo) libc.a(syscall_ret.lo) ___errno_location
  • Dog speaking

    Nice. Not quite as svelte as as our OS/161 executable, but 14 KB is pretty good.

  • Goat speaking

    Meh. Probably cuts some corners. I bet it doesn't have all the features of glibc.

  • Hedgehog speaking

    You complained that glibc was too big! Sometimes I think you just like to complain.

(When logged in, completion status appears here.)