SchOS/cmd/ldlsd/main.c
Nexoniarz f38eb8a7fd ldls: L4 was free after all, and the plan closes
L4 was listed as "free -- H2 built all of this" and never actually tested,
which is a claim rather than a result. Tested now, and it holds:

    ldlsd running     ping 2/2; holds index 7 and DMA slots 3 and 4
    killed            interface DOWN, device released, bus mastering
                      cleared, DMA reclaimed -- back to vgpud's 3 blocks
    ping              fails honestly: sendto: -5
    a second ldlsd    ping 3/3, and the arena hands back the same addresses

pciuser_release_all() and netuser_release_all() sit beside each other in
exit() for the same reason, and that they apply unchanged to a foreign driver
is the whole point of having built them against our own.

With that, every milestone on ldls.md is done, so it leaves docs/plan/ for
docs/architecture/ -- the rule docs/plan/README.md states and the one that
keeps the two directories meaning different things. The new document describes
what is, not what was planned: the five rules, the four pieces, what it costs
(polling, no stack protector, locks that are no-ops), that it can be killed,
and a "what it does not do" that leads with one driver, one kernel version,
tried once.

Sixteen source files pointed at docs/plan/ldls.md and would have been dangling
the moment it moved. All repointed, and the milestone references -- which the
architecture doc deliberately does not have -- rewritten to name sections
instead, so a reader lands somewhere real.

The register row closes with it. hybrid.md freed a slot, ldls.md took it, and
it is now free again.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VQ7Qx8E1q1M1bbCq8QLHS2
2026-09-14 13:37:04 +02:00

762 lines
26 KiB
C

