Object Files, Libraries, and Linking
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?
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:
- The
mainfunction is marked as a global symbol (.globl main). - There are different sections for different things.
- The string
"Hello World\n"is stored in the read-only data section (as indicated by the.rdatadirective), which is an area of memory for data that is read-only. - The code for the
mainfunction is in the “program text” section (as indicated by the.textdirective), which is an area of memory for code.
- The string
- We call an external function,
write, which is not defined in this file. We'll need to link in the implementation ofwritefrom somewhere else.
What's a “delay slot”?
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.
I bet the actual compiler's output is more complicated than this.
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!....
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.
But we call
writefrom the assembly code. It has to be there in the code somewhere, right?
Because we don't know where
writeis 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
LC0symbol (which is the same as the start of the.rodatasection). - At location 8, there is a MIPS instruction that we need to patch in the low 16 bits of the address of the
LC0symbol. - At location 14, there is a MIPS instruction that we need to patch in the address of the
writefunction. Here it's a 26-bit relative address, which means it's the difference between the address of thewritefunction and the address of the instruction, divided by 4.
So when do we fix up the relocation records?
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
- 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.
- Combining sections. The linker combines all the sections from all the object files. So, for example, if two object files have a
.rodatasection, the linker combines both of them into a single.rodatasection in the executable. - 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
So
crt0.odefines the symbol__start, which is where the program starts running. It also uses the symbols__argv,__environ,exit,_gp, andmain. It doesn't contain those, but it needs to know where they are.
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.ocomes first (because it's the start-up code, it needs to be at the beginning of the program).hello.ocomes next (because it's our program).- Optionally, it should load any
.ofiles fromlibc.athat 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.
So the linker is like a detective, figuring out which object files are needed to resolve all the symbols.
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
Wow, only 1376 bytes! That's pretty svelte!
Meh, it'd be even smaller if we hadn't included a bunch of code for syscalls we didn't use.
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$
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!
Whoa! Look at the size of that executable! 690 KB just to print “Hello World!” using a system call!
I guess that's the price of having a full-featured C library…?
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 |
Meh. Talk about bloat.
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.)
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
muslfor 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 |
Nice. Not quite as svelte as as our OS/161 executable, but 14 KB is pretty good.
Meh. Probably cuts some corners. I bet it doesn't have all the features of
glibc.
You complained that
glibcwas too big! Sometimes I think you just like to complain.
(When logged in, completion status appears here.)