aboutsummaryrefslogtreecommitdiff
path: root/kernel/vdso/vdso.c
diff options
context:
space:
mode:
authorMiquel Sabaté Solà <mssola@mssola.com>2025-09-22 21:39:30 +0200
committerMiquel Sabaté Solà <mssola@mssola.com>2025-09-22 22:05:38 +0200
commit7a721c4374f192ba025b27f790cf14339ca2088e (patch)
treeafee2dcf0f7b8ead535237aa0c8f9a74369a5093 /kernel/vdso/vdso.c
parent08f15adc6cd9de0db8244963177505cfdd634504 (diff)
downloadfarga-7a721c4374f192ba025b27f790cf14339ca2088e.tar.gz
farga-7a721c4374f192ba025b27f790cf14339ca2088e.zip
kernel/vdso: Add an example on vDSO
Signed-off-by: Miquel Sabaté Solà <mssola@mssola.com>
Diffstat (limited to 'kernel/vdso/vdso.c')
-rw-r--r--kernel/vdso/vdso.c151
1 files changed, 151 insertions, 0 deletions
diff --git a/kernel/vdso/vdso.c b/kernel/vdso/vdso.c
new file mode 100644
index 0000000..79c2f05
--- /dev/null
+++ b/kernel/vdso/vdso.c
@@ -0,0 +1,151 @@
+#define _GNU_SOURCE
+
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+// Needed for 'getauxval'.
+#include <sys/auxv.h>
+
+/*
+ * In projects like github.com/mssola/fbos I already dealt with ELF parsing
+ * myself and it was the nicest experience. Plus, in here I have to do some
+ * heave usage of some ELF sections. For this reason I will not do things
+ * manually and use the standard library instead.
+ */
+#include <elf.h>
+
+
+// Terminate the current program with a failure exit code while also printing
+// the given `message` into stderr.
+__attribute__((noreturn)) void die(const char *const message)
+{
+ fprintf(stderr, message);
+ exit(EXIT_FAILURE);
+}
+
+// Assert that the given address 'addr' points to a 64-bit little-endian ELF
+// file.
+void ensure_proper_elf_header(const char *const addr)
+{
+ if (addr[0] != 0x7F || memcmp(&addr[1], "ELF", 3) != 0) {
+ die("Bad ELF format\n");
+ }
+ if (addr[4] != 2) {
+ die("64-bit format is mandatory\n");
+ }
+ if (addr[5] != 1) {
+ die("Little-endian only\n");
+ }
+}
+
+// Returns true if the given name looks like a '__vdso_' function name.
+bool is_vdso_function(const char *const name)
+{
+ if (strlen(name) < 9) {
+ return false;
+ }
+
+ return name[0] == '_' &&
+ name[1] == '_' &&
+ name[2] == 'v' &&
+ name[3] == 'd' &&
+ name[4] == 's' &&
+ name[5] == 'o' &&
+ name[6] == '_';
+}
+
+int main(void)
+{
+ /*
+ * From the vDSO man page: "The base address of the vDSO (if one exists) is
+ * passed by the kernel to each program in the initial auxiliary vector (see
+ * getauxval(3)), via the AT_SYSINFO_EHDR tag".
+ */
+ void *vdso = (void *) getauxval(AT_SYSINFO_EHDR);
+ if (!vdso) {
+ die("Could not get base address for vDSO\n");
+ }
+ printf("=> vDSO base address: %pS\n", vdso);
+
+ // Basic assertion :)
+ ensure_proper_elf_header((const char *) vdso);
+
+ // The ELF header is the first thing starting from the fetched address.
+ const Elf64_Ehdr *ehdr = (const Elf64_Ehdr *) vdso;
+
+ /*
+ * Grab the base address for the Section Header Table for the ELF
+ * binary. From there, we will search section and fetch the address for the
+ * dynamic symbol table and the dynamic string table. This will be used
+ * later for looking up the address for each given function name.
+ */
+ const Elf64_Shdr *shdr = (const Elf64_Shdr *)((const char *) vdso + ehdr->e_shoff);
+ const Elf64_Shdr *symbols_header = NULL;
+ const Elf64_Shdr *strings_header = NULL;
+
+ for (int i = 0; i < ehdr->e_shnum; i++) {
+ if (shdr[i].sh_type == SHT_DYNSYM) {
+ symbols_header = &shdr[i];
+ } else if (shdr[i].sh_type == SHT_STRTAB) {
+ const char *shstrtab = (const char *) vdso + shdr[ehdr->e_shstrndx].sh_offset;
+ const char *strtab_name = shstrtab + shdr[i].sh_name;
+
+ if (strcmp(strtab_name, ".dynstr") == 0) {
+ strings_header = &shdr[i];
+ }
+ }
+ }
+ if (!symbols_header || !strings_header) {
+ die("Could not find .dynsym or .dynstr sections\n");
+ }
+
+ /*
+ * Now that we know where to pick the strings and their corresponding
+ * addresses, let's use their offsets to pair it with the original base
+ * 'vdso' address. This will give us a pointer to the proper tables.
+ */
+ const Elf64_Sym *symbols_table = (const Elf64_Sym *)((const char *) vdso + symbols_header->sh_offset);
+ const char *strings_table = (const char *)((const char *) vdso + strings_header->sh_offset);
+ int num_symbols = symbols_header->sh_size / symbols_header->sh_entsize;
+
+ /*
+ * As an example we will find the 'getcpu' system call which is exported on
+ * a Linux x86_64 system.
+ */
+ int (*vdso_getcpu)(unsigned int *, unsigned int*) = NULL;
+
+ printf("=> The following system calls are available via vDSO:\n");
+ for (int i = 0; i < num_symbols; i++) {
+ // The name of the function directly sits at the strings table plus the
+ // offset given on the symbols table.
+ const char *name = strings_table + symbols_table[i].st_name;
+
+ if (is_vdso_function(name)) {
+ void *address = (void *)((char *) vdso + symbols_table[i].st_value);
+ printf("\t%s (address: %p)\n", name, address);
+
+ if (strcmp(name, "__vdso_getcpu") == 0) {
+ vdso_getcpu = (int (*)(unsigned int *, unsigned int *)) address;
+ }
+ }
+ }
+
+ /*
+ * As an example, run the 'getcpu' system call via the vDSO mechanism.
+ */
+ if (vdso_getcpu) {
+ unsigned int cpu, core;
+ int res = vdso_getcpu(&cpu, &core);
+ if (res < 0) {
+ perror("");
+ exit(EXIT_FAILURE);
+ }
+ printf("=> After running 'getcpu' via vDSO: cpu: %d - core: %d\n", cpu, core);
+ } else {
+ printf("=> No 'getcpu' fun this time around!\n");
+ }
+
+ exit(EXIT_SUCCESS);
+}