413 lines
9.3 KiB
C++
413 lines
9.3 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 <malloc.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 std::string help_string = colors(R"(
|
|
usage: <R>fxos<> [<R>library<>|<R>info<>|<R>disasm<>|<R>analyze<>]
|
|
|
|
fxos is a reverse-engineering tool that disassembles and analyzes SuperH
|
|
programs and OS dumps for CASIO calculators of the fx-9860G and fx-CG 50
|
|
families, using an editable database of platform, syscall, and OS knowledge.
|
|
|
|
General options:
|
|
<R>-3<>, <R>--sh3<> Assume SH3 OS and platform (default: SH4)
|
|
<R>-4<>, <R>--sh4<> Assume SH4 OS and platform (default: SH4)
|
|
<R>-v<>, <R>--verbose<> Print logs about what's happening
|
|
|
|
<R>fxos library<> [<R>-t<>] [<R>-a<>]
|
|
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:
|
|
<R>-t<>, <R>--targets<> Print all targets
|
|
<R>-a<>, <R>--asm<> Print all assembler instruction sets
|
|
|
|
<R>fxos info<> <<P>TARGET<>>
|
|
Print adentification and basic information about an OS image: version,
|
|
platform, date, checksums...
|
|
|
|
Target specification:
|
|
<<P>TARGET-NAME<>> Named target in library (eg. "fx@3.10")
|
|
<R>-f<> <<P>FILE<>> Arbitrary file which is loaded as ROM
|
|
|
|
<R>fxos disasm<> <<P>TARGET<>> <<P>LOCATION<>> [options...]
|
|
Disassemble and annotate code with relative address targets, syscalls,
|
|
control flow, propagated constants and hints about memory structure.
|
|
|
|
Location specifiers:
|
|
<<P>ADDRESS<>> Start disassembling at this address (hexa)
|
|
<<P>ADDRESS<>>:<<P>LEN<>> Disassemble exactly the specified region. <P>len<> is an
|
|
hexadecimal number optionally followed by k, M, or G.
|
|
%<<P>SYSCALL-ID<>> Start disassembling at this syscall's address (hexa)
|
|
<<P>SYMBOL<>> Disassemble this library symbol (typically syscall name).
|
|
A name which is valid hexadecimal is treated as <P>ADDRESS<>.
|
|
Disassembly options:
|
|
<R>-p<> <<P>PASSES<>> 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 <W>cfg,pcrel,cstprop,syscall<>. When
|
|
disassembling a function (ie. no size specified on the command-line), the cfg
|
|
pass is always executed to obtain the code of the function.
|
|
)"+1);
|
|
|
|
/* fxos analyze [-f] [-s] [-a] [-r] <number> <TARGET> [options...]
|
|
-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 */
|
|
|
|
#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
|
|
{
|
|
int rc = disassembly(lib, target, ref, passes);
|
|
if(log_getminlevel() <= LEVEL_LOG) malloc_stats();
|
|
return rc;
|
|
}
|
|
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;
|
|
}
|