aboutsummaryrefslogtreecommitdiff
path: root/03_hello_dynld/dynld.c
blob: 1805b33cec83e7ec9c54a1ced92e48a0735ece11 (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
// Copyright (c) 2021 Johannes Stoelp

#include <syscall.h>
#include <auxv.h>
#include <io.h>

#include <stdint.h>
#include <asm/unistd.h>

#if !defined(__linux__) || !defined(__x86_64__)
#    error "Only supported in linux(x86_64)!"
#endif

void dl_entry(const uint64_t* prctx) {
    // Interpret data on the stack passed by the OS kernel as specified in the
    // x86_64 SysV ABI.
    uint64_t argc = *prctx;
    const char** argv = (const char**)(prctx + 1);
    const char** envv = (const char**)(argv + argc + 1);

    // Count the number of environment variables in the `ENVP` segment.
    int envc = 0;
    for (const char** env = envv; *env; ++env) {
        ++envc;
    }

    uint64_t auxv[AT_MAX_CNT];
    for (unsigned i = 0; i < AT_MAX_CNT; ++i) {
        auxv[i] = 0;
    }

    // Read the `AUXV` auxiliary vector segment.
    const Auxv64Entry* auxvp = (const Auxv64Entry*)(envv + envc + 1);
    for (; auxvp->tag != AT_NULL; ++auxvp) {
        if (auxvp->tag < AT_MAX_CNT) {
            auxv[auxvp->tag] = auxvp->val;
        }
    }

    // Get address of the entrypoint for the user executable and
    // transfer control.
    // Requirements for the user executable:
    //   - no dependencies
    //   - no relocations

    pfmt("[dynld]: Running %s @ %s\n", __FUNCTION__, __FILE__);

    // Either `AT_EXECFD` or `AT_PHDR` must be specified, we only
    // support `AT_PHDR` here.
    //
    // From the X86_64 SystemV ABI:
    // 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.
    if (auxv[AT_PHDR] == 0 || auxv[AT_EXECFD] != 0) {
        pfmt("[dynld]: ERROR, expected Linux Kernel to map user executable!\n");
        syscall1(__NR_exit, 1);
    }

    if (auxv[AT_ENTRY] == 0) {
        pfmt("[dynld]: ERROR, AT_ENTRY not found in auxiliary vector!\n");
        syscall1(__NR_exit, 1);
    }

    // Transfer control to user executable.
    void (*user_entry)() = (void (*)())auxv[AT_ENTRY];
    pfmt("[dynld]: Got user entrypoint @0x%x\n", user_entry);
    user_entry();

    syscall1(__NR_exit, 0);
}