402 lines
8.9 KiB
C++
402 lines
8.9 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
|
|
--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
|
|
|
|
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)
|
|
{
|
|
/* 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_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;
|
|
}
|
|
|
|
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;
|
|
__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);
|
|
|
|
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);
|
|
|
|
log(LOG "Disassembling target %s at %s", tname, refstr);
|
|
|
|
try
|
|
{
|
|
disassembly(lib, target, ref, passes);
|
|
}
|
|
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 == "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";
|
|
return 1;
|
|
}
|