aboutsummaryrefslogtreecommitdiff
path: root/03_hello_dynld/dynld.c
diff options
context:
space:
mode:
Diffstat (limited to '03_hello_dynld/dynld.c')
-rw-r--r--03_hello_dynld/dynld.c82
1 files changed, 82 insertions, 0 deletions
diff --git a/03_hello_dynld/dynld.c b/03_hello_dynld/dynld.c
new file mode 100644
index 0000000..1805b33
--- /dev/null
+++ b/03_hello_dynld/dynld.c
@@ -0,0 +1,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);
+}