fxos/lib/os.cpp

329 lines
8.1 KiB
C++

#include <fxos/os.h>
#include <fxos/vspace.h>
#include <fxos/memory.h>
#include <fxos/util/log.h>
#include <stdexcept>
#include <cstring>
#include <sstream>
#include <memory>
extern "C" {
extern char const FxOS_SyscallDefs_FX[];
extern int FxOS_SyscallDefs_FX_len;
extern char const FxOS_SyscallDefs_CG[];
extern int FxOS_SyscallDefs_CG_len;
} /* extern "C" */
namespace FxOS {
static std::map<OS::Type, std::unique_ptr<SyscallDefs>> syscallDefsCache;
static std::unique_ptr<SyscallDefs> buildSyscallDefs(char const *str, int len);
OS::OS(VirtualSpace const &space): type {UNKNOWN}, m_space {space}
{
if(!space.covers(0x80000000, (256 << 10))) {
FxOS_log(ERR,
"OS analysis failed: space doesn't have at least 256 kB "
"of ROM to analyze from");
return;
}
/* Detect OS type by heuristic */
if(space.read_str(0x80010000, 8).value == "CASIOWIN")
this->type = FX;
else if(space.read_str(0x80020000, 8).value == "CASIOWIN")
this->type = CG;
else {
FxOS_log(ERR, "OS analysis failed: cannot determine OS type");
return;
}
parse_header();
parse_syscall_table();
parse_footer();
}
void OS::parse_header()
{
VirtualSpace const &s = m_space;
if(this->type == FX) {
/* Bootcode timestamp at the very end of the bootcode */
this->bootcode_timestamp = s.read_str(0x8000ffb0, 14);
this->bootcode_checksum = s.read_u32(0x8000fffc);
this->version = s.read_str(0x80010020, 10);
this->serial_number = s.read_str(0x8000ffd0, 8);
}
else if(this->type == CG) {
this->bootcode_timestamp = s.read_str(0x8001ffb0, 14);
this->bootcode_checksum = s.read_u32(0x8001fffc);
this->version = s.read_str(0x80020020, 10);
this->serial_number = s.read_str(0x8001ffd0, 8);
}
/* Version has format MM.mm.pppp */
version_major = std::stoi(this->version.value.substr(0, 2));
version_minor = std::stoi(this->version.value.substr(3, 2));
version_patch = std::stoi(this->version.value.substr(6, 4));
}
bool OS::version_lt(int major, int minor, int patch) const noexcept
{
if(this->version_major < major)
return true;
if(this->version_major > major)
return false;
if(this->version_minor < minor)
return true;
if(this->version_minor > minor)
return false;
return (this->version_patch < patch);
}
bool OS::version_le(int major, int minor, int patch) const noexcept
{
if(this->version_major < major)
return true;
if(this->version_major > major)
return false;
if(this->version_minor < minor)
return true;
if(this->version_minor > minor)
return false;
return (this->version_patch <= patch);
}
bool OS::version_gt(int major, int minor, int patch) const noexcept
{
if(this->version_major < major)
return false;
if(this->version_major > major)
return true;
if(this->version_minor < minor)
return false;
if(this->version_minor > minor)
return true;
return (this->version_patch > patch);
}
bool OS::version_ge(int major, int minor, int patch) const noexcept
{
if(this->version_major < major)
return false;
if(this->version_major > major)
return true;
if(this->version_minor < minor)
return false;
if(this->version_minor > minor)
return true;
return (this->version_patch >= patch);
}
//---
// Syscall resolution
//---
int OS::syscall_count() const noexcept
{
return m_syscall_table.size();
}
uint32_t OS::syscall(int id) const
{
return m_syscall_table[id];
}
int OS::find_syscall(uint32_t entry) const noexcept
{
try {
return m_syscall_addresses.at(entry);
}
catch(std::out_of_range &e) {
return -1;
}
}
uint32_t OS::syscall_table_address() const noexcept
{
uint32_t address = (this->type == FX) ? 0x8001007c : 0x8002007c;
return m_space.read_u32(address);
}
void OS::parse_syscall_table()
{
/* Traverse the syscall table */
uint32_t syscall_table = syscall_table_address();
int id = 0;
while(1) {
uint32_t entry = m_space.read_u32(syscall_table + 4 * id);
MemoryRegion const *r = MemoryRegion::region_for(entry);
if(!r)
break;
m_syscall_table.push_back(entry);
m_syscall_addresses[entry] = id;
id++;
}
}
SyscallDefs const *OS::syscall_defs() const noexcept
{
if(syscallDefsCache.count(this->type))
return syscallDefsCache[this->type].get();
/* Use the preloaded definitions */
if(this->type == OS::FX) {
auto defs
= buildSyscallDefs(FxOS_SyscallDefs_FX, FxOS_SyscallDefs_FX_len);
syscallDefsCache.insert({OS::FX, std::move(defs)});
}
else if(this->type == OS::CG) {
auto defs
= buildSyscallDefs(FxOS_SyscallDefs_CG, FxOS_SyscallDefs_CG_len);
syscallDefsCache.insert({OS::CG, std::move(defs)});
}
if(syscallDefsCache.count(this->type))
return syscallDefsCache[this->type].get();
return nullptr;
}
//---
// Footer search
//---
void OS::parse_footer()
{
VirtualSpace const &s = m_space;
/* Find the footer address (occurrence of "CASIOABSLangdata") */
uint32_t start = MemoryRegion::ROM.start;
uint32_t end = MemoryRegion::ROM.end;
this->footer = s.search(start, end, "CASIOABSLangdata", 16);
if(this->footer == end) {
this->footer = -1;
this->timestamp = std::string("");
this->langdata = 0;
this->computed_checksum = -1;
return;
}
uint32_t addr = this->footer + 8;
this->langdata = 0;
while(1) {
void const *entry = s.translate(addr, 8);
if(!entry || memcmp(entry, "Langdata", 8) != 0)
break;
this->langdata++;
addr += 0x30;
}
this->timestamp = s.read_str(addr, 14);
this->checksum = s.read_u32(addr + 0x18);
this->computed_checksum = this->compute_checksum();
}
//---
// Checksum
//---
static uint32_t accumulate_range(
VirtualSpace const &m_space, uint32_t start, uint32_t end)
{
uint32_t sum = 0;
/* Read from dynamically-sized bindings; read_u8() would be too slow */
while(start < end) {
int size;
uint8_t *buf = (uint8_t *)m_space.translate_dynamic(start, &size);
if(!buf || size <= 0)
return -1;
if(start + size > end)
size = end - start;
for(int i = 0; i < size; i++)
sum += buf[i];
start += size;
}
return sum;
}
uint32_t OS::compute_checksum() const
{
if(this->type == FX) {
return accumulate_range(m_space, 0x80010000, this->checksum.address);
}
else if(this->type == CG) {
if(this->version_lt(2, 2, 0)) {
return accumulate_range(m_space, 0x80020000, 0x80b5feb0)
+ accumulate_range(m_space, 0x80b5fef0, 0x80b5fff8);
}
else {
return accumulate_range(m_space, 0x80020000, 0x80b20000)
+ accumulate_range(
m_space, this->footer + 8, this->checksum.address);
}
}
return -1;
}
//---
// Parsing predefined syscall definitions
//---
struct SyscallDef
{
int syscall;
std::string name;
// TODO: Prototype
};
static bool parseSyscallDef(std::string &str, SyscallDef *def)
{
/* Trim comments and spaces */
auto comm = str.find("//");
if(comm != std::string::npos)
str.erase(comm, std::string::npos);
str = str.substr(0, str.find_last_not_of(" \t") + 1);
int off;
// TODO: Does not really check for the colon
if(sscanf(str.c_str(), " %%%x : %n", &def->syscall, &off) == 1) {
def->name = str.substr(off, std::string::npos);
return true;
}
return false;
}
static std::unique_ptr<SyscallDefs> buildSyscallDefs(char const *str, int len)
{
std::istringstream iss({str, (size_t)len});
std::string line;
auto defs = std::make_unique<SyscallDefs>();
SyscallDef def;
while(std::getline(iss, line)) {
if(parseSyscallDef(line, &def))
defs->insert({def.syscall, def.name});
}
return defs;
}
} /* namespace FxOS */