405 lines
8.9 KiB
C++
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;
|
|
}
|