ldls: being the PCI core, with offsets nobody wrote down

docs/plan/ldls.md milestone L2, less the half that needs /dev/pci.

In Linux, __pci_register_driver ends at the PCI core, which matches a
driver's id_table against the bus and calls probe(). There is no PCI core
here, so LDLS keeps the pointer that call was given and plays the part.

To do that it must know where pci_driver.probe is, and it asks the kernel
rather than being told by us. ldls/btf.c reads /sys/kernel/btf/vmlinux --
122,279 types, every struct layout and every function prototype, as generated
data describing the kernel that is installed. Implemented from
Documentation/bpf/btf.rst, which is a published specification.

    $ ldls-types /sys/kernel/btf/vmlinux --need
      pci_driver   probe      offset    16  size 8
      pci_dev      resource   offset  1000  size 1088
      resource     start      offset     0  size 8
      all present

Cross-checked against pahole on the same file, field for field. No offset in
this is ours, which is rule 3's uncomfortable row disappearing rather than
being argued about.

And then the driver:

      driver name "8139too"
      devices it claims: 10ec:8139 ... and 16 more
      calling probe(pci_dev 10ec:8139, ...)
        1  alloc_etherdev_mqs(0x368, 1, 1)
        2  pci_enable_device / 3 pci_request_regions / 4 pci_set_master
        5  pci_iomap(pdev, 1, 0)
        6..544  ioread8 / iowrite8 -- 0x88, 0x8a, 0x8c, 0x8e, over and over
      545  dev_addr_mod / 547 netif_napi_add_weight_locked
      549  timer_init_key / 550 register_netdev / 551 netdev_info
      probe returned 0 after 552 stub call(s)

10ec:8139 is the real RTL8139 id. It mapped BAR 1, the one marked
IORESOURCE_MEM. Those 538 alternating byte accesses stepping 0x88/0x8a/0x8c/
0x8e are serial EEPROM clocking -- the driver bit-banging the chip for its MAC
address and handing the result to dev_addr_mod. Then NAPI, a watchdog timer,
register_netdev, its "found it" line, and zero.

Fifteen distinct symbols carry that entire probe, and three of them are 540 of
the 552 calls. L1 said 44 symbols carry 110 modules through init; L2 says 15
carry this one through probe. Neither is a number anyone would have guessed
and both came from running the thing.

Recorded as not done, because it is not: probe receives a pci_dev whose shape
is right and whose contents are invented. The BARs are made up, pci_iomap
returns a zeroed page, and 8139too read a MAC address of zeroes and was
perfectly happy about it. The "all through /dev/pci" half of L2 belongs to the
SchOS side. Also recorded: BTF has to come from somewhere, and SchOS has no
/sys/kernel/btf/vmlinux -- the user brings it with the module, from the kernel
the module was built for.

make ldls-check is 27 checks, four of them L2's, and it says so out loud when
it skips them on a machine with no BTF rather than passing silently.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VQ7Qx8E1q1M1bbCq8QLHS2
This commit is contained in:
Nexoniarz 2026-09-14 08:14:57 +02:00
commit 2d9a0ec6e3
8 changed files with 978 additions and 9 deletions

283
ldls/btf.c Normal file
View file

