fxos/fxos/main.cpp

425 lines
9.1 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 <getopt.h>
#include <filesystem>
#include <fstream>
#include <sstream>
#include <string>
#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, --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:
<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
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 <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)
{
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<std::string> 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;
}