add command help messages

This commit is contained in:
Lephenixnoir 2022-03-06 16:15:13 +00:00
parent bd5e1f918c
commit 4e76ff3a24
Signed by untrusted user: Lephenixnoir
GPG Key ID: 1BBA026E13FC0495
12 changed files with 303 additions and 128 deletions

View File

@ -213,18 +213,51 @@ void _af4(Session &session, uint32_t value, std::vector<MemoryRegion> &regions)
_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 <value> [<regions>...]
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 <start>:<size> 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=<value>] "<pattern>" [<regions>...]
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=<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.
)");

View File

@ -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 [<address>]
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 [<range>]
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 "<file>"
Loads a disassembly table from the specified file. This command is mostly
designed to be used in startup scripts.
)");

View File

@ -98,15 +98,35 @@ void _ev(Session &, std::string, std::vector<long> 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 [<expression>...]
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 <virtual_space> [<expression>...]
Same as e, but resolves symbols within the named virtual space rather than the
current one. See e? for more information.
)");

View File

@ -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 <address>
Moves the cursor of the current virtual space to the specified address.
)");

View File

@ -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 (<address>|<range>)
Dumps the specified range into hexadecimal and ASCII representations. When an
address is specified, defaults to a length of 128.
)");

View File

@ -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 <vspace_name> 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 [<vspace_name>]
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] [<vspace_name>]
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.
)");

View File

@ -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); }

View File

@ -16,17 +16,22 @@
#include "parser.h"
#include "commands.h"
struct CommandSpec {
ShellFunction function;
ShellCompleter completer;
};
static std::map<std::string, ShellCommand *> commands;
static std::map<std::string, CommandSpec> 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"(
. "<script_1>" "<script_2>"...
Reads file paths from its string arguments, and executes each of them as a
sequence of commands in the order of the command line.
)");

View File

@ -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 */

View File

@ -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 <address> <symbol>
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 %<hex> <symbol>
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.
)");

View File

@ -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 */

View File

@ -179,29 +179,62 @@ void _vm(Session &session, std::string file, std::vector<MemoryRegion> 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 [<space_name>...]
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 <space_name>
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 [<name>]
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 "<script_file>" [<name>]
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 "<file>" <region>...
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.
)");