1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
|
# Hello `dynld`
### Goals
- Build dynamic linker `dynld.so` which retrieves the user program's
entrypoint (`AT_ENTRY`) from the auxiliary vector and transfers
control to it.
- Build `no-std` program with a custom `PT_INTERP` entry pointing to
`dynld.so`.
---
## Crafting the `dynld.so`
As described in the `goals` above, the idea in this section is to
create a simple dynamic linker which just gets the `entrypoint` of the
user application and then jumps to it. This means the linker does not
support things like:
- Loading of additional dependencies.
- Performing re-locations.
That said, this dynamic linker will not be particularly useful but
act as a skeleton for the upcoming chapters.
The `entrypoint` of the user executable started by the dynamic linker
can be found in the `auxiliary vector` setup by the Linux Kernel (see
[02_process_init](../02_process_init/README.md)).
The entry of interest is the `AT_ENTRY`:
```text
AT_ENTRY
The `a_ptr` member of this entry holds the entry point of
the application program to which the interpreter program should
transfer control.
```
There are two additional entries that need to be discussed,
`AT_EXECFD` and `AT_PHDR`. The x86_64 SystemV ABI states that the OS
Kernel must provide one or the other in the `auxiliary vector`.
For simplicity the dynamic linker in this section only supports
`AT_PHDR`, which means it requires the OS Kernel to already memory map
the user executable.
```text
AT_EXECFD
At process creation the system may pass control to an
interpreter program. When this happens, the system places
either an entry of type `AT_EXECFD` or one of type `AT_PHDR`
in the auxiliary vector. The entry for type `AT_EXECFD`
contains a file descriptor open to read the application
program’s object file.
AT_PHDR
The system may create the memory image of the application
program before passing control to the interpreter
program. When this happens the `AT_PHDR` entry tells the
interpreter where to find the program header table in the
memory image.
```
Using the [`no-std` program](../02_process_init/entry.c) from chapter
[02_process_init](../02_process_init) as starting point, loading and
jumping to the `entrypoint` of the user program can be done as:
```c
void (*user_entry)() = (void (*)())auxv[AT_ENTRY];
user_entry();
```
## User program
The next step is to create the user program that will be loaded by
the dynamic linker created in the previous section.
For now the functionality of the user program is not important, but it
must full-fill the requirements no to depend on any shared libraries or
contain any relocations.
For this purpose the following simple `no-std` program is used:
```c
#include <syscall.h>
#include <io.h>
#include <asm/unistd.h>
void _start() {
pfmt("Running %s @ %s\n", __FUNCTION__, __FILE__);
syscall1(__NR_exit, 0);
}
```
The important step, when linking the user program, is to inform the
static linker to add the `dynld.so` created above in the `.interp`
section. This can be done with the following command line switch:
```bash
gcc ... -Wl,--dynamic-linker=<path> ...
```
> The full compile and link command can be seen in the [Makefile - main](./Makefile).
The result can be seen in the `.interp` sections referenced by the
`PT_INTERP` segment in the program headers:
```bash
readelf -W --string-dump .interp main
String dump of section '.interp':
[ 0] /home/johannst/dev/dynld/03_hello_dynld/dynld.so
```
```bash
readelf -W --program-headers main
Program Headers:
Type Offset VirtAddr PhysAddr FileSiz MemSiz Flg Align
PHDR 0x000040 0x0000000000000040 0x0000000000000040 0x0002d8 0x0002d8 R 0x8
INTERP 0x000318 0x0000000000000318 0x0000000000000318 0x000031 0x000031 R 0x1
[Requesting program interpreter: /home/johannst/dev/dynld/03_hello_dynld/dynld.so]
...
```
As discussed in [01_dynamic_linking](../01_dynamic_linking/README.md)
the `PT_INTERP` segment tells to Linux Kernel which dynamic linker to
load to handle the startup of the user executable.
When running the `./main` user program, the `dynld.so` will be loaded
by the Linux Kernel and controlled will be handed over to it. The
`dynld.so` will retrieve the `entrypoint` of the user program and then
jump to it.
## Things to remember
- As defined by the SystemV ABI the OS Kernel must either provide an
entry for `AT_EXECFD` or `AT_PHDR` in the `auxiliary vector`.
- The `AT_ENTRY` points to the `entrypoint` of the user program.
- When linking with gcc, the dynamic linker can be specified via `-Wl,--dynamic-linker=<path>`.
|