fxos/fxos/main.cpp

389 lines
8.8 KiB
C++

#include "fxos-cli.h"
#include <fxos/load.h>
#include <fxos/errors.h>
#include <fxos/target.h>
#include <fxos/os.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 <os file>
fxos disasm <target or file> <region or function> [options...]
fxos disasm -b <binary file> [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 platforms, syscalls, and OS knowledge.
Commands:
info Identify an OS image: version, platform, date, checksums...
disasm Disassemble and annotate code with relative address targets,
syscall invocations, control flow, constant propagation and hints
about memory structure.
analyze Dig an address or syscall number, finding syscall references,
4-aligned occurrences, memory region and probable role.
General options:
-b Work with an arbitrary binary file, not an OS
-3, --sh3 Assume SH3 OS and platform (default: SH4)
-4, --sh4 Assume SH4 OS and platform (default: SH4)
Database extensions:
--load <file> Read documentation from <file>
--load <folder> Read documentation recursively from <folder>
Disassembly file selection:
<target> Disassemble this target from the database (eg. "fx@3.10")
-f <file> Disassemble this file as standalone ROM
Disassembly options:
<address> Start disassembling at this address (hexa)
<address>:<len> Disassemble exactly the specified region. <len> is an
hexadecimal number optionnally followed by k, M, or G.
%<syscall id> Start disassembling at this syscall's address (hexa)
-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
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 pcrel
and cfg passes are always executed to explore the function.
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;
/* try {
FxOS::load("data/sh3.txt");
if(mpu == '4') FxOS::load("data/sh4.txt");
}
catch(FxOS::SyntaxError &e) {
std::cerr << e.file() << ":" << e.line() << ": " <<
e.what() << "\n" << std::flush;
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 t(targets[targetname], library);
char const *ref = argv[optind + 2];
std::cout << "disassembling target:" << targetname << " ref:" << ref << "\n";
}
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;
}