2. Compiler, Assembler, Linker, Loader - Compiler -> in: foo.c out: foo.s - Assembler -> in:foo.s out: foo.o - Linker -> in: foo.o out: a.out (The external lib can also join in) What is the purpose of the linker: -- collect/handle the code and data of each object file, turning it into the final executable file or library What is the object file? -- The blocks of machine code and data with placeholder addresses that references data functions in other object files or libraries - Loader -> in: a.out - Linking - Combine one or more object files in to a single executable file - Dynamic linking - Multiple programs share the same function - Advantage: save the usage of the memory - Static linking - Linker copies all libraries routines used in a program into the executable file - Need more storage and memory space than dynamic linking - More portable (why?) - Object file format (ELF) (See slide 20) - Header - Program header - Linker reads the program header when loading a program to search for necessary sections - Section header - display through "readelf" command - Text segment (machine code/CPU instructions) - Data segment (initialized variables, constant variable) - .bss -> store uninitialized data - Relocation table - identify lines of code that need to be handled - Symbol table - List of the file's labels and data that can be referenced - Debugging info ---------------------------------------------------------- A simple assembly hello world program ---------------------------------------------------------- .data msg: .ascii "hello, world!\n" .text .global _start _start: mov $1,%rax mov $1,%rdi mov $msg,%rsi mov $14,%rdx syscall mov $60,%rax mov $0,%rdi syscall ---------------------------------------------------------- 1. .text contains code of the program 2. .data contains initialized variables $ as -o hello.o hello.asm $ ld -o hello hello.o ---------------------------------------------------------- ---------------------------------------------------------- Linker script that links the hello.asm ---------------------------------------------------------- /* * Linker script for the factorial */ OUTPUT(hello) OUTPUT_FORMAT("elf64-x86-64") INPUT(hello.o) SECTIONS { . = 0x200000; .text : { *(.text) } . = 0x400000; .data : { *(.data) } } ---------------------------------------------------------- 1. OUTPUT: the name of our executable file 2. OUTPUT_FORMAT: the format of the executable file 3. INPUT: the input file to the ld linker 4. SECTIONS: the set and order of the sections which will be in the output file 5. ". = 0x200000": "." Command points to the current position of the output : the code should be loaded at address 0x20000 6. ". = 0x400000": data section should be loaded at address 0x400000 .text section starts from the address 0x200000 and the .data section starts from the Address 0x400000 7. "*(.text)": The * symbol is wildcard that matches any file name. e.g. hello.o(.text) ---------------------------------------------------------- $ as -o hello.o hello.S && ld -T linker.script && ./hello hello, world! ---------------------------------------------------------- $ objdump -D hello Disassembly of section .text: 0000000000200000 <_start>: 200000: 48 c7 c0 01 00 00 00 mov $0x1,%rax ... Disassembly of section .data: 0000000000400000 : 400000: 68 65 6c 6c 6f pushq $0x6f6c6c65 ... ---------------------------------------------------------- $ objdump -d ./a.out ... 0000000000400526
: 400526: 55 push %rbp 400527: 48 89 e5 mov %rsp,%rbp 40052a: bf c4 05 40 00 mov $0x4005c4,%edi 40052f: e8 cc fe ff ff callq 400400 400534: b8 00 00 00 00 mov $0x0,%eax 400539: 5d pop %rbp 40053a: c3 retq 40053b: 0f 1f 44 00 00 nopl 0x0(%rax,%rax,1) - 0000000000400526
indicates that main function is placed on 526 (hex) after the program header - 400000: the start address of the gcc when loading main function from the memory - address (bytes + 400000), machine language (hex), assembly - two hex characters are composed of a byte. e.g 48 - < > tells us the address that the program jumps to - gcc changes printf to puts - 'r'bp is 64-bit register - %edi is the lower 32-bit portion of a register - %eax is the lower 32-bit portion of rax register - 400526 & 400527 -> create the stack frame - Each stack frame corresponds to a call to a subroutine which has not yet terminated with a return - 40052a -> assign a parameter to hold the function - 40052f -> jump to 400400 (e8 -> callq), (cc->the address that is going to jump to) - This machine is little-endian because of "cc fe ff ff" rather than "ff ff fe cc" - "cc fe ff ff" is -134 (hex) -> 400534 - 134 = 400400 - 400534 -> put 0 into %eax, eax is used to return value (return 0) - 400539 -> pop %rbp from (400526) out - 40053a -> return calls main $ readelf -S ./a.out There are 31 section headers, starting at offset 0x19d8: Section Headers: [Nr] Name Type Address Offset Size EntSize Flags Link Info Align [ 0] NULL 0000000000000000 00000000 0000000000000000 0000000000000000 0 0 0 ... [16] .rodata PROGBITS 00000000004005c0 000005c0 0000000000000011 0000000000000000 A 0 0 4 ... - readelf -S: display each section of a executable file - The address of .rodata (read-only) section starts from 4005c0 - The size is 11 (hex) - What are contents of .rodata? $ objdump -s ./a.out ./a.out: file format elf64-x86-64 Contents of section .interp: 400238 2f6c6962 36342f6c 642d6c69 6e75782d /lib64/ld-linux- 400248 7838362d 36342e73 6f2e3200 x86-64.so.2. ... Contents of section .rodata: 4005c0 01000200 68656c6c 6f20776f 726c6421 ....hello world! 4005d0 00 . ... - 4005c4 is the start address of "hello world!" - move $0x4005c4, %edi is to place the pointer of "hello world!" in register edi ---------------------------------------------------------------------------------------------- Linking process ---------------------------------------------------------------------------------------------- main.c #include #include "lib.h" int main(int argc, char **argv) { printf("factorial of 5 is: %d\n", factorial(5)); return 0; } ---------------------------------------------------------------------------------------------- lib.c int factorial(int base) { int res,i = 1; if (base == 0) { return 1; } while (i <= base) { res *= i; i++; } return res; } ---------------------------------------------------------------------------------------------- lib.h #ifndef LIB_H #define LIB_H int factorial(int base); #endif ---------------------------------------------------------------------------------------------- $ gcc -c main.c $ nm -A main.o //GNU nm lists the symbols from object files main.o: U factorial main.o:0000000000000000 T main main.o: U printf 1. U: undefined symbols 2. T: the symbol placed in the .text section of the object ---------------------------------------------------------------------------------------------- $ objdump -S main.o main.o: file format elf64-x86-64 Disassembly of section .text: 0000000000000000
: 0: 55 push %rbp 1: 48 89 e5 mov %rsp,%rbp 4: 48 83 ec 10 sub $0x10,%rsp 8: 89 7d fc mov %edi,-0x4(%rbp) b: 48 89 75 f0 mov %rsi,-0x10(%rbp) f: bf 05 00 00 00 mov $0x5,%edi 14: e8 00 00 00 00 callq 19 19: 89 c6 mov %eax,%esi 1b: bf 00 00 00 00 mov $0x0,%edi 20: b8 00 00 00 00 mov $0x0,%eax 25: e8 00 00 00 00 callq 2a 2a: b8 00 00 00 00 mov $0x0,%eax 2f: c9 leaveq 30: c3 retq 1. The "callq" operation contain the linker stub or the function name and offset from it to the Next instruction 2. The stub will be updated to the real addresses of the functions ---------------------------------------------------------------------------------------------- $ objdump -S -r main.o ... 14: e8 00 00 00 00 callq 19 15: R_X86_64_PC32 factorial-0x4 19: 89 c6 mov %eax,%esi ... 25: e8 00 00 00 00 callq 2a 26: R_X86_64_PC32 printf-0x4 2a: b8 00 00 00 00 mov $0x0,%eax ... 1. The "-r" or "--reloc" prints the relocation entries of the file 2. What is relocation? The process of connecting symbolic references with symbolic definitions. 3. We can see functions' name by using "-r" flag on the objdump utility 4. Note that e8 00 00 00 00. The e8 is the opcode of the call and the remainder of the line is a relative offset. "e8 00 00 00 00" -> one-byte operation code followed by a four-byte address 5. Why only 4-bytes if an addresses can be 8-bytes in a x86_64 (64 bit) machine? The gcc default compile main.c with the -mcmodel=small ---------------------------------------------------------------------------------------------- $ gcc main.c lib.c -o factorial | objdump -S factorial | grep factorial factorial: file format elf64-x86-64 ... ... 0000000000400506
: 40051a: e8 18 00 00 00 callq 400537 ... ... 0000000000400537 : 400550: 75 07 jne 400559 400557: eb 1b jmp 400574 400559: eb 0e jmp 400569 40056f: 7e ea jle 40055b ... ... 1. When se compile main.c with all its dependencies to an executable file, then we can look at the Factorial call 2. The address of the main function is 0x0000000000400506. 3. The address of factorial function is 0x0000000000400537 = hex(0x40051a + 0x18 + 0x5) == hex(0x400537) a. Our call instruction is 5-bytes long (e8 18 00 00 00) b. The 0x18 is the offset after the call instruction to the factorial function 4. Why doesn't main start from 0x0? The standard C program is linked with "glibc" C standard library and includes constructor functions to initialize data in the program when the program is started. These functions need to be called before the main function is called. 5. What is the entry point of the program? a. The main function is NOT the entry point of the program, it is _start symbol instead. b. The _start symbol is defined in the crtl.o object file 5. The execution of this program ("glibc") will start from the code place in the special ".init" section. ---------------------------------------------------------------------------------------------- objdump -S factorial | less factorial: file format elf64-x86-64 Disassembly of section .init: 00000000004003a8 <_init>: 4003a8: 48 83 ec 08 sub $0x8,%rsp 4003ac: 48 8b 05 a5 05 20 00 mov 0x2005a5(%rip),%rax # 600958 <_DYNAMIC+0x1d0> 1. Note that it starts at the 0x00000000004003a8 address relative to the glibc code ---------------------------------------------------------------------------------------------- $ readelf -d factorial | grep \(INIT\) 0x000000000000000c (INIT) 0x4003a8 1. We can check it also in the ELF output by running "readelf" ---------------------------------------------------------------------------------------------- $ readelf -l factorial Elf file type is EXEC (Executable file) Entry point 0x4003c0 There are 7 program headers, starting at offset 64 Program Headers: Type Offset VirtAddr PhysAddr FileSiz MemSiz Flags Align PHDR 0x0000000000000040 0x0000000000400040 0x0000000000400040 0x0000000000000188 0x0000000000000188 R E 8 INTERP 0x00000000000001c8 0x00000000004001c8 0x00000000004001c8 0x000000000000001c 0x000000000000001c R 1 [Requesting program interpreter: /lib64/ld-linux-x86-64.so.2] LOAD 0x0000000000000000 0x0000000000400000 0x0000000000400000 0x0000000000000610 0x0000000000000610 R E 200000 LOAD 0x0000000000000610 0x0000000000600610 0x0000000000600610 0x00000000000001cc 0x00000000000001cc RW 200000 DYNAMIC 0x0000000000000610 0x0000000000600610 0x0000000000600610 0x0000000000000190 0x0000000000000190 RW 8 NOTE 0x00000000000001e4 0x00000000004001e4 0x00000000004001e4 0x0000000000000020 0x0000000000000020 R 4 GNU_STACK 0x0000000000000000 0x0000000000000000 0x0000000000000000 0x0000000000000000 0x0000000000000000 RW 10 Section to Segment mapping: Segment Sections... 00 01 .interp 02 .interp .note.ABI-tag .hash .dynsym .dynstr .gnu.version .gnu.version_r .rela.dyn .rela.plt .init .plt .text .fini .rodata .eh_frame 03 .dynamic .got .got.plt .data 04 .dynamic 05 .note.ABI-tag 06 1. The .interp section simply contains an ascii string that is the name of the dynamic linker 2. What is dynamic linker? The dynamic linker loads and links shared libraries needed by an executable when it is executed by copying the content of libraries from disk to RAM. 3. ---------------------------------------------------------------------------------------------- Steps of dynamic linking 1. main function calls function at first 2. jump to that plt of the callee's function 3. the plt corresponds to GOT and wants to jump to target function 4. a) First call, put function's number to the stack b) jump to public plt 5. the execution of the public plt 6. the public plt execution calls dynamic linker 7. the dynamic linker figures out which function that the main function wants to use from the stack and update GOT 8. the plt uses function's address in the GOT to jump to that target function - Global Offset Table (GOT) - A table that stores pointers (4 bytes in 32-bit machine, 8 bytes in 64-bit machine) - The pointer points to the variable or function - Procedure Linkage Table (PLT) - Each entry in the table is a segment of codes - The first entry is public plt -> call dynamic linker - The second and following entries -> corresponding to a dynamic linking's function