finish the base interfaces
* switch from File to Buffer to manage file contents * boilerplate for a proprer command-line interface, and complete help * add a notion of Addressable to avoid losing the source address of very information provided by the OS type * use addressables in the AbstractMemory interface * fully parse the header and footer of OSes * bring [fxos info] to the level of the previous implementation * make most of the OS attributes public * use char* instead of void* for translation, since void* arithmetic triggers a hell of warnings in C++
This commit is contained in:
parent
d78f7bca10
commit
c9ecb855de
|
@ -0,0 +1,13 @@
|
|||
//---
|
||||
// fxos-cli: A disassembler and OS reverse-engineering tool
|
||||
//---
|
||||
|
||||
#ifndef FXOS_CLI_H
|
||||
#define FXOS_CLI_H
|
||||
|
||||
#include <string>
|
||||
|
||||
/* Print general information on an OS file */
|
||||
void os_info(std::string path);
|
||||
|
||||
#endif /* FXOS_CLI_H */
|
|
@ -0,0 +1,76 @@
|
|||
#include "fxos-cli.h"
|
||||
#include <fxos/target.h>
|
||||
#include <fxos/os.h>
|
||||
#include <fxos/util.h>
|
||||
|
||||
using namespace FxOS;
|
||||
|
||||
static char const *info_str =
|
||||
"Header information:\n"
|
||||
" Bootcode timestamp (DateA) (0x%000008x) : %s\n"
|
||||
" Bootcode checksum (0x%000008x) : 0x%08x\n"
|
||||
//" Serial number (0x%000008x) : %s\n"
|
||||
" OS version (0x%000008x) : %s\n";
|
||||
|
||||
static char const *footer_str =
|
||||
"\nFooter information:\n"
|
||||
" Detected footer address : 0x%08x\n"
|
||||
" Langdata entries found : %d\n"
|
||||
" OS date (DateO) (0x%000008x) : %s\n"
|
||||
" OS checksum (0x%000008x) : 0x%08x\n";
|
||||
|
||||
static char const *syscall_str =
|
||||
"\nSyscall information:\n"
|
||||
" Syscall table address (0x8001007c) : 0x%08x\n"
|
||||
" Entries that point to valid memory : 0x%x\n"
|
||||
" First seemingly invalid entry : 0x%08x\n"
|
||||
" Syscall entries outside ROM:\n";
|
||||
|
||||
static char const *syscall_nonrom_str =
|
||||
" %%%03x -> 0x%08x (%s memory)\n";
|
||||
|
||||
void os_info(std::string path)
|
||||
{
|
||||
/* Create an 8M buffer and load the ROM there */
|
||||
Buffer romfile(path, MemoryRegion::ROM.size());
|
||||
OS os(romfile);
|
||||
|
||||
/* There is some stuff we want to read directly */
|
||||
Target &t = os.target;
|
||||
|
||||
printf(info_str,
|
||||
&os.bootcode_timestamp, os.bootcode_timestamp.value.c_str(),
|
||||
&os.bootcode_checksum, os.bootcode_checksum,
|
||||
// &os.serial_number, os.serial_number,
|
||||
&os.version, os.version.value.c_str());
|
||||
|
||||
if(os.footer == (uint32_t)-1)
|
||||
{
|
||||
printf("\nFooter could not be found.\n");
|
||||
}
|
||||
else
|
||||
{
|
||||
printf(footer_str, os.footer, os.langdata,
|
||||
&os.timestamp, os.timestamp.value.c_str(),
|
||||
&os.checksum, os.checksum);
|
||||
}
|
||||
|
||||
uint32_t syscall_table = t.read_u32(0x8001007c);
|
||||
uint32_t first_noncall = t.read_u32(syscall_table +
|
||||
4 * os.syscall_count());
|
||||
|
||||
printf(syscall_str, syscall_table, os.syscall_count(), first_noncall);
|
||||
|
||||
int total = 0;
|
||||
for(int i = 0; i < os.syscall_count(); i++)
|
||||
{
|
||||
uint32_t e = os.syscall(i);
|
||||
MemoryRegion const *r = MemoryRegion::region_for(e);
|
||||
if(!r || r->name == "ROM" || r->name == "ROM_P2") continue;
|
||||
|
||||
printf(syscall_nonrom_str, i, e, r->name.c_str());
|
||||
total++;
|
||||
}
|
||||
|
||||
if(!total) printf(" (none)\n");
|
||||
}
|
172
fxos/main.cpp
172
fxos/main.cpp
|
@ -1,66 +1,111 @@
|
|||
#include <fxos/lang.h>
|
||||
#include "fxos-cli.h"
|
||||
#include <fxos/load.h>
|
||||
#include <fxos/errors.h>
|
||||
#include <fxos/os.h>
|
||||
#include <cstdio>
|
||||
#include <getopt.h>
|
||||
|
||||
using namespace FxOS;
|
||||
|
||||
char const *info_str =
|
||||
"Header information:\n"
|
||||
" Bootcode timestamp (DateA) (0x8000ffb0) : %s\n"
|
||||
" Serial number (0x8000ffd0) : %s\n"
|
||||
" Bootcode checksum (0x8000fffc) : 0x%s\n"
|
||||
" OS version (0x80010020) : %s\n";
|
||||
static char const *help_string = R"(
|
||||
usage: fxos info <os file>
|
||||
fxos disasm <os file> (-a <address> | -s <syscall id>) [options...]
|
||||
fxos disasm -b <binary file> [options...]
|
||||
fxos analyze [-f] [-s] [-a] [-r] <number> <os file> [options...]
|
||||
|
||||
char const *footer_str =
|
||||
"\nFooter information:\n"
|
||||
" Detected footer address : 0x8%07x\n"
|
||||
" Langdata entries found : %d\n"
|
||||
" OS date (DateO) (0x8%07x)" " : %s\n"
|
||||
" OS checksum (0x8%07x)" " : 0x%s\n";
|
||||
fxos is a reverse-engineering tool to disassemble and analyze fx9860g-like
|
||||
OS dumps, providing efficient annotations through an editable database.
|
||||
|
||||
char const *syscall_str =
|
||||
"\nSyscall information:\n"
|
||||
" Syscall table address (0x8001007c) : 0x%08x\n"
|
||||
" Entries that point to valid memory : 0x%x\n"
|
||||
" First seemingly invalid entry : 0x%08x\n"
|
||||
" Syscall entries outside ROM:\n";
|
||||
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.
|
||||
|
||||
char const *syscall_nonrom_str =
|
||||
" %%%03x -> 0x%08x (%s memory)\n";
|
||||
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)
|
||||
|
||||
void info(std::string path)
|
||||
Database extensions:
|
||||
--load <file> Read documentation from <file>
|
||||
--load <folder> Read documentation recursively from <folder>
|
||||
|
||||
Disassembly options:
|
||||
-a <address> Start disassembling at this address
|
||||
-s <syscall id> Start disassembling at this syscall's address
|
||||
-l <length> Length of region
|
||||
--passes=<list> Execute the specified comma-separated list of passes
|
||||
|
||||
The default list of passes is pcrel,cfg,cstprop,syscall,regs. The available
|
||||
passes are the following:
|
||||
pcrel Resolve PC-relative references as their target address
|
||||
cfg Build the control flow graph (uses pcrel)
|
||||
cstprop Propagate constants by abstract interpretation (uses cfg)
|
||||
syscall Annotate code with reverse syscalls
|
||||
regs Annotate code with peripheral register addresses
|
||||
|
||||
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")
|
||||
|
||||
All numbers support base prefixes "0" (octal) and "0x" (hexadecimal).
|
||||
)"+1;
|
||||
|
||||
int main_info(int argc, char **argv)
|
||||
{
|
||||
File file(path);
|
||||
OS os(file);
|
||||
int error=0, option=0, mpu=0;
|
||||
|
||||
Target t;
|
||||
t.bind_region(MemoryRegion::ROM, file);
|
||||
t.bind_region(MemoryRegion::ROM_P2, file);
|
||||
struct option const longs[] = {
|
||||
{ "help", no_argument, NULL, 'h' },
|
||||
{ "sh3", no_argument, NULL, '3' },
|
||||
{ "sh4", no_argument, NULL, '4' },
|
||||
};
|
||||
|
||||
uint32_t syscall_table = t.read_u32(0x8001007c);
|
||||
uint32_t first_noncall = t.read_u32(syscall_table +
|
||||
4 * os.syscall_count());
|
||||
|
||||
printf(syscall_str, syscall_table, os.syscall_count(), first_noncall);
|
||||
|
||||
int total = 0;
|
||||
for(int i = 0; i < os.syscall_count(); i++)
|
||||
while(option >= 0 && option != '?')
|
||||
switch((option = getopt_long(argc, argv, "h34", longs, NULL)))
|
||||
{
|
||||
uint32_t e = os.syscall(i);
|
||||
MemoryRegion const *r = MemoryRegion::region_for(e);
|
||||
if(!r || r->name == "ROM" || r->name == "ROM_P2") continue;
|
||||
|
||||
printf(syscall_nonrom_str, i, e, r->name.c_str());
|
||||
total++;
|
||||
case 'h':
|
||||
std::cerr << help_string;
|
||||
break;
|
||||
case '3':
|
||||
case '4':
|
||||
/* TODO: Use sh3/sh4 information in [fxos info)? */
|
||||
mpu = option;
|
||||
break;
|
||||
case '?':
|
||||
error = 1;
|
||||
}
|
||||
|
||||
if(!total) printf(" (none)\n");
|
||||
if(error) return 1;
|
||||
char const *path = argv[optind + 1];
|
||||
|
||||
if(!path)
|
||||
{
|
||||
std::cerr << help_string;
|
||||
return 1;
|
||||
}
|
||||
|
||||
try {
|
||||
os_info(path);
|
||||
}
|
||||
catch(std::exception &e) {
|
||||
std::cerr << "error: " << e.what() << "\n";
|
||||
return 1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int main(void)
|
||||
int main_disassembly(int argc, char **argv)
|
||||
{
|
||||
std::cerr << "doing main_disasm, which is incomplete x_x\n";
|
||||
|
||||
try
|
||||
{
|
||||
FxOS::load("data/sh3.txt");
|
||||
|
@ -73,7 +118,40 @@ int main(void)
|
|||
return 1;
|
||||
}
|
||||
|
||||
info("/home/lake/Documents/PC/Données/OS Graph 35+E II/3.10.bin");
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int main_analyze(int argc, 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 *)"";
|
||||
|
||||
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;
|
||||
}
|
||||
|
|
|
@ -1,33 +0,0 @@
|
|||
//---
|
||||
// fxos.endianness. Somewhat cross-platform endianness conversion. (seriously?)
|
||||
//---
|
||||
|
||||
#ifndef LIFXOS_ENDIANNESS_H
|
||||
#define LIFXOS_ENDIANNESS_H
|
||||
|
||||
#if defined(__APPLE__)
|
||||
|
||||
#include <libkern/OSByteOrder.h>
|
||||
|
||||
#define htobe16(x) OSSwapHostToBigInt16(x)
|
||||
#define htole16(x) OSSwapHostToLittleInt16(x)
|
||||
#define be16toh(x) OSSwapBigToHostInt16(x)
|
||||
#define le16toh(x) OSSwapLittleToHostInt16(x)
|
||||
|
||||
#define htobe32(x) OSSwapHostToBigInt32(x)
|
||||
#define htole32(x) OSSwapHostToLittleInt32(x)
|
||||
#define be32toh(x) OSSwapBigToHostInt32(x)
|
||||
#define le32toh(x) OSSwapLittleToHostInt32(x)
|
||||
|
||||
#define htobe64(x) OSSwapHostToBigInt64(x)
|
||||
#define htole64(x) OSSwapHostToLittleInt64(x)
|
||||
#define be64toh(x) OSSwapBigToHostInt64(x)
|
||||
#define le64toh(x) OSSwapLittleToHostInt64(x)
|
||||
|
||||
#elif defined(__linux__)
|
||||
|
||||
#include <sys/types.h>
|
||||
|
||||
#endif
|
||||
|
||||
#endif /* LIFXOS_ENDIANNESS_H */
|
|
@ -33,13 +33,13 @@ void load(std::string path);
|
|||
The parameters [offset] and [line] are set to reflect the location in the
|
||||
file where the raw content starts. These parameters are used to initialize
|
||||
the lexers in all other load functions. */
|
||||
Header load_header(File &file, size_t &offset, int &line);
|
||||
Header load_header(Buffer const &file, size_t &offset, int &line);
|
||||
|
||||
/* Load an assembly instruction table for the disassembler.
|
||||
@file Data file, presumably analyzed with lex_header()
|
||||
@start_offset Offset of assembly data in the file
|
||||
@start_line Line where assembly data starts in the file (for errors) */
|
||||
void load_asm(File &file, size_t start_offset, size_t start_line);
|
||||
void load_asm(Buffer const &file, size_t start_offset, size_t start_line);
|
||||
|
||||
} /* namespace FxOS */
|
||||
|
||||
|
|
|
@ -16,11 +16,21 @@ namespace FxOS {
|
|||
class OS
|
||||
{
|
||||
public:
|
||||
/* Load an OS from a file. */
|
||||
OS(File &file);
|
||||
/* Load an OS from a buffer. */
|
||||
OS(Buffer &buffer);
|
||||
/* This target contains just the OS in ROM and ROM_P2. It can be used
|
||||
freely to extract data which is not already available here. */
|
||||
Target target;
|
||||
|
||||
/* Get OS version */
|
||||
std::string version() const noexcept;
|
||||
/* Bootcode timestamp and checksum */
|
||||
Addressable<std::string> bootcode_timestamp;
|
||||
Addressable<uint32_t> bootcode_checksum;
|
||||
|
||||
/* OS version, serial number, timestamp and checksum */
|
||||
Addressable<std::string> version;
|
||||
Addressable<std::string> serial_number;
|
||||
Addressable<std::string> timestamp;
|
||||
Addressable<uint32_t> checksum;
|
||||
|
||||
/* Get number of syscalls */
|
||||
int syscall_count() const noexcept;
|
||||
|
@ -29,39 +39,32 @@ public:
|
|||
/* Find a syscall entry. Returns -1 if syscall is not found */
|
||||
int find_syscall(uint32_t entry) const noexcept;
|
||||
|
||||
/* Get the footer address. Returns -1 if not found */
|
||||
uint32_t footer() const noexcept;
|
||||
/* Footer address, or -1 if not found */
|
||||
uint32_t footer;
|
||||
/* Number of langdata entries */
|
||||
int langdata;
|
||||
|
||||
private:
|
||||
/* Determine the OS version. This should be the first analysis function
|
||||
to be called, because it determines the type of model (ie. fx9860g
|
||||
versus fxcg50) thus the location of the syscall table and many more
|
||||
/* Parse the OS header. This should be the first analysis function to
|
||||
be called, because it determines the type of model (ie. fx9860g vs
|
||||
fxcg50) thus the location of the syscall table and many more
|
||||
important parameters. */
|
||||
void parse_version();
|
||||
void parse_header();
|
||||
|
||||
/* Locate and parse the syscall table. */
|
||||
void parse_syscall_table();
|
||||
|
||||
/* Locate the footer */
|
||||
/* Locate and parse the footer. */
|
||||
void parse_footer();
|
||||
|
||||
/* Working target which is a simulated memory with just the OS */
|
||||
Target m_target;
|
||||
|
||||
//---
|
||||
// OS information
|
||||
//---
|
||||
|
||||
/* Version */
|
||||
std::string m_version;
|
||||
|
||||
/* Syscall table, in order of syscall IDs */
|
||||
std::vector<uint32_t> m_syscall_table;
|
||||
/* Bimap converse, syscalls sorted by address */
|
||||
std::map<uint32_t,int> m_syscall_addresses;
|
||||
|
||||
/* Footer address */
|
||||
uint32_t m_footer;
|
||||
};
|
||||
|
||||
} /* namespace FxOS */
|
||||
|
|
|
@ -24,21 +24,34 @@ public:
|
|||
|
||||
/* Returns the data located at the provided virtual address. Throws
|
||||
std::out_of_range if the interval is not entirely simulated */
|
||||
virtual void const *translate(uint32_t addr, int size=1) const = 0;
|
||||
virtual char const *translate(uint32_t addr, int size=1) const = 0;
|
||||
|
||||
/* Read data, with signed or unsigned extension. Virtual addresses
|
||||
are used here, so they should be within the range of the region.
|
||||
Throws std::out_of_range if this is not satisfied.
|
||||
/* Read data from the memory. The following methods read data of
|
||||
various types. (Not a template because of the restriction about
|
||||
template specialization in non-namespaces scopes still in g++.)
|
||||
|
||||
These functions do *not* check alignment because exceptionally there
|
||||
are instructions which can read unaligned (movua.l). Check it
|
||||
yourself! */
|
||||
int32_t read_i8 (uint32_t addr) const;
|
||||
uint32_t read_u8 (uint32_t addr) const;
|
||||
int32_t read_i16(uint32_t addr) const;
|
||||
uint32_t read_u16(uint32_t addr) const;
|
||||
int32_t read_i32(uint32_t addr) const;
|
||||
uint32_t read_u32(uint32_t addr) const;
|
||||
When reading data, provide a virtual address. The addres is saved in
|
||||
the returned object for later printing or inspection. The returned
|
||||
object Addressable<T> automatically converts to T when used, and
|
||||
supports operator & which returns the original address.
|
||||
|
||||
The size parameter is only meaningful for variable-sized types such
|
||||
as string, and ignored for fixed-size types such as integers. If the
|
||||
desired object is not within the range of the simulated memory,
|
||||
throws std::out_of_range. */
|
||||
|
||||
/* Read integers with signed or unsigned extension. These functions do
|
||||
not check alignment, because exceptionnally the processor supports
|
||||
unaligned operations (eg. movual.l). */
|
||||
Addressable<int8_t> read_i8 (uint32_t addr) const;
|
||||
Addressable<uint8_t> read_u8 (uint32_t addr) const;
|
||||
Addressable<int16_t> read_i16(uint32_t addr) const;
|
||||
Addressable<uint16_t> read_u16(uint32_t addr) const;
|
||||
Addressable<int32_t> read_i32(uint32_t addr) const;
|
||||
Addressable<uint32_t> read_u32(uint32_t addr) const;
|
||||
|
||||
/* Read a non-NUL-terminated string */
|
||||
Addressable<std::string> read_str(uint32_t addr, size_t len) const;
|
||||
|
||||
/* Search a binary pattern in the specified area. Returns the virtual
|
||||
address of the first occurrence if any is found, [end] otherwise. */
|
||||
|
@ -49,14 +62,16 @@ public:
|
|||
/* A binding of a data buffer into a memory region of the target. */
|
||||
struct Binding: public AbstractMemory
|
||||
{
|
||||
/* Constructor from file */
|
||||
Binding(MemoryRegion const ®ion, File &file);
|
||||
/* Constructor from data buffer. An error is raised if the buffer is
|
||||
not at least of the size of the region. In this case, a new buffer
|
||||
can be constructed with the required size. */
|
||||
Binding(MemoryRegion const ®ion, Buffer const &buffer);
|
||||
|
||||
/* Targeted region, might overlap with other bindings */
|
||||
MemoryRegion region;
|
||||
/* Actual data. This buffer must have at least [size] bytes */
|
||||
uint8_t *data;
|
||||
/* Binding size, is the minimum of the region size and the data size */
|
||||
/* Actual data. This area must have at least [size] bytes */
|
||||
void const *data;
|
||||
/* Binding size (equal to the region size) */
|
||||
uint32_t size;
|
||||
|
||||
/* Checks if an address is covered by the binding */
|
||||
|
@ -64,26 +79,27 @@ struct Binding: public AbstractMemory
|
|||
|
||||
/* Returns this process' address (in [data]) corresponding to the
|
||||
provided virtual address */
|
||||
void const *translate(uint32_t addr, int size=1) const override;
|
||||
char const *translate(uint32_t addr, int size=1) const override;
|
||||
|
||||
/* Search a pattern */
|
||||
uint32_t search(uint32_t start, uint32_t end, void const *pattern,
|
||||
int size) const override;
|
||||
};
|
||||
|
||||
/* A composite target where regions can be bound dynamically */
|
||||
class Target: public AbstractMemory
|
||||
{
|
||||
public:
|
||||
/* Create an empty target with no sections */
|
||||
/* Create an empty target with no regions */
|
||||
Target();
|
||||
|
||||
/* Bind an OS. This is used to either disassemble the OS itself, or
|
||||
select the OS version for which code is being disassembled (typical
|
||||
use is for add-ins). */
|
||||
select the OS version for which code is being disassembled (for
|
||||
instance for add-ins). */
|
||||
void bind_os(OS &os);
|
||||
|
||||
/* Bind a memory region from a file. The region can either be standard
|
||||
(see <fxos/memory.h>) or custom.
|
||||
/* Bind a memory region from a buffer. The region can either be
|
||||
standard (see <fxos/memory.h>) or custom.
|
||||
|
||||
If several loaded regions overlap on some addresses, *the last
|
||||
loaded region will be used*. Thus, new regions can be loaded to
|
||||
|
@ -94,15 +110,15 @@ public:
|
|||
because bind_os() which will also enable OS-specific tasks such as
|
||||
syscall resolution.
|
||||
|
||||
If the file is smaller than the region being bound, the region is
|
||||
shrunk to fit the file. */
|
||||
void bind_region(MemoryRegion const ®ion, File &file);
|
||||
An error is raised if the buffer is smaller than the region being
|
||||
bound. */
|
||||
void bind_region(MemoryRegion const ®ion, Buffer const &buffer);
|
||||
|
||||
/* Check if an address is bound */
|
||||
bool covers(uint32_t addr, int size=1) const noexcept override;
|
||||
|
||||
/* Returns the data at the provided virtual address */
|
||||
void const *translate(uint32_t addr, int size=1) const override;
|
||||
char const *translate(uint32_t addr, int size=1) const override;
|
||||
|
||||
/* Search a pattern */
|
||||
uint32_t search(uint32_t start, uint32_t end, void const *pattern,
|
||||
|
|
|
@ -25,50 +25,63 @@ std::string format(std::string const &format, Args ... args)
|
|||
return std::string(buf.get(), buf.get() + size - 1);
|
||||
}
|
||||
|
||||
/* An object extracted from a targets, which has a virtual address */
|
||||
template<typename T>
|
||||
struct Addressable
|
||||
{
|
||||
/* Value */
|
||||
T value;
|
||||
/* Original virtual address */
|
||||
uint32_t address;
|
||||
|
||||
Addressable() = default;
|
||||
Addressable(T value): value(value), address(-1) {}
|
||||
Addressable(uint32_t addr, T value): value(value), address(addr) {}
|
||||
|
||||
/* Implicitly decay to the base type */
|
||||
operator T () const { return value; }
|
||||
|
||||
/* Return the address when using [&] */
|
||||
uint32_t operator & () const { return address; }
|
||||
};
|
||||
|
||||
/* An RAII contiguous memory buffer */
|
||||
class Buffer
|
||||
{
|
||||
public:
|
||||
/* Empty buffer initialized with given byte */
|
||||
Buffer(int size, int fill=0x00);
|
||||
Buffer(size_t size, int fill=0x00);
|
||||
|
||||
/* Buffer initialized from file, reading the given size and offset.
|
||||
* Default offset is beginning of file.
|
||||
* Default size (-1) is file size. If the specified region ends after
|
||||
the end of the file, the buffer is padded. */
|
||||
Buffer(std::string filepath, int size=-1, int offset=0, int fill=0x00);
|
||||
/* Buffer initialized from file, reading the given size from the
|
||||
beginning of the file. If the file is smaller than the specified
|
||||
size, the buffer is padded.
|
||||
|
||||
/* Create a buffer by copying (and possibly resizing) another buffer */
|
||||
Buffer(Buffer const &other, int new_size=-1);
|
||||
If this constructor is used, the file path is remembered. */
|
||||
Buffer(std::string filepath, ssize_t size=-1, int fill=0x00);
|
||||
|
||||
/* Size */
|
||||
int size() const noexcept;
|
||||
};
|
||||
/* Create a buffer by copying another buffer */
|
||||
Buffer(Buffer const &other);
|
||||
|
||||
/* A file abstraction that supports both direct load and memory mapping */
|
||||
class File
|
||||
{
|
||||
public:
|
||||
/* Load a file, either by buffer or by memory mapping */
|
||||
File(std::string path, bool mmap=false);
|
||||
/* Create a buffer by copying and resizing another buffer */
|
||||
Buffer(Buffer const &other, size_t new_size, int fill=0x00);
|
||||
|
||||
/* Get the path, size and loading address of the file */
|
||||
std::string path() const noexcept;
|
||||
/* Free allocated data, obviously */
|
||||
~Buffer();
|
||||
|
||||
/* Get buffer size */
|
||||
size_t size() const noexcept;
|
||||
char *data() const noexcept;
|
||||
|
||||
/* Free the allocated buffers */
|
||||
~File();
|
||||
/* Get data */
|
||||
char *data() noexcept;
|
||||
char const *data() const noexcept;
|
||||
|
||||
/* Get file path, when constructed from file */
|
||||
std::string path() const noexcept;
|
||||
|
||||
private:
|
||||
/* Path to file */
|
||||
std::string m_path;
|
||||
/* Size of buffer, or mapping */
|
||||
void *m_data;
|
||||
size_t m_size;
|
||||
/* Whether mmap() was used on the file */
|
||||
bool m_mmap;
|
||||
/* Data buffer (m_mmap=false) or mapping address (m_mmap=true) */
|
||||
char *m_addr;
|
||||
std::string m_path;
|
||||
};
|
||||
|
||||
#endif /* LIBFXOS_UTIL_H */
|
||||
|
|
|
@ -264,7 +264,7 @@ static void instantiate(struct Pattern p, std::string mnemonic, int argtoken1,
|
|||
}
|
||||
|
||||
/* Load an assembly instruction table for the disassembler. */
|
||||
void load_asm(File &file, size_t start_offset, size_t start_line)
|
||||
void load_asm(Buffer const &file, size_t start_offset, size_t start_line)
|
||||
{
|
||||
/* Lex all instructions and fill in the general assembly table */
|
||||
|
||||
|
@ -273,7 +273,7 @@ void load_asm(File &file, size_t start_offset, size_t start_line)
|
|||
yylineno = start_line;
|
||||
filename = file.path();
|
||||
|
||||
/* Insruction information */
|
||||
/* Instruction information */
|
||||
char *code=nullptr, *mnemonic=nullptr;
|
||||
int argtoken1=0, argtoken2=0;
|
||||
|
||||
|
|
|
@ -65,7 +65,7 @@ space [ \t]+
|
|||
namespace FxOS {
|
||||
|
||||
/* Load the header of a data file. */
|
||||
Header load_header(File &file, size_t &offset_ref, int &line_ref)
|
||||
Header load_header(Buffer const &file, size_t &offset_ref, int &line_ref)
|
||||
{
|
||||
/* Build a map of properties */
|
||||
FxOS::Header header;
|
||||
|
|
|
@ -6,7 +6,7 @@ namespace FxOS {
|
|||
/* Load any fxos data file. */
|
||||
void load(std::string path)
|
||||
{
|
||||
File file(path);
|
||||
Buffer file(path);
|
||||
size_t offset;
|
||||
int line;
|
||||
|
||||
|
|
70
lib/os.cpp
70
lib/os.cpp
|
@ -6,32 +6,38 @@
|
|||
|
||||
namespace FxOS {
|
||||
|
||||
OS::OS(File &file): m_target()
|
||||
OS::OS(Buffer &buffer): target()
|
||||
{
|
||||
/* OS files are all at least 1 MB large */
|
||||
if(file.size() < 1000000)
|
||||
if(buffer.size() < 1000000)
|
||||
throw std::runtime_error("OS files cannot be < 1MB");
|
||||
|
||||
/* Bind the given file to the internal analysis target */
|
||||
m_target.bind_region(MemoryRegion::ROM, file);
|
||||
m_target.bind_region(MemoryRegion::ROM_P2, file);
|
||||
this->target.bind_region(MemoryRegion::ROM, buffer);
|
||||
this->target.bind_region(MemoryRegion::ROM_P2, buffer);
|
||||
|
||||
parse_version();
|
||||
parse_header();
|
||||
parse_syscall_table();
|
||||
// parse_footer();
|
||||
parse_footer();
|
||||
}
|
||||
|
||||
void OS::parse_version()
|
||||
void OS::parse_header()
|
||||
{
|
||||
/* Extract the version string at 0x10020 */
|
||||
Target &t = m_target;
|
||||
Target &t = this->target;
|
||||
|
||||
char *version = (char *)t.translate(0x80010020, 10);
|
||||
m_version = std::string(version, 10);
|
||||
/* Bootcode timestamp at 0xffb0 (the very end of the bootcode) */
|
||||
this->bootcode_timestamp = t.read_str(0x8000ffb0, 14);
|
||||
/* Bootcode checksum at 0xfffc */
|
||||
this->bootcode_checksum = t.read_u32(0x8000fffc);
|
||||
|
||||
/* Version string at 0x10020 */
|
||||
this->version = t.read_str(0x80010020, 10);
|
||||
/* Serial numer at 0xffd0... sometimes? */
|
||||
this->serial_number = t.read_str(0x8000ffd0, 8);
|
||||
}
|
||||
|
||||
//---
|
||||
// Syscalle resolution
|
||||
// Syscall resolution
|
||||
//---
|
||||
|
||||
int OS::syscall_count() const noexcept
|
||||
|
@ -56,7 +62,7 @@ int OS::find_syscall(uint32_t entry) const noexcept
|
|||
|
||||
void OS::parse_syscall_table()
|
||||
{
|
||||
Target &t = m_target;
|
||||
Target &t = this->target;
|
||||
|
||||
/* Traverse the syscall table */
|
||||
uint32_t syscall_table = t.read_u32(0x8001007c);
|
||||
|
@ -80,31 +86,35 @@ void OS::parse_syscall_table()
|
|||
// Footer search
|
||||
//---
|
||||
|
||||
uint32_t OS::footer() const noexcept
|
||||
{
|
||||
return m_footer;
|
||||
}
|
||||
|
||||
void OS::parse_footer()
|
||||
{
|
||||
/* Find the footer address (last occurrence of "CASIOABSLangdata") */
|
||||
Target &t = this->target;
|
||||
|
||||
/* Find the footer address (occurrence of "CASIOABSLangdata") */
|
||||
uint32_t start = MemoryRegion::ROM.start;
|
||||
uint32_t end = MemoryRegion::ROM.end;
|
||||
|
||||
m_target.search(start, end, "CASIOABSLangdata", 16);
|
||||
#if 0
|
||||
char const *signature = "CASIOABSLangdata";
|
||||
void *occ = NULL, *next = memmem(os->data, os->len, signature, 16);
|
||||
void *end = os->data + os->len;
|
||||
|
||||
while(next)
|
||||
this->footer = t.search(start, end, "CASIOABSLangdata", 16);
|
||||
if(this->footer == end)
|
||||
{
|
||||
occ = next;
|
||||
next = memmem(next + 1, end - (next + 1), signature, 16);
|
||||
this->footer = -1;
|
||||
this->timestamp = std::string("");
|
||||
this->bootcode_timestamp = std::string("");
|
||||
this->langdata = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
os->footer = (occ) ? (occ - os->data) : (uint32_t)-1;
|
||||
#endif
|
||||
uint32_t addr = this->footer + 8;
|
||||
this->langdata = 0;
|
||||
|
||||
while(!memcmp(t.translate(addr, 8), "Langdata", 8))
|
||||
{
|
||||
this->langdata++;
|
||||
addr += 0x30;
|
||||
}
|
||||
|
||||
this->timestamp = t.read_str(addr, 14);
|
||||
this->checksum = t.read_u32(addr + 0x18);
|
||||
}
|
||||
|
||||
} /* namespace FxOS */
|
||||
|
|
|
@ -4,63 +4,66 @@
|
|||
namespace FxOS {
|
||||
|
||||
//---
|
||||
// Simulated memory primitives
|
||||
// Simulated memory access primitives
|
||||
//---
|
||||
|
||||
int32_t AbstractMemory::read_i8(uint32_t addr) const
|
||||
Addressable<int8_t> AbstractMemory::read_i8(uint32_t addr) const
|
||||
{
|
||||
int8_t *i8 = (int8_t *)translate(addr, 1);
|
||||
return *i8;
|
||||
return Addressable(addr, *i8);
|
||||
}
|
||||
|
||||
uint32_t AbstractMemory::read_u8(uint32_t addr) const
|
||||
Addressable<uint8_t> AbstractMemory::read_u8(uint32_t addr) const
|
||||
{
|
||||
uint8_t *u8 = (uint8_t *)translate(addr, 1);
|
||||
return *u8;
|
||||
return Addressable(addr, *u8);
|
||||
}
|
||||
|
||||
int32_t AbstractMemory::read_i16(uint32_t addr) const
|
||||
Addressable<int16_t> AbstractMemory::read_i16(uint32_t addr) const
|
||||
{
|
||||
uint8_t *i16 = (uint8_t *)translate(addr, 2);
|
||||
int16_t v = (i16[0] << 8) | i16[1];
|
||||
return v;
|
||||
return Addressable(addr, v);
|
||||
}
|
||||
|
||||
uint32_t AbstractMemory::read_u16(uint32_t addr) const
|
||||
Addressable<uint16_t> AbstractMemory::read_u16(uint32_t addr) const
|
||||
{
|
||||
uint8_t *u16 = (uint8_t *)translate(addr, 2);
|
||||
uint16_t v = (u16[0] << 8) | u16[1];
|
||||
return v;
|
||||
return Addressable(addr, v);
|
||||
}
|
||||
|
||||
int32_t AbstractMemory::read_i32(uint32_t addr) const
|
||||
Addressable<int32_t> AbstractMemory::read_i32(uint32_t addr) const
|
||||
{
|
||||
uint8_t *i32 = (uint8_t *)translate(addr, 4);
|
||||
int32_t v = (i32[0] << 24) | (i32[1] << 16) | (i32[2] << 8) | i32[3];
|
||||
return v;
|
||||
return Addressable(addr, v);
|
||||
}
|
||||
|
||||
uint32_t AbstractMemory::read_u32(uint32_t addr) const
|
||||
Addressable<uint32_t> AbstractMemory::read_u32(uint32_t addr) const
|
||||
{
|
||||
uint8_t *u32 = (uint8_t *)translate(addr, 4);
|
||||
uint32_t v = (u32[0] << 24) | (u32[1] << 16) | (u32[2] << 8) | u32[3];
|
||||
return v;
|
||||
return Addressable(addr, v);
|
||||
}
|
||||
|
||||
Addressable<std::string> AbstractMemory::read_str(uint32_t addr, size_t len)
|
||||
const
|
||||
{
|
||||
char const *str = translate(addr, len);
|
||||
return Addressable(addr, std::string(str, len));
|
||||
}
|
||||
|
||||
//---
|
||||
// Bindings of data buffers into memory regions
|
||||
//---
|
||||
|
||||
Binding::Binding(MemoryRegion const &source_region, File &file):
|
||||
region(source_region)
|
||||
Binding::Binding(MemoryRegion const &source_region, Buffer const &buffer):
|
||||
region(source_region), data(buffer.data()), size(region.size())
|
||||
{
|
||||
data = reinterpret_cast<uint8_t *>(file.data());
|
||||
size = region.size();
|
||||
|
||||
if(file.size() < region.size())
|
||||
if(buffer.size() < region.size())
|
||||
{
|
||||
region.end = region.start + file.size();
|
||||
size = file.size();
|
||||
throw std::runtime_error("Buffer too small to create binding");
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -69,14 +72,14 @@ bool Binding::covers(uint32_t addr, int size) const noexcept
|
|||
return addr >= region.start && addr + size <= region.end;
|
||||
}
|
||||
|
||||
void const *Binding::translate(uint32_t addr, int size) const
|
||||
char const *Binding::translate(uint32_t addr, int size) const
|
||||
{
|
||||
if(!covers(addr, size))
|
||||
{
|
||||
throw std::out_of_range("Out of binding range");
|
||||
}
|
||||
|
||||
return (void *)(data + (addr - region.start));
|
||||
return (char *)data + (addr - region.start);
|
||||
}
|
||||
|
||||
uint32_t Binding::search(uint32_t start, uint32_t end, void const *pattern,
|
||||
|
@ -109,9 +112,9 @@ void Target::bind_os(OS &os)
|
|||
m_os = &os;
|
||||
}
|
||||
|
||||
void Target::bind_region(MemoryRegion const ®ion, File &file)
|
||||
void Target::bind_region(MemoryRegion const ®ion, Buffer const &buffer)
|
||||
{
|
||||
Binding b(region, file);
|
||||
Binding b(region, buffer);
|
||||
m_bindings.push_back(b);
|
||||
}
|
||||
|
||||
|
@ -125,7 +128,7 @@ bool Target::covers(uint32_t addr, int size) const noexcept
|
|||
return false;
|
||||
}
|
||||
|
||||
void const *Target::translate(uint32_t addr, int size) const
|
||||
char const *Target::translate(uint32_t addr, int size) const
|
||||
{
|
||||
for(auto it = m_bindings.crbegin(); it != m_bindings.crend(); it++)
|
||||
{
|
||||
|
|
99
lib/util.cpp
99
lib/util.cpp
|
@ -1,16 +1,27 @@
|
|||
#include <fxos/util.h>
|
||||
|
||||
#include <sys/stat.h>
|
||||
#include <sys/mman.h>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
|
||||
#include <unistd.h>
|
||||
#include <fcntl.h>
|
||||
#include <sys/stat.h>
|
||||
|
||||
/* A file abstraction that supports both direct load and memory mapping */
|
||||
File::File(std::string file_path, bool use_mmap):
|
||||
m_path(file_path), m_mmap(use_mmap)
|
||||
/* Empty buffer initialized with given byte */
|
||||
Buffer::Buffer(size_t size, int fill)
|
||||
{
|
||||
char const *path = file_path.c_str();
|
||||
m_data = malloc(size);
|
||||
if(!m_data) throw std::bad_alloc();
|
||||
|
||||
m_size = size;
|
||||
memset(m_data, fill, size);
|
||||
|
||||
m_path = "(anonymous)";
|
||||
}
|
||||
|
||||
/* Buffer initialized from file */
|
||||
Buffer::Buffer(std::string filepath, ssize_t size, int fill)
|
||||
{
|
||||
char const *path = filepath.c_str();
|
||||
|
||||
int fd = open(path, O_RDONLY);
|
||||
if(!fd) throw std::runtime_error(format("cannot open '%s'", path));
|
||||
|
@ -24,53 +35,63 @@ File::File(std::string file_path, bool use_mmap):
|
|||
throw std::runtime_error(format("cannot stat '%s'", path));
|
||||
}
|
||||
|
||||
m_size = statbuf.st_size;
|
||||
m_size = (size < 0) ? statbuf.st_size : size;
|
||||
size_t size_to_read = std::min(m_size, (size_t)statbuf.st_size);
|
||||
|
||||
if(use_mmap)
|
||||
m_data = malloc(m_size);
|
||||
if(!m_data) throw std::bad_alloc();
|
||||
|
||||
/* Read buffer and fill whatever is left */
|
||||
memset(m_data, fill, m_size);
|
||||
x = read(fd, m_data, size_to_read);
|
||||
|
||||
close(fd);
|
||||
if(x != (ssize_t)size_to_read)
|
||||
{
|
||||
m_addr = (char *)mmap(nullptr, m_size, PROT_READ, MAP_SHARED,
|
||||
fd, 0);
|
||||
close(fd);
|
||||
|
||||
if(m_addr == (char *)MAP_FAILED)
|
||||
{
|
||||
throw std::runtime_error(format(
|
||||
"cannot map '%s'", path));
|
||||
}
|
||||
throw std::runtime_error(format(
|
||||
"error while reading '%s'", path));
|
||||
}
|
||||
else
|
||||
{
|
||||
m_addr = new char [m_size];
|
||||
|
||||
x = read(fd, m_addr, m_size);
|
||||
close(fd);
|
||||
|
||||
if(x != statbuf.st_size)
|
||||
{
|
||||
throw std::runtime_error(format(
|
||||
"error while reading '%s'", path));
|
||||
}
|
||||
}
|
||||
m_path = filepath;
|
||||
}
|
||||
|
||||
std::string File::path() const noexcept
|
||||
/* Create a buffer by copying another buffer */
|
||||
Buffer::Buffer(Buffer const &other):
|
||||
Buffer(other.size(), 0x00)
|
||||
{
|
||||
return m_path;
|
||||
}
|
||||
|
||||
size_t File::size() const noexcept
|
||||
/* Create a buffer by copying and resizing another buffer */
|
||||
Buffer::Buffer(Buffer const &other, size_t new_size, int fill):
|
||||
Buffer(new_size, fill)
|
||||
{
|
||||
memcpy(m_data, other.data(), std::min(new_size, other.size()));
|
||||
}
|
||||
|
||||
/* Free allocated data, obviously */
|
||||
Buffer::~Buffer()
|
||||
{
|
||||
free(m_data);
|
||||
}
|
||||
|
||||
/* Buffer size */
|
||||
size_t Buffer::size() const noexcept
|
||||
{
|
||||
return m_size;
|
||||
}
|
||||
|
||||
char *File::data() const noexcept
|
||||
/* Buffer data */
|
||||
char *Buffer::data() noexcept
|
||||
{
|
||||
return m_addr;
|
||||
return static_cast<char *>(m_data);
|
||||
}
|
||||
char const *Buffer::data() const noexcept
|
||||
{
|
||||
return static_cast<char const *>(m_data);
|
||||
}
|
||||
|
||||
|
||||
File::~File()
|
||||
/* File path */
|
||||
std::string Buffer::path() const noexcept
|
||||
{
|
||||
if(m_mmap) munmap(m_addr, m_size);
|
||||
else delete[] m_addr;
|
||||
return m_path;
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue