fxos/fxos/main.cpp

405 lines
8.9 KiB
C++

#include "fxos-cli.h"
#include <fxos/disassembly.h>
#include <fxos/errors.h>
#include <fxos/target.h>
#include <fxos/load.h>
#include <fxos/os.h>
#include <fxos/disasm-passes/cfg.h>
#include <getopt.h>
#include <filesystem>
#include <string>
#include <fstream>
#include <sstream>
#include <vector>
#include <map>
namespace fs = std::filesystem;
using namespace FxOS;
static char const *help_string = R"(
usage: 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)
A <target> is either:
<targetname> A target in library (eg "fx@3.10")
-f <file> An arbitrary file which is loaded as ROM
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;
//---
// Configuration
//---
std::map<std::string, TargetDescription> targets;
std::vector<std::string> library { FXOS_INSTALL_PREFIX "/share/fxos" };
/* Load any fxos data file */
void load(std::string path)
{
Buffer file(path);
size_t offset;
int line;
// std::cerr << "[fxos] loading resource file '" << path << "'...\n";
Header h = load_header(file, offset, line);
if(h.find("type") == h.end())
{
std::cerr << "error: no type in header of '" << path << "'\n";
return;
}
std::string type = h["type"];
if(type == "assembly")
{
try {
load_asm(file, offset, line);
}
catch(FxOS::SyntaxError &e) {
std::cerr << e.file() << ":" << e.line() << ": " <<
e.what() << "\n" << std::flush;
}
return;
}
else if(type == "target")
{
if(!h.count("name"))
{
std::cerr << "error: no name specified in '" << path
<< "'\n";
return;
}
try {
targets[h["name"]] = load_target(file, offset, line);
}
catch(FxOS::SyntaxError &e) {
std::cerr << e.file() << ":" << e.line() << ": " <<
e.what() << "\n" << std::flush;
}
return;
}
std::cerr << "unknown file type '" << type << "' in '" << path <<"'\n";
}
/* Load a whole folder into the database */
void loadfolder(std::string path)
{
try
{
fs::recursive_directory_iterator it(path);
for(auto &file: it) load(file.path());
}
catch(fs::filesystem_error &e)
{
if(e.code().value() == ENOENT)
{
std::cerr << "warning: directory '" << path << "' does"
" not exist\n";
}
else throw e;
}
}
void loadconfig(void)
{
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)
{
library.push_back(path);
}
else if(std::sscanf(line.c_str(), "load: %256s", path) == 1)
{
loadfolder(path);
}
}
}
//---
// Main routines
//---
int main_info(int argc, char **argv)
{
int error=0, option=0, mpu='4';
std::string path;
struct option const longs[] = {
{ "help", no_argument, NULL, 'h' },
{ "sh3", no_argument, NULL, '3' },
{ "sh4", no_argument, NULL, '4' },
};
while(option >= 0 && option != '?')
switch((option = getopt_long(argc, argv, "h34f:p:", longs, NULL)))
{
case 'h':
std::cerr << help_string;
break;
case '3':
case '4':
/* TODO: Use sh3/sh4 information in [fxos info]? */
mpu = option;
break;
case 'f':
path = optarg;
break;
case '?':
error = 1;
}
if(error) return 1;
/* 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) {
std::cerr << "error: " << e.what() << "\n";
return 1;
}
}
/* Load from target otherwise */
else
{
if(!argv[optind + 1])
{
std::cerr << help_string;
return 1;
}
std::string targetname = argv[optind + 1];
if(!targets.count(targetname))
{
std::cerr << "error: no target '" << targetname
<< "' in library\n";
return 1;
}
Target t(targets[targetname], library);
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"
};
std::string file;
struct option const longs[] = {
{ "help", no_argument, NULL, 'h' },
{ "sh3", no_argument, NULL, '3' },
{ "sh4", no_argument, NULL, '4' },
};
while(option >= 0 && option != '?')
switch((option = getopt_long(argc, argv, "h34p:f:", longs, NULL)))
{
case 'h':
std::cerr << help_string;
break;
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);
}
}
break;
case 'f':
file = optarg;
break;
case '?':
error = 1;
}
int remaining_args = (file.size() ? 1 : 2);
if(argc < optind + remaining_args + 1)
{
std::cerr << "error: missing file or address\n";
error = 1;
}
else if(argc > optind + remaining_args + 1)
{
std::cerr << "error: excess arguments\n";
error = 1;
}
if(error) return 1;
if(!file.size())
{
std::string targetname = argv[optind + 1];
if(!targets.count(targetname))
{
std::cerr << "error: no target '" << targetname
<< "' in library\n";
return 1;
}
Target target(targets[targetname], library);
char const *refstr = argv[optind + 2];
uint32_t ref;
sscanf(refstr, "%x", &ref);
Disassembly disasm(target);
OS *os = nullptr;
std::cout << "disassembling target:" << targetname << " ref:" << refstr << "\n";
for(auto pass: passes)
{
std::cout << "running pass: " << pass << "\n";
if(pass == "cfg")
{
CfgPass p(disasm);
p.run(ref);
}
}
}
else
{
char const *ref = argv[optind + 1];
std::cout << "disassembling file:" << file << " ref:" << ref << "\n";
}
return 0;
}
int main_analyze(__attribute__((unused)) int argc, __attribute__((unused)) char **argv)
{
std::cerr << "doing main_analyze, which is incomplete x_x\n";
return 0;
}
int main(int argc, char **argv)
{
if(argc < 2)
{
std::cerr << help_string;
return 1;
}
std::string cmd = argv[1];
argv[1] = (char *)"";
/* Load the configuration file if it exists */
loadconfig();
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")
{
std::cerr << help_string;
return 0;
}
std::cerr << "invalid operation '" << cmd << "'\n";
std::cerr << "Try '" << argv[0] << " --help'.\n";
return 1;
}