replace CLI with WIP shell (huge commit)

This commit is contained in:
Lephenixnoir 2022-03-04 11:29:33 +00:00
parent 0a659cc6e6
commit 3b684389e9
Signed by untrusted user: Lephenixnoir
GPG Key ID: 1BBA026E13FC0495
44 changed files with 2676 additions and 1320 deletions

View File

@ -1,25 +1,19 @@
cmake_minimum_required(VERSION 3.16)
project(fxos LANGUAGES C CXX)
find_package(BISON 3.7)
find_package(FLEX 2.6)
add_compile_options(-Wall -Wextra -D_GNU_SOURCE -std=c++17 -O2)
add_compile_options(-Wall -Wextra -D_GNU_SOURCE -std=c++17 -O0 -g)
#---
# fxos core
#---
flex_target(LoadAsm lib/load-asm.l "${CMAKE_CURRENT_BINARY_DIR}/load-asm.yy.cpp" COMPILE_FLAGS -s)
flex_target(LoadHeader lib/load-header.l "${CMAKE_CURRENT_BINARY_DIR}/load-header.yy.cpp" COMPILE_FLAGS -s)
flex_target(LoadSymbols lib/load-symbols.l "${CMAKE_CURRENT_BINARY_DIR}/load-symbols.yy.cpp" COMPILE_FLAGS -s)
flex_target(LoadTarget lib/load-target.l "${CMAKE_CURRENT_BINARY_DIR}/load-target.yy.cpp" COMPILE_FLAGS -s)
flex_target(LoadAsm lib/load-asm.l "${CMAKE_CURRENT_BINARY_DIR}/load-asm.yy.cpp" COMPILE_FLAGS -s)
set(fxos_core_SOURCES
lib/disassembly.cpp
lib/domains/relconst.cpp
lib/lang.cpp
lib/library.cpp
lib/log.cpp
lib/memory.cpp
lib/os.cpp
@ -32,27 +26,35 @@ set(fxos_core_SOURCES
lib/util.cpp
lib/vspace.cpp)
add_library(fxos-core ${fxos_core_SOURCES}
${FLEX_LoadAsm_OUTPUTS} ${FLEX_LoadHeader_OUTPUTS}
${FLEX_LoadSymbols_OUTPUTS} ${FLEX_LoadTarget_OUTPUTS})
add_library(fxos-core ${fxos_core_SOURCES} ${FLEX_LoadAsm_OUTPUTS})
target_include_directories(fxos-core PUBLIC include)
#---
# fxos shell
#---
#bison_target()
flex_target(Lexer shell/lexer.l "${CMAKE_CURRENT_BINARY_DIR}/lexer.yy.cpp"
COMPILE_FLAGS -s)
set(fxos_shell_SOURCES
fxos/disassembly.cpp
fxos/info.cpp
fxos/library.cpp
fxos/main.cpp)
shell/main.cpp
shell/parser.cpp
shell/session.cpp
shell/theme.cpp
shell/a.cpp
shell/d.cpp
shell/e.cpp
shell/g.cpp
shell/h.cpp
shell/i.cpp
shell/s.cpp
shell/v.cpp
${FLEX_Lexer_OUTPUTS}
)
add_executable(fxos ${fxos_shell_SOURCES})
target_link_libraries(fxos fxos-core)
target_include_directories(fxos PRIVATE shell)
target_link_libraries(fxos fxos-core -lreadline -lfmt)
#---
# Install

View File

@ -1,156 +0,0 @@
#include "fxos-cli.h"
#include <fxos/disassembly.h>
#include <fxos/memory.h>
#include <fxos/vspace.h>
#include <fxos/util.h>
#include <fxos/log.h>
#include <fxos/os.h>
#include <fxos/disasm-passes/cfg.h>
#include <fxos/disasm-passes/pcrel.h>
#include <fxos/disasm-passes/syscall.h>
#include <fxos/disasm-passes/print.h>
using namespace FxOS;
using namespace FxOS::Log;
int disassembly(Library &library, VirtualSpace &space, char const *ref,
std::vector<std::string> passes)
{
Disassembly disasm(space);
int len=0;
/* Observe the space only if it has an OS mapped */
std::unique_ptr<OS> os;
try {
os = std::make_unique<OS>(space);
}
catch(std::exception &) {}
/* Parameters inside the ref */
uint32_t address = -1;
uint32_t blocklen;
int syscall_id;
char blockmul[2] = { 0 };
enum { RefNone=0, RefSyscall, RefAddress, RefBlock } reftype = RefNone;
/* Parse different flavors of references. %<hexa>: syscall */
if(sscanf(ref, "%%%x", &syscall_id) == 1)
reftype = RefSyscall;
/* Pure hexa: address */
else if(sscanf(ref, "%x%n", &address, &len) == 1 && !ref[len])
reftype = RefAddress;
/* Hexa with a size: a block */
else if(sscanf(ref,"%x:%x%n",&address,&blocklen,&len)==2 && !ref[len])
reftype = RefBlock;
/* Variant of block size that includes a letter k/M/G (note that the
"%[]" specifier never matches empty strings */
else if(sscanf(ref, "%x:%x%1[kMG]%n", &address, &blocklen, blockmul,
&len) == 3 && !ref[len])
{
reftype = RefBlock;
int mul = blockmul[0];
if(mul == 'k') blocklen <<= 10;
if(mul == 'M') blocklen <<= 20;
if(mul == 'G') blocklen <<= 30;
}
/* Anything else: look up symbols */
else
{
std::string name = ref;
for(auto const &symtable: library.sym_tables())
{
std::optional<Symbol> sym = symtable.lookup(name);
if(!sym) continue;
if(sym->type == Symbol::Syscall)
reftype = RefSyscall, syscall_id = sym->value;
if(sym->type == Symbol::Address)
reftype = RefAddress, address = sym->value;
break;
}
}
/* Now try to load the address for this reference */
if(reftype == RefSyscall)
{
if(!os)
{
log(ERR "cannot disassemble syscall %s: virtual space does not "
"have an OS mapped", ref);
return 1;
}
if(syscall_id >= os->syscall_count())
{
log(ERR "this OS only has %#x syscalls",
os->syscall_count());
return 1;
}
address = os->syscall(syscall_id);
}
if(reftype == RefAddress || reftype == RefBlock)
{
if(address & 1)
{
log(WRN "address %08x is odd, will start at %08x",
address, address+1);
address++;
}
}
if(reftype == RefBlock)
{
/* Load the block into memory */
for(uint32_t pc = address; pc < address + blocklen; pc += 2)
{
disasm.readins(pc);
}
}
if(reftype == RefNone)
{
log(ERR "cannot interpret '%s' (not a syscall id, not an "
"address, and no such symbol in library)", ref);
return 1;
}
for(auto pass: passes)
{
auto start = timer_start();
log(LOG "Running pass %s...\\", pass);
if(pass == "cfg" && reftype != RefBlock)
{
CfgPass p(disasm);
p.run(address);
}
else if(pass == "pcrel")
{
PcrelPass p(disasm);
p.run();
}
else if(pass == "syscall")
{
SyscallPass p(disasm, os.get());
p.run();
}
else if(pass == "print")
{
PrintPass p(disasm, library.sym_tables());
p.promote_pcjump_loc = PrintPass::Promote;
p.promote_pcrel_loc = PrintPass::Promote;
p.promote_pcrel_value = PrintPass::Promote;
p.promote_syscall = PrintPass::Promote;
p.promote_syscallname = PrintPass::Append;
p.promote_symbol = PrintPass::Append;
p.promote_pcaddr_loc = PrintPass::Promote;
p.run();
}
log(LOG "%s", timer_format(timer_end(start)));
}
return 0;
}

View File

@ -1,23 +0,0 @@
//---
// fxos-cli: A disassembler and OS reverse-engineering tool
//---
#ifndef FXOS_CLI_H
#define FXOS_CLI_H
#include <fxos/vspace.h>
#include <fxos/library.h>
#include <string>
#include <vector>
/* Show library */
void show_library(FxOS::Library &library, bool targets, bool asmtables);
/* Print general information on an OS file */
void os_info(FxOS::VirtualSpace &space);
/* Disassemble */
int disassembly(FxOS::Library &library, FxOS::VirtualSpace &space,
char const *ref, std::vector<std::string> passes);
#endif /* FXOS_CLI_H */

View File

@ -1,77 +0,0 @@
#include "fxos-cli.h"
#include <fxos/vspace.h>
#include <fxos/os.h>
#include <fxos/util.h>
#include <cassert>
using namespace FxOS;
static char const *info_str =
"OS type: %s\n"
"\n"
"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 (0x%000008x) : 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 -> %08x (%s memory)\n";
void os_info(VirtualSpace &space)
{
/* Create an OS analysis over t. If t does not have a ROM mapped, this
will fail with an exception */
OS os(space);
printf(info_str, (os.type == OS::FX ? "FX" : "CG"),
&os.bootcode_timestamp, os.bootcode_timestamp.value.c_str(),
&os.bootcode_checksum, os.bootcode_checksum,
&os.serial_number, os.serial_number.value.c_str(),
&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 = os.syscall_table_address();
uint32_t first_noncall = space.read_u32(syscall_table +
4 * os.syscall_count());
printf(syscall_str, (os.type == OS::FX ? 0x8001007c : 0x8002007c),
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,49 +0,0 @@
#include "fxos-cli.h"
#include <fxos/vspace.h>
#include <fxos/util.h>
using namespace FxOS;
/* Print the details of a single target on standard output */
void show_target(std::string name, Target const &target)
{
printf(" %s:", name.c_str());
for(Target::Binding const &b: target.bindings)
{
MemoryRegion const &reg = b.first;
if(reg.name != "")
printf(" %s", reg.name.c_str());
else
printf(" %08x(%x)", reg.start, reg.size());
}
printf("\n");
}
void show_library(FxOS::Library &library, bool targets, bool asmtables)
{
if(targets)
{
printf("Targets:\n");
for(auto &pair: library.targets())
{
show_target(pair.first, pair.second);
}
}
if(asmtables)
{
printf("Assembly tables:\n");
for(auto const &pair: library.asm_tables())
{
std::string const &name = pair.first;
int instruction_count = pair.second;
printf(" %s (%d instructions)\n", name.c_str(),
instruction_count);
}
}
}

View File

@ -1,27 +1,3 @@
#include "fxos-cli.h"
#include <fxos/disassembly.h>
#include <fxos/library.h>
#include <fxos/errors.h>
#include <fxos/vspace.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<>]
@ -74,277 +50,12 @@ General options:
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());
VirtualSpace s;
s.bind_region(MemoryRegion::ROM, romfile);
s.bind_region(MemoryRegion::ROM_P2, romfile);
os_info(s);
}
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 {
VirtualSpace s(lib.targets().at(tname), lib.paths());
os_info(s);
}
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);
VirtualSpace space;
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;
}
space = VirtualSpace(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());
space = VirtualSpace();
space.bind_region(MemoryRegion::ROM, romfile);
space.bind_region(MemoryRegion::ROM_P2, romfile);
ref = argv[optind + 1];
log(LOG "Disassembling file %s at %s", file, ref);
}
if(passes.back() != "print") passes.push_back("print");
try
{
@ -363,50 +74,3 @@ int main_disassembly(int argc, char **argv)
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;
}

View File

