ldls: the loader, and what it measured
docs/plan/ldls.md milestone L0. ldls/ko.c indexes the sections of an ELF64
relocatable object, places the allocatable ones, resolves undefined symbols
against a caller's table and applies the relocations. Nothing is executed.
It is freestanding -- no libc, no allocation, no I/O -- so the same source
builds as a host tool now and as the SchOS-side host later. That is the
arrangement mkschfs and the kernel already share, and for the same reason:
the failures at this layer are format failures, and one found on the host
costs a second instead of a boot.
$ ldls-inspect 8139too.ko
module 8139too
license GPL
loads to 33988 bytes
relocations: 1980
R_X86_64_64 363, PC32 991, PLT32 428, 32S 169, PC64 29
undefined symbols: 94
stops at: unresolved: __fentry__
The licence is printed before anything is loaded, because it is the one
property that decides whether the object may be on the machine at all and it
is a plain string in .modinfo. Rule 1 is easier to keep when the answer is on
screen.
94 symbols is the measured API surface of 8139too on 6.18.46 -- the list,
exactly, rather than an estimate -- and it is L1's work queue. It is smaller
than it looks: __fentry__, __stack_chk_fail, __ref_stack_chk_guard,
__x86_return_thunk and __fortify_panic are compiler and instrumentation
artefacts that stub out in a line each.
Swept over every module in a stock 6.18.46 tree: 2,440 objects, all of them
laid out and relocated to completion, none with a relocation type the loader
does not handle. That sweep is also what retired the fixed section table --
nvidia-modeset has 7,069 sections and 74,305 relocations, against a median of
50 -- so the caller supplies the table and there is no limit to run into
rather than a generous one that fails later.
Loading is not running, and the doc says so: not one symbol is implemented,
and the distance between "relocates" and "transmits a packet" is the rest of
the plan.
No .ko is in this tree and .gitignore now refuses one outright. make
ldls-check runs 18 checks against ldls/test/refmod.c, which is ours, written
to contain one of each thing the loader has to get right.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VQ7Qx8E1q1M1bbCq8QLHS2
This commit is contained in:
parent
5263150d3d
commit
1be5f3a704
9 changed files with 1296 additions and 2 deletions
230
ldls/host/inspect.c
Normal file
230
ldls/host/inspect.c
Normal file
|
|
@ -0,0 +1,230 @@
|
|||
// 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 <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#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] <object.ko>\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] <object>\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;
|
||||
}
|
||||
556
ldls/ko.c
Normal file
556
ldls/ko.c
Normal file
|
|
@ -0,0 +1,556 @@
|
|||
// SPDX-License-Identifier: Apache-2.0
|
||||
//
|
||||
// Loading a relocatable object. See ldls/ko.h.
|
||||
//
|
||||
// Written from the ELF-64 gABI and the x86-64 psABI, both published
|
||||
// specifications -- ../docs/licensing.md §5. Nothing in this file was taken
|
||||
// from anyone's loader, and nothing in it needs to be: the format is fully
|
||||
// described and the relocation arithmetic is three lines per type.
|
||||
//
|
||||
// Every read from the image is bounds-checked against the length the caller
|
||||
// gave, and that is not defensive programming for its own sake. The input is a
|
||||
// file the *user* supplied, from a kernel we did not build, and a loader that
|
||||
// trusts its input is a loader that can be handed a crash.
|
||||
|
||||
#include "ko.h"
|
||||
|
||||
// --- small local helpers, so this file needs no libc on either side ---------
|
||||
|
||||
static void
|
||||
ko_memcpy(unsigned char *d, const unsigned char *s, unsigned long long n)
|
||||
{
|
||||
while (n--)
|
||||
*d++ = *s++;
|
||||
}
|
||||
|
||||
static void
|
||||
ko_memset(unsigned char *d, int c, unsigned long long n)
|
||||
{
|
||||
while (n--)
|
||||
*d++ = (unsigned char)c;
|
||||
}
|
||||
|
||||
static int
|
||||
ko_streq(const char *a, const char *b)
|
||||
{
|
||||
while (*a && *a == *b) { a++; b++; }
|
||||
return *a == 0 && *b == 0;
|
||||
}
|
||||
|
||||
static void
|
||||
ko_fail(struct ko *k, const char *msg, const char *what)
|
||||
{
|
||||
int i = 0;
|
||||
while (msg[i] && i < (int)sizeof(k->err) - 1) { k->err[i] = msg[i]; i++; }
|
||||
if (what) {
|
||||
int j = 0;
|
||||
while (what[j] && i < (int)sizeof(k->err) - 1) k->err[i++] = what[j++];
|
||||
}
|
||||
k->err[i] = 0;
|
||||
}
|
||||
|
||||
// A number, appended to the error. There is no formatter here and an error
|
||||
// that says "relocation type" without saying which is not worth printing.
|
||||
static void
|
||||
ko_fail_num(struct ko *k, const char *msg, unsigned long long v)
|
||||
{
|
||||
char d[24];
|
||||
int n = 0, i = 0;
|
||||
|
||||
while (msg[i] && i < (int)sizeof(k->err) - 1) { k->err[i] = msg[i]; i++; }
|
||||
if (v == 0) d[n++] = '0';
|
||||
while (v) { d[n++] = (char)('0' + v % 10); v /= 10; }
|
||||
while (n-- > 0 && i < (int)sizeof(k->err) - 1)
|
||||
k->err[i++] = d[n];
|
||||
k->err[i] = 0;
|
||||
}
|
||||
|
||||
// Is [off, off+n) inside the image? Every accessor goes through this.
|
||||
static int
|
||||
ko_fits(const struct ko *k, unsigned long long off, unsigned long long n)
|
||||
{
|
||||
if (off > k->len)
|
||||
return 0;
|
||||
if (n > k->len - off)
|
||||
return 0;
|
||||
return 1;
|
||||
}
|
||||
|
||||
// A NUL-terminated string at `off` in a table of `size` bytes starting at
|
||||
// `base` -- checked so that an unterminated string table cannot run away.
|
||||
static const char *
|
||||
ko_str_at(const struct ko *k, unsigned long long base, unsigned long long size,
|
||||
unsigned int off)
|
||||
{
|
||||
unsigned long long i;
|
||||
|
||||
if (!ko_fits(k, base, size) || off >= size)
|
||||
return 0;
|
||||
for (i = base + off; i < base + size; i++)
|
||||
if (k->img[i] == 0)
|
||||
return (const char *)(k->img + base + off);
|
||||
return 0; // runs to the end without a NUL
|
||||
}
|
||||
|
||||
static const struct ko_shdr *
|
||||
ko_sh(const struct ko *k, int i)
|
||||
{
|
||||
if (i < 0 || i >= k->nsh)
|
||||
return 0;
|
||||
return (const struct ko_shdr *)(k->shtab +
|
||||
(unsigned long long)i * k->eh->e_shentsize);
|
||||
}
|
||||
|
||||
// --- opening -----------------------------------------------------------------
|
||||
|
||||
int
|
||||
ko_sections_needed(const void *image, unsigned long long len)
|
||||
{
|
||||
const struct ko_ehdr *eh = (const struct ko_ehdr *)image;
|
||||
|
||||
if (len < sizeof(struct ko_ehdr))
|
||||
return -1;
|
||||
if (eh->e_ident[0] != 0x7f || eh->e_ident[1] != 'E' ||
|
||||
eh->e_ident[2] != 'L' || eh->e_ident[3] != 'F')
|
||||
return -1;
|
||||
if (eh->e_ident[4] != 2 || eh->e_type != KO_ET_REL)
|
||||
return -1;
|
||||
return eh->e_shnum;
|
||||
}
|
||||
|
||||
int
|
||||
ko_open(struct ko *k, const void *image, unsigned long long len,
|
||||
struct ko_section *sec, int nsec)
|
||||
{
|
||||
const struct ko_ehdr *eh;
|
||||
int i;
|
||||
|
||||
ko_memset((unsigned char *)k, 0, sizeof(*k));
|
||||
k->img = (const unsigned char *)image;
|
||||
k->len = len;
|
||||
k->sec = sec;
|
||||
|
||||
if (!sec || nsec <= 0)
|
||||
{ ko_fail(k, "no section table was provided", 0); return -1; }
|
||||
|
||||
if (len < sizeof(struct ko_ehdr))
|
||||
{ ko_fail(k, "not an ELF object: too short", 0); return -1; }
|
||||
|
||||
eh = (const struct ko_ehdr *)image;
|
||||
if (eh->e_ident[0] != 0x7f || eh->e_ident[1] != 'E' ||
|
||||
eh->e_ident[2] != 'L' || eh->e_ident[3] != 'F')
|
||||
{ ko_fail(k, "not an ELF object: bad magic", 0); return -1; }
|
||||
if (eh->e_ident[4] != 2) // ELFCLASS64
|
||||
{ ko_fail(k, "not a 64-bit object", 0); return -1; }
|
||||
if (eh->e_ident[5] != 1) // ELFDATA2LSB
|
||||
{ ko_fail(k, "not little-endian", 0); return -1; }
|
||||
if (eh->e_type != KO_ET_REL)
|
||||
{ ko_fail(k, "not a relocatable object -- a .ko is ET_REL, this is not", 0);
|
||||
return -1; }
|
||||
if (eh->e_machine != KO_EM_X86_64)
|
||||
{ ko_fail_num(k, "wrong machine, e_machine=", eh->e_machine); return -1; }
|
||||
if (eh->e_shentsize < sizeof(struct ko_shdr))
|
||||
{ ko_fail(k, "section headers are smaller than ELF64 requires", 0);
|
||||
return -1; }
|
||||
|
||||
k->eh = eh;
|
||||
k->nsh = eh->e_shnum;
|
||||
if (k->nsh <= 0)
|
||||
{ ko_fail(k, "no section headers", 0); return -1; }
|
||||
if (k->nsh > nsec)
|
||||
{ ko_fail_num(k, "the caller's section table is too small; it needs ",
|
||||
(unsigned long long)k->nsh); return -1; }
|
||||
ko_memset((unsigned char *)sec, 0,
|
||||
(unsigned long long)k->nsh * sizeof(struct ko_section));
|
||||
|
||||
if (!ko_fits(k, eh->e_shoff,
|
||||
(unsigned long long)k->nsh * eh->e_shentsize))
|
||||
{ ko_fail(k, "section headers run off the end of the file", 0); return -1; }
|
||||
k->shtab = k->img + eh->e_shoff;
|
||||
|
||||
// Section names.
|
||||
{
|
||||
const struct ko_shdr *sh = ko_sh(k, eh->e_shstrndx);
|
||||
if (!sh || sh->sh_type != KO_SHT_STRTAB)
|
||||
{ ko_fail(k, "no section-name string table", 0); return -1; }
|
||||
if (!ko_fits(k, sh->sh_offset, sh->sh_size))
|
||||
{ ko_fail(k, "section names run off the end of the file", 0); return -1; }
|
||||
k->shstr = (const char *)(k->img + sh->sh_offset);
|
||||
}
|
||||
|
||||
// Index every section, and find the symbol table on the way past.
|
||||
for (i = 0; i < k->nsh; i++) {
|
||||
const struct ko_shdr *sh = ko_sh(k, i);
|
||||
struct ko_section *s = &k->sec[i];
|
||||
|
||||
{
|
||||
const struct ko_shdr *ss = ko_sh(k, k->eh->e_shstrndx);
|
||||
const char *nm = ko_str_at(k, ss->sh_offset, ss->sh_size, sh->sh_name);
|
||||
// An unreadable name is not fatal -- the section is still loadable and
|
||||
// still relocatable. It only costs the report a label.
|
||||
s->name = nm ? nm : "(unnamed)";
|
||||
}
|
||||
s->type = sh->sh_type;
|
||||
s->flags = sh->sh_flags;
|
||||
s->size = sh->sh_size;
|
||||
s->align = sh->sh_addralign ? sh->sh_addralign : 1;
|
||||
s->alloc = (sh->sh_flags & KO_SHF_ALLOC) != 0;
|
||||
s->data = 0;
|
||||
|
||||
if (sh->sh_type != KO_SHT_NOBITS && sh->sh_size) {
|
||||
if (!ko_fits(k, sh->sh_offset, sh->sh_size))
|
||||
{ ko_fail(k, "section runs off the end of the file: ", s->name);
|
||||
return -1; }
|
||||
s->data = k->img + sh->sh_offset;
|
||||
}
|
||||
|
||||
if (sh->sh_type == KO_SHT_SYMTAB) {
|
||||
const struct ko_shdr *st;
|
||||
if (sh->sh_entsize != sizeof(struct ko_sym))
|
||||
{ ko_fail(k, "symbol entries are the wrong size", 0); return -1; }
|
||||
if (!s->data)
|
||||
{ ko_fail(k, "empty symbol table", 0); return -1; }
|
||||
k->sym = (const struct ko_sym *)s->data;
|
||||
k->nsym = (int)(sh->sh_size / sizeof(struct ko_sym));
|
||||
|
||||
st = ko_sh(k, sh->sh_link);
|
||||
if (!st || st->sh_type != KO_SHT_STRTAB)
|
||||
{ ko_fail(k, "symbol table has no string table", 0); return -1; }
|
||||
if (!ko_fits(k, st->sh_offset, st->sh_size))
|
||||
{ ko_fail(k, "symbol names run off the end of the file", 0);
|
||||
return -1; }
|
||||
k->str = (const char *)(k->img + st->sh_offset);
|
||||
}
|
||||
}
|
||||
|
||||
if (!k->sym)
|
||||
{ ko_fail(k, "no symbol table -- nothing could be resolved", 0);
|
||||
return -1; }
|
||||
|
||||
// The undefined symbols. This is L0's whole output and the reason the
|
||||
// milestone exists: it is the API surface of this driver, measured rather
|
||||
// than guessed at.
|
||||
for (i = 0; i < k->nsym; i++) {
|
||||
const char *nm;
|
||||
|
||||
if (k->sym[i].st_shndx != KO_SHN_UNDEF)
|
||||
continue;
|
||||
if (k->sym[i].st_name == 0)
|
||||
continue; // the index-0 placeholder
|
||||
nm = (const char *)(k->str + k->sym[i].st_name);
|
||||
if (nm[0] == 0)
|
||||
continue;
|
||||
|
||||
if (k->nundef < KO_MAXUNDEF)
|
||||
k->undef[k->nundef++] = nm;
|
||||
else
|
||||
k->undef_lost = 1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
const char *ko_err(const struct ko *k) { return k->err[0] ? k->err : "no error"; }
|
||||
|
||||
// --- layout ------------------------------------------------------------------
|
||||
|
||||
static unsigned long long
|
||||
ko_round(unsigned long long v, unsigned long long a)
|
||||
{
|
||||
if (a <= 1)
|
||||
return v;
|
||||
return (v + a - 1) & ~(a - 1);
|
||||
}
|
||||
|
||||
// Where each allocatable section lands, and how much that comes to.
|
||||
//
|
||||
// One contiguous block, sections in file order, each at its own alignment.
|
||||
// Linux's own loader splits by permission so that text can be made read-only;
|
||||
// that is a refinement worth having later and is not what L0 is measuring.
|
||||
static unsigned long long
|
||||
ko_plan(struct ko *k, unsigned long long base, void *mem)
|
||||
{
|
||||
unsigned long long off = 0;
|
||||
int i;
|
||||
|
||||
for (i = 0; i < k->nsh; i++) {
|
||||
struct ko_section *s = &k->sec[i];
|
||||
if (!s->alloc || s->size == 0)
|
||||
continue;
|
||||
off = ko_round(off, s->align);
|
||||
if (mem) {
|
||||
s->addr = base + off;
|
||||
s->where = (unsigned char *)mem + off;
|
||||
}
|
||||
off += s->size;
|
||||
}
|
||||
return off;
|
||||
}
|
||||
|
||||
unsigned long long
|
||||
ko_span(const struct ko *k)
|
||||
{
|
||||
return ko_plan((struct ko *)k, 0, 0);
|
||||
}
|
||||
|
||||
int
|
||||
ko_layout(struct ko *k, void *mem, unsigned long long cap,
|
||||
unsigned long long base)
|
||||
{
|
||||
unsigned long long need = ko_plan(k, 0, 0);
|
||||
int i;
|
||||
|
||||
if (need > cap)
|
||||
{ ko_fail_num(k, "the object does not fit; it needs ", need); return -1; }
|
||||
|
||||
k->span = ko_plan(k, base, mem);
|
||||
|
||||
for (i = 0; i < k->nsh; i++) {
|
||||
struct ko_section *s = &k->sec[i];
|
||||
if (!s->alloc || s->size == 0)
|
||||
continue;
|
||||
if (s->type == KO_SHT_NOBITS)
|
||||
ko_memset(s->where, 0, s->size); // .bss, and it must be zeroed
|
||||
else
|
||||
ko_memcpy(s->where, s->data, s->size);
|
||||
}
|
||||
|
||||
k->laid_out = 1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
// --- relocation --------------------------------------------------------------
|
||||
|
||||
// The address a symbol will have once loaded.
|
||||
static int
|
||||
ko_value(struct ko *k, unsigned int idx, ko_resolver r, void *ctx,
|
||||
unsigned long long *out)
|
||||
{
|
||||
const struct ko_sym *sy;
|
||||
const char *nm;
|
||||
|
||||
if ((int)idx >= k->nsym)
|
||||
{ ko_fail_num(k, "relocation names symbol ", idx); return -1; }
|
||||
sy = &k->sym[idx];
|
||||
|
||||
if (sy->st_shndx == KO_SHN_ABS) {
|
||||
*out = sy->st_value;
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (sy->st_shndx == KO_SHN_UNDEF) {
|
||||
nm = (const char *)(k->str + sy->st_name);
|
||||
if (!r || r(ctx, nm, out) != 0)
|
||||
{ ko_fail(k, "unresolved: ", nm); return -1; }
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (sy->st_shndx == KO_SHN_COMMON)
|
||||
{ ko_fail(k, "common symbols are not supported: ",
|
||||
(const char *)(k->str + sy->st_name)); return -1; }
|
||||
|
||||
if (sy->st_shndx >= k->nsh)
|
||||
{ ko_fail_num(k, "symbol points at section ", sy->st_shndx); return -1; }
|
||||
|
||||
// A symbol defined in a section that was not laid out has no address. That
|
||||
// happens for debug sections, and a relocation against one means the object
|
||||
// is asking for something the loader deliberately did not load.
|
||||
if (!k->sec[sy->st_shndx].alloc)
|
||||
{ ko_fail(k, "relocation against a section that is not loaded: ",
|
||||
k->sec[sy->st_shndx].name); return -1; }
|
||||
|
||||
*out = k->sec[sy->st_shndx].addr + sy->st_value;
|
||||
return 0;
|
||||
}
|
||||
|
||||
void
|
||||
ko_reltypes(struct ko *k)
|
||||
{
|
||||
int i;
|
||||
|
||||
for (i = 0; i < 64; i++)
|
||||
k->reltype[i] = 0;
|
||||
k->reltype_other = 0;
|
||||
k->nrel = 0;
|
||||
|
||||
for (i = 0; i < k->nsh; i++) {
|
||||
const struct ko_shdr *sh = ko_sh(k, i);
|
||||
unsigned long long n, j;
|
||||
|
||||
if (!sh || sh->sh_type != KO_SHT_RELA)
|
||||
continue;
|
||||
if (sh->sh_entsize != sizeof(struct ko_rela) || !k->sec[i].data)
|
||||
continue;
|
||||
if (sh->sh_info >= (unsigned int)k->nsh || !k->sec[sh->sh_info].alloc)
|
||||
continue; // relocations for sections we do not load
|
||||
|
||||
n = sh->sh_size / sizeof(struct ko_rela);
|
||||
for (j = 0; j < n; j++) {
|
||||
const struct ko_rela *rl = (const struct ko_rela *)(k->sec[i].data) + j;
|
||||
unsigned int t = KO_R_TYPE(rl->r_info);
|
||||
k->nrel++;
|
||||
if (t < 64)
|
||||
k->reltype[t]++;
|
||||
else
|
||||
k->reltype_other++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int
|
||||
ko_relocate(struct ko *k, ko_resolver r, void *ctx)
|
||||
{
|
||||
int i;
|
||||
|
||||
if (!k->laid_out)
|
||||
{ ko_fail(k, "relocate before layout", 0); return -1; }
|
||||
|
||||
for (i = 0; i < k->nsh; i++) {
|
||||
const struct ko_shdr *sh = ko_sh(k, i);
|
||||
struct ko_section *tgt;
|
||||
unsigned long long n, j;
|
||||
|
||||
if (!sh || sh->sh_type != KO_SHT_RELA)
|
||||
continue;
|
||||
if (sh->sh_entsize != sizeof(struct ko_rela))
|
||||
{ ko_fail(k, "relocation entries are the wrong size", 0); return -1; }
|
||||
if (sh->sh_info >= (unsigned int)k->nsh)
|
||||
{ ko_fail_num(k, "relocations target section ", sh->sh_info); return -1; }
|
||||
|
||||
tgt = &k->sec[sh->sh_info];
|
||||
if (!tgt->alloc || tgt->size == 0)
|
||||
continue; // relocations for .debug_* and friends
|
||||
|
||||
n = sh->sh_size / sizeof(struct ko_rela);
|
||||
for (j = 0; j < n; j++) {
|
||||
const struct ko_rela *rl =
|
||||
(const struct ko_rela *)(k->sec[i].data) + j;
|
||||
unsigned int type = KO_R_TYPE(rl->r_info);
|
||||
unsigned long long S, P;
|
||||
unsigned char *loc;
|
||||
long long X;
|
||||
|
||||
if (rl->r_offset + 4 > tgt->size &&
|
||||
!(type == KO_R_X86_64_64 || type == KO_R_X86_64_PC64))
|
||||
{ ko_fail(k, "relocation points outside ", tgt->name); return -1; }
|
||||
if ((type == KO_R_X86_64_64 || type == KO_R_X86_64_PC64) &&
|
||||
rl->r_offset + 8 > tgt->size)
|
||||
{ ko_fail(k, "relocation points outside ", tgt->name); return -1; }
|
||||
|
||||
if (type == KO_R_X86_64_NONE)
|
||||
continue;
|
||||
|
||||
if (ko_value(k, KO_R_SYM(rl->r_info), r, ctx, &S) < 0)
|
||||
return -1;
|
||||
|
||||
loc = tgt->where + rl->r_offset;
|
||||
P = tgt->addr + rl->r_offset;
|
||||
X = rl->r_addend;
|
||||
|
||||
switch (type) {
|
||||
case KO_R_X86_64_64:
|
||||
*(unsigned long long *)loc = S + (unsigned long long)X;
|
||||
break;
|
||||
case KO_R_X86_64_PC64:
|
||||
*(unsigned long long *)loc = S + (unsigned long long)X - P;
|
||||
break;
|
||||
case KO_R_X86_64_PC32:
|
||||
case KO_R_X86_64_PLT32: {
|
||||
// PLT32 is treated as PC32. There is no procedure linkage table here:
|
||||
// every call is resolved at load time, so the "call through the PLT"
|
||||
// a compiler emitted is just a call to where the symbol ended up.
|
||||
long long v = (long long)S + X - (long long)P;
|
||||
if (v < -0x80000000LL || v > 0x7fffffffLL)
|
||||
{ ko_fail(k, "a 32-bit PC-relative relocation does not reach; the "
|
||||
"object was loaded too far away", 0); return -1; }
|
||||
*(unsigned int *)loc = (unsigned int)(int)v;
|
||||
break;
|
||||
}
|
||||
case KO_R_X86_64_32: {
|
||||
unsigned long long v = S + (unsigned long long)X;
|
||||
if (v > 0xffffffffULL)
|
||||
{ ko_fail(k, "a 32-bit absolute relocation does not fit", 0);
|
||||
return -1; }
|
||||
*(unsigned int *)loc = (unsigned int)v;
|
||||
break;
|
||||
}
|
||||
case KO_R_X86_64_32S: {
|
||||
long long v = (long long)S + X;
|
||||
if (v < -0x80000000LL || v > 0x7fffffffLL)
|
||||
{ ko_fail(k, "a signed 32-bit relocation does not fit", 0);
|
||||
return -1; }
|
||||
*(unsigned int *)loc = (unsigned int)(int)v;
|
||||
break;
|
||||
}
|
||||
default:
|
||||
// Named, not ignored. A relocation quietly skipped is a wrong address
|
||||
// discovered a long way from here.
|
||||
ko_fail_num(k, "unhandled relocation type ", type);
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
// --- what the object says about itself ---------------------------------------
|
||||
|
||||
static const struct ko_section *
|
||||
ko_find(const struct ko *k, const char *name)
|
||||
{
|
||||
int i;
|
||||
for (i = 0; i < k->nsh; i++)
|
||||
if (ko_streq(k->sec[i].name, name))
|
||||
return &k->sec[i];
|
||||
return 0;
|
||||
}
|
||||
|
||||
// `.modinfo` is a run of NUL-terminated "key=value" strings. Reading it needs
|
||||
// nothing loaded and no symbols resolved, which is why the licence a module
|
||||
// declares can be printed before deciding whether to load it at all.
|
||||
const char *
|
||||
ko_modinfo(const struct ko *k, const char *key)
|
||||
{
|
||||
const struct ko_section *s = ko_find(k, ".modinfo");
|
||||
unsigned long long i = 0;
|
||||
|
||||
if (!s || !s->data)
|
||||
return 0;
|
||||
|
||||
while (i < s->size) {
|
||||
const char *e = (const char *)(s->data + i);
|
||||
unsigned long long n = 0;
|
||||
const char *p = e;
|
||||
const char *q = key;
|
||||
|
||||
while (i + n < s->size && e[n])
|
||||
n++;
|
||||
if (n == 0) { i++; continue; }
|
||||
|
||||
while (*q && *p == *q) { p++; q++; }
|
||||
if (*q == 0 && *p == '=')
|
||||
return p + 1;
|
||||
|
||||
i += n + 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
const char *
|
||||
ko_name(const struct ko *k)
|
||||
{
|
||||
int i;
|
||||
|
||||
// The module's `struct module` lives in `.gnu.linkonce.this_module` and its
|
||||
// name is at a fixed offset -- but that offset is a property of the kernel
|
||||
// version, which is exactly the kind of thing docs/plan/ldls.md rule 4 says
|
||||
// not to assume. The symbol table has the same information portably: the
|
||||
// object defines `__this_module`, and `.modinfo` carries the rest.
|
||||
for (i = 0; i < k->nsym; i++) {
|
||||
const char *nm = (const char *)(k->str + k->sym[i].st_name);
|
||||
if (ko_streq(nm, "__this_module"))
|
||||
return ko_modinfo(k, "name") ? ko_modinfo(k, "name") : "(unnamed)";
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
212
ldls/ko.h
Normal file
212
ldls/ko.h
Normal file
|
|
@ -0,0 +1,212 @@
|
|||
#pragma once
|
||||
|
||||
// Loading a relocatable object. docs/plan/ldls.md milestone L0.
|
||||
//
|
||||
// A Linux `.ko` is an ordinary ELF64 relocatable object -- the same thing
|
||||
// `gcc -c` produces -- and loading one is four steps with no mystery in any of
|
||||
// them: index the sections, place the allocatable ones, resolve the undefined
|
||||
// symbols against a table, and apply the relocations. There is no licence
|
||||
// question anywhere in this file: ELF and the x86-64 relocation set are
|
||||
// published specifications, which is the case [../docs/licensing.md] §5
|
||||
// describes.
|
||||
//
|
||||
// **It is freestanding on purpose.** No libc, no allocation, no I/O: the
|
||||
// caller hands over the whole image and a buffer, and everything happens in
|
||||
// them. That is what lets the same source build as a host tool -- where a test
|
||||
// takes a second -- and as the SchOS-side host, which is the discipline
|
||||
// `mkschfs` and the kernel already share and the reason SchFS format bugs are
|
||||
// found on the host.
|
||||
//
|
||||
// Types are spelled out rather than taken from a header, because the two sides
|
||||
// have different ones and this file has to mean the same thing in both.
|
||||
|
||||
// The section table is the caller's, not ours.
|
||||
//
|
||||
// A fixed array needs a limit, and every limit that would fit in a struct is
|
||||
// one a real object exceeds: measured over 300 modules the median is 50
|
||||
// sections and the 95th percentile is 67, but an object built with
|
||||
// -ffunction-sections runs to thousands -- nvidia-modeset has 7,069. Rather
|
||||
// than pick a number that is generous until it is not, the caller says how
|
||||
// many it is prepared for, and ko_sections_needed() says how many that is.
|
||||
//
|
||||
// A suggested size for a fixed table on the SchOS side, where allocating is
|
||||
// less convenient. It is a caller's choice and not the loader's limit.
|
||||
#define KO_SECHINT 128
|
||||
|
||||
// Undefined symbols the report will hold. Measured over the same corpus the
|
||||
// median is 25 and the worst seen is 245, so this is room to spare -- and
|
||||
// `undef_lost` says so rather than the list quietly ending.
|
||||
#define KO_MAXUNDEF 512
|
||||
|
||||
// --- ELF64, the relocatable subset ------------------------------------------
|
||||
//
|
||||
// include/elf.h has `elfhdr` and `proghdr`, which are the ET_EXEC half. None of
|
||||
// what a relocatable object needs is there, so it is here.
|
||||
|
||||
struct ko_ehdr {
|
||||
unsigned char e_ident[16];
|
||||
unsigned short e_type;
|
||||
unsigned short e_machine;
|
||||
unsigned int e_version;
|
||||
unsigned long long e_entry;
|
||||
unsigned long long e_phoff;
|
||||
unsigned long long e_shoff;
|
||||
unsigned int e_flags;
|
||||
unsigned short e_ehsize;
|
||||
unsigned short e_phentsize;
|
||||
unsigned short e_phnum;
|
||||
unsigned short e_shentsize;
|
||||
unsigned short e_shnum;
|
||||
unsigned short e_shstrndx;
|
||||
};
|
||||
|
||||
struct ko_shdr {
|
||||
unsigned int sh_name;
|
||||
unsigned int sh_type;
|
||||
unsigned long long sh_flags;
|
||||
unsigned long long sh_addr;
|
||||
unsigned long long sh_offset;
|
||||
unsigned long long sh_size;
|
||||
unsigned int sh_link;
|
||||
unsigned int sh_info;
|
||||
unsigned long long sh_addralign;
|
||||
unsigned long long sh_entsize;
|
||||
};
|
||||
|
||||
struct ko_sym {
|
||||
unsigned int st_name;
|
||||
unsigned char st_info;
|
||||
unsigned char st_other;
|
||||
unsigned short st_shndx;
|
||||
unsigned long long st_value;
|
||||
unsigned long long st_size;
|
||||
};
|
||||
|
||||
struct ko_rela {
|
||||
unsigned long long r_offset;
|
||||
unsigned long long r_info;
|
||||
long long r_addend;
|
||||
};
|
||||
|
||||
#define KO_ET_REL 1
|
||||
#define KO_EM_X86_64 62
|
||||
|
||||
#define KO_SHT_PROGBITS 1
|
||||
#define KO_SHT_SYMTAB 2
|
||||
#define KO_SHT_STRTAB 3
|
||||
#define KO_SHT_RELA 4
|
||||
#define KO_SHT_NOBITS 8
|
||||
|
||||
#define KO_SHF_WRITE 0x1
|
||||
#define KO_SHF_ALLOC 0x2
|
||||
#define KO_SHF_EXECINSTR 0x4
|
||||
|
||||
#define KO_SHN_UNDEF 0
|
||||
#define KO_SHN_ABS 0xfff1
|
||||
#define KO_SHN_COMMON 0xfff2
|
||||
|
||||
#define KO_R_SYM(i) ((unsigned int)((i) >> 32))
|
||||
#define KO_R_TYPE(i) ((unsigned int)((i) & 0xffffffffu))
|
||||
|
||||
// The x86-64 relocations a compiled object actually contains. Anything else is
|
||||
// refused by name rather than ignored -- a relocation silently skipped is a
|
||||
// wrong address found much later.
|
||||
#define KO_R_X86_64_NONE 0
|
||||
#define KO_R_X86_64_64 1
|
||||
#define KO_R_X86_64_PC32 2
|
||||
#define KO_R_X86_64_PLT32 4
|
||||
#define KO_R_X86_64_32 10
|
||||
#define KO_R_X86_64_32S 11
|
||||
#define KO_R_X86_64_PC64 24
|
||||
|
||||
// --- what the loader knows about a section ----------------------------------
|
||||
|
||||
struct ko_section {
|
||||
const char *name;
|
||||
unsigned int type;
|
||||
unsigned long long flags;
|
||||
unsigned long long size;
|
||||
unsigned long long align;
|
||||
const unsigned char *data; // 0 for NOBITS
|
||||
int alloc; // takes space at run time
|
||||
unsigned long long addr; // where it will run, once laid out
|
||||
unsigned char *where; // where it is right now, once laid out
|
||||
};
|
||||
|
||||
// Resolving a name to an address. Returns 0 and fills `out` on success, or
|
||||
// non-zero to say it does not know it -- which is not an error at L0, it is
|
||||
// the answer we are asking for.
|
||||
typedef int (*ko_resolver)(void *ctx, const char *name,
|
||||
unsigned long long *out);
|
||||
|
||||
struct ko {
|
||||
const unsigned char *img;
|
||||
unsigned long long len;
|
||||
|
||||
const struct ko_ehdr *eh;
|
||||
const unsigned char *shtab; // raw, indexed with e_shentsize
|
||||
int nsh;
|
||||
|
||||
const char *shstr; // section name strings
|
||||
const struct ko_sym *sym;
|
||||
int nsym;
|
||||
const char *str; // symbol name strings
|
||||
|
||||
struct ko_section *sec; // the caller's, nsh entries used
|
||||
|
||||
const char *undef[KO_MAXUNDEF];
|
||||
int nundef;
|
||||
int undef_lost; // more than KO_MAXUNDEF of them
|
||||
|
||||
unsigned long long span; // bytes ko_layout needs
|
||||
int laid_out;
|
||||
|
||||
// How many relocations of each type the object contains, counted by
|
||||
// ko_reltypes(). The question "what does the loader actually have to
|
||||
// handle" has an exact answer per module, and guessing at it is how a
|
||||
// loader ends up refusing a driver on its last relocation.
|
||||
unsigned int reltype[64];
|
||||
unsigned int reltype_other;
|
||||
unsigned int nrel;
|
||||
|
||||
char err[128];
|
||||
};
|
||||
|
||||
// How many `struct ko_section` this object needs. -1 if it is not an ELF64
|
||||
// object at all, which is worth knowing before allocating anything for it.
|
||||
int ko_sections_needed(const void *image, unsigned long long len);
|
||||
|
||||
// Validate and index, using `sec` for the section table. Returns 0, or -1 with
|
||||
// ko_err() set -- including when `nsec` is too small, which says how many were
|
||||
// needed rather than just refusing.
|
||||
int ko_open(struct ko *k, const void *image, unsigned long long len,
|
||||
struct ko_section *sec, int nsec);
|
||||
|
||||
// Bytes ko_layout will need. Valid after ko_open.
|
||||
unsigned long long ko_span(const struct ko *k);
|
||||
|
||||
// Place every allocatable section into `mem`, as if it will run at `base`.
|
||||
// The two differ when the object is staged somewhere before being mapped;
|
||||
// pass base == (address of mem) when they do not.
|
||||
int ko_layout(struct ko *k, void *mem, unsigned long long cap,
|
||||
unsigned long long base);
|
||||
|
||||
// Count the relocations by type, without applying any. Valid after ko_open.
|
||||
void ko_reltypes(struct ko *k);
|
||||
|
||||
// Apply every relocation. Undefined symbols go to `r`; a name it does not know
|
||||
// makes this fail, naming the first one.
|
||||
int ko_relocate(struct ko *k, ko_resolver r, void *ctx);
|
||||
|
||||
// A value from the `.modinfo` section -- "license", "description", "author",
|
||||
// "alias". Returns 0 if the object has no such key.
|
||||
//
|
||||
// Worth having at L0 rather than later: the first thing anyone should be able
|
||||
// to ask about a module is what licence it declares, and that is a string in
|
||||
// the file rather than anything that needs loading.
|
||||
const char *ko_modinfo(const struct ko *k, const char *key);
|
||||
|
||||
// The module's own name, from `.gnu.linkonce.this_module`, or 0.
|
||||
const char *ko_name(const struct ko *k);
|
||||
|
||||
const char *ko_err(const struct ko *k);
|
||||
79
ldls/test/refmod.c
Normal file
79
ldls/test/refmod.c
Normal file
|
|
@ -0,0 +1,79 @@
|
|||
// SPDX-License-Identifier: Apache-2.0
|
||||
//
|
||||
// The loader's test object. **Ours, on purpose.**
|
||||
//
|
||||
// docs/plan/ldls.md rule 1 says nothing GPL ever enters an image we build, and
|
||||
// the same rule applies to the tree itself: there is no `.ko` committed here
|
||||
// and there never will be. So the thing `make ldls-check` loads is this file,
|
||||
// compiled with `-c`, which produces exactly what a `.ko` is -- an ELF64
|
||||
// relocatable object for x86-64.
|
||||
//
|
||||
// It is written to contain one of each thing the loader has to get right,
|
||||
// because a test object that only exercises the easy path is a test that
|
||||
// passes on a loader that does not work:
|
||||
//
|
||||
// - undefined symbols, of both the function and the data kind
|
||||
// - a `.text` with calls needing PC32/PLT32
|
||||
// - `.data` with an absolute pointer needing R_X86_64_64
|
||||
// - `.rodata` a pointer points into, so section-relative values matter
|
||||
// - `.bss`, which must arrive zeroed and is NOBITS with no file content
|
||||
// - a `.modinfo` shaped the way a module's is, so ko_modinfo() is exercised
|
||||
//
|
||||
// It is never linked and never run. Compiling it is the whole of its job.
|
||||
|
||||
// Things this object does not define. A real module's undefined list is
|
||||
// hundreds of names from the kernel it was built against; five is enough to
|
||||
// prove the loader reports them rather than to pretend this is a driver.
|
||||
extern int ldls_test_alloc(unsigned long size);
|
||||
extern void ldls_test_free(void *p);
|
||||
extern int ldls_test_print(const char *s);
|
||||
extern int ldls_test_counter; // undefined *data*, not a function
|
||||
|
||||
// .rodata, and something in .data pointing at it -- which is the case that
|
||||
// needs a section-relative symbol value to come out right.
|
||||
static const char greeting[] = "ldls reference module";
|
||||
const char *ldls_test_greeting = greeting;
|
||||
|
||||
// .bss. The loader must zero this rather than copy it, because NOBITS has no
|
||||
// bytes in the file to copy.
|
||||
static char scratch[256];
|
||||
char *ldls_test_scratch = scratch;
|
||||
|
||||
// .data with a known value, so a test can check the bytes survived layout.
|
||||
int ldls_test_magic = 0x1D15;
|
||||
|
||||
// .text with calls, which is where PC32 and PLT32 come from.
|
||||
int
|
||||
ldls_test_entry(void)
|
||||
{
|
||||
int n = ldls_test_alloc(sizeof(scratch));
|
||||
|
||||
if (n == 0)
|
||||
return -1;
|
||||
|
||||
ldls_test_print(greeting);
|
||||
ldls_test_counter++;
|
||||
scratch[0] = (char)ldls_test_magic;
|
||||
ldls_test_free(scratch);
|
||||
return ldls_test_counter;
|
||||
}
|
||||
|
||||
// A second function, called by the first, so at least one relocation resolves
|
||||
// inside the object and does not need the resolver at all.
|
||||
int
|
||||
ldls_test_twice(void)
|
||||
{
|
||||
return ldls_test_entry() + ldls_test_entry();
|
||||
}
|
||||
|
||||
// Shaped like a module's `.modinfo`: NUL-terminated key=value strings in their
|
||||
// own section. A real one is produced by MODULE_LICENSE() and friends; this is
|
||||
// the same bytes by hand, because the macros that emit them are not ours.
|
||||
#define MODINFO __attribute__((section(".modinfo"), used, aligned(1)))
|
||||
MODINFO static const char mi_license[] = "license=Apache-2.0";
|
||||
MODINFO static const char mi_description[] = "description=LDLS loader test object";
|
||||
MODINFO static const char mi_name[] = "name=refmod";
|
||||
|
||||
// Modules define this; ko_name() looks for it to tell a kernel module from an
|
||||
// ordinary object file.
|
||||
int __this_module;
|
||||
Loading…
Reference in a new issue