aboutsummaryrefslogtreecommitdiff
path: root/03_hello_dynld/README.md
blob: bcee3f4c9e2246b97956e993246361c8f625b5b3 (plain) (blame)
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
128
# 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 jumped to by the
dynamic linker created above.
For now the functionality of the user program is not really important, but it
must full-fill the requirements to not depend on any shared libraries or
contain any relocations.
For this purpose the following `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
> target](./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 the 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>`.