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:
Lephenixnoir 2019-12-15 18:40:05 +01:00
parent d78f7bca10
commit c9ecb855de
Signed by: Lephenixnoir
GPG Key ID: 1BBA026E13FC0495
14 changed files with 458 additions and 258 deletions

13
fxos/fxos-cli.h Normal file
View File

@ -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 */

76
fxos/info.cpp Normal file
View File

@ -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");
}

View File

@ -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;
}

View File

@ -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 */

View File

@ -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 */

View File

@ -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 */

View File

@ -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 &region, 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 &region, 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 &region, File &file);
An error is raised if the buffer is smaller than the region being
bound. */
void bind_region(MemoryRegion const &region, 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,

View File

@ -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 */

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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 */

View File

@ -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 &region, File &file)
void Target::bind_region(MemoryRegion const &region, 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++)
{

View File

@ -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;
}