@ -10,15 +10,15 @@
#include <fxos/disassembly.h>
#include <fxos/symbols.h>
#include <fxos/os.h>
namespace FxOS {
class OS;
class PrintPass: public InstructionDisassemblyPass
{
public:
PrintPass(Disassembly &disasm,
std::vector<SymbolTable> const &symtables);
PrintPass(Disassembly &disasm);
void analyze(uint32_t pc, ConcreteInstruction &inst) override;
//---
@ -56,12 +56,12 @@ public:
private:
/* Symbol tables to look up names */
std::vector<SymbolTable> const &m_symtables;
std::vector<std::reference_wrapper<SymbolTable const>> m_symtables;
/* Query symbol tables, most recent first */
std::optional<std::string> symquery(Symbol::Type type, uint32_t value);
/* OS for the target, to mark syscalls before instructions */
std::unique_ptr<OS> m_os;
OS *m_os;
/* Last printed address (for ellipses) */
uint32_t m_last_address;

View File

@ -24,6 +24,10 @@ namespace FxOS {
instructions with parameters, not manually. See <fxos/load.h>. */
void register_instruction(Instruction ins);
/* Load an assembly instruction table for the disassembler. This function
directly feeds register_instruction() and does not return anything. */
int load_instructions(Buffer const &file);
/* An argument for a concrete instruction. */
struct ConcreteInstructionArg
{

View File

@ -1,57 +0,0 @@
//---
// fxos.library: Management of resource files into libraries
//---
#ifndef FXOS_LIBRARY_H
#define FXOS_LIBRARY_H
#include <fxos/vspace.h>
#include <fxos/symbols.h>
#include <string>
#include <vector>
#include <map>
namespace FxOS {
struct Library
{
Library() = default;
/* Add a new search path in the library. */
void add_path(std::string path);
/* Recursively explore the fxos data file in a folder. */
void explore(std::string path);
/* Directly load an fxos data file into the library. Normally called by
explore() only, but might be useful here. */
void load(std::string path);
/* List of search directories */
const std::vector<std::string> &paths() {
return m_paths;
}
/* Targets loaded by exploration */
const std::map<std::string, Target> &targets() {
return m_targets;
}
/* Simple list of assembly tables */
const std::vector<std::pair<std::string, int>> &asm_tables() {
return m_asmtables;
}
/* List of symbol tables */
const std::vector<SymbolTable> &sym_tables() {
return m_symtables;
}
private:
std::vector<std::string> m_paths;
std::map<std::string, Target> m_targets;
std::vector<std::pair<std::string, int>> m_asmtables;
std::vector<SymbolTable> m_symtables;
};
} /* namespace FxOS */
#endif /* FXOS_LIBRARY_H */

View File

@ -1,53 +0,0 @@
//---
// fxos.load: Data file lexers and loaders
//---
#ifndef LIBFXOS_LOAD_H
#define LIBFXOS_LOAD_H
#include <fxos/util.h>
#include <fxos/vspace.h>
#include <fxos/symbols.h>
#include <string>
#include <map>
namespace FxOS {
using Header = std::map<std::string, std::string>;
/* Load the header of a data file.
@file Data file, assumed with an fxos header type-specific contents
@offset Will be set to the byte offset where content starts
@line Will be set to the liner number where content starts
This function is used when reading all data files for fxos. The header
indicates the file type, thus the syntax of the contents. Some metadata can
also be specified here.
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(Buffer const &file, size_t &offset, int &line);
/* Load an assembly instruction table for the disassembler. This function
directly feeds the disassembler and does not return anything.
@file Data file, presumably analyzed with lex_header()
@offset Offset of assembly data in the file (as set by load_header)
@line Line where assembly data starts in the file (idem) */
int load_asm(Buffer const &file, size_t offset, size_t line);
/* Load a target description into the target database. This function returns
the complete target description */
Target load_target(Buffer const &file, size_t offset, size_t line);
/* Load a symbol table into a previously allocated SymbolTable object. This
function populates the table by calls to add(), which may result in
duplicates or unused syscall numbers and addresses. */
void load_symbols(Buffer const &file, size_t offset, size_t line,
SymbolTable &table);
} /* namespace FxOS */
#endif /* LIBFXOS_LOAD_H */

View File

@ -34,7 +34,7 @@ void logmsg(int level, char const *function, std::string message);
#define log(level, ...) \
loghelper(level, __VA_ARGS__)
#define loghelper(level, fmtstr, ...) \
logmsg(level, __func__, format(fmtstr __VA_OPT__(,) __VA_ARGS__))
FxOS::Log::logmsg(level, __func__, format(fmtstr __VA_OPT__(,)__VA_ARGS__))
} /* namespace FxOS::Log */

View File

@ -60,12 +60,13 @@ struct MemoryRegion
static MemoryRegion const XRAM;
static MemoryRegion const YRAM;
/* All standard regions. */
static std::array<MemoryRegion const *, 8> const all();
/* All standard regions */
static std::array<MemoryRegion const *, 8> const &all();
/* Determine if an address falls into one of the standard regions.
Throws std::out_of_range if none. */
/* Determine if an address falls into one of the standard regions */
static MemoryRegion const *region_for(uint32_t address);
/* Determine if a region falls entirely into one of the standard regions */
static MemoryRegion const *region_for(MemoryRegion r);
/* Empty region at 0 */
MemoryRegion();

View File

@ -5,14 +5,14 @@
#ifndef LIBFXOS_OS_H
#define LIBFXOS_OS_H
#include <fxos/vspace.h>
#include <fxos/util.h>
#include <vector>
#include <map>
namespace FxOS {
class VirtualSpace;
class OS
{
public:

View File

@ -10,11 +10,12 @@
#include <vector>
#include <fxos/os.h>
#include <fxos/vspace.h>
namespace FxOS
{
class VirtualSpace;
/* A named symbol that can be substituted to literal values in the code. */
struct Symbol
{
@ -39,6 +40,10 @@ public:
represents no constraint. */
SymbolTable(std::string os="", std::string mpu="");
/* Allow move but disable copy */
SymbolTable(SymbolTable const &other) = delete;
SymbolTable(SymbolTable &&other) = default;
std::string table_name;
std::vector<Symbol> symbols;

View File

@ -7,10 +7,13 @@
#include <fxos/memory.h>
#include <fxos/util.h>
#include <fxos/os.h>
#include <fxos/symbols.h>
#include <optional>
#include <vector>
#include <cstdint>
#include <memory>
namespace FxOS {
@ -67,14 +70,12 @@ struct Binding: public AbstractMemory
/* 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 region, Buffer const &buffer);
Binding(MemoryRegion region, Buffer buffer);
/* Targeted region, might overlap with other bindings */
MemoryRegion region;
/* Actual data. This area must have at least [size] bytes */
std::shared_ptr<char> data;
/* Binding size (equal to the region size) */
uint32_t size;
/* Underlying buffer (copy of the original one) */
Buffer buffer;
/* Checks if an address is covered by the binding */
bool covers(uint32_t addr, int size=1) const noexcept override;
@ -113,6 +114,21 @@ public:
/* MPU used by this target, or an empty string if unspecified */
std::string mpu;
/* List of bindings */
std::vector<Binding> const &bindings() const {
return m_bindings;
}
/* OS analysis; performed on-demand. Returns the new or cached OS analysis,
and nullptr only if OS cannot be analyzed */
OS *os_analysis(bool force=false);
/* Cursor position, used by the interactive shell */
uint32_t cursor;
/* Symbol table */
SymbolTable symbols;
/* Bind a memory region from a buffer. The region can either be
standard (see <fxos/memory.h>) or custom.
@ -141,6 +157,8 @@ private:
std::vector<Binding> m_bindings;
/* Buffers owned by the target (when loaded from description) */
std::vector<Buffer> m_buffers;
/* Current OS analyzer */
std::unique_ptr<OS> m_os;
};
} /* namespace FxOS */

View File

@ -1,104 +0,0 @@
#include <fxos/library.h>
#include <fxos/load.h>
#include <fxos/errors.h>
#include <fxos/util.h>
#include <fxos/log.h>
#include <filesystem>
#include <ctime>
namespace fs = std::filesystem;
using namespace FxOS::Log;
namespace FxOS {
/* Add a new search path in the library. */
void Library::add_path(std::string path)
{
m_paths.push_back(path);
}
/* Recursively explore the fxos data file in a folder. */
void Library::explore(std::string path)
{
try
{
fs::recursive_directory_iterator it(path);
for(auto &file: it)
{
if(!fs::is_directory(file)) try {
load(file.path());
}
catch(FxOS::SyntaxError &e) {
log(ERR "%s", e.str());
}
}
}
catch(fs::filesystem_error &e)
{
if(e.code().value() == ENOENT)
{
log(WRN "directory %s does not exist", path);
}
else throw e;
}
}
/* Directly load an fxos data file into the library. */
void Library::load(std::string path)
{
Buffer file(path);
size_t offset;
int line;
log(LOG "Loading resource file '%s'...\\", path);
/* Time the loading */
auto start = timer_start();
Header h = load_header(file, offset, line);
if(h.find("type") == h.end())
{
log(ERR "no type set in the header of '%s'", path);
return;
}
std::string type = h["type"];
if(type == "assembly")
{
int total = load_asm(file, offset, line);
m_asmtables.push_back(std::make_pair(h["name"],total));
}
else if(type == "target")
{
if(!h.count("name"))
{
log(ERR "no target name set in '%s'", path);
return;
}
Target t = load_target(file, offset, line);
t.mpu = h["mpu"];
m_targets[h["name"]] = t;
}
else if(type == "symbols")
{
SymbolTable st(h["os"], h["mpu"]);
load_symbols(file, offset, line, st);
m_symtables.push_back(st);
}
else
{
log(ERR "unknown file type '%s' in '%s'", type, path);
return;
}
long long ns = timer_end(start);
log(LOG "%s", timer_format(ns));
}
} /* namespace FxOS */

View File

@ -1,11 +1,11 @@
%{
#include <fxos/load.h>
#include <fxos/lang.h>
#include <fxos/disassembly.h>
#include <fxos/errors.h>
#include <fxos/util.h>
#include <cstdarg>
#include <string>
/* Text value for parser */
static char *yylval;
@ -280,13 +280,12 @@ static int instantiate(struct Pattern p, char const *mnemonic, int argtoken1,
}
/* Load an assembly instruction table for the disassembler. */
int load_asm(Buffer const &file, size_t start_offset, size_t start_line)
int load_instructions(Buffer const &file)
{
/* Lex all instructions and fill in the general assembly table */
YY_BUFFER_STATE buf = yy_scan_bytes(file.data.get() + start_offset,
file.size - start_offset);
yylineno = start_line;
YY_BUFFER_STATE buf = yy_scan_bytes(file.data.get(), file.size);
yylineno = 1;
filename = file.path;
/* Number of instructions lexed */

View File

@ -1,134 +0,0 @@
%{
#include <fxos/load.h>
#include <fxos/errors.h>
#include <fxos/util.h>
#include <string>
#include <cstring>
#include <cstdio>
#include <cstdarg>
/* Text value for parser */
static char *yylval;
/* Tokens */
#define LITERAL 1
#define COLON 2
#define HEADER_END 3
/* Current file name and number of characters lexed */
static std::string filename;
static int lexed;
/* Automatically count lexed characters */
#define YY_USER_ACTION lexed += yyleng;
/* Error messages and exceptions */
static void err(char const *format, ...)
{
static char buf[256];
va_list args;
va_start(args, format);
vsnprintf(buf, 256, format, args);
va_end(args);
throw FxOS::SyntaxError(filename.c_str(), yylineno, buf);
}
%}
%option prefix="header"
%option noyywrap
%option nounput
literal [^ \t\n:]+
space [ \t]+
%%
^#[^\n]* ;
{space} ;
[\n] yylineno++;
{literal} { yylval = strdup(yytext); return LITERAL; }
^[ ]*-{3,}[ ]*$ { return HEADER_END; }
^. { err("invalid header line"); }
":" { return COLON; }
<<EOF>> { err("EOF reached before header ends"); }
%%
namespace FxOS {
/* Load the header of a data file. */
Header load_header(Buffer const &file, size_t &offset_ref, int &line_ref)
{
/* Build a map of properties */
FxOS::Header header;
YY_BUFFER_STATE buf = yy_scan_bytes(file.data.get(), file.size);
filename = file.path;
yylineno = 1;
lexed = 0;
/* Current line */
int line = -1;
/* Property name and value */
char *name = nullptr;
char *value = nullptr;
while(1)
{
int t = yylex();
if(line >= 0 && (yylineno != line || t == HEADER_END))
{
/* Finalize current line */
if(!name || !value) {
yylineno = line;
err("incomplete header line");
}
/* Fill in the map */
header[name] = value;
free(name);
free(value);
name = nullptr;
value = nullptr;
}
if(t == HEADER_END) break;
line = yylineno;
if(t == COLON)
{
if(!name || value) err("misplaced colon in header");
}
else if(!name)
{
name = yylval;
}
else if(!value)
{
value = yylval;
}
else
{
err("unexpected stuff after header line");
}
}
offset_ref = lexed;
line_ref = yylineno;
yy_delete_buffer(buf);
return header;
}
} /* namespace FxOS */

View File

@ -1,111 +0,0 @@
%{
#include <fxos/symbols.h>
#include <fxos/errors.h>
#include <fxos/util.h>
#include <fxos/load.h>
#include <cstdarg>
/* Text value and integer value for parser */
static char *yylval;
uint32_t yyival;
/* Tokens */
#define SYSCALL 1
#define ADDRESS 2
#define NAME 3
/* Current file name */
static std::string filename;
/* Error messages and exceptions */
static void err(char const *format, ...)
{
static char buf[256];
va_list args;
va_start(args, format);
vsnprintf(buf, 256, format, args);
va_end(args);
throw FxOS::SyntaxError(filename.c_str(), yylineno, buf);
}
%}
%option prefix="symbols"
%option noyywrap
%option nounput
syscall ^%[0-9A-Fa-f]{3,}
address ^[0-9A-Fa-f]{8}
name [a-zA-Z_][a-zA-Z_0-9.]*
space [ \t]+
%%
^#[^\n]* ;
{space} ;
[\n] yylineno++;
{syscall} { yyival = strtol(yytext+1, NULL, 16); return SYSCALL; }
{address} { yyival = strtol(yytext, NULL, 16); return ADDRESS; }
{name} { yylval = strdup(yytext); return NAME; }
. { err("lex error near '%s'", yytext); }
<<EOF>> { return -1; }
%%
namespace FxOS {
/* Load a symbol table into the disassembler */
void load_symbols(Buffer const &file, size_t start_offset, size_t start_line,
SymbolTable &table)
{
YY_BUFFER_STATE buf = yy_scan_bytes(file.data.get() + start_offset,
file.size - start_offset);
yylineno = start_line;
filename = file.path;
/* Current symbol and line */
Symbol symbol;
int line = -1;
while(1)
{
int t = yylex();
if(line >= 0 && (yylineno != line || t != NAME || t == -1))
{
/* Finalize current symbol */
if(symbol.name == "") err("%d: missing name", line);
else table.add(symbol);
symbol = Symbol();
}
if(t == -1) break;
if(t == SYSCALL)
{
symbol.type = Symbol::Syscall;
symbol.value = yyival;
line = yylineno;
}
else if(t == ADDRESS)
{
symbol.type = Symbol::Address;
symbol.value = yyival;
line = yylineno;
}
else if(t == NAME)
{
symbol.name = yylval;
free(yylval);
}
}
yy_delete_buffer(buf);
}
} /* namespace FxOS */

View File

@ -1,156 +0,0 @@
%{
#include <fxos/vspace.h>
#include <fxos/errors.h>
#include <fxos/util.h>
#include <string>
#include <cstring>
#include <cstdio>
#include <cstdarg>
/* Tokens */
#define NAME 0
#define ADDRESS 1
#define SIZE 2
#define PATH 3
/* Value for parser */
static union {
char *name;
uint32_t address;
uint32_t size;
char *path;
} yylval;
/* Current file name */
static std::string filename;
/* Error messages and exceptions */
__attribute__((noreturn))
static void err(char const *format, ...)
{
static char buf[256];
va_list args;
va_start(args, format);
vsnprintf(buf, 256, format, args);
va_end(args);
throw FxOS::SyntaxError(filename.c_str(), yylineno, buf);
}
%}
%option prefix="target"
%option noyywrap
%option nounput
name [a-zA-Z_][a-zA-Z0-9_]*
hexa [0-9a-fA-F]+
path [^: \t\n]+$
space [ \t]+
%%
^#[^\n]* ;
{space} ;
[\n] yylineno++;
{hexa} { sscanf(yytext, "%x", &yylval.address); return ADDRESS; }
"("{hexa}")" { sscanf(yytext, "(%x)", &yylval.size); return SIZE; }
{name} { yylval.name = strdup(yytext); return NAME; }
":" { return ':'; }
{path} { yylval.path = strdup(yytext); return PATH; }
. { err("lex error near '%s'", yytext); }
<<EOF>> { return -1; }
%%
namespace FxOS {
static int expect(std::vector<int> types)
{
std::array description {
"region name", "address", "size", "file path" };
int t = yylex();
for(int allowed: types) if(t == allowed) return t;
std::string errmsg = "expected";
for(int allowed: types)
{
if(allowed >= 0 && allowed < (int)description.size())
{
errmsg += " ";
errmsg += description[t];
errmsg += ",";
}
else if(allowed == -1)
{
errmsg += " end of file,";
}
else if(allowed == ':')
{
errmsg += " colon,";
}
}
err(errmsg.c_str());
}
static int expect(int type)
{
std::vector types { type };
return expect(types);
}
/* Load a target description into the target database. */
Target load_target(Buffer const &file, size_t offset, size_t line)
{
/* Build a target description without actually loading the files. */
Target descr;
YY_BUFFER_STATE buf = yy_scan_bytes(file.data.get() + offset,
file.size - offset);
yylineno = line;
filename = file.path;
while(1)
{
MemoryRegion reg;
/* One iteration per line */
int t = expect({ NAME, ADDRESS, -1 });
if(t == -1) break;
if(t == NAME)
{
reg = MemoryRegion(yylval.name);
free(yylval.name);
}
else if(t == ADDRESS)
{
uint32_t start = yylval.address;
expect(SIZE);
uint32_t size = yylval.size;
reg = MemoryRegion("(anonymous)", start, start + size,
true);
}
expect(':');
expect(PATH);
std::string path = yylval.path;
free(yylval.path);
Target::Binding b = std::make_pair(reg, path);
descr.bindings.push_back(b);
}
yy_delete_buffer(buf);
return descr;
}
} /* namespace FxOS */

View File

@ -118,7 +118,7 @@ std::array<MemoryRegion const *, 8> const MemoryRegion::m_all = {
&R::RS, &R::ILRAM, &R::XRAM, &R::YRAM,
};
std::array<MemoryRegion const *, 8> const MemoryRegion::all()
std::array<MemoryRegion const *, 8> const &MemoryRegion::all()
{
return m_all;
}
@ -131,6 +131,14 @@ MemoryRegion const *MemoryRegion::region_for(uint32_t address)
return nullptr;
}
MemoryRegion const *MemoryRegion::region_for(MemoryRegion region)
{
for(auto r: MemoryRegion::all()) {
if(r->start <= region.start && region.end <= r->end) return r;
}
return nullptr;
}
MemoryRegion::MemoryRegion(std::string name)
{
for(auto r: MemoryRegion::all()) {
@ -139,7 +147,7 @@ MemoryRegion::MemoryRegion(std::string name)
return;
}
}
throw std::runtime_error("No standard region named '" + name + "'");
throw std::invalid_argument("No standard region named '" + name + "'");
}
} /* namespace FxOS */

View File

@ -10,18 +10,17 @@
namespace FxOS {
PrintPass::PrintPass(Disassembly &disasm,
std::vector<SymbolTable> const &symtables):
InstructionDisassemblyPass(disasm, "print"), m_symtables(symtables),
PrintPass::PrintPass(Disassembly &disasm):
InstructionDisassemblyPass(disasm, "print"), m_symtables(),
m_last_address(0xffffffff)
{
/* Default parameters: all 0 */
/* Use an OS observer to describe syscalls in header lines */
try {
m_os = std::make_unique<OS>(disasm.space());
}
catch(std::exception &) {}
m_os = disasm.space().os_analysis();
/* Use the symbol tables from the virtual space */
m_symtables.push_back(disasm.space().symbols);
}
void PrintPass::analyze(uint32_t pc, ConcreteInstruction &ci)

View File

@ -1,4 +1,5 @@
#include <fxos/symbols.h>
#include <fxos/vspace.h>
namespace FxOS {

View File

@ -110,6 +110,7 @@ Buffer::Buffer(Buffer const &other, size_t new_size, int fill):
Buffer(new_size, fill)
{
memcpy(data.get(), other.data.get(), std::min(new_size, other.size));
this->path = other.path;
}
/* Generic timer which returns times in ns using CLOCK_REALTIME */

View File

@ -65,15 +65,13 @@ Addressable<std::string> AbstractMemory::read_str(uint32_t addr, size_t len)
// Bindings of data buffers into memory regions
//---
Binding::Binding(MemoryRegion source_region, Buffer const &buffer):
region(source_region), data(buffer.data), size(region.size())
Binding::Binding(MemoryRegion source_region, Buffer source_buffer):
region(source_region), buffer(source_buffer)
{
/* Extend the buffers with zeros if it's too small. */
if(buffer.size >= region.size()) return;
Buffer larger_buffer(buffer, region.size());
data = larger_buffer.data;
size = larger_buffer.size;
/* Extend the buffer if it's not at least as large as the region */
if(buffer.size < region.size()) {
buffer = Buffer(buffer, region.size());
}
}
bool Binding::covers(uint32_t addr, int size) const noexcept
@ -81,15 +79,15 @@ bool Binding::covers(uint32_t addr, int size) const noexcept
return addr >= region.start && addr + size <= region.end;
}
bool Binding::covers(MemoryRegion const &region) const noexcept
bool Binding::covers(MemoryRegion const &r) const noexcept
{
return covers(region.start, region.size());
return covers(r.start, r.size());
}
char const *Binding::translate(uint32_t addr, int size) const
{
if(!covers(addr, size)) return nullptr;
return data.get() + (addr - region.start);
return buffer.data.get() + (addr - region.start);
}
uint32_t Binding::search(uint32_t start, uint32_t end, void const *pattern,
@ -110,7 +108,7 @@ uint32_t Binding::search(uint32_t start, uint32_t end, void const *pattern,
//---
VirtualSpace::VirtualSpace():
m_bindings {}, m_buffers {}
m_bindings {}, m_buffers {}, m_os {nullptr}
{
}
@ -131,6 +129,14 @@ VirtualSpace::VirtualSpace(Target const &target,
this->mpu = target.mpu;
}
OS *VirtualSpace::os_analysis(bool force)
{
if(!m_os || force) {
m_os = std::make_unique<OS>(*this);
}
return m_os.get();
}
void VirtualSpace::bind_region(MemoryRegion const &region,Buffer const &buffer)
{
Binding b(region, buffer);

229
shell/a.cpp Normal file
View File

@ -0,0 +1,229 @@
#include "shell.h"
#include "parser.h"
#include "commands.h"
#include "errors.h"
#include <fmt/core.h>
#include <endian.h>
//---
// afh
//---
static bool matches(char const *data, char const *reference,
char const *pattern, size_t size)
{
for(size_t i = 0; i < size; i++) {
if(pattern[i] && data[i] != reference[i]) return false;
}
return true;
}
static int hexa(int c) {
if(c >= '0' && c <= '9')
return c - '0';
return (c | 0x20) - 'a' + 10;
}
struct _afh_args {
/* String to find */
std::string reference;
/* Bytes to match within reference (not all are required) */
std::string pattern;
/* Size of search */
size_t size;
/* Regions to search in, may be empty */
std::vector<MemoryRegion> regions;
/* Distance to show hexump around a match */
int distance;
/* Required alignment for matches to be used */
int align;
};
static _afh_args parse_afh(Session &session, Parser &parser)
{
_afh_args args;
args.distance = -1;
args.align = -1;
parser.option('a', [&args](std::string const &value){
args.align = atoi(value.c_str());
});
parser.option('d', [&args](std::string const &value){
args.distance = atoi(value.c_str());
});
parser.accept_options();
std::string needle = parser.str();
/* Check the structure of the needle */
if(needle.size() == 0 || needle.size() % 2 != 0)
throw CommandError("search pattern '{}' should be of even non-zero "
"size", needle);
size_t bad_index = needle.find_first_not_of("0123456789abcdefABCDEF.");
if(bad_index != std::string::npos)
throw CommandError("invalid character '{}' in seach pattern",
needle[bad_index]);
for(size_t i = 0; i < needle.size(); i += 2) {
char c1 = needle[i], c2 = needle[i+1];
if((c1 == '.') != (c2 == '.'))
throw CommandError("invalid search byte '{}{}', should be either "
"'..' or fully specified", c1, c2);
}
/* Convert into a reference/pattern form */
args.size = needle.size() / 2;
args.reference.reserve(args.size);
args.pattern.reserve(args.size);
for(size_t i = 0; i < args.size; i++) {
char c1 = needle[2*i], c2 = needle[2*i+1];
if(c1 == '.') {
args.reference[i] = 0;
args.pattern[i] = 0;
}
else {
args.reference[i] = (hexa(c1) << 4) | hexa(c2);
args.pattern[i] = 1;
}
}
while(!parser.at_end()) {
parser.accept_options();
args.regions.push_back(parser.region(session.current_space));
}
parser.accept_options();
parser.end();
return args;
}
void _afh(Session &session, char const *reference, char const *pattern,
size_t size, int align, int distance, std::vector<MemoryRegion> &regions)
{
session.require_vspace();
/* Default values */
if(distance < 0) distance = 32;
if(align <= 0) align = 1;
VirtualSpace const &v = *session.current_space;
/* If no region is specified, explore the regions for all bindings */
if(regions.size() == 0) {
for(auto &b: v.bindings())
regions.push_back(b.region);
}
int match_count = 0;
bool output_started = false;
/* Matches are not shown right away because if they are close enough a
single local hexdump will show several of them */
std::vector<std::pair<uint32_t,int>> pending;
for(auto const &r: regions) {
uint32_t region_size = r.end - r.start;
char const *data = v.translate(r.start, region_size);
if(!data) throw CommandError("region 0x{:08x} .. 0x{:08x} is not "
"fully bound", r.start, r.end);
/* Reach the required alignemnt */
int i = 0;
while((r.start + i) % align != 0) i++;
/* Search patterns for (size) bytes inside (data) */
for(; i <= (int)region_size - (int)size; i += align) {
if(!matches(data + i, reference, pattern, size)) continue;
uint32_t start = r.start + i;
/* Flush pending matches if this new match is far away */
if(pending.size() > 0) {
auto const &p = pending[pending.size() - 1];
if(p.first + p.second + distance < start) {
Range r;
r.start = pending[0].first - distance;
r.end = p.first + p.second + distance;
if(output_started) fmt::print("...\n");
_h_hexdump(session, r, pending);
output_started = true;
pending.clear();
}
}
pending.emplace_back(start, size);
match_count++;
start += size;
if(match_count >= 128) break;
}
if(match_count >= 128) break;
}
/* Print the last pending elements */
if(pending.size()) {
auto const &p = pending[pending.size() - 1];
Range r = { pending[0].first-distance, p.first+p.second+distance };
if(output_started) fmt::print("...\n");
_h_hexdump(session, r, pending);
}
if(match_count == 0)
fmt::print("No occurrence found.\n");
else if(match_count < 128)
fmt::print("{} occurences found.\n", match_count);
else
fmt::print("Stopped after 128 occurrences.\n");
}
//---
// af4
//---
struct _af4_args {
uint32_t value;
std::vector<MemoryRegion> regions;
};
static _af4_args parse_af4(Session &session, Parser &parser)
{
_af4_args args;
args.value = parser.expr(session.current_space);
while(!parser.at_end()) {
args.regions.push_back(parser.region(session.current_space));
}
parser.end();
return args;
}
void _af4(Session &session, uint32_t value, std::vector<MemoryRegion> &regions)
{
uint32_t value_big_endian = htobe32(value);
char pattern[4] = { 1, 1, 1, 1 };
_afh(session, (char *)&value_big_endian, pattern, 4, 4, -1, regions);
}
[[gnu::constructor]] static void _(void)
{
shell_register_command("af4",
[](Session &s, Parser &p){
auto args = parse_af4(s, p);
_af4(s, args.value, args.regions); },
[](Session &s, Parser &p){ parse_af4(s, p); });
shell_register_command("afh",
[](Session &s, Parser &p) {
auto args = parse_afh(s, p);
_afh(s, args.reference.c_str(), args.pattern.c_str(), args.size,
args.align, args.distance, args.regions); },
[](Session &s, Parser &p){ parse_afh(s, p); });
}

73
shell/commands.h Normal file
View File

@ -0,0 +1,73 @@
//---
// fxos-shell.commands: Internal functions for each command
//---
#ifndef _FXOS_COMMANDS_H
#define _FXOS_COMMANDS_H
#include <vector>
#include <string>
#include <fxos/memory.h>
#include "session.h"
//---
// Meta commands
//---
/* Include the specified list of files *AFTER* the current command ends */
void _dot(Session &s, std::vector<std::string> const &files, bool absolute);
//---
// Virtual spaces
//---
/* List specified spaces (all if no arguments) */
void _vl(Session &s, std::vector<std::string> const &spaces);
/* Select the current virtual space */
void _vs(Session &s, std::string const &name);
/* Create a new virtual space from a target */
void _vct(Session &s, std::string filename, std::string space="");
/* Map a file into one or more regions inside the current virtual space */
void _vm(Session &s, std::string filename, std::vector<MemoryRegion> regions);
//---
// Evaluation
//---
/* Evaluate a numerical input in the current virtual space */
void _e(Session &s, std::vector<long> const &values);
/* Evaluate a numerical input in another virtual space */
void _ev(Session &s, std::string space_name, std::vector<long> const &values);
//---
// Goto
//---
/* Goto specified location */
void _g(Session &s, long location);
//---
// Hexdump
//---
/* Dump memory contents of a byte-boundary region */
void _h(Session &s, FxOS::MemoryRegion r);
/* Advanced memory view. Displays the specified region, highlighting provided
selections (in the form of <start, size> pairs). */
void _h_hexdump(Session &s, Range r,
std::vector<std::pair<uint32_t,int>> selections = {});
//---
// Information
//---
/* General information about the OS */
void _io(Session &s, std::string space="");
#endif /* _FXOS_COMMANDS_H */

161
shell/d.cpp Normal file
View File

@ -0,0 +1,161 @@
#include "shell.h"
#include "parser.h"
#include "commands.h"
#include "errors.h"
#include <fmt/core.h>
#include <fxos/disassembly.h>
#include <fxos/util.h>
#include <fxos/log.h>
#include <fxos/disasm-passes/cfg.h>
#include <fxos/disasm-passes/pcrel.h>
#include <fxos/disasm-passes/syscall.h>
#include <fxos/disasm-passes/print.h>
static void disassemble(Session &session, Disassembly &disasm,
std::vector<std::string> const &passes, uint32_t address)
{
for(auto pass: passes)
{
auto start = timer_start();
log(LOG "Running pass %s...\\", pass);
if(pass == "cfg")
{
CfgPass p(disasm);
p.run(address);
}
else if(pass == "pcrel")
{
PcrelPass p(disasm);
p.run();
}
else if(pass == "syscall")
{
OS *os = session.current_space->os_analysis();
if(os) {
SyscallPass p(disasm, os);
p.run();
}
}
else if(pass == "print")
{
PrintPass p(disasm);
p.promote_pcjump_loc = PrintPass::Promote;
p.promote_pcrel_loc = PrintPass::Promote;
p.promote_pcrel_value = PrintPass::Promote;
p.promote_syscall = PrintPass::Promote;
p.promote_syscallname = PrintPass::Append;
p.promote_symbol = PrintPass::Append;
p.promote_pcaddr_loc = PrintPass::Promote;
p.run();
}
log(LOG "%s", timer_format(timer_end(start)));
}
}
//---
// d
//---
static uint32_t parse_d(Session &session, Parser &parser)
{
uint32_t address = session.current_space->cursor;
if(!parser.at_end())
address = parser.expr(session.current_space);
parser.end();
return address;
}
void _d(Session &session, uint32_t address)
{
session.require_vspace();
FxOS::Disassembly disasm(*session.current_space);
if(address & 1) {
fmt::print("address 0x{:08x} is odd, starting at 0x{:08x}\n",
address, address+1);
address++;
}
disassemble(session, disasm,
{ "cfg", "pcrel", "constprop", "syscall", "print" }, address);
}
//---
// dr
//---
static Range parse_dr(Session &session, Parser &parser)
{
Range range = parser.range(session.current_space);
parser.end();
return range;
}
void _dr(Session &session, Range range)
{
session.require_vspace();
FxOS::Disassembly disasm(*session.current_space);
if(range.start == range.end) return;
if(range.start & 1) {
fmt::print("address 0x{:08x} is odd, starting at 0x{:08x}\n",
range.start, range.start+1);
range.start++;
}
if(range.end & 1) {
fmt::print("address 0x{:08x} is odd, ending at 0x{:08x}\n",
range.end, range.end-1);
range.end--;
}
/* Load the block into memory */
for(uint32_t pc = range.start; pc < range.end; pc += 2)
disasm.readins(pc);
disassemble(session, disasm, { "pcrel", "constprop", "syscall", "print" },
-1);
}
//---
// dtl
//---
static std::string parse_dtl(Session &session, Parser &parser)
{
std::string filename = parser.str();
parser.end();
return session.file(filename);
}
void _dtl(Session &, std::string filename)
{
Buffer buf(filename);
FxOS::load_instructions(buf);
}
//---
// Command registration
//---
[[gnu::constructor]] static void _(void)
{
shell_register_command("d",
[](Session &s, Parser &p){ _d(s, parse_d(s, p)); },
[](Session &s, Parser &p){ parse_d(s, p); });
shell_register_command("dr",
[](Session &s, Parser &p){ _dr(s, parse_dr(s, p)); },
[](Session &s, Parser &p){ parse_dr(s, p); });
shell_register_command("dtl",
[](Session &s, Parser &p){ _dtl(s, parse_dtl(s, p)); },
[](Session &s, Parser &p){ parse_dtl(s, p); });
}

111
shell/e.cpp Normal file
View File

@ -0,0 +1,111 @@
#include "shell.h"
#include "parser.h"
#include "commands.h"
#include <fmt/core.h>
#include <optional>
//---
// e
//---
static std::vector<long> parse_e(Session &session, Parser &parser)
{
std::vector<long> values;
while(!parser.at_end()) {
values.push_back(parser.expr(session.current_space));
}
parser.end();
return values;
}
void _e(Session &session, std::vector<long> const &values)
{
for(long value: values) {
/* Hexa format */
int length = (labs(value) <= (1ll << 32) ? 8 : 16) + 2 + (value < 0);
std::string format = fmt::format("{{:#0{}x}}", length);
fmt::print(format, value);
long a = abs(value);
if(a <= 100 || a % 100 <= 1 || a % 100 >= 99)
fmt::print(" = {}", a);
VirtualSpace *space = session.current_space;
if(space) {
std::optional<std::string> opt;
/* Promote to syscall ID */
OS *os = space->os_analysis();
int syscall_id;
if(os && (syscall_id = os->find_syscall(value)) != -1) {
fmt::print(" = %{:03x}", syscall_id);
opt = space->symbols.query(Symbol::Syscall, syscall_id);
if(opt) fmt::print(" = {}", *opt);
}
opt = space->symbols.query(Symbol::Address, value);
if(opt) fmt::print(" = {}", *opt);
}
fmt::print("\n");
}
}
//---
// ev
//---
struct _ev_args {
std::string space_name;
std::vector<long> values;
};
static _ev_args parse_ev(Session &session, Parser &parser)
{
_ev_args args {};
args.space_name = parser.symbol("space_name");
session.require_vspace(args.space_name);
VirtualSpace *space = &session.spaces.at(args.space_name);
while(!parser.at_end()) {
args.values.push_back(parser.expr(space));
}
parser.end();
return args;
}
void _ev(Session &, std::string, std::vector<long> const &values)
{
for(long value: values) {
/* Hexa format */
int length = (labs(value) <= (1ll << 32) ? 8 : 16) + 2 + (value < 0);
std::string format = fmt::format("{{:#0{}x}}", length);
fmt::print(format, value);
long a = abs(value);
if(a <= 100 || a % 100 <= 1 || a % 100 >= 99)
fmt::print(" = {}", a);
fmt::print("\n");
}
}
[[gnu::constructor]] static void _(void)
{
shell_register_command("e",
[](Session &s, Parser &p){ _e(s, parse_e(s, p)); },
[](Session &s, Parser &p){ parse_e(s, p); });
shell_register_command("ev",
[](Session &s, Parser &p){
auto const &args = parse_ev(s, p);
_ev(s, args.space_name, args.values); },
[](Session &s, Parser &p){ parse_ev(s, p); });
}

42
shell/errors.h Normal file
View File

@ -0,0 +1,42 @@
//---
// fxos-shell.errors: Exceptions with particular handling
//---
#ifndef FXOS_ERRORS_H
#define FXOS_ERRORS_H
#include <fxos/errors.h>
#include <fmt/core.h>
#include <string>
/* Generic command error, either parsing or execution, which prevents a command
from accomplishing its intended task. */
class CommandError: public std::exception
{
public:
CommandError(std::string what): m_what(what) {}
/* Build directly from format arguments */
template <typename... Args>
CommandError(std::string const &format_str, Args&&... args):
m_what(fmt::format(format_str, args...)) {}
char const *what() const noexcept override {
return m_what.c_str();
}
private:
std::string m_what;
};
/* Command error when no virtual space is selected */
class NoVirtualSpaceError: public CommandError
{
public:
NoVirtualSpaceError():
CommandError("no virtual space") {}
NoVirtualSpaceError(std::string name):
CommandError("no virtual space '{}'", name) {}
};
#endif /* FXOS_ERRORS_H */

29
shell/g.cpp Normal file
View File

@ -0,0 +1,29 @@
#include "shell.h"
#include "parser.h"
#include "commands.h"
#include <fmt/core.h>
//---
// g
//---
static long parse_g(Session &session, Parser &parser)
{
long addr = parser.expr(session.current_space);
parser.end();
return addr;
}
void _g(Session &session, long value)
{
session.require_vspace();
session.current_space->cursor = (value & 0xffffffff);
}
[[gnu::constructor]] static void _(void)
{
shell_register_command("g",
[](Session &s, Parser &p){ _g(s, parse_g(s, p)); },
[](Session &s, Parser &p){ parse_g(s, p); });
}

85
shell/h.cpp Normal file
View File

@ -0,0 +1,85 @@
#include "shell.h"
#include "parser.h"
#include "commands.h"
#include "theme.h"
#include <fmt/core.h>
using Selections = std::vector<std::pair<uint32_t,int>>;
static bool is_selected(uint32_t address, Selections const &sel)
{
for(auto &p: sel) {
if(address >= p.first && address < p.first + p.second) return true;
}
return false;
}
void _h_hexdump(Session &session, Range r, Selections sel)
{
session.require_vspace();
VirtualSpace &v = *session.current_space;
uint32_t start = r.start & ~0xf;
uint32_t end = (r.end + 15) & ~0xf;
for(uint32_t pos = start; pos != end; pos += 16) {
fmt::print(theme(3), " 0x{:08x} ", pos);
/* Hexdump */
for(int offset = 0; offset < 16; offset++) {
if(!(offset % 4)) fmt::print(" ");
uint32_t addr = pos + offset;
if(addr < r.start || addr >= r.end) {
fmt::print(" ");
}
else if(is_selected(addr, sel)) {
fmt::print(theme(13), "{:02x}", v.read_u8(addr));
}
else {
fmt::print("{:02x}", v.read_u8(addr));
}
}
/* ASCII */
fmt::print(" ");
for(int offset = 0; offset < 16; offset++) {
if(!(offset % 4)) fmt::print(" ");
uint32_t addr = pos + offset;
if(addr < r.start || addr >= r.end) {
fmt::print(" ");
}
else {
int c = v.read_u8(addr);
fmt::print(theme(11), "{:c}", isprint(c) ? c : '.');
}
}
fmt::print("\n");
}
}
//---
// h
//---
static Range parse_h(Session &session, Parser &parser)
{
Range r = parser.range(session.current_space, 0, 128);
parser.end();
return r;
}
void _h(Session &session, Range r)
{
_h_hexdump(session, r, {});
}
[[gnu::constructor]] static void _(void)
{
shell_register_command("h",
[](Session &s, Parser &p){ _h(s, parse_h(s, p)); },
[](Session &s, Parser &p){ parse_h(s, p); });
}

178
shell/i.cpp Normal file
View File

@ -0,0 +1,178 @@
#include "shell.h"
#include "parser.h"
#include "commands.h"
#include "errors.h"
#include "theme.h"
#include <algorithm>
#include <fmt/core.h>
//---
// io
//---
static char const *info_str =
"OS type: %s\n"
"\n"
"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 (0x%000008x) : 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 -> %08x (%s memory)\n";
static std::string parse_io(Session &, Parser &parser)
{
std::string name = parser.at_end() ? "" : parser.symbol("vspace_name");
parser.end();
return name;
}
void _io(Session &session, std::string name)
{
VirtualSpace *space = nullptr;
session.require_vspace(name);
if(name == "")
space = session.current_space;
else
space = &session.spaces[name];
OS *os = space->os_analysis();
if(!os) throw CommandError("os analysis on '{}' failed", name);
printf(info_str, (os->type == OS::FX ? "FX" : "CG"),
&os->bootcode_timestamp, os->bootcode_timestamp.value.c_str(),
&os->bootcode_checksum, os->bootcode_checksum,
&os->serial_number, os->serial_number.value.c_str(),
&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 = os->syscall_table_address();
uint32_t first_noncall = space->read_u32(syscall_table +
4 * os->syscall_count());
printf(syscall_str, (os->type == OS::FX ? 0x8001007c : 0x8002007c),
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");
}
//---
// is
//---
struct _is_args {
std::string vspace_name;
bool sort;
};
static struct _is_args parse_is(Session &, Parser &parser)
{
struct _is_args args {};
parser.option('s', [&args](std::string const &value){
args.sort = true;
});
parser.accept_options();
std::string name = parser.at_end() ? "" : parser.symbol("vspace_name");
parser.accept_options();
parser.end();
return args;
}
struct SyscallInfo {
uint32_t address;
int id;
};
bool operator < (const SyscallInfo &left, const SyscallInfo &right)
{
return (left.address < right.address) || (left.id < right.id);
}
void _is(Session &session, std::string vspace_name, bool sort)
{
VirtualSpace *space = nullptr;
session.require_vspace(vspace_name);
if(vspace_name == "")
space = session.current_space;
else
space = &session.spaces[vspace_name];
OS *os = space->os_analysis();
if(!os) throw CommandError("os analysis on '{}' failed", vspace_name);
int total = os->syscall_count();
auto info = std::make_unique<SyscallInfo[]>(total);
for(int i = 0; i < total; i++) {
info[i] = (SyscallInfo){ .address = os->syscall(i), .id = i };
}
if(sort) std::sort(&info[0], &info[total]);
for(int i = 0; i < total; i++) {
fmt::print(theme(3), " 0x{:08x}", info[i].address);
fmt::print(theme(10), (total >= 0x1000 ? " %{:04x}":" %{:03x}"),
info[i].id);
fmt::print("\n");
}
}
//---
// Command registration
//---
[[gnu::constructor]] static void _(void)
{
shell_register_command("io",
[](Session &s, Parser &p){ _io(s, parse_io(s, p)); },
[](Session &s, Parser &p){ parse_io(s, p); });
shell_register_command("is",
[](Session &s, Parser &p){
auto args = parse_is(s, p);
_is(s, args.vspace_name, args.sort); },
[](Session &s, Parser &p){ parse_is(s, p); });
}

241
shell/lexer.l Normal file
View File

@ -0,0 +1,241 @@
%{
#include "parser.h"
#include <string>
#include <cstdarg>
#include <cctype>
#include <stack>
#include <deque>
#include <fmt/core.h>
#include <fxos/errors.h>
/* Values to attach to the token */
typedef Token::Attribute YYSTYPE;
YYSTYPE yylval;
/* Buffer for literal strings */
#define LEX_STR_MAX 1023
static char STR_buffer[LEX_STR_MAX + 1];
/* Number of characters written to string in STR mode so far */
static int STR_len = 0;
/* Input details for a single file being read */
struct Input {
/* File path or a dummy string at top-level */
std::string filename;
/* Current line */
int line;
/* Whether the file is from REPL */
bool repl;
/* Current parenthesis depth */
int expr_depth;
/* Flex buffer (this is a pointer) */
YY_BUFFER_STATE buffer;
};
/* Stack of queues of files being lexed. A new entry on the stack is added
whenever the include (.) command is used. A queue is created with the
its arguments, which are waiting to be read in sequence. */
static std::stack<std::deque<Input>> lex_inputs;
/* Input used in the last token, in case it is needed after the last command
has been read but is still being executed */
static Input lex_last_used_input {};
#define YY_USER_ACTION \
if(!lex_idle()) lex_last_used_input = lex_current_input();
bool lex_idle()
{
return lex_inputs.size() == 0;
}
/* Current input. Throws when there is no current input */
static Input &lex_current_input()
{
return lex_inputs.top().front();
}
std::string lex_last_used_file()
{
Input const &in = lex_last_used_input;
return (in.repl ? "" : in.filename);
}
/* Push a new queue of inputs. */
static void lex_push(std::deque<Input> queue)
{
if(!queue.size()) return;
lex_inputs.push(queue);
yy_switch_to_buffer(lex_current_input().buffer);
}
/* Pop a single input when end-of-file is reached. Return whether to stop. */
static bool lex_pop()
{
if(!lex_inputs.size()) return true;
auto &q = lex_inputs.top();
q.pop_front();
if(q.empty()) lex_inputs.pop();
if(!lex_inputs.size()) {
return true;
}
else {
yy_switch_to_buffer(lex_current_input().buffer);
return false;
}
}
/* Error messages and exceptions */
static void err(char const *format, ...)
{
static char buf[1024];
va_list args;
va_start(args, format);
vsnprintf(buf, 1024, format, args);
va_end(args);
Input const &in = lex_current_input();
throw FxOS::SyntaxError(in.filename.c_str(), in.line, buf);
}
/* Parse numerical values */
long parse_num(char const *text)
{
/* Determine base */
int base = 10;
if(text[0] == '0' && text[1] == 'x') base = 16, text += 2;
else if(text[0] == '0' && text[1] == 'b') base = 2, text += 2;
char *end;
long val = strtoul(text, &end, base);
if(*end == 'k') val <<= 10;
if(*end == 'M') val <<= 20;
if(*end == 'G') val <<= 30;
return val;
}
%}
%option prefix="shell"
%option noyywrap
%option nounput
%x EXPR
%x STR
/* Used in error rules for word boundary violations */
letter [a-zA-Z0-9_.%]
num_hex 0x[a-zA-Z0-9]+
num_dec (0d)?[0-9]+
num_bin 0b[0-1]+
num_suffix [kMG]
num ({num_hex}|{num_dec}|{num_bin}){num_suffix}?
syscall [%][a-fA-F0-9]+
symbol [a-zA-Z_.][a-zA-Z0-9_.]*
option -[a-zA-Z](=[^ ]+)?
space [ \t]+
%%
<*>"#"[^\n]* ;
<*>{space} { return T::SPC; }
<*>[\n] { lex_current_input().line++;
lex_current_input().expr_depth = 0;
BEGIN(INITIAL); return T::SEPARATOR; }
<*>[;] { lex_current_input().expr_depth = 0;
BEGIN(INITIAL); return T::SEPARATOR; }
<EXPR>"+" { return '+'; }
<EXPR>"-" { return '-'; }
<EXPR>"*" { return '*'; }
<EXPR>"/" { return '/'; }
<EXPR>"%" { return '%'; }
<EXPR>")" { int d = std::max(lex_current_input().expr_depth - 1, 0);
lex_current_input().expr_depth = d;
if(d == 0) BEGIN(INITIAL); return ')'; }
<EXPR>">>" { return '>'; }
<EXPR>"<<" { return '<'; }
<*>"$" { return '$'; }
<*>"(" { lex_current_input().expr_depth++;
BEGIN(EXPR); return '('; }
":" { return ':'; }
".." { return '.'; }
["] { BEGIN(STR); STR_len = 0; }
<STR>\" { BEGIN(INITIAL); STR_buffer[STR_len] = 0;
yylval.STRING = strdup(STR_buffer); return T::STRING; }
<STR>[^\\\n"]+ {
int length = std::min(yyleng, LEX_STR_MAX - STR_len);
memcpy(STR_buffer + STR_len, yytext, length);
STR_len += length; }
<STR>\\n { if(STR_len < LEX_STR_MAX) STR_buffer[STR_len++] = '\n'; }
<STR>\\t { if(STR_len < LEX_STR_MAX) STR_buffer[STR_len++] = '\t'; }
<*>{syscall} { yylval.NUM = strtoul(yytext+1, NULL, 16); return T::SYSCALL; }
<*>{num} { yylval.NUM = parse_num(yytext); return T::NUM; }
<*>{symbol} { yylval.SYMBOL = strdup(yytext); return T::SYMBOL; }
{option} { yylval.OPTION = strdup(yytext); return T::OPTION; }
/* Generic error and word boundaries violations */
<*>{syscall}{letter} { err("invalid syscall number '%s'", yytext); }
<*>{num}{letter} { err("invalid numerical value '%s'", yytext); }
<*>. { err("invalid token near '%s'", yytext); }
<<EOF>> { if(lex_pop()) return T::END; }
%%
void lex_repl(std::string input)
{
/* yy_scan_bytes() switches buffer, but lex_push() will fix that */
Input in = {
.filename = "<command-line>",
.line = 1,
.repl = true,
.expr_depth = 0,
.buffer = yy_scan_bytes(input.c_str(), input.size()),
};
lex_push({ in });
}
void lex_include(std::vector<std::string> paths)
{
std::deque<Input> ins;
for(auto const &path: paths) {
yyin = fopen(path.c_str(), "r");
if(!yyin) {
fmt::print("\x1b[31;1merror:\x1b cannot read '{}'\n", path);
return;
}
ins.push_back({
.filename = path,
.line = 1,
.repl = false,
.expr_depth = 0,
.buffer = yy_create_buffer(yyin, YY_BUF_SIZE),
});
}
lex_push(ins);
}
Token lex_read()
{
Token t;
t.type = yylex();
t.value = yylval;
return t;
}

304
shell/main.cpp Normal file
View File

@ -0,0 +1,304 @@
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <csignal>
#include <csetjmp>
#include <readline/readline.h>
#include <readline/history.h>
#include <fmt/core.h>
#include <fmt/color.h>
#include "session.h"
#include "shell.h"
#include "theme.h"
#include "parser.h"
#include "commands.h"
struct CommandSpec {
ShellFunction function;
ShellCompleter completer;
};
static std::map<std::string, CommandSpec> commands;
void shell_register_command(std::string name, ShellFunction function,
ShellCompleter completer)
{
commands[name] = (CommandSpec){ function, completer };
}
static Session global_session;
//---
// Autocompletion
//---
char *complete_command(char const *text, int state, Session &)
{
static auto it = commands.begin();
if(state == 0) it = commands.begin();
while(it != commands.end()) {
char const *current = (it++)->first.c_str();
if(!strncmp(current, text, strlen(text)))
return strdup(current);
}
return NULL;
}
char *complete_vspace(char const *text, int state, Session &session)
{
static auto it = session.spaces.begin();
if(state == 0) it = session.spaces.begin();
while(it != session.spaces.end()) {
char const *current = (it++)->first.c_str();
if(!strncmp(current, text, strlen(text)))
return strdup(current);
}
return NULL;
}
char *complete_region(char const *text, int state, Session &)
{
static auto const &all = MemoryRegion::all();
static auto it = all.begin();
if(state == 0) it = all.begin();
while(it != all.end()) {
char const *current = (*it++)->name.c_str();
if(!strncmp(current, text, strlen(text)))
return strdup(current);
}
return NULL;
}
std::string autocomplete_category(Session &session, char const *line_buffer,
int point)
{
/* Parse partial input and try to get a category of suggestions */
std::string input(line_buffer, point);
lex_repl(input);
Parser p(true);
p.start();
/* Read commands until hitting an unfinished one */
try {
do {
/* First obtain command name */
p.skip_separators();
std::string cmd = p.symbol("command");
/* Then run parser on that command's completer */
if(commands.count(cmd) && commands[cmd].completer)
commands[cmd].completer(session, p);
} while(!lex_idle());
}
catch(Parser::CompleteCategory &e) {
// fmt::print("Completing: {}\n", e.category());
return e.category();
}
catch(Parser::SyntaxError &e) {
// fmt::print("Syntax error: {}\n", e.what());
}
return "";
}
char *autocomplete(char const *text, int state)
{
static char * (*current_completer)(char const *, int, Session &) = NULL;
if(state == 0) {
std::string category = autocomplete_category(global_session,
rl_line_buffer, rl_point);
if(category == "command")
current_completer = complete_command;
else if(category == "vspace_name")
current_completer = complete_vspace;
else if(category == "memory_region")
current_completer = complete_region;
else return strdup(category.c_str());
}
if(current_completer == NULL)
return NULL;
char *rc = current_completer(text, state, global_session);
if(rc == NULL) current_completer = NULL;
return rc;
}
//---
// Shell routine
//---
static std::vector<std::string> parse_dot(Session &, Parser &parser)
{
std::vector<std::string> files;
while(!parser.at_end())
files.push_back(parser.str());
return files;
}
void _dot(Session &s, std::vector<std::string> const &files, bool absolute)
{
std::vector<std::string> paths;
for(auto const &file: files) {
paths.push_back(absolute ? file : s.file(file).string());
}
lex_include(paths);
}
static std::string read_interactive(Session const &s, bool &leave)
{
std::string prompt = "(empty)> ";
if(s.current_space) {
std::string name = "(none)";
for(auto &it: s.spaces) {
if(&it.second == s.current_space)
name = it.first;
}
prompt = fmt::format("{} $=0x{:08x}> ", name, s.current_space->cursor);
}
/* We need to insert RL_PROMPT_{START,END}_IGNORE into the color
formatting, so we trick a little bit by using a space */
std::string color = fmt::format(theme(9), " ");
int space_pos = color.find(' ');
std::string SC = color.substr(0, space_pos);
std::string EC = color.substr(space_pos+1);
std::string SI(1, RL_PROMPT_START_IGNORE);
std::string EI(1, RL_PROMPT_END_IGNORE);
prompt = SI+SC+EI + prompt + SI+EC+EI;
/* Get a command to execute */
char *cmd_ptr = readline(prompt.c_str());
if(!cmd_ptr) {
leave = true;
return "";
}
std::string cmd = cmd_ptr;
add_history(cmd_ptr);
free(cmd_ptr);
return cmd;
}
int main(int argc, char **argv)
{
Session &s = global_session;
/* Register a no-op quit command for auto-completion */
shell_register_command("q", NULL, NULL);
/* Register the include command */
shell_register_command(".",
[](Session &s, Parser &p) { _dot(s, parse_dot(s, p), false); },
[](Session &s, Parser &p) { parse_dot(s, p); });
theme_builtin("tomorrow-night");
rl_completion_entry_function = autocomplete;
rl_basic_word_break_characters =
" \t\n\"\\'`@$><=;|&{(" /* Readline's default */
"+-*%)"; /* Word breaks with special characters in fxos */
/* Load path into the session */
char const *fxos_path_env = std::getenv("FXOS_PATH");
if(fxos_path_env) {
std::string fxos_path = fxos_path_env;
size_t offset=0, end;
while(true) {
offset = fxos_path.find_first_not_of(":", offset);
if(offset >= fxos_path.size()) break;
end = fxos_path.find_first_of(":", offset);
s.path.push_back(fxos_path.substr(offset, end-offset));
offset = end;
}
}
else {
fmt::print("warning: no FXOS_PATH in environment, using WD\n");
s.path.push_back(fs::current_path());
}
/* Clear readline input when receiving SIGINT */
static sigjmp_buf sigint_buf;
std::signal(SIGINT, [](int) {
rl_free_line_state();
rl_cleanup_after_signal();
rl_line_buffer[rl_point = rl_end = rl_mark = 0] = 0;
printf("\n");
siglongjmp(sigint_buf, 1);
});
rl_set_signals();
/* Load command history */
char histfile[128] = "";
if(std::getenv("HOME"))
snprintf(histfile, 128, "%s/.fxos_history", std::getenv("HOME"));
if(histfile[0]) read_history(histfile);
/* Load fxosrc files from all library folders */
std::vector<std::string> fxosrc_files;
for(auto const &path: s.path) {
if(fs::exists(path / "fxosrc"))
fxosrc_files.push_back(path / "fxosrc");
}
if(fxosrc_files.size() > 0)
_dot(s, fxosrc_files, true);
/* Shell main loop */
while(true) {
/* Get a command if there isn't a file being executed */
if(lex_idle()) {
while(sigsetjmp(sigint_buf, 1) != 0);
bool leave = false;
std::string cmdline = read_interactive(s, leave);
if(leave) break;
lex_repl(cmdline);
}
Parser parser(false);
try {
/* Read the next command from the lexer */
parser.start();
parser.skip_separators();
/* Read the command name */
if(parser.lookahead().type == T::END)
continue;
std::string cmd = parser.symbol("command");
if(cmd == "q") break;
if(cmd == "p") {
parser.dump_command();
}
else if(commands.count(cmd))
{
if(commands[cmd].function) commands[cmd].function(s, parser);
}
else fmt::print("\e[31;1merror:\e[0m unknown command '{}'\n", cmd);
}
catch(std::exception &e) {
fmt::print("\e[31;1merror:\e[0m {}\n", e.what());
}
/* Exhaust command input (if not all used by the command) */
parser.exhaust_until_separator();
}
/* Save command history */
if(histfile[0]) write_history(histfile);
return 0;
}

382
shell/parser.cpp Normal file
View File

@ -0,0 +1,382 @@
#include "parser.h"
#include "errors.h"
#include <stdexcept>
#include <fmt/core.h>
//---
// Lexing tools
//---
std::string T::str() const
{
switch((int)m_name) {
case T::END:
return "end of file";
case T::SPC:
return "whitespace";
case T::SEPARATOR:
return "end of command";
case T::NUM:
return "number";
case T::SYMBOL:
return "symbol";
case T::SYSCALL:
return "syscall number";
case T::OPTION:
return "command option";
case T::STRING:
return "string";
case '.':
return "'..'";
case '<':
return "'<<'";
case '>':
return "'>>'";
default:
return fmt::format("'{}'", (char)m_name);
}
}
std::string Token::str() const
{
switch((int)this->type) {
case T::END:
return "end of file";
case T::SPC:
return "whitespace";
case T::SEPARATOR:
return "end of command";
case T::NUM:
return fmt::format("number {}", this->value.NUM);
case T::SYMBOL:
return fmt::format("symbol '{}'", this->value.SYMBOL);
case T::SYSCALL:
return fmt::format("syscall number %{:03x}", this->value.NUM);
case T::OPTION:
return fmt::format("command option '{}'", this->value.OPTION);
case T::STRING:
return fmt::format("string '{}'", this->value.STRING);
case '.':
return "'..'";
case '<':
return "'<<'";
case '>':
return "'>>'";
default:
return fmt::format("'{}'", (char)this->type);
}
}
//---
// Parser
//---
Parser::Parser(bool complete):
m_complete {complete}, m_la {-1,0}, m_expr_space {nullptr}
{
}
void Parser::start()
{
feed();
}
Token Parser::feed(bool ignore_spaces)
{
Token t = m_la;
do m_la = lex_read();
while(ignore_spaces && m_la.type == T::SPC);
return t;
}
Token Parser::lookahead() const
{
return m_la;
}
bool Parser::at_end() const
{
/* When parsing to complete we try to go infinitely far, so we ignore
T::END. We supply T::SEPARATOR to complete the compound commands */
if(m_complete) return (m_la.type == T::SEPARATOR);
return (m_la.type == T::SEPARATOR || m_la.type == T::END);
}
void Parser::end()
{
m_options.clear();
if(!at_end())
throw SyntaxError("expected end of command");
}
void Parser::skip_separators()
{
while(m_la.type == T::SEPARATOR) feed();
}
void Parser::exhaust_until_separator()
{
while(!at_end()) {
try {
feed();
}
catch(FxOS::SyntaxError const &e) {}
}
}
void Parser::dump_command()
{
while(!at_end()) {
Token t = m_la;
if(t.type == T::NUM)
fmt::print("NUM {:#x}\n", t.value.NUM);
else if(t.type == T::SYSCALL)
fmt::print("SYSCALL %{:03x}\n", t.value.NUM);
else if(t.type == T::SYMBOL)
fmt::print("SYMBOL '{}'\n", t.value.SYMBOL);
else if(t.type == T::OPTION)
fmt::print("OPTION '{}'\n", t.value.OPTION);
else if(t.type == T::STRING)
fmt::print("STRING '{}'\n", t.value.STRING);
else if(t.type == '>')
fmt::print(">>\n");
else if(t.type == '<')
fmt::print("<<\n");
else
fmt::print("{}\n", (char)t.type);
feed();
}
}
//---
// Main parsing rules
//---
void Parser::option(char name, OptionHandler callback)
{
m_options.emplace(name, callback);
}
Token Parser::expect(std::initializer_list<T> types, bool ignore_spaces)
{
bool correct_type = false;
for(T type: types) {
if(m_la.type == type) correct_type = true;
}
if(!correct_type) {
static char err[128];
int offset = sprintf(err, "expected ");
for(auto it = types.begin(); it != types.end(); it++) {
offset += sprintf(err + offset, "%s%s%s",
(it != types.begin() && it + 1 == types.end() ? "or " : ""),
(*it).str().c_str(),
(it + 1 == types.end() ? "; " : ", "));
}
sprintf(err + offset, "instead found %s", m_la.str().c_str());
throw SyntaxError(err);
}
Token t = feed(ignore_spaces);
return t;
}
Token Parser::expect(T type, bool ignore_spaces)
{
return expect({ type }, ignore_spaces);
}
std::string Parser::symbol(std::string category)
{
/* Auto-complete a symbol which has not been typed yet */
if(m_complete && m_la.type == T::END)
throw CompleteCategory(category, "");
if(!m_complete)
return expect(T::SYMBOL).value.SYMBOL;
/* When completing, we have to know whether the symbol is finished (ie.
there is another token after, including a space) or not */
Token t = expect(T::SYMBOL, false);
std::string sym = t.value.SYMBOL;
free(t.value.SYMBOL);
/* This will throw only if there is no token after, not even spaces */
if(m_la.type == T::END)
throw CompleteCategory(category, sym);
/* If a space is found, get rid of it */
if(m_la.type == T::SPC) feed();
return sym;
}
std::string Parser::str()
{
Token t = expect(T::STRING);
std::string str = t.value.STRING;
free(t.value.STRING);
return str;
}
long Parser::num()
{
return expect(T::NUM).value.NUM;
}
Range Parser::range(VirtualSpace *space, long before, long after)
{
long start = expr(space);
/* Accept non-rangs if (before) and (after) are provided */
if(m_la.type != ':' && m_la.type != '.' && before >= 0 && after >= 0)
return { start - before, start + after };
Token t = expect({ ':', '.' });
long other = expr(space);
Range r = { start, (t.type == ':' ? start + other : other) };
if(r.start > r.end) std::swap(r.start, r.end);
return r;
}
FxOS::MemoryRegion Parser::region(VirtualSpace *space, long before, long after)
{
if(m_la.type == '$' || m_la.type == '(' || m_la.type == '-'
|| m_la.type == T::NUM || m_la.type == T::SYSCALL) {
Range r = range(space, before, after);
return FxOS::MemoryRegion("<anonymous>", r.start, r.end-1, false);
}
/* Return symbol by default so that an empty input autocompletes to a
memory region name */
try {
return FxOS::MemoryRegion(symbol("memory_region"));
}
catch(std::invalid_argument const &e) {
/* Ignore nonexisting regions when autocompleting */
if(m_complete) return FxOS::MemoryRegion("<anonymous>", 0, 1, false);
else throw e;
}
}
void Parser::accept_options()
{
while(m_la.type == T::OPTION) {
Token t = expect(T::OPTION);
char *text = t.value.OPTION;
char name = text[1];
if(!m_options.count(name)) {
throw CommandError("unrecognized option -{}", name);
}
std::string value = "";
if(strnlen(text, 3) >= 3) value = text + 3;
m_options[name](value);
}
}
//---
// Parsing rules for expressions
//---
long Parser::atom()
{
Token t = expect({ '$', '(', '-', T::SYMBOL, T::NUM, T::SYSCALL });
if(t.type == T::SYMBOL) {
long val = 0; /* TODO: Query symbol and return its value */
if(m_expr_space) {
auto const &opt = m_expr_space->symbols.lookup(t.value.SYMBOL);
if(opt && opt->type == FxOS::Symbol::Address) {
val = opt->value;
}
else if(opt && opt->type == FxOS::Symbol::Syscall) {
OS *os = m_expr_space->os_analysis();
if(os && (int)opt->value < os->syscall_count())
val = os->syscall(opt->value);
}
else {
throw CommandError("symbol '{}' is undefined", t.value.SYMBOL);
}
}
else throw CommandError("cannot query symbol '{}', no virtual space",
t.value.SYMBOL);
if(m_complete && m_la.type == T::END)
throw CompleteCategory("expression", t.value.SYMBOL);
return val;
}
else if(t.type == T::SYSCALL) {
if(!m_expr_space) return 0;
OS *os = m_expr_space->os_analysis();
if(!os || t.value.NUM < 0 || t.value.NUM > os->syscall_count())
return 0;
return os->syscall(t.value.NUM);
}
else if(t.type == '$') {
return (m_expr_space ? m_expr_space->cursor : 0);
}
else if(t.type == '-') {
return -atom();
}
else if(t.type == T::NUM) {
return t.value.NUM;
}
else {
long v = term();
expect(')');
return v;
}
}
long Parser::factor()
{
long v = atom();
while(m_la.type == '*' || m_la.type == '/' || m_la.type == '%') {
int op = expect({ '*', '/', '%' }).type;
if(op == '*')
v *= atom();
else if(op == '/')
v /= atom();
else if(op == '%')
v %= atom();
}
return v;
}
long Parser::term()
{
long v = factor();
while(m_la.type == '+' || m_la.type == '-') {
int op = expect({ '+', '-' }).type;
if(op == '+')
v += factor();
else if(op == '-')
v -= factor();
}
return v;
}
long Parser::expr(VirtualSpace *space)
{
m_expr_space = space;
long val = atom();
m_expr_space = nullptr;
return val;
}

209
shell/parser.h Normal file
View File

@ -0,0 +1,209 @@
//---
// fxos-shell.parser: Command-line parser
//---
#ifndef FXOS_PARSER_H
#define FXOS_PARSER_H
#include <cstdint>
#include <string>
#include <stdexcept>
#include <initializer_list>
#include <vector>
#include <map>
#include <functional>
#include <fxos/memory.h>
#include <fxos/vspace.h>
#include "session.h"
/* Token values; just integers, but with conversion to and from string */
class T
{
public:
enum TokenName: int8_t {
END = 0,
SPC = 1,
SEPARATOR = 2,
NUM = 3,
SYMBOL = 4,
SYSCALL = 5,
OPTION = 6,
STRING = 7,
/* Other tokens using character literals:
+ - * / % ( ) $ :
'.' for "..", '<' for "<<", '>' for ">>" */
};
/* Construction with no value and explicit value */
T() = default;
constexpr T(int name): m_name((TokenName)name) {}
constexpr T(TokenName name): m_name(name) {}
/* Conversion to integer value, comparisons */
constexpr operator TokenName() const noexcept { return m_name; }
/* Conversion to string */
std::string str() const;
private:
TokenName m_name;
};
/* Range with start included and end excluded */
struct Range
{
long start;
long end;
};
/* Token with their data */
struct Token
{
union Attribute {
/* Value of a numerical constant or syscall number */
long NUM;
/* Name of a symbol (to free(3) after use) */
char *SYMBOL;
/* Text of an option (to free(3) after use) */
char *OPTION;
/* Text of a literal string */
char *STRING;
};
/* Token type and value */
T type;
Attribute value;
/* Conversion to string */
std::string str() const;
};
//---
// Lexer interface
//---
/* Whether all files have finished executing */
bool lex_idle();
/* Path of file that provided the last token, "" if at top-level */
std::string lex_last_used_file();
/* Lex this input coming from the interactive command-line */
void lex_repl(std::string input);
/* Lex the input coming from these files, sequentially */
void lex_include(std::vector<std::string> files);
/* For the parser: get one token from the current source */
Token lex_read();
//---
// Parser interface for commands
//---
/* Parser class, used both for final command parsing and completion */
class Parser
{
public:
Parser(bool complete);
/* Start parsing, should be called just once after constructing */
void start();
/* Whether the end of a command has been reached */
bool at_end() const;
/* Lookahead token */
Token lookahead() const;
/* Require that command is finished */
void end();
/* Expect a token of this type, or of any of the listed types */
Token expect(T type, bool ignore_spaces=true);
Token expect(std::initializer_list<T> types, bool ignore_spaces=true);
using OptionHandler = std::function<void(std::string const &value)>;
/* Specify an option to be accepted until the next SEPARATOR or END token
(basically for the current command). The function will be called when
the option is found during a call to accept_options(). */
void option(char name, OptionHandler callback);
/* Read a symbol from the specified category; the category name is used to
determine completion options on the command-line */
std::string symbol(std::string category="");
/* Literal string */
std::string str();
/* Read a numerical constant, or an expression. Expression uses the space
to access the program counter and query symbol values */
long num();
long expr(VirtualSpace *space);
/* Read a range; again $ and symbols are interpreted. If (before) and
(after) are both specified, a single value will also be accepted, and
the range [value+before, value.after) will be returned. */
Range range(VirtualSpace *space, long before=-1, long after=-1);
/* Read a memory region (allows both ranges and symbolic names) */
FxOS::MemoryRegion region(VirtualSpace *space, long before=-1,
long after=-1);
/* Read options, if any. Never fails */
void accept_options();
/* Skip separators, used when reading several commands at once */
void skip_separators();
/* Read everything until end of command (used when commands abort) */
void exhaust_until_separator();
/* Exhaust input until separators, used for debugging the lexer */
void dump_command();
//---
// Completion system
//---
class SyntaxError: public std::invalid_argument
{
public:
SyntaxError(char const *what): std::invalid_argument(what) {}
};
class CompleteCategory: public std::exception
{
public:
CompleteCategory(std::string category, std::string value) {
m_category = category;
m_value = value;
}
std::string category() const {
return m_category;
}
std::string value() const {
return m_value;
}
private:
std::string m_category, m_value;
};
//---
// Internal stuff
//---
private:
/* Read a token into the lookahead and return the former*/
Token feed(bool ignore_spaces=true);
/* Parsing rules for expressions */
long term();
long factor();
long atom();
/* true if we're completing a partial command, false if we're parsing a
finished one */
bool m_complete;
/* Lookahead token */
Token m_la;
/* Virtual space for evaluation of symbols, can only be non-null during
calls to expr() */
VirtualSpace *m_expr_space;
/* Options (retained until next SEPARATOR or END) */
std::map<char, OptionHandler> m_options;
};
#endif /* FXOS_PARSER_H */

101
shell/s.cpp Normal file
View File

@ -0,0 +1,101 @@
#include "shell.h"
#include "parser.h"
#include "commands.h"
#include "errors.h"
#include "theme.h"
#include <fxos/symbols.h>
#include <fmt/core.h>
//---
// sl
//---
static void parse_sl(Session &, Parser &parser)
{
parser.end();
}
void _sl(Session &session)
{
session.require_vspace();
for(auto const &s: session.current_space->symbols.symbols) {
if(s.type == FxOS::Symbol::Syscall && s.value < 0x1000) {
fmt::print(theme(10), " %{:03x}", s.value);
}
else if(s.type == FxOS::Symbol::Syscall) {
fmt::print(theme(10), " %{:04x}", s.value);
}
else {
fmt::print(" 0x{:08x}", s.value);
}
fmt::print(" {}\n", s.name);
}
}
//---
// sa
//---
static FxOS::Symbol parse_sa(Session &session, Parser &parser)
{
session.require_vspace();
FxOS::Symbol s;
s.type = FxOS::Symbol::Address;
s.value = parser.expr(session.current_space);
s.name = parser.symbol();
parser.end();
return s;
}
void _sa(Session &session, Symbol s)
{
session.require_vspace();
session.current_space->symbols.add(s);
}
//---
// ss
//---
static FxOS::Symbol parse_ss(Session &session, Parser &parser)
{
session.require_vspace();
FxOS::Symbol s;
s.type = FxOS::Symbol::Syscall;
s.value = parser.expect({ T::SYSCALL }).value.NUM;
s.name = parser.symbol();
parser.end();
return s;
}
void _ss(Session &session, Symbol s)
{
session.require_vspace();
session.current_space->symbols.add(s);
}
//---
// Command registration
//---
[[gnu::constructor]] static void _(void)
{
shell_register_command("sl",
[](Session &s, Parser &p){ parse_sl(s, p); _sl(s); },
[](Session &s, Parser &p){ parse_sl(s, p); });
shell_register_command("sa",
[](Session &s, Parser &p){ _sa(s, parse_sa(s, p)); },
[](Session &s, Parser &p){ parse_sa(s, p); });
shell_register_command("ss",
[](Session &s, Parser &p){ _ss(s, parse_ss(s, p)); },
[](Session &s, Parser &p){ parse_ss(s, p); });
}

63
shell/session.cpp Normal file
View File

@ -0,0 +1,63 @@
#include "session.h"
#include "parser.h"
#include "errors.h"
#include <fmt/core.h>
Session::Session():
spaces {}
{
this->current_space = nullptr;
this->pc = -1;
}
std::string Session::space_name(std::string prefix, bool force_suffix)
{
if(!force_suffix && this->spaces.count(prefix) == 0)
return prefix;
int counter = 0;
while(1) {
std::string name = fmt::format("{}_{}", prefix, counter);
if(!this->spaces.count(name))
return name;
counter++;
}
}
void Session::require_vspace(std::string name) const
{
if(name == "" && !this->current_space)
throw NoVirtualSpaceError();
if(name != "" && !this->spaces.count(name))
throw NoVirtualSpaceError(name);
}
fs::path Session::file(std::string name)
{
#define err(...) std::runtime_error(fmt::format(__VA_ARGS__))
fs::path relative_to = lex_last_used_file();
if(name[0] != '/') {
fs::path rel = relative_to.parent_path() / name;
if(!fs::exists(rel))
throw err("cannot find {} in current directory", name);
if(fs::symlink_status(rel).type() != fs::file_type::regular)
throw err("{} is neither a file nor a symlink to a file", name);
return rel;
}
fs::path filepath(name.substr(1));
for(auto const &p: this->path) {
if(!fs::exists(p / filepath)) continue;
/* This file exists, it can be the only one selected */
if(fs::symlink_status(p / filepath).type() != fs::file_type::regular)
throw err("{} is neither a file nor a symlink to a file", name);
return p / filepath;
}
throw err("cannot find {} in library", name);
#undef err
}

60
shell/session.h Normal file
View File

@ -0,0 +1,60 @@
//---
// fxos-shell.session: All the data used in an interactive session
//---
#ifndef FXOS_SESSION_H
#define FXOS_SESSION_H
#include <fxos/vspace.h>
#include <map>
#include <string>
#include <filesystem>
using namespace FxOS;
namespace fs = std::filesystem;
struct Session
{
/* Empty session with a single empty virtual space */
Session();
//---
// Environment
//---
/* Search path, folders from FXOS_LIBRARY essentially */
std::vector<fs::path> path;
/* Find file by name by searching through the path */
fs::path file(std::string name);
//---
// Virtual spaces
//---
/* Virtual spaces organized by name */
std::map<std::string, VirtualSpace> spaces;
/* Find a virtual space by name */
VirtualSpace *get_space(std::string name);
/* Current virtual space */
VirtualSpace *current_space;
/* Find an unused name from this prefix. If force_suffix is set, always
adds a suffix even if the name itself is free */
std::string space_name(std::string prefix, bool force_suffix=false);
/* Ensure a virtual space is loaded and selected. If not, throws a
NoVirtualSpaceError. */
void require_vspace(std::string name="") const;
//---
//
//---
/* Current cursor location */
uint32_t pc;
};
#endif /* FXOS_SESSION_H */

23
shell/shell.h Normal file
View File

@ -0,0 +1,23 @@
//---
// fxos-shell.shell: Top-level shell functions
//---
#ifndef FXOS_SHELL_H
#define FXOS_SHELL_H
#include <utility>
#include <string>
#include "session.h"
#include "parser.h"
/* Type of functions to be called as shell commands */
using ShellFunction = void (*)(Session &session, Parser &parser);
/* Type of functions to complete arguments to shell commands */
using ShellCompleter = void (*)(Session &session, Parser &parser);
/* Register a command in the shell */
void shell_register_command(std::string name, ShellFunction function,
ShellCompleter completer);
#endif /* FXOS_SHELL_H */

55
shell/theme.cpp Normal file
View File

@ -0,0 +1,55 @@
#include "theme.h"
#include <cstring>
#include <map>
#include <array>
uint32_t base16[16];
bool theme_load(std::string filename)
{
FILE *fp = fopen(filename.c_str(), "r");
if(!fp) return false;
for(int i = 0; i < 16; i++) base16[i] = 0xffffff;
char entry[64], value[256];
int colors_found = 0;
while(fscanf(fp, " %64[^:]: \"%64[^\"\n]\"", entry, value) == 2)
{
if(strlen(entry) == 6 && !strncmp(entry, "base0", 5))
{
int index = entry[5]-'0' - 7*(entry[5]>='A') - 32*(entry[5]>='a');
colors_found++;
sscanf(value, "%x", &base16[index]);
}
}
fclose(fp);
return true;
}
fmt::text_style theme(int color)
{
return fg(fmt::rgb(base16[color & 0xf]));
}
//---
// Built-in themes
//---
static std::map<std::string, std::array<uint32_t,16>> builtin_themes = {
/* Tomorrow Night by Chris Kempson (http://chriskempson.com) */
{ "tomorrow-night", {
0x1d1f21, 0x282a2e, 0x373b41, 0x969896, 0xb4b7b4, 0xc5c8c6,
0xe0e0e0, 0xffffff, 0xcc6666, 0xde935f, 0xf0c674, 0xb5bd68,
0x8abeb7, 0x81a2be, 0xb294bb, 0xa3685a, }},
};
bool theme_builtin(std::string name)
{
if(!builtin_themes.count(name)) return false;
for(int i = 0; i < 16; i++)
base16[i] = builtin_themes[name][i];
return true;
}

21
shell/theme.h Normal file
View File

@ -0,0 +1,21 @@
//---
// fxos-shell.theme: Color themes
//---
#ifndef FXOS_THEME_H
#define FXOS_THEME_H
#include <cstdint>
#include <fmt/core.h>
#include <fmt/color.h>
/* Load a theme from file name */
bool theme_load(std::string name);
/* Get base color for current theme */
fmt::text_style theme(int color);
/* Load a built-in theme */
bool theme_builtin(std::string name);
#endif /* FXOS_THEME_H */

201
shell/v.cpp Normal file
View File

@ -0,0 +1,201 @@
#include "shell.h"
#include "theme.h"
#include "parser.h"
#include "commands.h"
#include "errors.h"
#include <fmt/core.h>
#include <fmt/color.h>
#include <fxos/memory.h>
using namespace FxOS;
//---
// vl
//---
static std::vector<std::string> parse_vl(Session &, Parser &parser)
{
std::vector<std::string> spaces;
while(!parser.at_end())
spaces.push_back(parser.symbol("vspace_name"));
parser.end();
return spaces;
}
static void show_vspace(std::string name, VirtualSpace &s, Session &session)
{
bool is_current = (&s == session.current_space);
if(is_current) fmt::print("* ");
fmt::print(theme(11), "{}\n", name);
if(s.bindings().size() == 0) {
fmt::print(" (no bindings)\n");
return;
}
fmt::print(" Region Start End File\n");
for(auto &b: s.bindings()) {
MemoryRegion const *ref = MemoryRegion::region_for(b.region);
fmt::print(" {:<7s} 0x{:08x} .. 0x{:08x}", (ref ? ref->name : ""),
b.region.start, b.region.end);
if(b.buffer.path != "")
fmt::print(" {}", b.buffer.path);
fmt::print("\n");
}
}
void _vl(Session &session, std::vector<std::string> const &args)
{
if(!args.size()) {
for(auto &it: session.spaces)
show_vspace(it.first, it.second, session);
}
else for(auto &name: args) {
session.require_vspace(name);
show_vspace(name, session.spaces[name], session);
}
}
//---
// vs
//---
static std::string parse_vs(Session &, Parser &parser)
{
std::string name = parser.symbol("vspace_name");
parser.end();
return name;
}
void _vs(Session &session, std::string const &name)
{
session.require_vspace(name);
session.current_space = &session.spaces[name];
}
//---
// vc
//---
static std::string parse_vc(Session &, Parser &parser)
{
std::string name = "";
if(!parser.at_end()) name = parser.symbol();
parser.end();
return name;
}
static void _vc(Session &session, std::string name)
{
if(name == "") name = session.space_name("space", true);
else name = session.space_name(name, false);
/* Create an empty space and select it */
session.spaces.emplace(name, VirtualSpace {});
_vs(session, name);
_g(session, 0x80000000);
}
//---
// vct
//---
struct _vct_args {
std::string path;
std::string vspace_name;
};
static _vct_args parse_vct(Session &, Parser &parser)
{
_vct_args args;
args.path = parser.str();
if(!parser.at_end()) args.vspace_name = parser.symbol();
parser.end();
return args;
}
void _vct(Session &session, std::string filename, std::string name)
{
fs::path path = session.file(filename);
if(name == "")
name = session.space_name(path.filename(), false);
else if(session.spaces.count(name))
throw CommandError("virtual space '{}' already exists", name);
_vc(session, name);
_dot(session, { filename }, false);
}
//---
// vm
//---
struct _vm_args {
std::string path;
std::vector<MemoryRegion> regions;
};
static _vm_args parse_vm(Session &session, Parser &parser)
{
_vm_args args {};
args.path = parser.str();
/* TODO: vm: Allow specifying address without a size */
do args.regions.push_back(parser.region(session.current_space));
while(!parser.at_end());
parser.end();
return args;
}
void _vm(Session &session, std::string file, std::vector<MemoryRegion> regions)
{
session.require_vspace();
std::string path = session.file(file);
Buffer contents(path);
/* If no files are loaded yet, set the PC to the first loaded region */
if(!session.current_space->bindings().size())
session.pc = regions[0].start;
for(auto &r: regions)
session.current_space->bind_region(r, contents);
}
//---
// Command registration
//---
[[gnu::constructor]] static void _(void)
{
shell_register_command("vl",
[](Session &s, Parser &p) { _vl(s, parse_vl(s, p)); },
[](Session &s, Parser &p){ parse_vl(s, p); });
shell_register_command("vs",
[](Session &s, Parser &p) { _vs(s, parse_vs(s, p)); },
[](Session &s, Parser &p){ parse_vs(s, p); });
shell_register_command("vc",
[](Session &s, Parser &p) { _vc(s, parse_vc(s, p)); },
[](Session &s, Parser &p){ parse_vc(s, p); });
shell_register_command("vct",
[](Session &s, Parser &p) {
auto const &args = parse_vct(s, p);
_vct(s, args.path, args.vspace_name); },
[](Session &s, Parser &p){ parse_vct(s, p); });
shell_register_command("vm",
[](Session &s, Parser &p) {
auto const &args = parse_vm(s, p);
_vm(s, args.path, args.regions); },
[](Session &s, Parser &p){ parse_vm(s, p); });
}