kit

kit
git clone https://git.ryansepassi.com/git/kit.git
Log | Files | Refs | README

commit da3d9116be8b625fb3a0b18d5338c88a05e52d35
parent 9ce8b07f265cea0852745786b5e21c200a3db146
Author: Ryan Sepassi <rsepassi@gmail.com>
Date:   Sat,  9 May 2026 11:08:06 -0700

driver --help

Diffstat:
Mdriver/ar.c | 97+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++------
Mdriver/as.c | 34+++++++++++++++++++++++++++++++++-
Mdriver/cc.c | 112+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++----------
Mdriver/dbg.c | 76++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++----
Mdriver/emu.c | 50++++++++++++++++++++++++++++++++++++++++++++++----
Mdriver/ld.c | 80++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-------
Mdriver/main.c | 112++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++---
Mdriver/objdump.c | 59+++++++++++++++++++++++++++++++++++++++++++++++++++++++----
Mdriver/run.c | 75++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-------
9 files changed, 644 insertions(+), 51 deletions(-)

diff --git a/driver/ar.c b/driver/ar.c @@ -21,21 +21,85 @@ * Each member is opened with cfree_obj_open and its globally- * defined symbols (CFREE_SB_GLOBAL with section != NONE) are * indexed; non-object members contribute no symbols. + * v verbose. With t, list each member as "<size>\t<name>" instead + * of name-only. With x, print "x - <name>" per extracted member. + * With p, prepend a "<archive>(<name>):\n" header to every member + * (not just when there are 0 or >=2 filters). With r/c, print + * "a - <name>" for newly added members and "r - <name>" for + * replacements (r mode only). * * Reproducibility: SOURCE_DATE_EPOCH (when set to a positive integer) is * written to ar_date for every member on write. Long member names are * routed through a `//` extended-name table. * - * Not yet implemented: d (delete), q (quick append), v (verbose), and - * standalone `s` without r/c. Encountering any of those yields a usage - * error with exit code 2. */ + * Not yet implemented: d (delete), q (quick append), and standalone `s` + * without r/c. Encountering any of those yields a usage error with exit + * code 2. */ #define AR_TOOL "ar" static void ar_usage(void) { driver_errf(AR_TOOL, "%s", - "usage: cfree ar {rc|r|c|t|x|p} archive.a [members...]"); + "usage: cfree ar {rc|r|c|t|x|p}[s] archive.a [members...]\n" + " cfree ar --help for full operation reference"); +} + +void driver_help_ar(void) +{ + driver_printf("%s", + "cfree ar — POSIX `ar` archive front-end\n" + "\n" + "USAGE\n" + " cfree ar MODE archive.a [members...]\n" + "\n" + "MODE is a string of one or more letters; exactly one operation must\n" + "be selected and any number of modifiers may follow. The archive\n" + "path and (optional) member list follow the mode.\n" + "\n" + "OPERATIONS (mutually exclusive)\n" + " r Replace listed members in place; preserve unlisted; append\n" + " new ones. Warns when the archive must be created.\n" + " c Create / overwrite the archive with exactly the listed\n" + " members. Suppresses the create warning.\n" + " rc, cr `r` semantics with the create warning suppressed.\n" + " t List member names.\n" + " x Extract members to the current working directory. With no\n" + " member list, extract everything.\n" + " p Print members to stdout. With no list, print all members;\n" + " with two or more members each is preceded by a header.\n" + "\n" + "MODIFIERS\n" + " s Emit a System-V `/` symbol-index member at the head of\n" + " the archive. Valid only combined with r and/or c (rs, cs,\n" + " rcs, crs). Globally-defined symbols (CFREE_SB_GLOBAL with\n" + " a defined section) of each object member are indexed; non-\n" + " object members contribute no symbols.\n" + " v Verbose. With t list <size>\\t<name>; with x/r/c print one\n" + " line per affected member (\"x - name\", \"a - name\", or\n" + " \"r - name\" for replacements); with p force the per-member\n" + " \"archive(name):\" header even on a single match.\n" + "\n" + "REPRODUCIBILITY\n" + " When SOURCE_DATE_EPOCH is set to a positive integer, that value is\n" + " written to ar_date for every member on write. Long member names\n" + " are routed through a `//` extended-name table.\n" + "\n" + "NOT YET IMPLEMENTED\n" + " d (delete), q (quick append), and standalone `s` without r/c.\n" + " Encountering any of those yields a usage error with exit code 2.\n" + "\n" + "EXAMPLES\n" + " cfree ar rcs libfoo.a a.o b.o c.o\n" + " cfree ar t libfoo.a\n" + " cfree ar x libfoo.a a.o\n" + " cfree ar p libfoo.a a.o > a.o.copy\n" + "\n" + "GETTING HELP\n" + " -h, --help Show this help and exit\n" + "\n" + "EXIT CODES\n" + " 0 success 1 archive I/O error 2 bad usage\n"); } /* Parse SOURCE_DATE_EPOCH into a u64; 0 (or unset/invalid) means "no epoch". */ @@ -81,7 +145,7 @@ static int ar_open_for_read(DriverEnv* env, const char* path, return 1; } -static int ar_do_list(DriverEnv* env, const char* archive_path) +static int ar_do_list(DriverEnv* env, const char* archive_path, int verbose) { CfreeEnv cenv; CfreeFileData fd = {0}; @@ -98,8 +162,22 @@ static int ar_do_list(DriverEnv* env, const char* archive_path) return 1; } - rc = cfree_ar_list(&input, out); - if (rc) driver_errf(AR_TOOL, "failed to read archive: %s", archive_path); + if (verbose) { + CfreeArIter it; + CfreeArMember m; + if (!cfree_ar_iter_init(&it, &input)) { + driver_errf(AR_TOOL, "not an archive: %s", archive_path); + cfree_writer_close(out); + cenv.file_io->release(cenv.file_io->user, &fd); + return 1; + } + while (cfree_ar_iter_next(&it, &m)) + driver_printf("%zu\t%s\n", m.size, m.name); + rc = cfree_writer_error(out) ? 1 : 0; + } else { + rc = cfree_ar_list(&input, out); + if (rc) driver_errf(AR_TOOL, "failed to read archive: %s", archive_path); + } cfree_writer_close(out); cenv.file_io->release(cenv.file_io->user, &fd); return rc; @@ -477,6 +555,11 @@ int driver_ar(int argc, char** argv) int i; int rc; + if (argc < 2 || driver_argv_wants_help(argc, argv, 1)) { + driver_help_ar(); + return 0; + } + if (argc < 3) { ar_usage(); return 2; diff --git a/driver/as.c b/driver/as.c @@ -19,7 +19,34 @@ typedef struct AsOptions { static void as_usage(void) { driver_errf(AS_TOOL, "%s", - "usage: cfree as [-g] [-target TRIPLE] -o out.o input.s"); + "usage: cfree as [-g] [-target TRIPLE] -o out.o input.s\n" + " cfree as --help for full option reference"); +} + +void driver_help_as(void) +{ + driver_printf("%s", + "cfree as — standalone assembler\n" + "\n" + "USAGE\n" + " cfree as [options] -o OUT.o INPUT.s\n" + "\n" + "DESCRIPTION\n" + " Reads a single text source written in a GAS subset (AT&T syntax on\n" + " x86, standard mnemonics on aarch64/riscv64) and writes a relocatable\n" + " object via the same back-end the C compiler emits to. Exactly one\n" + " positional input is required; -o is required.\n" + "\n" + "OPTIONS\n" + " -o PATH Output object path (required)\n" + " -g Emit DWARF debug info from .file/.loc directives\n" + " -target TRIPLE Cross-assemble target. See `cfree cc --help` for the\n" + " full list of recognized arches and OSes. Default:\n" + " the host target.\n" + " -h, --help Show this help and exit\n" + "\n" + "EXIT CODES\n" + " 0 success 1 assemble error 2 bad usage\n"); } /* Returns 0 on success; non-zero on bad args (already reported). */ @@ -87,6 +114,11 @@ int driver_as(int argc, char** argv) int rc = 1; int loaded = 0; + if (argc < 2 || driver_argv_wants_help(argc, argv, 1)) { + driver_help_as(); + return 0; + } + driver_env_init(&env); if (as_parse(argc, argv, &o) != 0) { diff --git a/driver/cc.c b/driver/cc.c @@ -123,19 +123,100 @@ typedef struct CcOptions { static void cc_usage(void) { driver_errf(CC_TOOL, "%s", - "usage: cfree cc [-c|-E|--dump-tokens] [-o out] [-O0|-O1|-O2] [-g]\n" - " [-I dir]... [-isystem dir]...\n" - " [-D name[=body]]... [-U name]...\n" - " [-M|-MM|-MD|-MMD] [-MF file] [-MT target]... [-MP]\n" - " [-target TRIPLE] [-fPIC|-fPIE|-fpic|-fpie]\n" - " [-mcmodel=small|medium|large]\n" - " [-Werror] [-fmax-errors=N]\n" - " [--build-id=none|sha256|uuid|0xHEX]\n" - " [-ffile-prefix-map=old=new]\n" - " [-e symbol] [-T script]\n" - " [-static|-pie|-no-pie]\n" - " [-l name]... [-L dir]...\n" - " [-x c] input.c... [-] [input.o]... [input.a]..."); + "usage: cfree cc [-c|-E|--dump-tokens|-shared] [-o out] [options...] inputs...\n" + " cfree cc --help for full option reference"); +} + +void driver_help_cc(void) +{ + driver_printf("%s", + "cfree cc — C compiler driver\n" + "\n" + "USAGE\n" + " cfree cc [options] inputs... compile and link to exe\n" + " cfree cc -c [options] input.c compile one source to .o\n" + " cfree cc -E [options] input.c preprocess to -o\n" + " cfree cc --dump-tokens [options] input.c token dump to -o\n" + " cfree cc -shared [options] inputs... link a shared library\n" + " cfree cc -M|-MM [options] input.c print header deps; no compile\n" + "\n" + "DESCRIPTION\n" + " Compiles C11 sources and links them with .o/.a inputs. Inputs are\n" + " classified by suffix:\n" + " .c .cc .cpp C source\n" + " .o .obj object file (link-time input)\n" + " .a static archive (link-time input)\n" + " - read C source from stdin (single source only)\n" + " With -c the driver requires exactly one source and emits a relocatable\n" + " object. Without -c every C source is compiled and linked together with\n" + " any .o / .a / -l inputs.\n" + "\n" + "OUTPUT MODE\n" + " -c Compile to a relocatable object (one source per call)\n" + " -E Run the preprocessor only; write to -o\n" + " --dump-tokens Dump the token stream to -o\n" + " -shared Link a position-independent shared library\n" + " (default) Compile + link to an executable\n" + "\n" + "OUTPUT\n" + " -o PATH Output path (required except for -M / -MM)\n" + "\n" + "CODEGEN\n" + " -O0 -O1 -O2 Optimization level (default -O0)\n" + " -g Emit DWARF debug info\n" + " -fPIC -fpic Position-independent code\n" + " -fPIE -fpie Position-independent executable\n" + " -static Non-PIC, non-PIE\n" + " -pie Position-independent executable\n" + " -no-pie Disable PIE\n" + " -mcmodel=MODEL small | medium | large\n" + " -target TRIPLE Cross-compile target. Recognized arches: x86_64,\n" + " i386/i486/i586/i686, aarch64/arm64, arm/armv7,\n" + " riscv64, riscv32, wasm32, wasm64.\n" + " Recognized OSes: linux, darwin/macos, windows/win32,\n" + " wasi, none/freestanding.\n" + "\n" + "PREPROCESSOR\n" + " -I DIR Add quoted-include search path\n" + " -isystem DIR Add system-include search path\n" + " -D NAME[=BODY] Define a macro (BODY defaults to 1)\n" + " -U NAME Undefine a builtin/predefined macro\n" + "\n" + "DEPENDENCY EMISSION (subset of GCC -M family)\n" + " -M Print all header deps to stdout; do not compile\n" + " -MM Like -M, but skip <bracketed> system headers\n" + " -MD Compile and write all deps to a side file\n" + " -MMD Like -MD, without system headers\n" + " -MF FILE Write deps to FILE (default <out>.d for MD/MMD)\n" + " -MT TARGET Set the make-target name (repeatable)\n" + " -MQ TARGET Like -MT (with quoting)\n" + " -MP Emit phony rules for each prerequisite\n" + "\n" + "DIAGNOSTICS\n" + " -Werror Treat warnings as errors\n" + " -fmax-errors=N Stop after N errors (0 = unlimited)\n" + "\n" + "LINKER (also applies to -shared)\n" + " -e SYMBOL Set entry symbol\n" + " -T SCRIPT.ld Use a linker script\n" + " -L DIR Add library search path\n" + " -l NAME Link against lib<NAME>.a (resolved via -L)\n" + " -Wl,TOK[,TOK...] Pass tokens to the linker. Recognized tokens:\n" + " -soname=NAME, -rpath=DIR,\n" + " --enable-new-dtags / --disable-new-dtags\n" + "\n" + "REPRODUCIBILITY\n" + " --build-id=MODE none | sha256 | uuid | 0xHEX\n" + " -ffile-prefix-map=OLD=NEW\n" + " Rewrite OLD path prefix as NEW in debug info\n" + " SOURCE_DATE_EPOCH=N (env) embed N as build epoch\n" + "\n" + "OTHER\n" + " -x c No-op (only C is accepted)\n" + " -h, --help Show this help and exit\n" + "\n" + "EXIT CODES\n" + " 0 success 1 compile/link error 2 bad usage\n"); } static int cc_alloc_arrays(CcOptions* o, int argc) @@ -1299,6 +1380,11 @@ int driver_cc(int argc, char** argv) CfreePpOptions pp; int rc; + if (argc < 2 || driver_argv_wants_help(argc, argv, 1)) { + driver_help_cc(); + return 0; + } + driver_env_init(&env); co.env = &env; diff --git a/driver/dbg.c b/driver/dbg.c @@ -57,10 +57,73 @@ typedef struct DbgOpts { static void dbg_usage(void) { driver_errf(DBG_TOOL, "%s", - "usage: cfree dbg [-O0|-O1|-O2] [-g] [-e entry]\n" - " [-I dir]... [-isystem dir]...\n" - " [-D name[=body]]... [-U name]...\n" - " input.c... [-- arg...]"); + "usage: cfree dbg [options] input.c... [-- arg...]\n" + " cfree dbg --help for full option reference"); +} + +void driver_help_dbg(void) +{ + driver_printf("%s", + "cfree dbg — interactive JIT debugger\n" + "\n" + "USAGE\n" + " cfree dbg [options] input.c ... [-- prog-arg ...]\n" + "\n" + "DESCRIPTION\n" + " Mirrors `cfree run` for compile flags and argv shape, but instead\n" + " of calling the entry directly drops into a REPL that drives the\n" + " JIT session: breakpoints, single-step (insn / source line / over),\n" + " finish, backtrace, registers, locals/args, variable read/write,\n" + " and raw memory examine. -g is forced on so source lines and\n" + " variable locations are available at runtime.\n" + "\n" + " Anything after `--` is passed to the JITed program as argv.\n" + "\n" + "COMPILE OPTIONS\n" + " -O0 -O1 -O2 Optimization level (default -O0)\n" + " -g Emit DWARF (forced on)\n" + " -e SYMBOL Entry symbol (default `main`)\n" + " -I DIR Add quoted-include search path\n" + " -isystem DIR Add system-include search path\n" + " -D NAME[=BODY] Define a macro\n" + " -U NAME Undefine a builtin/predefined macro\n" + "\n" + "REPL COMMANDS (also shown by `h` at the prompt)\n" + " h, help show REPL help\n" + " q, quit exit (Ctrl-D also works)\n" + " r, run start fresh execution at entry\n" + " c, cont continue after a stop\n" + " s, step single-step one instruction\n" + " sl step to next source line (into calls)\n" + " n, next step to next source line (over calls)\n" + " finish run until current frame returns\n" + " jump ADDR set PC to ADDR (no resume)\n" + " bt, backtrace print stack trace with arguments\n" + " b LOC set breakpoint at LOC:\n" + " 0xADDR | sym[+off] | file.c:line\n" + " ignore N COUNT skip the next COUNT hits of bp N\n" + " d N, delete N delete breakpoint N\n" + " enable N | disable N toggle breakpoint N\n" + " p NAME print variable / global\n" + " set NAME VALUE write VALUE into NAME\n" + " x ADDR [count] examine memory (default 16 bytes)\n" + " info b list breakpoints\n" + " info reg, info registers dump registers\n" + " info locals | info args list locals / args at current PC\n" + " info functions [PATTERN] list JIT functions matching PATTERN\n" + " info variables [PATTERN] list JIT globals matching PATTERN\n" + "\n" + "SIGNALS\n" + " Ctrl-C is forwarded into the running session as an interrupt; at\n" + " the REPL prompt it terminates the program normally.\n" + "\n" + "GETTING HELP\n" + " -h, --help Show this help and exit (this is the\n" + " command-line help; once the REPL is\n" + " running, type `h` for REPL commands)\n" + "\n" + "EXIT CODES\n" + " 0 clean exit 1 compile/link or session error 2 bad usage\n"); } static int dbg_alloc_arrays(DbgOpts* o, int argc) @@ -1742,6 +1805,11 @@ int driver_dbg(int argc, char** argv) CfreeTarget target; int rc; + if (argc < 2 || driver_argv_wants_help(argc, argv, 1)) { + driver_help_dbg(); + return 0; + } + driver_env_init(&env); o.env = &env; diff --git a/driver/emu.c b/driver/emu.c @@ -38,10 +38,47 @@ typedef struct EmuOptions { static void emu_usage(void) { driver_errf(EMU_TOOL, "%s", - "usage: cfree emu [-O0|-O1|-O2]\n" - " [-arch aarch64|riscv64]\n" - " [-tracepc] [-traceinsn] [-traceblock]\n" - " guest.elf [-- arg...]"); + "usage: cfree emu [options] guest.elf [-- guest-arg...]\n" + " cfree emu --help for full option reference"); +} + +void driver_help_emu(void) +{ + driver_printf("%s", + "cfree emu — run a guest user-mode ELF on the host\n" + "\n" + "USAGE\n" + " cfree emu [options] guest.elf [-- guest-arg...]\n" + "\n" + "DESCRIPTION\n" + " Loads a static guest user-mode ELF and runs it on the host via the\n" + " per-basic-block JIT translator. v1 supports two guest architectures:\n" + " aarch64 and riscv64; the host code generated by the translator runs\n" + " natively on the host arch.\n" + "\n" + " The driver returns the guest's exit code on a clean exit, or 1 on\n" + " internal failure. Argv shape mirrors `cfree run`: anything after\n" + " `--` is forwarded as the guest argv. With no `--`, argv[0] defaults\n" + " to the guest ELF path.\n" + "\n" + "OPTIONS\n" + " -O0 -O1 -O2 Translator optimization level (default -O0)\n" + " -arch ARCH Force guest arch: aarch64 (alias arm64) or\n" + " riscv64 (alias rv64). When omitted the arch is\n" + " auto-detected from the ELF.\n" + " -tracepc Trace each translated PC\n" + " -traceinsn Trace each guest instruction\n" + " -traceblock Trace each translated basic block\n" + " -h, --help Show this help and exit\n" + "\n" + "EXAMPLES\n" + " cfree emu hello-arm64.elf\n" + " cfree emu -arch riscv64 hello-rv64.elf -- foo bar\n" + " cfree emu -O2 -tracepc prog.elf\n" + "\n" + "EXIT CODES\n" + " Returns the guest's exit code on clean exit, or 1 on internal\n" + " failure. 2 on bad command-line usage.\n"); } static int emu_alloc_arrays(EmuOptions* o, int argc) @@ -198,6 +235,11 @@ int driver_emu(int argc, char** argv) int exit_code = 0; int rc = 1; + if (argc < 2 || driver_argv_wants_help(argc, argv, 1)) { + driver_help_emu(); + return 0; + } + driver_env_init(&env); eo.env = &env; diff --git a/driver/ld.c b/driver/ld.c @@ -91,13 +91,74 @@ typedef struct LdOptions { static void ld_usage(void) { driver_errf(LD_TOOL, "%s", - "usage: cfree ld -o out [-e entry] [-T script.ld]\n" - " [-L dir]... [-l name]...\n" - " [-static|-pie|-no-pie]\n" - " [--whole-archive|--no-whole-archive]\n" - " [--start-group ... --end-group]\n" - " [--build-id={none|sha256|uuid|0xHEX}]\n" - " input.o|input.a..."); + "usage: cfree ld -o out [options...] inputs.o|inputs.a...\n" + " cfree ld --help for full option reference"); +} + +void driver_help_ld(void) +{ + driver_printf("%s", + "cfree ld — link objects/archives into an executable or shared library\n" + "\n" + "USAGE\n" + " cfree ld -o OUT [options] inputs.o ... inputs.a ...\n" + " cfree ld -shared -o libfoo.so [options] inputs.o ...\n" + "\n" + "DESCRIPTION\n" + " Loads each input via host file I/O, optionally parses a -T linker\n" + " script, and emits an executable (default) or shared library\n" + " (-shared). The full link surface is exposed: per-archive flags,\n" + " cyclic-resolution groups, build-id, soname/rpath/exports.\n" + "\n" + "OUTPUT\n" + " -o PATH Output path (required, exactly one)\n" + " -shared Emit a position-independent shared library /\n" + " dylib instead of an executable\n" + "\n" + "ENTRY / SCRIPT\n" + " -e SYMBOL Entry symbol\n" + " -T SCRIPT.ld Use a linker script (parsed, not raw)\n" + "\n" + "TARGET\n" + " -static Non-PIC, non-PIE\n" + " -pie Position-independent executable\n" + " -no-pie Disable PIE\n" + " (target is otherwise auto-detected from the first object input)\n" + "\n" + "LIBRARY RESOLUTION\n" + " -L DIR Add library search path\n" + " -l NAME Resolve to lib<NAME>.a via -L\n" + "\n" + "POSITIONAL ARCHIVE STATE (apply to following .a inputs)\n" + " --whole-archive Pull every member of following archives\n" + " --no-whole-archive Reset (default behaviour)\n" + " -Bstatic Force static link mode\n" + " -Bdynamic Force dynamic link mode\n" + " --as-needed Link only if a real reference exists\n" + " --no-as-needed Reset to dynamic\n" + " --start-group ...\n" + " --end-group Cyclic-resolution archive group\n" + "\n" + "SHARED-LIBRARY OPTIONS (require -shared)\n" + " -soname NAME DT_SONAME / LC_ID_DYLIB\n" + " -soname=NAME equivalent attached form\n" + " -rpath DIR DT_RPATH/DT_RUNPATH entry (repeatable)\n" + " -rpath=DIR attached form\n" + " -rpath-link DIR Link-time-only search path (advisory)\n" + " --enable-new-dtags rpaths land in DT_RUNPATH (default)\n" + " --disable-new-dtags rpaths land in DT_RPATH\n" + " -E, --export-dynamic Promote defined globals into dynsym\n" + " (no-op for -shared; recorded for exe)\n" + "\n" + "BUILD ID\n" + " --build-id Same as --build-id=sha256\n" + " --build-id=MODE none | sha256 | uuid | 0xHEX\n" + "\n" + "GETTING HELP\n" + " -h, --help Show this help and exit\n" + "\n" + "EXIT CODES\n" + " 0 success 1 link error 2 bad usage\n"); } /* ---------- argv-sized scratch arrays ---------- */ @@ -631,6 +692,11 @@ int driver_ld(int argc, char** argv) LdOptions lo = {0}; int rc; + if (argc < 2 || driver_argv_wants_help(argc, argv, 1)) { + driver_help_ld(); + return 0; + } + driver_env_init(&env); lo.env = &env; diff --git a/driver/main.c b/driver/main.c @@ -2,7 +2,22 @@ /* Multi-call dispatch. Looks at argv[0]'s basename for "cc", "as", "ld", * "ar", "objdump", "dbg", "run", or "emu"; if argv[0] is the bare "cfree" - * binary, falls back to argv[1]. Preprocessor-only mode is `cc -E`. */ + * binary, falls back to argv[1]. Preprocessor-only mode is `cc -E`. + * + * Help routing is layered on top of dispatch: + * + * - `cfree` → driver_help_top() + * - `cfree -h | --help | -help` + * → driver_help_top() + * - `cfree help` → driver_help_top() + * - `cfree help <tool>` → driver_help_<tool>() + * - any tool entry sees no args, --help, or -h (objdump excepted) + * → driver_help_<tool>() and return 0. + * + * Each tool gates its own entry on argc < 2 / driver_argv_wants_help so + * the same handling applies regardless of whether the user invoked the + * multi-call form (`cfree cc --help`) or the symlinked form (`cc --help`). + */ static int dispatch(const char* name, int argc, char** argv) { @@ -17,6 +32,72 @@ static int dispatch(const char* name, int argc, char** argv) return -1; } +/* If `name` matches a tool, print its help and return 0. Otherwise return + * non-zero (caller reports the error). */ +static int print_tool_help(const char* name) +{ + if (driver_streq(name, "cc")) { driver_help_cc(); return 0; } + if (driver_streq(name, "as")) { driver_help_as(); return 0; } + if (driver_streq(name, "ld")) { driver_help_ld(); return 0; } + if (driver_streq(name, "ar")) { driver_help_ar(); return 0; } + if (driver_streq(name, "objdump")) { driver_help_objdump(); return 0; } + if (driver_streq(name, "dbg")) { driver_help_dbg(); return 0; } + if (driver_streq(name, "run")) { driver_help_run(); return 0; } + if (driver_streq(name, "emu")) { driver_help_emu(); return 0; } + return -1; +} + +int driver_is_help_flag(const char* arg) +{ + if (!arg) return 0; + return driver_streq(arg, "--help") || driver_streq(arg, "-help"); +} + +int driver_argv_wants_help(int argc, char** argv, int accept_short_h) +{ + int i; + for (i = 1; i < argc; ++i) { + /* `--` ends the driver's own option scan: anything after it is + * forwarded to the JITed program (run/dbg) or the guest (emu), + * so a user's own `--help` to their program must not be hijacked. */ + if (driver_streq(argv[i], "--")) break; + if (driver_is_help_flag(argv[i])) return 1; + if (accept_short_h && driver_streq(argv[i], "-h")) return 1; + } + return 0; +} + +void driver_help_top(void) +{ + driver_printf("%s", + "cfree — freestanding C compiler toolchain\n" + "\n" + "USAGE\n" + " cfree <tool> [args...] multi-call dispatch by name\n" + " <tool> [args...] when invoked via the tool's symlink\n" + " cfree help [<tool>] this help, or per-tool help\n" + "\n" + "TOOLS\n" + " cc Compile (and link) C sources, with cpp / dep-emit / -shared modes\n" + " as Assemble a GAS-subset text source into a relocatable object\n" + " ld Link objects/archives into an executable or shared library\n" + " ar Create / modify / list / extract POSIX `ar` archives\n" + " objdump Dump sections, symbols, disassembly, hex, and relocations\n" + " run JIT-compile inputs and invoke the entry symbol in-process\n" + " dbg Interactive JIT debugger (REPL on top of the JIT image)\n" + " emu Run a guest user-mode ELF (aarch64/riscv64) on the host\n" + "\n" + "GETTING HELP\n" + " cfree <tool> --help full per-tool help (also -h, except objdump)\n" + " cfree <tool> same as --help (no args ⇒ show help)\n" + " cfree help <tool> same as `cfree <tool> --help`\n" + "\n" + "EXIT CODES\n" + " 0 success\n" + " 1 tool-reported error (compile, link, I/O, ...)\n" + " 2 bad command-line usage\n"); +} + int driver_main(int argc, char** argv) { const char* name; @@ -24,12 +105,35 @@ int driver_main(int argc, char** argv) if (argc < 1) return 2; name = driver_basename(argv[0]); + + /* Multi-call form first: argv[0] is the tool name (e.g. installed as + * a `cc` symlink). Help inside the tool is gated by the tool itself. */ rc = dispatch(name, argc, argv); if (rc != -1) return rc; - /* `cfree <tool> ...` form. */ - if (argc < 2) return 2; - return dispatch(argv[1], argc - 1, argv + 1); + /* From here on argv[0] was the bare "cfree" binary. Apply the + * top-level help / `help <tool>` routing before delegating. */ + if (argc < 2) { driver_help_top(); return 0; } + + if (driver_is_help_flag(argv[1]) || + driver_streq(argv[1], "-h")) { + driver_help_top(); + return 0; + } + + if (driver_streq(argv[1], "help")) { + if (argc < 3) { driver_help_top(); return 0; } + if (print_tool_help(argv[2]) == 0) return 0; + driver_errf("cfree", "no such tool: %s", argv[2]); + return 2; + } + + rc = dispatch(argv[1], argc - 1, argv + 1); + if (rc != -1) return rc; + + driver_errf("cfree", "no such tool: %s", argv[1]); + driver_help_top(); + return 2; } int main(int argc, char** argv) diff --git a/driver/objdump.c b/driver/objdump.c @@ -23,7 +23,56 @@ typedef struct ObjdumpOpts { static void objdump_usage(void) { driver_errf(OBJDUMP_TOOL, "%s", - "usage: cfree objdump [-h] [-t] [-d] [-D] [-r] [-s] [-j NAME ...] input..."); + "usage: cfree objdump [-h] [-t] [-d] [-D] [-r] [-s] [-j NAME ...] input...\n" + " cfree objdump --help for full option reference"); +} + +void driver_help_objdump(void) +{ + driver_printf("%s", + "cfree objdump — print info about object files and archives\n" + "\n" + "USAGE\n" + " cfree objdump [options] input ...\n" + "\n" + "DESCRIPTION\n" + " Prints section/symbol info, disassembly, hex contents, and\n" + " relocations for object files (ELF / COFF / Mach-O / Wasm) and `ar`\n" + " archives. Archives are auto-detected by magic; each member is\n" + " dumped in turn with an `archive(member)` label.\n" + "\n" + " With no operation flags the default is `-h -t` (section headers +\n" + " symbol table), matching GNU objdump's default-ish behaviour.\n" + "\n" + "OPERATIONS (any combination)\n" + " -h Print section headers (idx, name, size, align,\n" + " flags). NOTE: this is the GNU objdump meaning of\n" + " -h — it does NOT print this help; use --help.\n" + " -t Print the symbol table\n" + " -d Disassemble executable sections\n" + " -D Disassemble all sections\n" + " -r Print relocation records\n" + " -s Print section contents as a hex+ASCII dump\n" + "\n" + "FILTERS\n" + " -j NAME Restrict output to the named section. Repeatable;\n" + " affects -h / -t / -d / -D / -r / -s.\n" + "\n" + "SUPPORTED INPUTS\n" + " *.o / *.obj Single object file (ELF / COFF / Mach-O / Wasm)\n" + " *.a POSIX `ar` archive — every object member is dumped\n" + " in turn with an `<archive>(<member>)` label\n" + "\n" + "GETTING HELP\n" + " --help Show this help and exit (-h is `section headers`)\n" + "\n" + "EXAMPLES\n" + " cfree objdump -d a.o\n" + " cfree objdump -h -j .text -j .rodata a.o\n" + " cfree objdump -t libfoo.a\n" + "\n" + "EXIT CODES\n" + " 0 success 1 parse / I/O error 2 bad usage\n"); } static const char* fmt_str(CfreeObjFmt fmt, uint8_t ptr_size) @@ -376,9 +425,11 @@ int driver_objdump(int argc, char** argv) CfreeCompiler* dc = NULL; CfreeEnv cenv; - if (argc < 2) { - objdump_usage(); - return 2; + /* For objdump, -h means "section headers" (GNU objdump convention), + * so we only honour --help / -help (and bare invocation) as help. */ + if (argc < 2 || driver_argv_wants_help(argc, argv, 0)) { + driver_help_objdump(); + return 0; } driver_env_init(&env); diff --git a/driver/run.c b/driver/run.c @@ -48,13 +48,69 @@ typedef struct RunOptions { static void run_usage(void) { driver_errf(RUN_TOOL, "%s", - "usage: cfree run [-O0|-O1|-O2] [-g] [-e entry]\n" - " [-I dir]... [-isystem dir]...\n" - " [-D name[=body]]... [-U name]...\n" - " [-target TRIPLE] [-fPIC|-fPIE|-fpic|-fpie]\n" - " [-mcmodel=small|medium|large]\n" - " [-Werror] [-fmax-errors=N]\n" - " input.c... [-] [input.o]... [input.a]... [-- arg...]"); + "usage: cfree run [options] inputs... [-- prog-arg...]\n" + " cfree run --help for full option reference"); +} + +void driver_help_run(void) +{ + driver_printf("%s", + "cfree run — JIT-compile inputs and invoke the entry symbol in-process\n" + "\n" + "USAGE\n" + " cfree run [options] inputs... [-- prog-arg...]\n" + "\n" + "DESCRIPTION\n" + " Compiles and JIT-links every input, looks up the entry symbol\n" + " (default `main`), and calls it as `int(*)(int, char**)`. Args\n" + " after `--` are passed to the JITed program as argv. The driver\n" + " returns the entry's exit code, or 1 on a compile/link/lookup\n" + " error.\n" + "\n" + " Inputs are classified by suffix:\n" + " .c .cc .cpp C source\n" + " .o .obj object file\n" + " .a static archive\n" + " - read C source from stdin (single source only)\n" + "\n" + " JITed code may call any host symbol resolvable via dlsym(RTLD_DEFAULT)\n" + " — typically libc. The JIT path forces PIC; -fPIC / -fPIE / -mcmodel\n" + " are accepted but have no observable effect.\n" + "\n" + "COMPILE OPTIONS\n" + " -O0 -O1 -O2 Optimization level (default -O0)\n" + " -g Emit DWARF debug info\n" + " -e SYMBOL Entry symbol (default `main`)\n" + " -target TRIPLE Cross-compile target (see `cfree cc --help`)\n" + " -fPIC -fpic Position-independent code (no-op for the JIT)\n" + " -fPIE -fpie Position-independent executable (no-op for the JIT)\n" + " -mcmodel=MODEL small | medium | large (no-op for the JIT)\n" + " -Werror Treat warnings as errors\n" + " -fmax-errors=N Stop after N errors (0 = unlimited)\n" + "\n" + "PREPROCESSOR\n" + " -I DIR Add quoted-include search path\n" + " -isystem DIR Add system-include search path\n" + " -D NAME[=BODY] Define a macro\n" + " -U NAME Undefine a builtin/predefined macro\n" + "\n" + "ARGV PASSTHROUGH\n" + " -- End of `cfree run` options. Tokens after `--` are\n" + " passed verbatim to the JITed program's main(argc,\n" + " argv). Without `--` the program receives an empty\n" + " argv.\n" + "\n" + "GETTING HELP\n" + " -h, --help Show this help and exit\n" + "\n" + "EXAMPLES\n" + " cfree run hello.c\n" + " cfree run -O2 -DNDEBUG main.c util.c\n" + " cfree run main.c -- arg1 arg2\n" + "\n" + "EXIT CODES\n" + " Returns the exit code of the JITed entry, or 1 on internal failure.\n" + " 2 on bad command-line usage.\n"); } static int run_alloc_arrays(RunOptions* o, int argc) @@ -377,6 +433,11 @@ int driver_run(int argc, char** argv) MainFn entry_fn; int rc; + if (argc < 2 || driver_argv_wants_help(argc, argv, 1)) { + driver_help_run(); + return 0; + } + driver_env_init(&env); ro.env = &env;