// SPDX-License-Identifier: Apache-2.0
//
// ldlsd -- LDLS, on SchOS.
//
// docs/architecture/ldls.md. `ldls-run` does this on the build machine so
// that loader failures cost a second instead of a boot; this is the same
// loader, the same BTF reader and the same trampolines, in a process on the
// system they were written for.
//
// ldlsd <module.ko> [vmlinux-btf]
//
// **Neither file ships with SchOS.** Both are the user's: a module they
// obtained, and the BTF of the kernel it was built for. docs/licensing.md §6
// is the whole argument, and `.gitignore` refusing `*.ko` is how it is kept
// mechanically rather than by intention.
//
// What it does *not* yet do is implement any of the symbols the driver asks
// for -- every one is still a stub that reports itself and returns zero, which
// is L3c. So the useful output here is the same as `ldls-run`'s: what the
// driver asks for, in what order, and how far it gets.
#include "types.h"
#include "user.h"
#include "fcntl.h"
#include "posix.h"
#include "stat.h"
#include "ko.h"
#include "stub.h"
#include "btf.h"
#include "pciuser.h"
#include "kpi/kpi.h"
extern void kpi_unregister_netdev(void *dev);
extern void *kpi_skb_alloc(unsigned int len);
extern void *kpi_skb_put(void *skb, unsigned int n);
extern void *kpi_netdev(void);
// --- small output, since lib/ has no printf ---------------------------------
static void
puts_(const char *s)
{
int n = 0;
while (s[n]) n++;
write(1, (void *)s, n);
}
static void
putn(long long v)
{
char b[24];
int i = 0;
if (v < 0) { write(1, "-", 1); v = -v; }
if (v == 0) { write(1, "0", 1); return; }
while (v > 0) { b[i++] = (char)('0' + (v % 10)); v /= 10; }
while (i-- > 0) write(1, &b[i], 1);
}
static void
puthexn(unsigned long long v, int digits)
{
static const char h[] = "0123456789abcdef";
char b[16];
int i;
if (digits > 16) digits = 16;
for (i = digits - 1; i >= 0; i--) { b[i] = h[v & 0xF]; v >>= 4; }
write(1, b, digits);
}
static void
puthex(unsigned long long v)
{
puts_("0x");
puthexn(v, 16);
}
// lib/ has no string.h either, so the two byte helpers this needs are here.
static void
bcopy_(void *d, const void *s, int n)
{
unsigned char *a = (unsigned char *)d;
const unsigned char *b = (const unsigned char *)s;
while (n-- > 0) *a++ = *b++;
}
static int
streq(const char *a, const char *b)
{
while (*a && *a == *b) { a++; b++; }
return *a == 0 && *b == 0;
}
// --- allocation -------------------------------------------------------------
//
// mmap and nothing else. lib/ has no malloc, and this needs both large
// buffers (a kernel's BTF is several megabytes) and memory it can execute --
// which on SchOS is every mapping, because the kernel never sets EFER.NXE.
// That is convenient here and is a gap rather than a feature; see the note in
// docs/architecture/ldls.md.
//
// The module lands in the low 2 GB automatically: a process's mmap area starts
// at 0x40000000 and bumps upward, which is exactly the window R_X86_64_32S
// relocations need. L1 predicted that and this is where it is collected.
static void *
getmem(unsigned long long n)
{
void *p = mmap(0, n, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS,
-1, 0);
if (p == 0 || (long)p == -1)
return 0;
return p;
}
static unsigned char *
slurp(const char *path, unsigned long long *len)
{
struct stat st;
unsigned char *buf;
int fd, got = 0;
fd = open((char *)path, O_RDONLY);
if (fd < 0)
return 0;
if (fstat(fd, &st) < 0 || st.size == 0) { close(fd); return 0; }
buf = getmem(st.size);
if (!buf) { close(fd); return 0; }
while (got < (int)st.size) {
int n = read(fd, (char *)buf + got, (int)st.size - got);
if (n <= 0)
break;
got += n;
}
close(fd);
if (got != (int)st.size)
return 0;
*len = st.size;
return buf;
}
// --- the stubs ---------------------------------------------------------------
static const char **g_names;
static int g_count;
static int g_calls;
static unsigned long g_pci_driver;
unsigned long
ldls_stub(long idx, unsigned long *args)
{
const char *name = (idx >= 0 && idx < g_count) ? g_names[idx] : "(?)";
g_calls++;
if (g_calls <= 40) {
puts_(" "); putn(g_calls); puts_(" "); puts_(name);
puts_("("); puthex(args[0]); puts_(", "); puthex(args[1]);
puts_(")\n");
} else if (g_calls == 41) {
puts_(" ... (further calls not printed)\n");
}
// The driver's own words, which is the clearest evidence a load worked.
// Linux format strings carry a leading level byte and %p extensions that
// nothing here can expand, so the string is printed raw.
if (streq(name, "_printk") || streq(name, "printk")) {
const char *f = (const char *)args[0];
if (f) {
if ((unsigned char)f[0] == 0x01 && f[1] >= '0' && f[1] <= '7')
f += 2;
puts_(" | "); puts_(f);
{
int n = 0;
while (f[n]) n++;
if (n == 0 || f[n - 1] != '\n') puts_("\n");
}
}
}
if (streq(name, "__pci_register_driver") && !g_pci_driver)
g_pci_driver = args[0];
return 0;
}
struct rctx { struct ko *k; struct stubs *s; };
// The KPI first, a logging stub second.
//
// An unimplemented symbol still resolves -- to something that reports itself
// and returns zero -- so the driver carries on and the *next* thing it wants
// becomes visible. That is what made L1's measurement possible and it stays
// useful for exactly the same reason: the work queue keeps writing itself.
static int g_kpi_used;
static int k_nundef_global;
static int
resolve(void *ctx, const char *name, unsigned long long *out)
{
struct rctx *r = (struct rctx *)ctx;
void *fn;
int i;
fn = kpi_find(name);
if (fn) {
*out = (unsigned long long)fn;
g_kpi_used++;
return 0;
}
for (i = 0; i < r->k->nundef; i++)
if (streq(r->k->undef[i], name)) {
*out = stub_addr(r->s, i);
return 0;
}
return -1;
}
static unsigned long long
find_symbol(struct ko *k, const char *want)
{
int i;
for (i = 0; i < k->nsym; i++) {
if (!streq(k->str + k->sym[i].st_name, want))
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;
}
// --- reading and writing a struct at an offset BTF gave us --------------------
static unsigned int
rd32(unsigned long base, unsigned int off)
{ unsigned int v; bcopy_(&v, (void *)(base + off), 4); return v; }
static unsigned long
rd64(unsigned long base, unsigned int off)
{ unsigned long v; bcopy_(&v, (void *)(base + off), 8); return v; }
static void
wr16(unsigned long base, unsigned int off, unsigned short v)
{ bcopy_((void *)(base + off), &v, 2); }
static void
wr32(unsigned long base, unsigned int off, unsigned int v)
{ bcopy_((void *)(base + off), &v, 4); }
static void
wr64(unsigned long base, unsigned int off, unsigned long v)
{ bcopy_((void *)(base + off), &v, 8); }
// --- finding the hardware ------------------------------------------------------
// Claim the first device the driver says it supports.
//
// The driver's own id_table is the authority on what it can drive, so the
// match is made against that rather than against anything LDLS knows -- which
// is the property that keeps this general: nothing here has heard of an
// rtl8139.
static int
claim_for(unsigned long idtab, unsigned int id_size, unsigned int id_vendor,
unsigned int id_device, struct pciuser_info *out)
{
int count = 0, i, n;
kpi.pcifd = open("/dev/pci", O_RDWR);
if (kpi.pcifd < 0) { puts_("ldlsd: cannot open /dev/pci\n"); return -1; }
if (ioctl(kpi.pcifd, PCIIOC_COUNT, &count) < 0) return -1;
for (i = 0; i < count; i++) {
struct pciuser_info in;
in.index = (unsigned int)i;
if (ioctl(kpi.pcifd, PCIIOC_INFO, &in) < 0)
continue;
for (n = 0; n < 4096; n++) {
unsigned long e = idtab + (unsigned long)n * id_size;
unsigned int v = rd32(e, id_vendor), d = rd32(e, id_device);
if (!v && !d) break;
if (v != in.vendor || d != in.device) continue;
puts_(" found "); puthexn(in.vendor, 4); puts_(":");
puthexn(in.device, 4); puts_(" at ");
puthexn(in.bus, 2); puts_(":"); puthexn(in.slot, 2);
puts_("."); puthexn(in.func, 1);
if (in.driver[0]) { puts_(", held by "); puts_(in.driver); }
puts_("\n");
if (ioctl(kpi.pcifd, PCIIOC_CLAIM, &i) < 0) {
puts_(" the kernel would not release it\n");
return -1;
}
// Re-read: claiming is what enables memory space and bus
// mastering, and a BAR read before that can legitimately be zero.
in.index = (unsigned int)i;
if (ioctl(kpi.pcifd, PCIIOC_INFO, &in) < 0) return -1;
kpi.index = i;
for (int b = 0; b < 6; b++) {
kpi.bar_base[b] = in.bar[b].base;
kpi.bar_size[b] = in.bar[b].size;
kpi.bar_flags[b] = in.bar[b].flags;
}
*out = in;
return 0;
}
}
puts_(" no device on this machine matches the driver's id_table\n");
return -1;
}
// --- being the PCI core -------------------------------------------------------
static unsigned int
lay(struct btf *b, const char *type, const char *member)
{
unsigned int off, size;
int id = btf_find(b, type, BTF_KIND_STRUCT);
if (!id) {
puts_("ldlsd: this BTF has no struct "); puts_(type); puts_("\n");
return 0xffffffffu;
}
if (!member)
return btf_type_size(b, id);
if (btf_member_off(b, id, member, &off, &size) < 0) {
puts_("ldlsd: "); puts_(type); puts_("."); puts_(member);
puts_(": "); puts_(btf_err(b)); puts_("\n");
return 0xffffffffu;
}
return off;
}
#define LDLS_IORESOURCE_IO 0x00000100
#define LDLS_IORESOURCE_MEM 0x00000200
// Fill in the offsets the KPI needs, from the same BTF the PCI ones came from.
static int
kpi_layout_from_btf(struct btf *b)
{
unsigned int off, size;
int nd, ops, skb;
nd = btf_find(b, "net_device", BTF_KIND_STRUCT);
ops = btf_find(b, "net_device_ops", BTF_KIND_STRUCT);
skb = btf_find(b, "sk_buff", BTF_KIND_STRUCT);
if (!nd || !ops || !skb) {
puts_("ldlsd: this BTF is missing net_device, net_device_ops or "
"sk_buff\n");
return -1;
}
#define GET(id, name, field) \
do { if (btf_member_off(b, id, name, &off, &size) < 0) { \
puts_("ldlsd: no "); puts_(name); puts_("\n"); return -1; } \
field = off; } while (0)
GET(nd, "netdev_ops", kpi_l.nd_netdev_ops);
GET(nd, "dev_addr", kpi_l.nd_dev_addr);
GET(nd, "perm_addr", kpi_l.nd_perm_addr);
GET(nd, "addr_len", kpi_l.nd_addr_len);
GET(nd, "mc", kpi_l.nd_mc);
GET(nd, "uc", kpi_l.nd_uc);
GET(nd, "_tx", kpi_l.nd_tx);
GET(nd, "num_tx_queues", kpi_l.nd_num_tx);
GET(nd, "real_num_tx_queues", kpi_l.nd_real_num_tx);
GET(nd, "state", kpi_l.nd_state);
GET(nd, "flags", kpi_l.nd_flags);
{
int nq = btf_find(b, "netdev_queue", BTF_KIND_STRUCT);
if (!nq) { puts_("ldlsd: no struct netdev_queue\n"); return -1; }
kpi_l.nq_size = btf_type_size(b, nq);
GET(nq, "dev", kpi_l.nq_dev);
}
GET(ops, "ndo_open", kpi_l.ndo_open);
GET(ops, "ndo_stop", kpi_l.ndo_stop);
GET(ops, "ndo_start_xmit", kpi_l.ndo_start_xmit);
GET(skb, "len", kpi_l.skb_len);
GET(skb, "data", kpi_l.skb_data);
GET(skb, "head", kpi_l.skb_head);
GET(skb, "tail", kpi_l.skb_tail);
GET(skb, "end", kpi_l.skb_end);
GET(skb, "dev", kpi_l.skb_dev);
#undef GET
kpi_l.skb_size = btf_type_size(b, skb);
return 0;
}
// Bring the interface up and carry frames.
//
// **This polls.** A Linux driver expects its interrupt handler to be called
// from an interrupt, and /dev/pci does deliver them -- by blocking read, which
// needs a thread this program does not have. Calling the handler from a loop
// is the same work in a context LDLS can actually provide, and it costs
// latency and a spinning process. The honest shape here, and it is written
// down as a limitation rather than hidden.
//
// A NAPI driver splits the work: the handler acknowledges the chip and
// schedules, and the poll takes packets off it. Both are called, in that
// order, which is the order a kernel would call them in.
static void
run_interface(struct btf *b, void *ndev)
{
void *ops, *open_fn, *xmit_fn;
unsigned char frame[1600];
long tx = 0, rx = 0, idle = 0;
ops = (void *)rd64((unsigned long)ndev, kpi_l.nd_netdev_ops);
if (!ops) { puts_(" the driver published no netdev_ops\n"); return; }
open_fn = (void *)rd64((unsigned long)ops, kpi_l.ndo_open);
xmit_fn = (void *)rd64((unsigned long)ops, kpi_l.ndo_start_xmit);
if (!open_fn || !xmit_fn) {
puts_(" the driver has no ndo_open or no ndo_start_xmit\n");
return;
}
// netif_running(dev) has to be true before ndo_open, not after.
//
// Linux's dev_open() sets __LINK_STATE_START *before* calling the driver,
// and 8139too's receive loop is gated on it: without it the driver saw the
// packet arrive, found netif_running false, left the loop immediately and
// acked the interrupt -- so the chip's buffer filled, its status cleared,
// and nothing came out. That is a silent failure with no error anywhere,
// and it was found by reading the chip's own CBR register rather than by
// anything the driver said.
//
// IFF_UP too, for the same class of reason.
{
unsigned char *d = (unsigned char *)ndev;
d[kpi_l.nd_state] |= 1; /* __LINK_STATE_START */
d[kpi_l.nd_flags] |= 1; /* IFF_UP */
}
puts_("\n calling ndo_open\n\n");
{
int (*op)(void *) = (int (*)(void *))open_fn;
int r = op(ndev);
puts_("\n ndo_open returned "); putn(r); puts_("\n");
if (r != 0) { puts_(" the interface will not come up\n"); return; }
}
puts_("\n carrying frames. the interface is ldls0.\n\n");
for (;;) {
int n, moved = 0;
// Anything the stack wants sent. The descriptor is non-blocking, so
// this returns immediately when there is nothing -- the receive side
// has to be given a turn either way.
n = read(kpi.netfd, (char *)frame, (int)sizeof(frame));
if (n > 0) {
void *skb = kpi_skb_alloc((unsigned int)n);
if (skb) {
unsigned char *d = kpi_skb_put(skb, (unsigned int)n);
int i;
for (i = 0; i < n; i++) d[i] = frame[i];
{
int (*xmit)(void *, void *) =
(int (*)(void *, void *))xmit_fn;
xmit(skb, ndev);
}
tx++;
moved = 1;
}
}
// And anything the chip has. The driver's own poll does the work; all
// this decides is when it gets to run.
if (kpi_napi_poll) {
int got = kpi_napi_poll(kpi_napi, 16);
if (got > 0) { rx += got; moved = 1; }
}
if (!moved) {
if (++idle > 200) { sleep(1); idle = 0; }
} else {
idle = 0;
if (((tx + rx) % 64) == 0) {
puts_(" ldls0: tx "); putn(tx);
puts_(" rx "); putn(rx); puts_("\n");
}
}
}
}
static void
drive_probe(const char *btfpath)
{
struct btf b;
unsigned char *bimg;
unsigned long long blen;
unsigned int *boff;
long nt;
unsigned int drv_probe, drv_idtable, drv_name;
unsigned int id_size, id_vendor, id_device;
unsigned int dev_size, dev_vendor, dev_device, dev_devfn, dev_irq, dev_res;
unsigned int res_size, res_start, res_end, res_flags;
unsigned long idtab, pdev;
int nid = 0;
bimg = slurp(btfpath, &blen);
if (!bimg) { puts_("ldlsd: cannot read the BTF\n"); return; }
nt = btf_count(bimg, blen);
if (nt < 0) { puts_("ldlsd: that file is not BTF\n"); return; }
boff = getmem(((unsigned long long)nt + 2) * 4);
if (!boff) { puts_("ldlsd: no memory for the BTF index\n"); return; }
if (btf_open(&b, bimg, blen, boff, (unsigned int)nt + 2) < 0) {
puts_("ldlsd: "); puts_(btf_err(&b)); puts_("\n");
return;
}
puts_(" btf "); putn(nt); puts_(" types\n");
drv_probe = lay(&b, "pci_driver", "probe");
drv_idtable = lay(&b, "pci_driver", "id_table");
drv_name = lay(&b, "pci_driver", "name");
id_size = lay(&b, "pci_device_id", 0);
id_vendor = lay(&b, "pci_device_id", "vendor");
id_device = lay(&b, "pci_device_id", "device");
dev_size = lay(&b, "pci_dev", 0);
dev_vendor = lay(&b, "pci_dev", "vendor");
dev_device = lay(&b, "pci_dev", "device");
dev_devfn = lay(&b, "pci_dev", "devfn");
dev_irq = lay(&b, "pci_dev", "irq");
dev_res = lay(&b, "pci_dev", "resource");
res_size = lay(&b, "resource", 0);
res_start = lay(&b, "resource", "start");
res_end = lay(&b, "resource", "end");
res_flags = lay(&b, "resource", "flags");
if (drv_probe == 0xffffffffu || dev_size == 0xffffffffu ||
res_size == 0xffffffffu) {
puts_("ldlsd: this kernel's BTF cannot drive probe()\n");
return;
}
puts_(" layout pci_driver.probe +"); putn(drv_probe);
puts_(" pci_dev "); putn(dev_size); puts_(" bytes\n");
{
const char *dn = (const char *)rd64(g_pci_driver, drv_name);
if (dn) { puts_(" driver \""); puts_(dn); puts_("\"\n"); }
}
idtab = rd64(g_pci_driver, drv_idtable);
if (!idtab) { puts_(" it has no id_table\n"); return; }
puts_("\n devices it claims:\n");
for (;;) {
unsigned long e = idtab + (unsigned long)nid * id_size;
unsigned int v = rd32(e, id_vendor), d = rd32(e, id_device);
if (!v && !d) break;
if (nid < 6) {
puts_(" "); puthexn(v, 4); puts_(":"); puthexn(d, 4);
puts_("\n");
}
nid++;
if (nid > 4096) break;
}
if (nid > 6) { puts_(" ... and "); putn(nid - 6); puts_(" more\n"); }
if (!nid) return;
// Find the real device, claim it, and describe *it* rather than something
// made up. This is the line between L2 and L3c: probe used to be handed a
// pci_dev whose shape was right and whose contents were invented, and now
// every field in it came off the bus.
{
struct pciuser_info in;
if (claim_for(idtab, id_size, id_vendor, id_device, &in) < 0)
return;
pdev = (unsigned long)getmem(dev_size);
if (!pdev) { puts_("ldlsd: no memory for a pci_dev\n"); return; }
wr16(pdev, dev_vendor, in.vendor);
wr16(pdev, dev_device, in.device);
wr32(pdev, dev_devfn, (unsigned int)((in.slot << 3) | in.func));
wr32(pdev, dev_irq, in.irq);
for (int b = 0; b < 6; b++) {
unsigned long r = pdev + dev_res + (unsigned long)b * res_size;
if (!in.bar[b].base)
continue;
wr64(r, res_start, in.bar[b].base);
wr64(r, res_end, in.bar[b].base + in.bar[b].size - 1);
wr64(r, res_flags, (in.bar[b].flags & PCIUSER_BAR_IO)
? LDLS_IORESOURCE_IO : LDLS_IORESOURCE_MEM);
puts_(" bar "); putn(b); puts_(" 0x");
puthexn(in.bar[b].base, 16); puts_(" ");
putn((long long)in.bar[b].size);
puts_((in.bar[b].flags & PCIUSER_BAR_IO) ? " io\n" : " mem\n");
}
}
// sizeof(struct net_device), for alloc_etherdev_mqs -- a driver reaches
// its private area past it and gets silent corruption if this is wrong.
{
extern unsigned int kpi_netdev_size;
int nd = btf_find(&b, "net_device", BTF_KIND_STRUCT);
kpi_netdev_size = nd ? btf_type_size(&b, nd) : 0;
puts_(" netdev "); putn(kpi_netdev_size); puts_(" bytes\n");
}
// Before probe, not after: alloc_etherdev_mqs runs *during* probe and
// needs net_device's offsets to set dev->dev_addr up.
if (kpi_layout_from_btf(&b) < 0)
return;
// Counted over the *symbol list*, not over resolutions: a symbol is
// resolved once per relocation that names it, and reporting that number
// would have said 275 of 94.
{
int impl = 0, i;
for (i = 0; i < k_nundef_global; i++)
if (kpi_find(g_names[i])) impl++;
puts_("\n calling probe, with "); putn(impl);
puts_(" of "); putn(k_nundef_global);
puts_(" symbols implemented\n\n");
}
g_calls = 0;
{
int (*probe)(void *, void *) =
(int (*)(void *, void *))(void *)rd64(g_pci_driver, drv_probe);
if (!probe) { puts_(" the driver has no probe\n"); return; }
{
int r = probe((void *)pdev, (void *)idtab);
puts_("\n probe returned "); putn(r);
puts_(" after "); putn(g_calls); puts_(" stub call(s)\n");
if (r == 0 && kpi.netfd >= 0) {
// The *net_device*, not the pci_dev. See kpi_netdev().
run_interface(&b, kpi_netdev());
return;
}
if (0) {
// The driver is up on real hardware and has registered an
// interface. What is *not* built is the path that carries
// frames -- ndo_open, ndo_start_xmit and the receive side.
//
// So the interface is released rather than left up. A link
// that reports itself up with nothing draining its queue is a
// black hole: the stack would send DHCP into it for ever and
// every layer above would believe the network was merely
// quiet. Down is the true answer until packets move.
puts_("\n the driver is up on real hardware and has an "
"interface.\n");
puts_(" carrying frames is the half of L3 that is not built, "
"so the\n interface is released rather than left up "
"and silent.\n");
kpi_unregister_netdev(0);
}
}
}
}
// --- main ----------------------------------------------------------------------
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;
if (argc < 2) {
puts_("usage: ldlsd <module.ko> [vmlinux-btf]\n");
puts_(" neither file ships with SchOS; both are yours.\n");
exit();
}
img = slurp(argv[1], &len);
if (!img) { puts_("ldlsd: cannot read "); puts_(argv[1]); puts_("\n"); exit(); }
nsec = ko_sections_needed(img, len);
if (nsec < 0) {
puts_("ldlsd: that is not an ELF64 relocatable object\n");
exit();
}
sec = getmem((unsigned long long)(nsec + 1) * sizeof(struct ko_section));
if (!sec) { puts_("ldlsd: out of memory\n"); exit(); }
if (ko_open(&k, img, len, sec, nsec) < 0) {
puts_("ldlsd: "); puts_(ko_err(&k)); puts_("\n");
exit();
}
puts_(argv[1]); puts_("\n");
puts_(" module ");
puts_(ko_name(&k) ? ko_name(&k) : "(not a kernel module)"); puts_("\n");
puts_(" license ");
puts_(ko_modinfo(&k, "license") ? ko_modinfo(&k, "license")
: "(none declared)"); puts_("\n");
puts_(" wants "); putn(k.nundef); puts_(" symbols\n");
span = ko_span(&k);
mem = getmem(span ? span : 4096);
code = getmem((unsigned long long)(k.nundef + 1) * STUB_SIZE);
if (!mem || !code) { puts_("ldlsd: out of memory\n"); exit(); }
stub_build(&stubs, code, k.nundef);
g_names = k.undef;
g_count = k.nundef;
k_nundef_global = k.nundef;
kpi.netfd = -1;
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) {
puts_("ldlsd: layout: "); puts_(ko_err(&k)); puts_("\n");
exit();
}
rc.k = &k; rc.s = &stubs;
if (ko_relocate(&k, resolve, &rc) < 0) {
puts_("ldlsd: relocate: "); puts_(ko_err(&k)); puts_("\n");
exit();
}
// The stack canary is fetched through %gs, which belongs to the kernel
// here. See ko_defuse_gs().
{
int g = ko_defuse_gs(&k, "__ref_stack_chk_guard");
if (g < 0) g = 0;
if (g == 0) g = ko_defuse_gs(&k, "__stack_chk_guard");
if (g > 0) { puts_(" defused "); putn(g); puts_(" %gs stack-guard load(s)\n"); }
}
puts_(" loaded "); putn(span); puts_(" bytes at "); puthex((unsigned long long)mem);
puts_("\n");
entry = find_symbol(&k, "init_module");
if (!entry) { puts_(" no init_module -- nothing to call\n"); exit(); }
puts_("\n calling init_module\n\n");
{
int (*init)(void) = (int (*)(void))(void *)entry;
int r = init();
puts_("\n init_module returned "); putn(r);
puts_(" after "); putn(g_calls); puts_(" stub call(s)\n");
}
if (argc > 2 && g_pci_driver)
drive_probe(argv[2]);
else if (argc > 2)
puts_("\n no pci_driver was registered -- nothing to probe\n");
exit();
}