From 4e76ff3a2443135d134b84286b70a6dd4dbef6b7 Mon Sep 17 00:00:00 2001 From: Lephenixnoir Date: Sun, 6 Mar 2022 16:15:13 +0000 Subject: [PATCH] add command help messages --- shell/a.cpp | 61 ++++++++++++++++++++++++++-------- shell/d.cpp | 46 +++++++++++++++++++------- shell/e.cpp | 42 ++++++++++++++++------- shell/g.cpp | 14 ++++---- shell/h.cpp | 15 +++++---- shell/i.cpp | 36 +++++++++++++------- shell/lexer.l | 1 + shell/main.cpp | 86 ++++++++++++++++++++++++++++++++---------------- shell/parser.cpp | 2 +- shell/s.cpp | 38 ++++++++++++++------- shell/shell.h | 13 ++++++-- shell/v.cpp | 77 ++++++++++++++++++++++++++++++------------- 12 files changed, 303 insertions(+), 128 deletions(-) diff --git a/shell/a.cpp b/shell/a.cpp index 861ba35..295a660 100644 --- a/shell/a.cpp +++ b/shell/a.cpp @@ -213,18 +213,51 @@ void _af4(Session &session, uint32_t value, std::vector ®ions) _afh(session, (char *)&value_big_endian, pattern, 4, 4, -1, regions); } -[[gnu::constructor]] static void _(void) -{ - shell_register_command("af4", - [](Session &s, Parser &p){ - auto args = parse_af4(s, p); - _af4(s, args.value, args.regions); }, - [](Session &s, Parser &p){ parse_af4(s, p); }); +static ShellCommand _af4_cmd("af4", + [](Session &s, Parser &p){ + auto args = parse_af4(s, p); + _af4(s, args.value, args.regions); }, + [](Session &s, Parser &p){ parse_af4(s, p); }, + "Analysis Find 4-aligned value", R"( +af4 [...] - shell_register_command("afh", - [](Session &s, Parser &p) { - auto args = parse_afh(s, p); - _afh(s, args.reference.c_str(), args.pattern.c_str(), args.size, - args.align, args.distance, args.regions); }, - [](Session &s, Parser &p){ parse_afh(s, p); }); -} +Searches mapped memory for a 4-aligned 32-bit value. If regions are specified +(by name or the : syntax), searches these regions. Otherwise, +searches every binding in the current virtual space. + +af4 0xb4000000 ROM 0x88000000:512 + Searches occurrences of 0xb4000000, 4-aligned, within mapped ROM and within + the first 512 bytes of RAM. + +af4 0x43415349 + Seacrhes for the 4-aligned string "CASI" in all currently bound regions. +)"); + +static ShellCommand _afh_cmd("afh", + [](Session &s, Parser &p) { + auto args = parse_afh(s, p); + _afh(s, args.reference.c_str(), args.pattern.c_str(), args.size, + args.align, args.distance, args.regions); }, + [](Session &s, Parser &p){ parse_afh(s, p); }, + "Analysis Find Hexadecimal pattern", R"( +afh [align=] "" [...] + +Searches mapped memory for a hexadecimal pattern with hole bytes. The pattern +should be a hexadecimal string like "0123..6789..cd", consisting either of +explicit hexadecimal values or hole bytes marked by "..". The holes must start +and end at byte boundaries. + +An occurrence of the pattern is any sequence of bytes that matches the explicit +bytes exactly, with no constraint on the hole bytes (ie. any value matches). + +If regions are specified, searches these regions. Otherwise, searches every +binding in the current virtual space. If align= is specified, only +considers addresses that are multiples of the specified alignment value. + +afh "434153494f......00" ROM + Searches strings ending with "CASIO" followed by up to 3 characters in ROM. + +afh align=4 "b4..0000" + Searches 4-aligned values close to the display interface 0xb4000000 within + all currently bound regions. +)"); diff --git a/shell/d.cpp b/shell/d.cpp index 3804efc..c142af0 100644 --- a/shell/d.cpp +++ b/shell/d.cpp @@ -147,17 +147,39 @@ void _dtl(Session &, std::string filename) // Command registration //--- -[[gnu::constructor]] static void _(void) -{ - shell_register_command("d", - [](Session &s, Parser &p){ _d(s, parse_d(s, p)); }, - [](Session &s, Parser &p){ parse_d(s, p); }); +static ShellCommand _d_cmd("d", + [](Session &s, Parser &p){ _d(s, parse_d(s, p)); }, + [](Session &s, Parser &p){ parse_d(s, p); }, + "Disassemble", R"( +d [
] - shell_register_command("dr", - [](Session &s, Parser &p){ _dr(s, parse_dr(s, p)); }, - [](Session &s, Parser &p){ parse_dr(s, p); }); +Disassembles code starting at the specified address, exploring branches until +function terminators, invalid instructions, or dynamically-computed jumps. The +default address is $ (the cursor of the current virtual space). - shell_register_command("dtl", - [](Session &s, Parser &p){ _dtl(s, parse_dtl(s, p)); }, - [](Session &s, Parser &p){ parse_dtl(s, p); }); -} +The following disassembler passes are run: + cfg Explores the code reachable from the start address + pcrel Computes PC-relative addresses (eg mov.l, mova, bf, bra...) + syscall Annotates uses of syscall table entries with the syscall number +)"); + +static ShellCommand _dr_cmd("dr", + [](Session &s, Parser &p){ _dr(s, parse_dr(s, p)); }, + [](Session &s, Parser &p){ parse_dr(s, p); }, + "Disassemble Range", R"( +dr [] + +Disassembles an explicit region of memory. This is similar to d, except that +the disassembled code is pre-loaded from the region instead of being explored +by the cfg pass. See d? for more information. +)"); + +static ShellCommand _dtl_cmd("dtl", + [](Session &s, Parser &p){ _dtl(s, parse_dtl(s, p)); }, + [](Session &s, Parser &p){ parse_dtl(s, p); }, + "Disassembly Table Load", R"( +dtl "" + +Loads a disassembly table from the specified file. This command is mostly +designed to be used in startup scripts. +)"); diff --git a/shell/e.cpp b/shell/e.cpp index 1e69014..32f998d 100644 --- a/shell/e.cpp +++ b/shell/e.cpp @@ -98,15 +98,35 @@ void _ev(Session &, std::string, std::vector const &values) } } -[[gnu::constructor]] static void _(void) -{ - shell_register_command("e", - [](Session &s, Parser &p){ _e(s, parse_e(s, p)); }, - [](Session &s, Parser &p){ parse_e(s, p); }); +static ShellCommand _e_cmd("e", + [](Session &s, Parser &p){ _e(s, parse_e(s, p)); }, + [](Session &s, Parser &p){ parse_e(s, p); }, + "Evaluate expression", R"( +e [...] - shell_register_command("ev", - [](Session &s, Parser &p){ - auto const &args = parse_ev(s, p); - _ev(s, args.space_name, args.values); }, - [](Session &s, Parser &p){ parse_ev(s, p); }); -} +Evaluates the specified expressions. The expressions may include syscall +references (%0ab), named symbols (TRA), the current cursor ($), and +arithmetic expressions. + +The parser doesn't accept arithmetic expressions directly on the command-line; +they must be placed within parentheses. + +The resulting values undergo a simple analysis which recovers symbol names and +syscall addresses. + +e TRA ($+2) + Evaluate the address of the TRA register, and the address of the next + instruction. +)"); + +static ShellCommand _ev_cmd("ev", + [](Session &s, Parser &p){ + auto const &args = parse_ev(s, p); + _ev(s, args.space_name, args.values); }, + [](Session &s, Parser &p){ parse_ev(s, p); }, + "Evaluate expression in Virtual space", R"( +ev [...] + +Same as e, but resolves symbols within the named virtual space rather than the +current one. See e? for more information. +)"); diff --git a/shell/g.cpp b/shell/g.cpp index 92c48c8..7437569 100644 --- a/shell/g.cpp +++ b/shell/g.cpp @@ -22,9 +22,11 @@ void _g(Session &session, long value) session.current_space->cursor = (value & 0xffffffff); } -[[gnu::constructor]] static void _(void) -{ - shell_register_command("g", - [](Session &s, Parser &p){ _g(s, parse_g(s, p)); }, - [](Session &s, Parser &p){ parse_g(s, p); }); -} +static ShellCommand _g_cmd("g", + [](Session &s, Parser &p){ _g(s, parse_g(s, p)); }, + [](Session &s, Parser &p){ parse_g(s, p); }, + "Goto address", R"( +g
+ +Moves the cursor of the current virtual space to the specified address. +)"); diff --git a/shell/h.cpp b/shell/h.cpp index 2a416e0..0a33439 100644 --- a/shell/h.cpp +++ b/shell/h.cpp @@ -78,9 +78,12 @@ void _h(Session &session, Range r) _h_hexdump(session, r, {}); } -[[gnu::constructor]] static void _(void) -{ - shell_register_command("h", - [](Session &s, Parser &p){ _h(s, parse_h(s, p)); }, - [](Session &s, Parser &p){ parse_h(s, p); }); -} +static ShellCommand _h_cmd("h", + [](Session &s, Parser &p){ _h(s, parse_h(s, p)); }, + [](Session &s, Parser &p){ parse_h(s, p); }, + "Hexdump", R"( +h (
|) + +Dumps the specified range into hexadecimal and ASCII representations. When an +address is specified, defaults to a length of 128. +)"); diff --git a/shell/i.cpp b/shell/i.cpp index eb7a404..de062a2 100644 --- a/shell/i.cpp +++ b/shell/i.cpp @@ -111,7 +111,7 @@ static struct _is_args parse_is(Session &, Parser &parser) }); parser.accept_options(); - std::string name = parser.at_end() ? "" : parser.symbol("vspace_name"); + args.vspace_name = parser.at_end() ? "" : parser.symbol("vspace_name"); parser.accept_options(); parser.end(); @@ -136,6 +136,8 @@ void _is(Session &session, std::string vspace_name, bool sort) if(!space) return; + // TODO: is doesn't work + OS *os = space->os_analysis(); if(!os) throw CommandError("os analysis on '{}' failed", vspace_name); @@ -160,15 +162,25 @@ void _is(Session &session, std::string vspace_name, bool sort) // Command registration //--- -[[gnu::constructor]] static void _(void) -{ - shell_register_command("io", - [](Session &s, Parser &p){ _io(s, parse_io(s, p)); }, - [](Session &s, Parser &p){ parse_io(s, p); }); +static ShellCommand _io_cmd("io", + [](Session &s, Parser &p){ _io(s, parse_io(s, p)); }, + [](Session &s, Parser &p){ parse_io(s, p); }, + "Info OS", R"( +io [] - shell_register_command("is", - [](Session &s, Parser &p){ - auto args = parse_is(s, p); - _is(s, args.vspace_name, args.sort); }, - [](Session &s, Parser &p){ parse_is(s, p); }); -} +Prints information about the OS mapped in the named virtual space (defaults to +the current one). This usually requires an OS binary to be mapped to ROM. +)"); + +static ShellCommand _is_cmd("is", + [](Session &s, Parser &p){ + auto args = parse_is(s, p); + _is(s, args.vspace_name, args.sort); }, + [](Session &s, Parser &p){ parse_is(s, p); }, + "Info Syscalls", R"( +is [sort=true] [] + +Prints the syscall table for the specified virtual space (defaults to the +current one). By default, syscalls are enumerated by syscall number. If +sort=true is specified, they are instead sorted by address. +)"); diff --git a/shell/lexer.l b/shell/lexer.l index 333bdf7..f9cdec2 100644 --- a/shell/lexer.l +++ b/shell/lexer.l @@ -188,6 +188,7 @@ space [ \t]+ <*>{syscall} { yylval.NUM = strtoul(yytext+1, NULL, 16); return T::SYSCALL; } <*>{num} { yylval.NUM = parse_num(yytext); return T::NUM; } <*>{symbol} { yylval.SYMBOL = strdup(yytext); return T::SYMBOL; } +<*>"?" { return '?'; } /* Generic error and word boundaries violations */ <*>{syscall}{letter} { err("invalid syscall number '%s'", yytext); } diff --git a/shell/main.cpp b/shell/main.cpp index d111fbd..3d52751 100644 --- a/shell/main.cpp +++ b/shell/main.cpp @@ -16,17 +16,22 @@ #include "parser.h" #include "commands.h" -struct CommandSpec { - ShellFunction function; - ShellCompleter completer; -}; +static std::map commands; -static std::map commands; - -void shell_register_command(std::string name, ShellFunction function, - ShellCompleter completer) +ShellCommand::ShellCommand(std::string _name, ShellFunction _function, + ShellCompleter _completer, std::string _shortd, std::string _longd): + function {_function}, completer {_completer}, + short_description {_shortd} { - commands[name] = (CommandSpec){ function, completer }; + /* Left trim */ + _longd.erase(0, _longd.find_first_not_of("\n")); + /* Right trim */ + int r = _longd.size(); + while(r > 0 && _longd[--r] == '\n') {} + _longd.erase(r+1, _longd.size()); + + this->long_description = _longd; + commands[_name] = this; } static Session global_session; @@ -84,23 +89,21 @@ Parser::CompletionRequest parse_until_autocomplete(Session &session, Parser p(true); p.start(); - /* Read commands until hitting an unfinished one */ + /* Read commands until the parser runs out of input and throws a + CompletionRequest */ try { do { - /* First obtain command name */ p.skip_separators(); std::string cmd = p.symbol("command"); - /* Then run parser on that command's completer */ - if(commands.count(cmd) && commands[cmd].completer) - commands[cmd].completer(session, p); - + if(commands.count(cmd) && commands[cmd]->completer) + commands[cmd]->completer(session, p); } while(!lex_idle()); } catch(Parser::CompletionRequest r) { return r; } catch(Parser::SyntaxError &e) { -// fmt::print("Syntax error: {}\n", e.what()); + /* Ignore syntax errors, just don't autocomplete */ } return Parser::CompletionRequest("", ""); @@ -129,6 +132,7 @@ char *autocomplete(char const *text, int state) return (i < options.size()) ? strdup(options[i++].c_str()) : NULL; } + //--- // Shell routine //--- @@ -186,7 +190,8 @@ static std::string read_interactive(Session const &s, bool &leave) } std::string cmd = cmd_ptr; - add_history(cmd_ptr); + if(strlen(cmd_ptr) > 0) + add_history(cmd_ptr); free(cmd_ptr); return cmd; } @@ -194,13 +199,6 @@ static std::string read_interactive(Session const &s, bool &leave) int main(int argc, char **argv) { Session &s = global_session; - /* Register a no-op quit command for auto-completion */ - shell_register_command("q", NULL, NULL); - /* Register the include command */ - shell_register_command(".", - [](Session &s, Parser &p) { _dot(s, parse_dot(s, p), false); }, - [](Session &s, Parser &p) { parse_dot(s, p); }); - theme_builtin("tomorrow-night"); rl_completion_entry_function = autocomplete; @@ -276,17 +274,33 @@ int main(int argc, char **argv) /* Read the command name */ if(parser.lookahead().type == T::END) continue; + if(parser.lookahead().type == '?') { + for(auto const &it: commands) { + fmt::print(" {:5s} {}\n", it.first, + it.second->short_description); + } + continue; + } std::string cmd = parser.symbol("command"); - if(cmd == "q") break; + if(cmd == "q" && parser.lookahead().type != '?') + break; if(cmd == "p") { parser.dump_command(); } - else if(commands.count(cmd)) - { - if(commands[cmd].function) commands[cmd].function(s, parser); + else if(commands.count(cmd)) { + if(parser.lookahead().type == '?') { + fmt::print("{}: {}\n\n{}\n", cmd, + commands[cmd]->short_description, + commands[cmd]->long_description); + continue; + } + if(commands[cmd]->function) + commands[cmd]->function(s, parser); + } + else { + fmt::print("\e[31;1merror:\e[0m unknown command '{}'\n", cmd); } - else fmt::print("\e[31;1merror:\e[0m unknown command '{}'\n", cmd); } catch(std::exception &e) { fmt::print("\e[31;1merror:\e[0m {}\n", e.what()); @@ -301,3 +315,17 @@ int main(int argc, char **argv) return 0; } + +/* Register a no-op quit command for auto-completion */ +static ShellCommand _q_cmd("q", NULL, NULL, "Quit", "Quits fxos."); + +/* Register the include command */ +static ShellCommand _dot_cmd(".", + [](Session &s, Parser &p) { _dot(s, parse_dot(s, p), false); }, + [](Session &s, Parser &p) { parse_dot(s, p); }, + "Include scripts", R"( +. "" ""... + +Reads file paths from its string arguments, and executes each of them as a +sequence of commands in the order of the command line. +)"); diff --git a/shell/parser.cpp b/shell/parser.cpp index c5ac543..a430e95 100644 --- a/shell/parser.cpp +++ b/shell/parser.cpp @@ -209,7 +209,7 @@ std::string Parser::symbol(std::string category) free(t.value.SYMBOL); /* This will throw only if there is no token after, not even spaces */ - if(m_la.type == T::END) + if(m_complete && m_la.type == T::END) throw CompletionRequest(category, sym); /* If a space is found, get rid of it */ diff --git a/shell/s.cpp b/shell/s.cpp index b45b79b..9ada392 100644 --- a/shell/s.cpp +++ b/shell/s.cpp @@ -84,17 +84,31 @@ void _ss(Session &session, Symbol s) // Command registration //--- -[[gnu::constructor]] static void _(void) -{ - shell_register_command("sl", - [](Session &s, Parser &p){ parse_sl(s, p); _sl(s); }, - [](Session &s, Parser &p){ parse_sl(s, p); }); +static ShellCommand _sl_cmd("sl", + [](Session &s, Parser &p){ parse_sl(s, p); _sl(s); }, + [](Session &s, Parser &p){ parse_sl(s, p); }, + "Symbol List", R"( +sl - shell_register_command("sa", - [](Session &s, Parser &p){ _sa(s, parse_sa(s, p)); }, - [](Session &s, Parser &p){ parse_sa(s, p); }); +Lists all symbols in the current virtual space. +)"); - shell_register_command("ss", - [](Session &s, Parser &p){ _ss(s, parse_ss(s, p)); }, - [](Session &s, Parser &p){ parse_ss(s, p); }); -} +static ShellCommand _sa_cmd("sa", + [](Session &s, Parser &p){ _sa(s, parse_sa(s, p)); }, + [](Session &s, Parser &p){ parse_sa(s, p); }, + "Symbol at Address", R"( +sa
+ +Defines a new symbol at the specified address in the current virtual space. +)"); + +static ShellCommand _ss_cmd("ss", + [](Session &s, Parser &p){ _ss(s, parse_ss(s, p)); }, + [](Session &s, Parser &p){ parse_ss(s, p); }, + "Symbol at Syscall", R"( +ss % + +Defines a new symbol at the specified syscall number in the current virtual +space. The address is not resolved; the same syscall symbol can be used accross +multiple OS versions regardless of where the functions are actually located. +)"); diff --git a/shell/shell.h b/shell/shell.h index 0e184d3..ac42e73 100644 --- a/shell/shell.h +++ b/shell/shell.h @@ -16,8 +16,15 @@ using ShellFunction = void (*)(Session &session, Parser &parser); /* Type of functions to complete arguments to shell commands */ using ShellCompleter = void (*)(Session &session, Parser &parser); -/* Register a command in the shell */ -void shell_register_command(std::string name, ShellFunction function, - ShellCompleter completer); +/* Shell command. The constructor registers it automatically */ +struct ShellCommand { + ShellFunction function; + ShellCompleter completer; + std::string short_description; + std::string long_description; + + ShellCommand(std::string name, ShellFunction function, + ShellCompleter completer, std::string shortd, std::string longd); +}; #endif /* FXOS_SHELL_H */ diff --git a/shell/v.cpp b/shell/v.cpp index d6f3b29..b4efb3b 100644 --- a/shell/v.cpp +++ b/shell/v.cpp @@ -179,29 +179,62 @@ void _vm(Session &session, std::string file, std::vector regions) // Command registration //--- -[[gnu::constructor]] static void _(void) -{ - shell_register_command("vl", - [](Session &s, Parser &p) { _vl(s, parse_vl(s, p)); }, - [](Session &s, Parser &p){ parse_vl(s, p); }); +static ShellCommand _vl_cmd("vl", + [](Session &s, Parser &p) { _vl(s, parse_vl(s, p)); }, + [](Session &s, Parser &p){ parse_vl(s, p); }, + "Virtual space List", R"( +vl [...] - shell_register_command("vs", - [](Session &s, Parser &p) { _vs(s, parse_vs(s, p)); }, - [](Session &s, Parser &p){ parse_vs(s, p); }); +Shows the bound regions of each specified virtual space. If none is specified, +shows all the virtual spaces. +)"); - shell_register_command("vc", - [](Session &s, Parser &p) { _vc(s, parse_vc(s, p)); }, - [](Session &s, Parser &p){ parse_vc(s, p); }); +static ShellCommand _vs_cmd("vs", + [](Session &s, Parser &p) { _vs(s, parse_vs(s, p)); }, + [](Session &s, Parser &p){ parse_vs(s, p); }, + "Virtual space Select", R"( +vs - shell_register_command("vct", - [](Session &s, Parser &p) { - auto const &args = parse_vct(s, p); - _vct(s, args.path, args.vspace_name); }, - [](Session &s, Parser &p){ parse_vct(s, p); }); +Selects the specified virtual space. +)"); - shell_register_command("vm", - [](Session &s, Parser &p) { - auto const &args = parse_vm(s, p); - _vm(s, args.path, args.regions); }, - [](Session &s, Parser &p){ parse_vm(s, p); }); -} +static ShellCommand _vc_cmd("vc", + [](Session &s, Parser &p) { _vc(s, parse_vc(s, p)); }, + [](Session &s, Parser &p){ parse_vc(s, p); }, + "Virtual space Create", R"( +vc [] + +Creates a new virtual space under the specified name. If such a space already +exists, adds a numerical suffix like _0 until a fresh name is found. The space +is created with no bindings and automatically selected. +)"); + +static ShellCommand _vct_cmd("vct", + [](Session &s, Parser &p) { + auto const &args = parse_vct(s, p); + _vct(s, args.path, args.vspace_name); }, + [](Session &s, Parser &p){ parse_vct(s, p); }, + "Virtual space Create from Target", R"( +vct "" [] + +Creates a new virtual space from a script file. If no name is specified, the +basename of the script file is used. The new space is created with vc, then the +script file is run. +)"); + +static ShellCommand _vm_cmd("vm", + [](Session &s, Parser &p) { + auto const &args = parse_vm(s, p); + _vm(s, args.path, args.regions); }, + [](Session &s, Parser &p){ parse_vm(s, p); }, + "Virtual space Map file", R"( +vm "" ... + +Maps the named file into all the specified regions of the current virtual +space. If the file is smaller than the region, it is zero-padded; if the region +is smaller, the extra data is ignored. The amount of data mapped is always +exactly the size of the requested region. + +vm "/os/fx/3.10/3.10.bin" ROM ROM_P2 + Maps a binary file 3.10.bin to ROM, through both P1 and P2. +)");