// SPDX-License-Identifier: Apache-2.0 // // ldls-run -- load a module and call its init, on the build machine. // // docs/plan/ldls.md milestone L1. L0 answered "what does this driver ask // for"; this answers "and in what order does it ask", which is the other thing // that cannot be guessed and falls straight out of running the thing. // // This is a **development harness, not the deliverable.** LDLS proper runs on // SchOS in a process, reaching hardware through /dev/pci. Running the same // loader here first is the arrangement mkschfs already has with the kernel, // and for the same reason: the failures at this stage are loader failures, and // one found here costs a second instead of a boot. // // ldls-run 8139too.ko // // Nothing touches hardware. Every symbol the module wants resolves to a stub // that logs and returns zero, so a driver gets as far as its first real // dependency on the kernel doing something, and then says so. // // **No module is committed to this tree.** See docs/plan/ldls.md rule 1. #include #include #include #include #include #include #include #include "../ko.h" #include "../stub.h" // --- what the stubs report --------------------------------------------------- static const char **g_names; static int g_count; static int g_calls; static int g_verbose; // A handful of symbols are worth actually implementing rather than logging, // because what they do is print -- and a driver printing its own banner // through our code is the clearest evidence there is that the load worked. static int is_printk(const char *n) { return strcmp(n, "_printk") == 0 || strcmp(n, "printk") == 0; } // Linux format strings are printf's with extensions: a leading KERN_SOH level // byte, and %p followed by a letter meaning "pretty-print this kind of // pointer". vprintf understands neither, and handing it a %pS would read an // argument that is not there. // // So the level prefix is stripped and anything carrying a pointer extension is // printed raw. Enough for a banner, honest about the rest, and the real // implementation belongs with the rest of the shim. static void printk_like(const char *fmt, unsigned long *args) { const char *p = fmt; const char *s; if (!fmt) { printf(" (printk with a null format)\n"); return; } if ((unsigned char)p[0] == 0x01 && p[1] >= '0' && p[1] <= '7') p += 2; /* KERN_SOH + level */ for (s = p; *s; s++) if (s[0] == '%' && s[1] == 'p' && ((s[2] >= 'a' && s[2] <= 'z') || (s[2] >= 'A' && s[2] <= 'Z'))) { printf(" | %s", p); if (p[strlen(p) - 1] != '\n') printf("\n"); return; } printf(" | "); printf(p, args[1], args[2], args[3], args[4], args[5]); if (p[0] && p[strlen(p) - 1] != '\n') printf("\n"); } unsigned long ldls_stub(long idx, unsigned long *args) { const char *name = (idx >= 0 && idx < g_count) ? g_names[idx] : "(?)"; g_calls++; printf(" %3d %s(", g_calls, name); printf("%#lx, %#lx, %#lx", args[0], args[1], args[2]); printf(")\n"); if (is_printk(name)) printk_like((const char *)args[0], args); if (g_verbose) printf(" args: %#lx %#lx %#lx %#lx %#lx %#lx\n", args[0], args[1], args[2], args[3], args[4], args[5]); // Zero, so the driver carries on and shows the next call. Stopping at the // first tells you one name; continuing tells you the sequence, which is what // this milestone is for. return 0; } // --- surviving the module ----------------------------------------------------- static sigjmp_buf g_jump; static volatile int g_signal; static void on_fault(int sig) { g_signal = sig; siglongjmp(g_jump, 1); } // --- loading ------------------------------------------------------------------ static unsigned char * slurp(const char *path, unsigned long long *len) { FILE *f = fopen(path, "rb"); unsigned char *buf; long n; if (!f) return 0; if (fseek(f, 0, SEEK_END) != 0) { fclose(f); return 0; } n = ftell(f); if (n <= 0) { fclose(f); return 0; } rewind(f); buf = malloc((size_t)n); if (!buf) { fclose(f); return 0; } if (fread(buf, 1, (size_t)n, f) != (size_t)n) { free(buf); fclose(f); return 0; } fclose(f); *len = (unsigned long long)n; return buf; } struct rctx { struct ko *k; struct stubs *s; }; static int resolve(void *ctx, const char *name, unsigned long long *out) { struct rctx *r = (struct rctx *)ctx; int i; for (i = 0; i < r->k->nundef; i++) if (strcmp(r->k->undef[i], name) == 0) { *out = stub_addr(r->s, i); return 0; } return -1; // cannot happen: the list came from the object } // The module's entry point. `module_init(x)` makes `init_module` an alias for // x, so it is an ordinary defined global and needs no struct layout to find -- // which matters, because the struct that *would* hold it is one of the things // rule 4 says not to assume. static unsigned long long find_symbol(struct ko *k, const char *want) { int i; for (i = 0; i < k->nsym; i++) { const char *nm = k->str + k->sym[i].st_name; if (strcmp(nm, want) != 0) continue; if (k->sym[i].st_shndx == KO_SHN_UNDEF || k->sym[i].st_shndx >= (unsigned short)k->nsh) continue; if (!k->sec[k->sym[i].st_shndx].alloc) continue; return k->sec[k->sym[i].st_shndx].addr + k->sym[i].st_value; } return 0; } int main(int argc, char **argv) { struct ko k; struct ko_section *sec; struct stubs stubs; struct rctx rc; unsigned char *img, *mem, *code; unsigned long long len, span, entry; int nsec, i, rv = 0; if (argc > 1 && strcmp(argv[1], "-v") == 0) { g_verbose = 1; argv++; argc--; } if (argc < 2) { fprintf(stderr, "usage: ldls-run [-v] \n"); return 2; } img = slurp(argv[1], &len); if (!img) { fprintf(stderr, "ldls-run: cannot read %s\n", argv[1]); return 1; } nsec = ko_sections_needed(img, len); if (nsec < 0) { fprintf(stderr, "ldls-run: %s: not an ELF64 relocatable object\n", argv[1]); return 1; } sec = calloc((size_t)(nsec ? nsec : 1), sizeof(struct ko_section)); if (!sec) { fprintf(stderr, "ldls-run: out of memory\n"); return 1; } if (ko_open(&k, img, len, sec, nsec) < 0) { fprintf(stderr, "ldls-run: %s\n", ko_err(&k)); return 1; } printf("%s\n", argv[1]); printf(" module %s\n", ko_name(&k) ? ko_name(&k) : "(not a module)"); printf(" license %s\n", ko_modinfo(&k, "license") ? ko_modinfo(&k, "license") : "(none declared)"); printf(" wants %d symbols\n", k.nundef); // Executable memory for both the module and the stubs. A module's .text has // to be executed and a trampoline has to be jumped to, so neither can live // in ordinary malloc'd memory on a machine that enforces W^X. // // **And it has to be low.** A module is compiled with -mcmodel=kernel, which // lets the compiler emit R_X86_64_32S -- a 32-bit signed absolute address -- // for anything it believes lives in the top 2 GB of the address space. Load // the object at an ordinary mmap address around 0x7f... and those // relocations simply do not fit, which is what the loader said the first // time this was run. // // Linux solves it by giving modules their own region within 2 GB of the // kernel text. Here MAP_32BIT does the same job from the other end: the // first 2 GB, where a positive signed 32-bit value reaches. On SchOS this // costs nothing, because a process's mmap area starts at 0x40000000 and is // already inside that window -- but it is a real constraint on where the // LDLS host may put a module, and it is not one you would guess. // // The stubs are mapped low for the same reason: a 32S relocation can name an // undefined symbol just as easily as a section. span = ko_span(&k); mem = mmap(0, (size_t)(span ? span : 4096), PROT_READ | PROT_WRITE | PROT_EXEC, MAP_PRIVATE | MAP_ANONYMOUS | MAP_32BIT, -1, 0); code = mmap(0, (size_t)(k.nundef + 1) * STUB_SIZE, PROT_READ | PROT_WRITE | PROT_EXEC, MAP_PRIVATE | MAP_ANONYMOUS | MAP_32BIT, -1, 0); if (mem == MAP_FAILED || code == MAP_FAILED) { fprintf(stderr, "ldls-run: cannot get executable memory\n"); return 1; } stub_build(&stubs, code, k.nundef); g_names = k.undef; g_count = k.nundef; // The ones that must not be logged, because they are called at the top of // every function and have register rules of their own. See stub_is_bare(). for (i = 0; i < k.nundef; i++) if (stub_is_bare(k.undef[i])) stub_make_bare(code + (unsigned long long)i * STUB_SIZE); if (ko_layout(&k, mem, span, (unsigned long long)mem) < 0) { fprintf(stderr, "ldls-run: layout: %s\n", ko_err(&k)); return 1; } rc.k = &k; rc.s = &stubs; if (ko_relocate(&k, resolve, &rc) < 0) { fprintf(stderr, "ldls-run: relocate: %s\n", ko_err(&k)); return 1; } printf(" loaded %llu bytes at %p, %d stubs at %p\n\n", span, (void *)mem, k.nundef, (void *)code); entry = find_symbol(&k, "init_module"); if (!entry) { printf(" no init_module -- nothing to call\n"); return 0; } printf(" calling init_module at %#llx\n\n", entry); // A module that dereferences the zero our stubs return will fault, and that // is a *result* rather than a crash: it says exactly how far the driver got // before it needed the kernel to have done something real. signal(SIGSEGV, on_fault); signal(SIGBUS, on_fault); signal(SIGILL, on_fault); signal(SIGFPE, on_fault); if (sigsetjmp(g_jump, 1) == 0) { int (*init)(void) = (int (*)(void))(void *)entry; int r = init(); printf("\n init_module returned %d after %d stub call(s)\n", r, g_calls); } else { printf("\n stopped by signal %d after %d stub call(s)\n", g_signal, g_calls); printf(" that is a result, not a crash: the driver got that far before it\n" " needed one of those stubs to have done something real\n"); rv = 0; } free(sec); free(img); return rv; }