fxos/fxos/main.cpp

388 lines
8.7 KiB
C++

#include "fxos-cli.h"
#include <fxos/disassembly.h>
#include <fxos/library.h>
#include <fxos/errors.h>
#include <fxos/target.h>
#include <fxos/load.h>
#include <fxos/log.h>
#include <fxos/os.h>
#include <fxos/disasm-passes/cfg.h>
#include <fxos/disasm-passes/pcrel.h>
#include <fxos/disasm-passes/print.h>
#include <getopt.h>
#include <filesystem>
#include <string>
#include <fstream>
#include <sstream>
#include <vector>
#include <map>
namespace fs = std::filesystem;
using namespace FxOS;
using namespace FxOS::Log;
static char const *help_string = R"(
usage: fxos library [-t] [-a]
fxos info <target>
fxos disasm <target> <region or function> [options...]
fxos analyze [-f] [-s] [-a] [-r] <number> <os file> [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 <target> is either:
<targetname> A target in library (eg "fx@3.10")
-f <file> 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 Print all targets
-a 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:
<address> Start disassembling at this address (hexa)
<address>:<len> Disassemble exactly the specified region. <len> is an
hexadecimal number optionally followed by k, M, or G.
%<syscall id> Start disassembling at this syscall's address (hexa)
<symbol> Disassemble this library symbol (typically a syscall name).
Note that <address> takes precedence if ambiguous.
Disassembly options:
-p <list> Execute the specified comma-separated list of passes
--load <file> Read additional documentation from <file>
--load <folder> Read additional documentation recursively from <folder>
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
regs Annotate code with peripheral register addresses
The default sequence of passes is cfg,pcrel,cstprop,syscall,regs. 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 <number> (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 <num> Show at most <num> occurrences (integer or "all")
)"+1;
#define usage(exitcode) { \
std::cerr << help_string; \
return exitcode; \
}
//---
// Configuration
//---
void loadconfig(Library &lib)
{
/* First add the fxos install folder as PATH */
lib.add_path(FXOS_INSTALL_PREFIX "/share/fxos");
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_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;
}
Target t(lib.targets().at(tname), lib.paths());
os_info(t);
}
return 0;
}
int main_disassembly(int argc, char **argv)
{
int error=0, option=0, mpu='4';
std::vector<std::string> passes {
"cfg", "pcrel", "constprop", "syscall", "regs", "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);
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());
char const *refstr = argv[optind + 2];
uint32_t ref;
sscanf(refstr, "%x", &ref);
Disassembly disasm(target);
OS *os = nullptr;
log(LOG "Disassembling target %s at %s", tname, refstr);
try
{
for(auto pass: passes)
{
auto start = timer_start();
log(LOG "Running pass %s...\\", pass);
if(pass == "cfg")
{
CfgPass p(disasm);
p.run(ref);
}
else if(pass == "pcrel")
{
PcrelPass p(disasm);
p.run(ref);
}
else if(pass == "print")
{
PrintPass p(disasm);
p.hide_resolved_pcjump = true;
p.hide_resolved_pcrel = true;
p.hide_movpc_address =
PrintPass::Hide_MovPC_Region;
p.run();
}
log(LOG "%s", timer_format(timer_end(start)));
}
}
catch(LangError &e)
{
log(ERR "%08x: %s", e.addr(), e.what());
}
catch(AddrError &e)
{
log(ERR "%08x[%d]: %s", e.addr(), e.size(), e.what());
}
}
else
{
char const *ref = argv[optind + 1];
log(LOG "Disassembling file %s at %s", file, ref);
}
return 0;
}
#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 *)"";
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";
return 1;
}