replace CLI with WIP shell (huge commit)
This commit is contained in:
parent
0a659cc6e6
commit
3b684389e9
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
}
|
|
@ -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 */
|
|
@ -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");
|
||||
}
|
|
@ -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 ® = 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);
|
||||
}
|
||||
}
|
||||
}
|
338
fxos/main.cpp
338
fxos/main.cpp
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
{
|
||||
|
|
|
@ -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 */
|
|
@ -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 */
|
|
@ -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 */
|
||||
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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 */
|
||||
|
|
104
lib/library.cpp
104
lib/library.cpp
|
@ -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 */
|
|
@ -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 */
|
||||
|
|
|
@ -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 */
|
|
@ -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 */
|
|
@ -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 */
|
|
@ -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 */
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
#include <fxos/symbols.h>
|
||||
#include <fxos/vspace.h>
|
||||
|
||||
namespace FxOS {
|
||||
|
||||
|
|
|
@ -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 */
|
||||
|
|
|
@ -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 ®ion) 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 ®ion,Buffer const &buffer)
|
||||
{
|
||||
Binding b(region, buffer);
|
||||
|
|
|
@ -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> ®ions)
|
||||
{
|
||||
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> ®ions)
|
||||
{
|
||||
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); });
|
||||
}
|
|
@ -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 */
|
|
@ -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); });
|
||||
}
|
|
@ -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); });
|
||||
}
|
|
@ -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 */
|
|
@ -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); });
|
||||
}
|
|
@ -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); });
|
||||
}
|
|
@ -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); });
|
||||
}
|
|
@ -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;
|
||||
}
|
|
@ -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;
|
||||
}
|
|
@ -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;
|
||||
}
|
|
@ -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 */
|
|
@ -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); });
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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 */
|
|
@ -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 */
|
|
@ -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;
|
||||
}
|
|
@ -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 */
|
|
@ -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); });
|
||||
}
|
Loading…
Reference in New Issue