Naming conventions¶
KernLens has two naming surfaces:
- the GDB command line, where commands must stay short and easy to
type (
kernlens-walk,kl-walk); - the Python API, where names are read in scripts, test failures, and tracebacks, so they must stay readable without becoming long.
The rules below describe both surfaces and are enforced by review, not by a linter.
GDB commands¶
- Canonical commands are
kernlens-<verb>; the suffix is one or two words:kernlens-slab,kernlens-kaslr-offset. - Every canonical command has a
kl-<verb>alias. The alias is the only spelling a user-facing surface shows:kl-help, the usage line, an error message, and the tutorial all name the commandkl-<verb>, andkernlens-<verb>is stated once as the canonical name. Thirty commands in two spellings is thirty pairs to remember; printing both everywhere would buy nothing but habits, so the help picks the one a user types. - Canonical names are what the registry holds and what a script should
call. They are the registered
gdb.Commandnames, so they survive a rename of the aliases. - Commands are not renamed when the Python API changes. The command layer is a stable user interface; the Python layer is the implementation.
- Commands live in
kernlens.commands(one module per domain) as plain functions in the pwndbg style: a function namedkernlens_<verb>decorated with@Command(parser, category, ...). The framework derives thekernlens-<verb>command name from the function name (underscores become dashes); the decorator records aCommandDefinitionandregister_all()instantiates thegdb.Commandwrappers, so no class is written by hand. - Every command binds an
argparse.ArgumentParser;-h/--helprender the parser (with examples, notes, and the alias list folded into the epilog). Domain functions annotate their options with the same names as the argparsedests. - Ported pwndbg commands keep pwndbg's option spellings -- both the
long names and the short ones (
-p/--progs,-p/--pcp-only,-b/--break,-r/--range) -- and pwndbg's metavars (<vaddr>,<paddr>), so a line copied from a pwndbg workflow keeps working. KernLens-only options are added in the same style, and options that pwndbg does not have (kl-base --rebase,kl-slab --detail) stay functional but are registered throughframework.hide()so--helpshows only the pwndbg surface.
Modules and packages¶
- Packages are short, lower-case nouns:
core,output,gdb,commands,inspectors. - Modules are
snake_caseand name one concern:bitops.py,codec.py,paging.py,palette.py,walker.py. - A package may group many small modules under a private-prefixed
shared base (
inspectors/_base.py); the underscore marks it as internal plumbing rather than a domain module. - A package's
__init__.pyre-exports its public surface; it may contain lazy import wiring but no domain logic. - Every module owns one implementation; there are no historical re-export modules at the package root.
Example files¶
Examples are not library modules: a reader meets one before knowing what
it is for, and the filename is the only description available in a
directory listing, a traceback or a git diff. So they carry one extra
piece of information -- what kind of thing the file is.
<subject>_<kind>.py, lower-case snake_case, no digits-only
subjects, no verbs standing in for kinds.
| Kind | The file | Its product |
|---|---|---|
_tour |
installs one command and prints a sequence of sections | a walkthrough of one subject |
_walk |
installs one command and follows a structure link by link | the chain it traversed |
_tracer |
installs breakpoints that never stop the debuggee | a log |
_bp |
installs a breakpoint whose purpose is to stop, or to decide | one trap |
_snapshot |
computes a report from constants, with no target | a table |
_sample |
the smallest thing that works | a starting point to copy |
| File | Subject | Kind |
|---|---|---|
api/address_tour.py |
page-table views of one address | _tour |
api/installed_api_tour.py |
the names gdbinit.py installs |
_tour |
api/kernel_facts_tour.py |
identity of the target | _tour |
api/freelist_walk.py |
the SLUB freelist encoding | _walk |
api/slab_snapshot.py |
a slab report from constants | _snapshot |
api/hello_sample.py |
the minimum viable script | _sample |
tracing/gdb_tracer.py |
kfree arguments |
_tracer |
tracing/cve_2025_38616_tracer.py |
one CVE's UAF chain | _tracer |
breakpoints/conditional_bp.py |
one object's frees | _bp |
breakpoints/per_cpu_bp.py |
per-CPU addressing | _bp |
Exempt: api/gdbinit.example is a dotfile fragment, not a script.
The kind is not decoration -- it is the first thing a reader needs and
the last thing a filename tends to say. trace.py named neither its
subject nor its kind; it is now installed_api_tour.py, and the rename
is the whole argument for the rule. gdb_tracer.py keeps its subject
(gdb) only because it is the generic tracer that other tracers are
modelled on; a file about a specific subsystem names the subsystem.
tools/check_examples.py --check enforces the pattern, so a new example
with an unclassified name fails the build rather than being discovered
later.
Import paths¶
from kernlens import ...is the convenient public surface for the pure-Python core and output layers.from kernlens.core.<module> import ...andfrom kernlens.output import ...are the canonical layer imports.- GDB code uses
from kernlens.gdb.facade import kdbg; component modules such askernlens.gdb.walkerare imported directly when only one adapter component is needed, and the kernel subsystem mixins live underkernlens.gdb.inspectors. - GDB commands live in
kernlens.commands;gdbinit.pyonly bootstraps them. - Do not add compatibility aliases or re-export modules; new code uses these canonical paths.
Python API¶
Classes¶
PascalCase, noun-first:PageTableWalker,KernelDebugger,MemoryReader,SlubInspector.- Kernel acronyms are written as words:
CpuInfo,Kaslr,SlubInspector. KAddrandKPageTableare established exceptions; do not add newK-prefixed classes.
Functions and methods¶
snake_case, verb-first for actions:read_*,write_*,print_*,walk,kvar_addr,decode_*.- Predicates start with
is_/has_:is_present,is_direct_map. - Formatters start with
fmt_or are short nouns inside a formatter namespace:fmt_line,IntFmt.addr,Styler.hex64. - Converters use
<source>_to_<target>:virt_to_phys,phys_to_pfn. - Readers use
read_*; a reader that returns a kernel address useskvar_addrrather than a secondread_*verb. - Avoid the
get_prefix on simple queries:counters(), notget_counters(). - One concept, one spelling: do not mix
format_andfmt_,typandint_type, oroffsetandoffinside the same layer.
Variables and parameters¶
snake_case; noun plus unit where it matters:kaslr_offset,page_size,byte_count.- Single letters are allowed only as loop indices (
i,j) or conventional math names (x,y). - Booleans read as assertions:
color_enabled,five_level,in_partial. - Private module state and helpers start with
_; constants areUPPER_SNAKE.
Abbreviations¶
These are part of the house style and may be used without expansion:
addr off len nr cur prev cfg ctx buf tmp ptr
val ret err pfn pte pgd pud pmd p4d va pa
phys virt kaslr percpu slub kvar freeptr fmt bt kv id
u8 u16 u32 u64 i8 i16 i32 i64 le be
Everything else is spelled out. In particular, do not add C-library
abbreviations such as clz, ctz, ffs, fls, rotl, rotr, or
bswap to the public API; the readable forms are leading_zeros,
trailing_zeros, first_set, last_set, rotate_left,
rotate_right, and byte_swap.
Length¶
- Target two words, tolerate three.
- A public name longer than ~20 characters needs a reason; prefer a
shorter noun (
cpu_slab_ptr, notcpu_slab_template_pointer). - Private helpers may be longer when the extra words prevent a bug, but the same abbreviation table applies.