From 3b684389e9ecbd14ef4fef0ded3b7c340e774da8 Mon Sep 17 00:00:00 2001 From: Lephenixnoir Date: Fri, 4 Mar 2022 11:29:33 +0000 Subject: [PATCH] replace CLI with WIP shell (huge commit) --- CMakeLists.txt | 40 +-- fxos/disassembly.cpp | 156 ------------ fxos/fxos-cli.h | 23 -- fxos/info.cpp | 77 ------ fxos/library.cpp | 49 ---- fxos/main.cpp | 338 +------------------------ include/fxos/disasm-passes/print.h | 10 +- include/fxos/disassembly.h | 4 + include/fxos/library.h | 57 ----- include/fxos/load.h | 53 ---- include/fxos/log.h | 2 +- include/fxos/memory.h | 9 +- include/fxos/os.h | 4 +- include/fxos/symbols.h | 7 +- include/fxos/vspace.h | 28 ++- lib/library.cpp | 104 -------- lib/load-asm.l | 9 +- lib/load-header.l | 134 ---------- lib/load-symbols.l | 111 --------- lib/load-target.l | 156 ------------ lib/memory.cpp | 12 +- lib/passes/print.cpp | 13 +- lib/symbols.cpp | 1 + lib/util.cpp | 1 + lib/vspace.cpp | 30 ++- shell/a.cpp | 229 +++++++++++++++++ shell/commands.h | 73 ++++++ shell/d.cpp | 161 ++++++++++++ shell/e.cpp | 111 +++++++++ shell/errors.h | 42 ++++ shell/g.cpp | 29 +++ shell/h.cpp | 85 +++++++ shell/i.cpp | 178 ++++++++++++++ shell/lexer.l | 241 ++++++++++++++++++ shell/main.cpp | 304 +++++++++++++++++++++++ shell/parser.cpp | 382 +++++++++++++++++++++++++++++ shell/parser.h | 209 ++++++++++++++++ shell/s.cpp | 101 ++++++++ shell/session.cpp | 63 +++++ shell/session.h | 60 +++++ shell/shell.h | 23 ++ shell/theme.cpp | 55 +++++ shell/theme.h | 21 ++ shell/v.cpp | 201 +++++++++++++++ 44 files changed, 2676 insertions(+), 1320 deletions(-) delete mode 100644 fxos/disassembly.cpp delete mode 100644 fxos/fxos-cli.h delete mode 100644 fxos/info.cpp delete mode 100644 fxos/library.cpp delete mode 100644 include/fxos/library.h delete mode 100644 include/fxos/load.h delete mode 100644 lib/library.cpp delete mode 100644 lib/load-header.l delete mode 100644 lib/load-symbols.l delete mode 100644 lib/load-target.l create mode 100644 shell/a.cpp create mode 100644 shell/commands.h create mode 100644 shell/d.cpp create mode 100644 shell/e.cpp create mode 100644 shell/errors.h create mode 100644 shell/g.cpp create mode 100644 shell/h.cpp create mode 100644 shell/i.cpp create mode 100644 shell/lexer.l create mode 100644 shell/main.cpp create mode 100644 shell/parser.cpp create mode 100644 shell/parser.h create mode 100644 shell/s.cpp create mode 100644 shell/session.cpp create mode 100644 shell/session.h create mode 100644 shell/shell.h create mode 100644 shell/theme.cpp create mode 100644 shell/theme.h create mode 100644 shell/v.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 9ea7862..0451980 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -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 diff --git a/fxos/disassembly.cpp b/fxos/disassembly.cpp deleted file mode 100644 index b01214a..0000000 --- a/fxos/disassembly.cpp +++ /dev/null @@ -1,156 +0,0 @@ -#include "fxos-cli.h" -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include - -using namespace FxOS; -using namespace FxOS::Log; - -int disassembly(Library &library, VirtualSpace &space, char const *ref, - std::vector passes) -{ - Disassembly disasm(space); - int len=0; - - /* Observe the space only if it has an OS mapped */ - std::unique_ptr os; - try { - os = std::make_unique(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. %: 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 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; -} diff --git a/fxos/fxos-cli.h b/fxos/fxos-cli.h deleted file mode 100644 index e89514b..0000000 --- a/fxos/fxos-cli.h +++ /dev/null @@ -1,23 +0,0 @@ -//--- -// fxos-cli: A disassembler and OS reverse-engineering tool -//--- - -#ifndef FXOS_CLI_H -#define FXOS_CLI_H - -#include -#include -#include -#include - -/* 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 passes); - -#endif /* FXOS_CLI_H */ diff --git a/fxos/info.cpp b/fxos/info.cpp deleted file mode 100644 index e734676..0000000 --- a/fxos/info.cpp +++ /dev/null @@ -1,77 +0,0 @@ -#include "fxos-cli.h" -#include -#include -#include -#include - -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"); -} diff --git a/fxos/library.cpp b/fxos/library.cpp deleted file mode 100644 index c92c6b3..0000000 --- a/fxos/library.cpp +++ /dev/null @@ -1,49 +0,0 @@ -#include "fxos-cli.h" -#include -#include - -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); - } - } -} diff --git a/fxos/main.cpp b/fxos/main.cpp index b59276d..ca52dee 100644 --- a/fxos/main.cpp +++ b/fxos/main.cpp @@ -1,27 +1,3 @@ -#include "fxos-cli.h" - -#include -#include -#include -#include -#include -#include -#include - -#include -#include - -#include -#include -#include -#include -#include -#include - -namespace fs = std::filesystem; -using namespace FxOS; -using namespace FxOS::Log; - static std::string help_string = colors(R"( usage: fxos<> [library<>|info<>|disasm<>|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] [options...] - -f, --full Run all analysis passes on (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 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; -} diff --git a/include/fxos/disasm-passes/print.h b/include/fxos/disasm-passes/print.h index 9a823b0..abebaf4 100644 --- a/include/fxos/disasm-passes/print.h +++ b/include/fxos/disasm-passes/print.h @@ -10,15 +10,15 @@ #include #include -#include namespace FxOS { +class OS; + class PrintPass: public InstructionDisassemblyPass { public: - PrintPass(Disassembly &disasm, - std::vector 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 const &m_symtables; + std::vector> m_symtables; /* Query symbol tables, most recent first */ std::optional symquery(Symbol::Type type, uint32_t value); /* OS for the target, to mark syscalls before instructions */ - std::unique_ptr m_os; + OS *m_os; /* Last printed address (for ellipses) */ uint32_t m_last_address; diff --git a/include/fxos/disassembly.h b/include/fxos/disassembly.h index 69a4715..965dd8d 100644 --- a/include/fxos/disassembly.h +++ b/include/fxos/disassembly.h @@ -24,6 +24,10 @@ namespace FxOS { instructions with parameters, not manually. See . */ 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 { diff --git a/include/fxos/library.h b/include/fxos/library.h deleted file mode 100644 index 47f01cb..0000000 --- a/include/fxos/library.h +++ /dev/null @@ -1,57 +0,0 @@ -//--- -// fxos.library: Management of resource files into libraries -//--- - -#ifndef FXOS_LIBRARY_H -#define FXOS_LIBRARY_H - -#include -#include - -#include -#include -#include - -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 &paths() { - return m_paths; - } - /* Targets loaded by exploration */ - const std::map &targets() { - return m_targets; - } - /* Simple list of assembly tables */ - const std::vector> &asm_tables() { - return m_asmtables; - } - /* List of symbol tables */ - const std::vector &sym_tables() { - return m_symtables; - } - -private: - std::vector m_paths; - std::map m_targets; - std::vector> m_asmtables; - std::vector m_symtables; -}; - -} /* namespace FxOS */ - -#endif /* FXOS_LIBRARY_H */ diff --git a/include/fxos/load.h b/include/fxos/load.h deleted file mode 100644 index 8ee4345..0000000 --- a/include/fxos/load.h +++ /dev/null @@ -1,53 +0,0 @@ -//--- -// fxos.load: Data file lexers and loaders -//--- - -#ifndef LIBFXOS_LOAD_H -#define LIBFXOS_LOAD_H - -#include -#include -#include - -#include -#include - -namespace FxOS { - -using Header = std::map; - -/* 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 */ diff --git a/include/fxos/log.h b/include/fxos/log.h index 9278f0d..cea912b 100644 --- a/include/fxos/log.h +++ b/include/fxos/log.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 */ diff --git a/include/fxos/memory.h b/include/fxos/memory.h index 2e8c981..3c6d41c 100644 --- a/include/fxos/memory.h +++ b/include/fxos/memory.h @@ -60,12 +60,13 @@ struct MemoryRegion static MemoryRegion const XRAM; static MemoryRegion const YRAM; - /* All standard regions. */ - static std::array const all(); + /* All standard regions */ + static std::array 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(); diff --git a/include/fxos/os.h b/include/fxos/os.h index ba96fff..a36f96a 100644 --- a/include/fxos/os.h +++ b/include/fxos/os.h @@ -5,14 +5,14 @@ #ifndef LIBFXOS_OS_H #define LIBFXOS_OS_H -#include #include - #include #include namespace FxOS { +class VirtualSpace; + class OS { public: diff --git a/include/fxos/symbols.h b/include/fxos/symbols.h index 83e485b..871e74e 100644 --- a/include/fxos/symbols.h +++ b/include/fxos/symbols.h @@ -10,11 +10,12 @@ #include #include -#include 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 symbols; diff --git a/include/fxos/vspace.h b/include/fxos/vspace.h index 64ee2bc..e1b3606 100644 --- a/include/fxos/vspace.h +++ b/include/fxos/vspace.h @@ -7,10 +7,13 @@ #include #include +#include +#include #include #include #include +#include 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 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 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 ) or custom. @@ -141,6 +157,8 @@ private: std::vector m_bindings; /* Buffers owned by the target (when loaded from description) */ std::vector m_buffers; + /* Current OS analyzer */ + std::unique_ptr m_os; }; } /* namespace FxOS */ diff --git a/lib/library.cpp b/lib/library.cpp deleted file mode 100644 index 4c07932..0000000 --- a/lib/library.cpp +++ /dev/null @@ -1,104 +0,0 @@ -#include -#include - -#include -#include -#include - -#include -#include - -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 */ diff --git a/lib/load-asm.l b/lib/load-asm.l index 7728d2e..90b7916 100644 --- a/lib/load-asm.l +++ b/lib/load-asm.l @@ -1,11 +1,11 @@ %{ -#include #include #include #include #include #include +#include /* 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 */ diff --git a/lib/load-header.l b/lib/load-header.l deleted file mode 100644 index de0b971..0000000 --- a/lib/load-header.l +++ /dev/null @@ -1,134 +0,0 @@ -%{ -#include -#include -#include - -#include -#include -#include -#include - -/* 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; } - -<> { 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 */ diff --git a/lib/load-symbols.l b/lib/load-symbols.l deleted file mode 100644 index 3d26bb3..0000000 --- a/lib/load-symbols.l +++ /dev/null @@ -1,111 +0,0 @@ -%{ -#include -#include -#include -#include - -#include - -/* 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); } -<> { 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 */ diff --git a/lib/load-target.l b/lib/load-target.l deleted file mode 100644 index 8f7a03e..0000000 --- a/lib/load-target.l +++ /dev/null @@ -1,156 +0,0 @@ -%{ -#include -#include -#include - -#include -#include -#include -#include - -/* 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); } -<> { return -1; } - -%% - -namespace FxOS { - -static int expect(std::vector 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 */ diff --git a/lib/memory.cpp b/lib/memory.cpp index f4019cc..7dd354d 100644 --- a/lib/memory.cpp +++ b/lib/memory.cpp @@ -118,7 +118,7 @@ std::array const MemoryRegion::m_all = { &R::RS, &R::ILRAM, &R::XRAM, &R::YRAM, }; -std::array const MemoryRegion::all() +std::array 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 */ diff --git a/lib/passes/print.cpp b/lib/passes/print.cpp index 7f1f799..7d838b9 100644 --- a/lib/passes/print.cpp +++ b/lib/passes/print.cpp @@ -10,18 +10,17 @@ namespace FxOS { -PrintPass::PrintPass(Disassembly &disasm, - std::vector 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(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) diff --git a/lib/symbols.cpp b/lib/symbols.cpp index 55b1a8d..d7f87de 100644 --- a/lib/symbols.cpp +++ b/lib/symbols.cpp @@ -1,4 +1,5 @@ #include +#include namespace FxOS { diff --git a/lib/util.cpp b/lib/util.cpp index 07c9b88..82854ee 100644 --- a/lib/util.cpp +++ b/lib/util.cpp @@ -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 */ diff --git a/lib/vspace.cpp b/lib/vspace.cpp index 827d685..5cfae79 100644 --- a/lib/vspace.cpp +++ b/lib/vspace.cpp @@ -65,15 +65,13 @@ Addressable 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(*this); + } + return m_os.get(); +} + void VirtualSpace::bind_region(MemoryRegion const ®ion,Buffer const &buffer) { Binding b(region, buffer); diff --git a/shell/a.cpp b/shell/a.cpp new file mode 100644 index 0000000..8e5b080 --- /dev/null +++ b/shell/a.cpp @@ -0,0 +1,229 @@ +#include "shell.h" +#include "parser.h" +#include "commands.h" +#include "errors.h" + +#include +#include + +//--- +// 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 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 ®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> 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 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 ®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); }); +} diff --git a/shell/commands.h b/shell/commands.h new file mode 100644 index 0000000..9bd45c8 --- /dev/null +++ b/shell/commands.h @@ -0,0 +1,73 @@ +//--- +// fxos-shell.commands: Internal functions for each command +//--- + +#ifndef _FXOS_COMMANDS_H +#define _FXOS_COMMANDS_H + +#include +#include + +#include +#include "session.h" + +//--- +// Meta commands +//--- + +/* Include the specified list of files *AFTER* the current command ends */ +void _dot(Session &s, std::vector const &files, bool absolute); + +//--- +// Virtual spaces +//--- + +/* List specified spaces (all if no arguments) */ +void _vl(Session &s, std::vector 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 regions); + +//--- +// Evaluation +//--- + +/* Evaluate a numerical input in the current virtual space */ +void _e(Session &s, std::vector const &values); + +/* Evaluate a numerical input in another virtual space */ +void _ev(Session &s, std::string space_name, std::vector 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 pairs). */ +void _h_hexdump(Session &s, Range r, + std::vector> selections = {}); + +//--- +// Information +//--- + +/* General information about the OS */ +void _io(Session &s, std::string space=""); + +#endif /* _FXOS_COMMANDS_H */ diff --git a/shell/d.cpp b/shell/d.cpp new file mode 100644 index 0000000..80969fe --- /dev/null +++ b/shell/d.cpp @@ -0,0 +1,161 @@ +#include "shell.h" +#include "parser.h" +#include "commands.h" +#include "errors.h" + +#include + +#include +#include +#include +#include +#include +#include +#include + +static void disassemble(Session &session, Disassembly &disasm, + std::vector 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); }); +} diff --git a/shell/e.cpp b/shell/e.cpp new file mode 100644 index 0000000..c11f009 --- /dev/null +++ b/shell/e.cpp @@ -0,0 +1,111 @@ +#include "shell.h" +#include "parser.h" +#include "commands.h" + +#include +#include + +//--- +// e +//--- + +static std::vector parse_e(Session &session, Parser &parser) +{ + std::vector values; + + while(!parser.at_end()) { + values.push_back(parser.expr(session.current_space)); + } + + parser.end(); + return values; +} + +void _e(Session &session, std::vector 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 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 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 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); }); +} diff --git a/shell/errors.h b/shell/errors.h new file mode 100644 index 0000000..e3527f3 --- /dev/null +++ b/shell/errors.h @@ -0,0 +1,42 @@ +//--- +// fxos-shell.errors: Exceptions with particular handling +//--- + +#ifndef FXOS_ERRORS_H +#define FXOS_ERRORS_H + +#include +#include +#include + +/* 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 + 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 */ diff --git a/shell/g.cpp b/shell/g.cpp new file mode 100644 index 0000000..1ce5995 --- /dev/null +++ b/shell/g.cpp @@ -0,0 +1,29 @@ +#include "shell.h" +#include "parser.h" +#include "commands.h" + +#include + +//--- +// 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); }); +} diff --git a/shell/h.cpp b/shell/h.cpp new file mode 100644 index 0000000..8cd00dc --- /dev/null +++ b/shell/h.cpp @@ -0,0 +1,85 @@ +#include "shell.h" +#include "parser.h" +#include "commands.h" +#include "theme.h" + +#include + +using Selections = std::vector>; + +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); }); +} diff --git a/shell/i.cpp b/shell/i.cpp new file mode 100644 index 0000000..1aa6368 --- /dev/null +++ b/shell/i.cpp @@ -0,0 +1,178 @@ +#include "shell.h" +#include "parser.h" +#include "commands.h" +#include "errors.h" +#include "theme.h" + +#include +#include + +//--- +// 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(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); }); +} diff --git a/shell/lexer.l b/shell/lexer.l new file mode 100644 index 0000000..998592d --- /dev/null +++ b/shell/lexer.l @@ -0,0 +1,241 @@ +%{ + +#include "parser.h" +#include +#include +#include +#include +#include + +#include +#include + +/* 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> 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 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; } + +"+" { return '+'; } +"-" { return '-'; } +"*" { return '*'; } +"/" { return '/'; } +"%" { return '%'; } +")" { int d = std::max(lex_current_input().expr_depth - 1, 0); + lex_current_input().expr_depth = d; + if(d == 0) BEGIN(INITIAL); return ')'; } +">>" { return '>'; } +"<<" { return '<'; } + +<*>"$" { return '$'; } +<*>"(" { lex_current_input().expr_depth++; + BEGIN(EXPR); return '('; } +":" { return ':'; } +".." { return '.'; } +["] { BEGIN(STR); STR_len = 0; } + +\" { BEGIN(INITIAL); STR_buffer[STR_len] = 0; + yylval.STRING = strdup(STR_buffer); return T::STRING; } +[^\\\n"]+ { + int length = std::min(yyleng, LEX_STR_MAX - STR_len); + memcpy(STR_buffer + STR_len, yytext, length); + STR_len += length; } +\\n { if(STR_len < LEX_STR_MAX) STR_buffer[STR_len++] = '\n'; } +\\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); } +<> { 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 = "", + .line = 1, + .repl = true, + .expr_depth = 0, + .buffer = yy_scan_bytes(input.c_str(), input.size()), + }; + lex_push({ in }); +} + +void lex_include(std::vector paths) +{ + std::deque 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; +} diff --git a/shell/main.cpp b/shell/main.cpp new file mode 100644 index 0000000..a2b889a --- /dev/null +++ b/shell/main.cpp @@ -0,0 +1,304 @@ +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "session.h" +#include "shell.h" +#include "theme.h" +#include "parser.h" +#include "commands.h" + +struct CommandSpec { + ShellFunction function; + ShellCompleter completer; +}; + +static std::map 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 parse_dot(Session &, Parser &parser) +{ + std::vector files; + + while(!parser.at_end()) + files.push_back(parser.str()); + + return files; +} + +void _dot(Session &s, std::vector const &files, bool absolute) +{ + std::vector 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 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; +} diff --git a/shell/parser.cpp b/shell/parser.cpp new file mode 100644 index 0000000..dc0ab17 --- /dev/null +++ b/shell/parser.cpp @@ -0,0 +1,382 @@ +#include "parser.h" +#include "errors.h" +#include +#include + +//--- +// 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 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("", 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("", 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; +} diff --git a/shell/parser.h b/shell/parser.h new file mode 100644 index 0000000..b77022d --- /dev/null +++ b/shell/parser.h @@ -0,0 +1,209 @@ +//--- +// fxos-shell.parser: Command-line parser +//--- + +#ifndef FXOS_PARSER_H +#define FXOS_PARSER_H + +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#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 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 types, bool ignore_spaces=true); + + using OptionHandler = std::function; + + /* 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 m_options; +}; + +#endif /* FXOS_PARSER_H */ diff --git a/shell/s.cpp b/shell/s.cpp new file mode 100644 index 0000000..0dc2607 --- /dev/null +++ b/shell/s.cpp @@ -0,0 +1,101 @@ +#include "shell.h" +#include "parser.h" +#include "commands.h" +#include "errors.h" +#include "theme.h" + +#include +#include + +//--- +// 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); }); +} diff --git a/shell/session.cpp b/shell/session.cpp new file mode 100644 index 0000000..0984403 --- /dev/null +++ b/shell/session.cpp @@ -0,0 +1,63 @@ +#include "session.h" +#include "parser.h" +#include "errors.h" +#include + +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 +} diff --git a/shell/session.h b/shell/session.h new file mode 100644 index 0000000..a998543 --- /dev/null +++ b/shell/session.h @@ -0,0 +1,60 @@ +//--- +// fxos-shell.session: All the data used in an interactive session +//--- + +#ifndef FXOS_SESSION_H +#define FXOS_SESSION_H + +#include + +#include +#include +#include + +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 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 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 */ diff --git a/shell/shell.h b/shell/shell.h new file mode 100644 index 0000000..0e184d3 --- /dev/null +++ b/shell/shell.h @@ -0,0 +1,23 @@ +//--- +// fxos-shell.shell: Top-level shell functions +//--- + +#ifndef FXOS_SHELL_H +#define FXOS_SHELL_H + +#include +#include + +#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 */ diff --git a/shell/theme.cpp b/shell/theme.cpp new file mode 100644 index 0000000..910146f --- /dev/null +++ b/shell/theme.cpp @@ -0,0 +1,55 @@ +#include "theme.h" +#include +#include +#include + +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> 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; +} diff --git a/shell/theme.h b/shell/theme.h new file mode 100644 index 0000000..2901c04 --- /dev/null +++ b/shell/theme.h @@ -0,0 +1,21 @@ +//--- +// fxos-shell.theme: Color themes +//--- + +#ifndef FXOS_THEME_H +#define FXOS_THEME_H + +#include +#include +#include + +/* 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 */ diff --git a/shell/v.cpp b/shell/v.cpp new file mode 100644 index 0000000..57f8189 --- /dev/null +++ b/shell/v.cpp @@ -0,0 +1,201 @@ +#include "shell.h" +#include "theme.h" +#include "parser.h" +#include "commands.h" +#include "errors.h" + +#include +#include + +#include + +using namespace FxOS; + +//--- +// vl +//--- + +static std::vector parse_vl(Session &, Parser &parser) +{ + std::vector 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 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 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 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); }); +}