#include "fxos-cli.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace fs = std::filesystem; using namespace FxOS; using namespace FxOS::Log; static char const *help_string = R"( usage: fxos library [-t] [-a] fxos info fxos disasm [options...] fxos analyze [-f] [-s] [-a] [-r] [options...] fxos is a reverse-engineering tool that disassembles and analyzes SuperH programs and OS dumps for fx9860g and fxcg50-like CASIO calculators, using an editable database of platform, syscall, and OS knowledge. General options: -3, --sh3 Assume SH3 OS and platform (default: SH4) -4, --sh4 Assume SH4 OS and platform (default: SH4) -v, --verbose Print logs about what's happening A is either: A target in library (eg "fx@3.10") -f An arbitrary file which is loaded as ROM LIBRARY COMMAND Prints out the contents of the library. If an option is set, the results are printed in a simple easily-parsable form without header. Selectors: -t, --targets Print all targets -a, --asm Print all assembler instruction sets INFO COMMAND Identify an OS image: version, platform, date, checksums... DISASM COMMAND Disassemble and annotate code with relative address targets, syscalls, control flow, propagated constants and hints about memory structure. Location specifiers:
Start disassembling at this address (hexa)
: Disassemble exactly the specified region. is an hexadecimal number optionally followed by k, M, or G. % Start disassembling at this syscall's address (hexa) Disassemble this library symbol (typically a syscall name). Note that
takes precedence if ambiguous. Disassembly options: -p Execute the specified comma-separated list of passes Available passes: cfg Build the control flow graph (always required) pcrel Resolve PC-relative references as their target address cstprop Propagate constants by abstract interpretation syscall Annotate code with reverse syscalls The default sequence of passes is cfg,pcrel,cstprop,syscall. When disassembling a function (ie. no size specified on the command-line), the cfg pass is always executed to explore the function. ANALYZE COMMAND Dig an address or syscall number. Finds syscall references, 4-aligned occurrences, memory region... Analysis modes: -f, --full Run all analysis passes on (same as -sar) -s, --syscall Run syscall ID analysis -a, --address Run code/data address analyis -r, --register Run peripheral register analysis Analysis options: --occurrences Show at most occurrences (integer or "all") )"+1; #define usage(exitcode) { \ std::cerr << help_string; \ return exitcode; \ } //--- // Configuration //--- void loadconfig(Library &lib) { std::string home = getenv("HOME"); fs::path configpath = home + "/.config/fxos/config"; if(!fs::exists(configpath)) return; std::ifstream stream(configpath); /* Read line by line and register paths for the library or load files from the database */ while(stream) { char path[256]; std::string line; std::getline(stream, line); if(std::sscanf(line.c_str(), "library: %256s", path) == 1) { lib.add_path(path); } else if(std::sscanf(line.c_str(), "load: %256s", path) == 1) { lib.explore(path); } } } //--- // Main routines //--- int main_library(int argc, char **argv) { int error=0, option=0; bool show_targets = false; bool show_asm = false; struct option const longs[] = { { "help", no_argument, NULL, 'h' }, { "targets", no_argument, NULL, 't' }, { "asm", no_argument, NULL, 'a' }, }; while(option >= 0 && option != '?') switch((option = getopt_long(argc, argv, "hta", longs, NULL))) { case 'h': usage(0); case 't': show_targets = true; break; case 'a': show_asm = true; break; case '?': error = 1; } if(error) return 1; /* If no option is given, display everything */ if(!show_targets && !show_asm) { show_targets = true; show_asm = true; } /* Load the library */ Library lib; loadconfig(lib); show_library(lib, show_targets, show_asm); return 0; } int main_info(int argc, char **argv) { int error=0, option=0; std::string path; struct option const longs[] = { { "help", no_argument, NULL, 'h' }, { "sh3", no_argument, NULL, '3' }, { "sh4", no_argument, NULL, '4' }, { "verbose", no_argument, NULL, 'v' }, }; while(option >= 0 && option != '?') switch((option = getopt_long(argc, argv, "h34f:p:v", longs, NULL))) { case 'h': usage(0); case '3': case '4': /* TODO: Use sh3/sh4 information in [fxos info]? */ break; case 'f': path = optarg; break; case 'v': log_setminlevel(LEVEL_LOG); break; case '?': error = 1; } if(error) return 1; //~ /* Load the configuration and library */ Library lib; loadconfig(lib); /* Load from path if one is specified */ if(path.size()) { try { /* Load the file in ROM over 8M */ Buffer romfile(path, MemoryRegion::ROM.size()); Target t; t.bind_region(MemoryRegion::ROM, romfile); t.bind_region(MemoryRegion::ROM_P2, romfile); os_info(t); } catch(std::exception &e) { log(ERR "%s", e.what()); return 1; } } /* Load from target otherwise */ else { if(!argv[optind + 1]) usage(1); std::string tname = argv[optind + 1]; if(!lib.targets().count(tname)) { log(ERR "no target '%s' in library", tname); return 1; } try { Target t(lib.targets().at(tname), lib.paths()); os_info(t); } catch(std::exception &e) { log(ERR "%s", e.what()); return 1; } } return 0; } int main_disassembly(int argc, char **argv) { int error=0, option=0; __attribute__((unused)) int mpu='4'; std::vector passes { "cfg", "pcrel", "constprop", "syscall", "print" }; std::string file; struct option const longs[] = { { "help", no_argument, NULL, 'h' }, { "sh3", no_argument, NULL, '3' }, { "sh4", no_argument, NULL, '4' }, { "verbose", no_argument, NULL, 'v' }, }; while(option >= 0 && option != '?') switch((option = getopt_long(argc, argv, "h34p:f:v", longs, NULL))) { case 'h': usage(0); case '3': case '4': mpu = option; break; case 'p': { passes.clear(); std::istringstream in(optarg); std::string pass; while(std::getline(in, pass, ',')) { passes.push_back(pass); } if(!passes.size()) error = 1, log(ERR "no pass specified"); if(passes.back() != "print") passes.push_back("print"); break; } case 'f': file = optarg; break; case 'v': log_setminlevel(LEVEL_LOG); break; case '?': error = 1; } int remaining_args = (file.size() ? 1 : 2); if(argc < optind + remaining_args + 1) { log(ERR "missing file or address"); error = 1; } else if(argc > optind + remaining_args + 1) { log(ERR "excess argument"); error = 1; } if(error) return 1; //~ /* Load the configuration and library */ Library lib; loadconfig(lib); Target target; char const *ref; if(!file.size()) { std::string tname = argv[optind + 1]; if(!lib.targets().count(tname)) { log(ERR "no target '%s' in library", tname); return 1; } target = Target(lib.targets().at(tname), lib.paths()); ref = argv[optind + 2]; log(LOG "Disassembling target %s at %s", tname, ref); } else { /* Load the file in ROM over 8M */ Buffer romfile(file, MemoryRegion::ROM.size()); target = Target(); target.bind_region(MemoryRegion::ROM, romfile); target.bind_region(MemoryRegion::ROM_P2, romfile); ref = argv[optind + 1]; log(LOG "Disassembling file %s at %s", file, ref); } try { return disassembly(lib, target, ref, passes); } catch(LangError &e) { log(ERR "%08x: %s", e.addr(), e.what()); return 1; } catch(AddressError &e) { log(ERR "%08x[%d]: %s", e.addr(), e.size(), e.what()); return 1; } } #define UN __attribute__((unused)) int main_analyze(UN int argc, UN char **argv) { std::cerr << "doing main_analyze, which is incomplete x_x\n"; return 0; } int main(int argc, char **argv) { if(argc < 2) usage(1); std::string cmd = argv[1]; argv[1] = (char *)""; try { if(cmd == "library") return main_library(argc, argv); else if(cmd == "info") return main_info(argc, argv); else if(cmd == "disasm") return main_disassembly(argc, argv); // else if(cmd == "analyze") // return main_analyze(argc, argv); else if(cmd == "-?" || cmd == "-h" || cmd == "--help") usage(0); std::cerr << "invalid operation '" << cmd << "'\n"; std::cerr << "Try '" << argv[0] << " --help'.\n"; } catch(std::logic_error &e) { log(ERR "%s o(x_x)o", e.what()); } catch(std::runtime_error &e) { log(ERR "%s", e.what()); } catch(FxOS::LimitError &e) { log(ERR "%s _(x_x)_", e.what()); } return 1; }