// SPDX-License-Identifier: Apache-2.0 // // ldls-inspect -- what is in a relocatable object, and what it wants. // // docs/plan/ldls.md milestone L0, run on the machine doing the building rather // than inside SchOS. That is the same arrangement `mkschfs` has with the // kernel, for the same reason: the interesting failures here are format // failures, and a format failure found on the host costs a second instead of a // boot. // // It loads and relocates for real -- into a buffer at a pretended address -- // so that "it parses" and "it can actually be laid out and relocated" are not // confused with each other. Nothing is executed. // // ldls-inspect 8139too.ko // // **No module is committed to this tree**, and none ever will be; see // docs/plan/ldls.md rule 1. `make ldls-check` runs this against an object // built from ldls/test/refmod.c, which is ours. #include #include #include #include "../ko.h" 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; } // At L0 nothing is known, so the resolver knows nothing -- and that is the // point rather than a placeholder. Every name it refuses becomes a line in the // work queue. static int resolve_nothing(void *ctx, const char *name, unsigned long long *out) { (void)ctx; (void)name; (void)out; return -1; } // Resolve every name to one plausible address, so relocation runs to the end. // // This answers a different question from the list of undefined symbols, and it // is the one that decides whether the loader is finished: does this module // contain a relocation type the loader does not handle? With nothing resolved // that can never be reached, because the first unknown name stops everything. // // The address is near the module rather than anywhere, because PC32 and PLT32 // have to reach it -- a fake symbol two gigabytes away would fail the range // check and say something true about the fake rather than about the loader. static int resolve_to_stub(void *ctx, const char *name, unsigned long long *out) { (void)name; *out = *(unsigned long long *)ctx; return 0; } static const char * flags_of(const struct ko_section *s) { static char b[8]; int n = 0; b[n++] = (s->flags & KO_SHF_ALLOC) ? 'a' : '-'; b[n++] = (s->flags & KO_SHF_WRITE) ? 'w' : '-'; b[n++] = (s->flags & KO_SHF_EXECINSTR) ? 'x' : '-'; b[n] = 0; return b; } int main(int argc, char **argv) { struct ko k; unsigned char *img, *mem; unsigned long long len, span; const char *lic; int i, quiet = 0, stub = 0, nsec; struct ko_section *sec; if (argc < 2) { fprintf(stderr, "usage: ldls-inspect [-q] [-r] \n"); return 2; } if (strcmp(argv[1], "-q") == 0) { quiet = 1; argv++; argc--; } if (argc > 1 && strcmp(argv[1], "-r") == 0) { stub = 1; argv++; argc--; } if (argc < 2) { fprintf(stderr, "usage: ldls-inspect [-q] \n"); return 2; } img = slurp(argv[1], &len); if (!img) { fprintf(stderr, "ldls-inspect: cannot read %s\n", argv[1]); return 1; } // Exactly what this object needs, so there is no limit to run into. nsec = ko_sections_needed(img, len); if (nsec < 0) { fprintf(stderr, "ldls-inspect: %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-inspect: out of memory\n"); return 1; } if (ko_open(&k, img, len, sec, nsec) < 0) { fprintf(stderr, "ldls-inspect: %s: %s\n", argv[1], ko_err(&k)); return 1; } printf("%s\n", argv[1]); printf(" %llu bytes, %d sections, %d symbols\n", (unsigned long long)len, k.nsh, k.nsym); // The licence first, and deliberately so. It is the one property that // decides whether this object may be on this machine at all, it is a string // in the file, and nothing has to be loaded to read it. lic = ko_modinfo(&k, "license"); printf(" module %s\n", ko_name(&k) ? ko_name(&k) : "(not a kernel module)"); printf(" license %s\n", lic ? lic : "(none declared)"); if (ko_modinfo(&k, "description")) printf(" describes %s\n", ko_modinfo(&k, "description")); span = ko_span(&k); printf(" loads to %llu bytes\n", span); if (!quiet) { printf("\n sections that take space at run time\n"); for (i = 0; i < k.nsh; i++) { const struct ko_section *s = &k.sec[i]; if (!s->alloc || s->size == 0) continue; printf(" %-24s %s %8llu align %llu%s\n", s->name, flags_of(s), s->size, s->align, s->type == KO_SHT_NOBITS ? " (zeroed)" : ""); } } // Lay it out and relocate at a plausible address, so that the report covers // the parts that only fail once something has an address. mem = malloc(span ? (size_t)span : 1); if (!mem) { fprintf(stderr, "ldls-inspect: out of memory\n"); return 1; } if (ko_layout(&k, mem, span, 0x40000000ULL) < 0) { fprintf(stderr, "ldls-inspect: layout: %s\n", ko_err(&k)); return 1; } // What the loader is being asked to handle, exactly. ko_reltypes(&k); printf("\n relocations: %u\n", k.nrel); { static const struct { unsigned int t; const char *n; } known[] = { { KO_R_X86_64_64, "R_X86_64_64" }, { KO_R_X86_64_PC32, "R_X86_64_PC32" }, { KO_R_X86_64_PLT32, "R_X86_64_PLT32" }, { KO_R_X86_64_32, "R_X86_64_32" }, { KO_R_X86_64_32S, "R_X86_64_32S" }, { KO_R_X86_64_PC64, "R_X86_64_PC64" }, { KO_R_X86_64_NONE, "R_X86_64_NONE" }, }; unsigned int accounted = 0; for (i = 0; i < (int)(sizeof(known) / sizeof(known[0])); i++) { if (!k.reltype[known[i].t]) continue; printf(" %-16s %u\n", known[i].n, k.reltype[known[i].t]); accounted += k.reltype[known[i].t]; } for (i = 0; i < 64; i++) { unsigned int t = (unsigned int)i; if (!k.reltype[i] || t == KO_R_X86_64_64 || t == KO_R_X86_64_PC32 || t == KO_R_X86_64_PLT32 || t == KO_R_X86_64_32 || t == KO_R_X86_64_32S || t == KO_R_X86_64_PC64 || t == KO_R_X86_64_NONE) continue; printf(" type %-11d %u <-- the loader does not handle this\n", i, k.reltype[i]); } if (k.reltype_other) printf(" type >= 64 %u <-- the loader does not handle this\n", k.reltype_other); (void)accounted; } printf("\n undefined symbols: %d%s\n", k.nundef, k.undef_lost ? " (and more than the loader will hold)" : ""); for (i = 0; i < k.nundef; i++) printf(" %s\n", k.undef[i]); // With a resolver that knows nothing this must fail, and the first name it // names is the first thing L1 has to provide. Reaching the end instead means // the object needed nothing at all, which a real module never does. if (stub) { unsigned long long at = 0x40000000ULL + 0x200000ULL; if (ko_relocate(&k, resolve_to_stub, &at) == 0) printf("\n relocated in full against stubs -- every relocation type in\n" " this object is one the loader handles\n"); else printf("\n relocation failed: %s\n", ko_err(&k)); } else if (ko_relocate(&k, resolve_nothing, 0) == 0) { printf("\n relocated with an empty symbol table -- it needs nothing\n"); } else { printf("\n stops at: %s\n", ko_err(&k)); printf(" (try -r to resolve everything to a stub and reach the end)\n"); } free(mem); free(sec); free(img); return 0; }