the symbol table
When you split a program across several source files, one file calls a function or uses a variable defined in another. The compiler, looking at just one file, does not know where that function will end up in memory — it only knows the name. The symbol table is the index that records names: every function and global variable a file defines, and every name it uses but expects someone else to provide. It is the linker's address book, the thing that lets separately compiled pieces find each other.
In ELF, the .symtab section holds an array of symbol entries; each entry has a name (an index into the string table .strtab), a value (usually an address or offset once resolved), a size, and attributes. Two attributes matter most. A symbol's binding says how widely it is visible to the linker: LOCAL symbols are private to one object file (think 'static' in C), GLOBAL symbols are shared and must be resolved across files, and WEAK is a softer kind of global discussed under its own entry. A symbol's type says what it names (a function, an object/variable, a section, and so on). A defined symbol has a real value; an undefined symbol (binding GLOBAL but section UNDEF) is a promise the linker must fulfill by finding a definition elsewhere. There is also .dynsym, a smaller symbol table kept in the running binary so the dynamic linker can resolve names at load time.
It matters because almost every link error you will ever see comes from this table: 'undefined reference to foo' means a GLOBAL UNDEF symbol named foo was never matched to a definition; 'multiple definition of foo' means two objects each defined the same strong GLOBAL symbol. A useful mental check: declaring 'extern int x;' creates an undefined reference (a use, a promise to find x later), while 'int x = 5;' creates a definition (a real entry with a value). Tools like nm print the table so you can see exactly which symbols an object defines (often capital T for text/code, D for data) versus references (U for undefined).
$ nm main.o 0000000000000000 T main # T = defined in .text (global) U printf # U = undefined, must be resolved at link
nm reads the symbol table: main is defined here (T), printf is referenced but undefined (U) until linked against the C library.
A LOCAL symbol (a C 'static' name) cannot collide across files and is invisible to other objects; only GLOBAL and WEAK symbols participate in cross-file resolution. The .symtab can be stripped to shrink a binary; .dynsym cannot if the binary uses dynamic linking.