commit da3d9116be8b625fb3a0b18d5338c88a05e52d35
parent 9ce8b07f265cea0852745786b5e21c200a3db146
Author: Ryan Sepassi <rsepassi@gmail.com>
Date: Sat, 9 May 2026 11:08:06 -0700
driver --help
Diffstat:
| M | driver/ar.c | | | 97 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++------ |
| M | driver/as.c | | | 34 | +++++++++++++++++++++++++++++++++- |
| M | driver/cc.c | | | 112 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++---------- |
| M | driver/dbg.c | | | 76 | ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++---- |
| M | driver/emu.c | | | 50 | ++++++++++++++++++++++++++++++++++++++++++++++---- |
| M | driver/ld.c | | | 80 | ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++------- |
| M | driver/main.c | | | 112 | ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--- |
| M | driver/objdump.c | | | 59 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++---- |
| M | driver/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;