@ -0,0 +1,283 @@
// SPDX-License-Identifier: Apache-2.0
//
// Reading BTF. See ldls/btf.h.
//
// Implemented from Documentation/bpf/btf.rst. The format is small enough to
// hold in your head: a header, a run of variable-length type records, and a
// string table. The only fiddly part is that a record's length depends on its
// kind, so iterating means knowing how much trails each one -- which is the
// table in trailing_bytes() below and nothing more.
#include "btf.h"
static void
btf_fail(struct btf *b, const char *msg, const char *what)
{
int i = 0, j = 0;
while (msg[i] && i < (int)sizeof(b->err) - 1) { b->err[i] = msg[i]; i++; }
if (what)
while (what[j] && i < (int)sizeof(b->err) - 1) b->err[i++] = what[j++];
b->err[i] = 0;
}
static int
btf_streq(const char *a, const char *c)
{
while (*a && *a == *c) { a++; c++; }
return *a == 0 && *c == 0;
}
const char *btf_err(const struct btf *b) { return b->err[0] ? b->err : "no error"; }
// How many bytes follow a type record, given its kind and vlen.
static unsigned int
trailing_bytes(unsigned int info)
{
unsigned int kind = BTF_INFO_KIND(info);
unsigned int vlen = BTF_INFO_VLEN(info);
switch (kind) {
case BTF_KIND_INT: return 4;
case BTF_KIND_ARRAY: return 12; /* type, index_type, nelems */
case BTF_KIND_STRUCT:
case BTF_KIND_UNION: return vlen * 12; /* struct btf_member */
case BTF_KIND_ENUM: return vlen * 8;
case BTF_KIND_ENUM64: return vlen * 12;
case BTF_KIND_FUNC_PROTO: return vlen * 8; /* struct btf_param */
case BTF_KIND_VAR: return 4;
case BTF_KIND_DATASEC: return vlen * 12;
case BTF_KIND_DECL_TAG: return 4;
default: return 0; /* PTR, TYPEDEF, CONST, ... */
}
}
long
btf_count(const void *image, unsigned long long len)
{
const struct btf_header *h = (const struct btf_header *)image;
const unsigned char *types;
unsigned int at = 0;
long n = 0;
if (len < sizeof(struct btf_header))
return -1;
if (h->magic != BTF_MAGIC || h->version != 1)
return -1;
if ((unsigned long long)h->hdr_len + h->type_off + h->type_len > len)
return -1;
types = (const unsigned char *)image + h->hdr_len + h->type_off;
while (at + sizeof(struct btf_type) <= h->type_len) {
const struct btf_type *t = (const struct btf_type *)(types + at);
unsigned int step = sizeof(struct btf_type) + trailing_bytes(t->info);
if (step == 0 || at + step > h->type_len)
break;
at += step;
n++;
if (n > BTF_MAXTYPES)
return -1;
}
return n;
}
int
btf_open(struct btf *b, const void *image, unsigned long long len,
unsigned int *off, unsigned int noff)
{
const struct btf_header *h = (const struct btf_header *)image;
unsigned int at = 0, id = 1;
int i;
for (i = 0; i < (int)sizeof(b->err); i++)
b->err[i] = 0;
b->img = (const unsigned char *)image;
b->len = len;
b->off = off;
b->ntypes = 0;
if (len < sizeof(struct btf_header))
{ btf_fail(b, "too short to be BTF", 0); return -1; }
if (h->magic != BTF_MAGIC)
{ btf_fail(b, "not BTF: wrong magic", 0); return -1; }
if (h->version != 1)
{ btf_fail(b, "BTF version is not 1", 0); return -1; }
if ((unsigned long long)h->hdr_len + h->type_off + h->type_len > len ||
(unsigned long long)h->hdr_len + h->str_off + h->str_len > len)
{ btf_fail(b, "BTF sections run off the end of the file", 0); return -1; }
b->h = h;
b->types = b->img + h->hdr_len + h->type_off;
b->types_len = h->type_len;
b->str = (const char *)(b->img + h->hdr_len + h->str_off);
b->str_len = h->str_len;
// Index every type. Ids are assigned in order starting at 1; id 0 is void
// and is not stored.
while (at + sizeof(struct btf_type) <= b->types_len) {
const struct btf_type *t = (const struct btf_type *)(b->types + at);
unsigned int step = sizeof(struct btf_type) + trailing_bytes(t->info);
if (at + step > b->types_len)
break;
if (id >= noff)
{ btf_fail(b, "the caller's type index is too small", 0); return -1; }
off[id++] = at;
at += step;
}
b->ntypes = id - 1;
return 0;
}
static const struct btf_type *
btf_at(struct btf *b, int id)
{
if (id <= 0 || (unsigned int)id > b->ntypes)
return 0;
return (const struct btf_type *)(b->types + b->off[id]);
}
const char *
btf_name_of(struct btf *b, int id)
{
const struct btf_type *t = btf_at(b, id);
if (!t || t->name_off >= b->str_len)
return "";
return b->str + t->name_off;
}
int
btf_kind_of(struct btf *b, int id)
{
const struct btf_type *t = btf_at(b, id);
return t ? (int)BTF_INFO_KIND(t->info) : -1;
}
int
btf_find(struct btf *b, const char *name, int kind)
{
unsigned int id;
for (id = 1; id <= b->ntypes; id++) {
const struct btf_type *t = (const struct btf_type *)(b->types + b->off[id]);
if ((int)BTF_INFO_KIND(t->info) != kind)
continue;
if (t->name_off >= b->str_len)
continue;
if (btf_streq(b->str + t->name_off, name))
return (int)id;
}
return 0;
}
// Follow the things that wrap a type without changing its layout.
static int
btf_strip(struct btf *b, int id)
{
int guard = 0;
while (id > 0 && guard++ < 32) {
const struct btf_type *t = btf_at(b, id);
if (!t)
return 0;
switch (BTF_INFO_KIND(t->info)) {
case BTF_KIND_TYPEDEF:
case BTF_KIND_CONST:
case BTF_KIND_VOLATILE:
case BTF_KIND_RESTRICT:
case BTF_KIND_TYPE_TAG:
id = (int)t->u.type;
continue;
default:
return id;
}
}
return 0;
}
unsigned int
btf_type_size(struct btf *b, int id)
{
const struct btf_type *t;
id = btf_strip(b, id);
t = btf_at(b, id);
if (!t)
return 0;
switch (BTF_INFO_KIND(t->info)) {
case BTF_KIND_INT:
case BTF_KIND_STRUCT:
case BTF_KIND_UNION:
case BTF_KIND_ENUM:
case BTF_KIND_ENUM64:
case BTF_KIND_FLOAT:
return t->u.size;
case BTF_KIND_PTR:
return 8; // x86-64; this reader is built for one ABI
case BTF_KIND_ARRAY: {
const unsigned int *a = (const unsigned int *)(t + 1);
return btf_type_size(b, (int)a[0]) * a[2];
}
default:
return 0;
}
}
int
btf_member_off(struct btf *b, int struct_id, const char *member,
unsigned int *off_bytes, unsigned int *size_bytes)
{
const struct btf_type *t;
const struct btf_member *m;
unsigned int i, vlen, kflag;
struct_id = btf_strip(b, struct_id);
t = btf_at(b, struct_id);
if (!t)
{ btf_fail(b, "no such type", 0); return -1; }
if (BTF_INFO_KIND(t->info) != BTF_KIND_STRUCT &&
BTF_INFO_KIND(t->info) != BTF_KIND_UNION)
{ btf_fail(b, "not a struct or union", 0); return -1; }
vlen = BTF_INFO_VLEN(t->info);
kflag = BTF_INFO_KFLAG(t->info);
m = (const struct btf_member *)(t + 1);
for (i = 0; i < vlen; i++, m++) {
const char *nm = (m->name_off < b->str_len) ? b->str + m->name_off : "";
unsigned int bit;
// An unnamed member is an anonymous struct or union, and what looks like
// a field of the outer type is really a field of that. Recurse, adding
// its own offset -- without this, every kernel struct with an anonymous
// union in it reports its later fields as missing.
if (nm[0] == 0) {
unsigned int sub_off, sub_size;
unsigned int here = kflag ? (m->offset & 0xffffff) : m->offset;
if (btf_member_off(b, (int)m->type, member, &sub_off, &sub_size) == 0) {
*off_bytes = here / 8 + sub_off;
*size_bytes = sub_size;
return 0;
}
continue;
}
if (!btf_streq(nm, member))
continue;
if (kflag && (m->offset >> 24) != 0)
{ btf_fail(b, "that member is a bitfield: ", member); return -1; }
bit = kflag ? (m->offset & 0xffffff) : m->offset;
if (bit % 8)
{ btf_fail(b, "that member is not byte-aligned: ", member); return -1; }
*off_bytes = bit / 8;
*size_bytes = btf_type_size(b, (int)m->type);
return 0;
}
btf_fail(b, "no such member: ", member);
return -1;
}