From fcdcdba423e8206dbb45a3248e24aaef1c614da1 Mon Sep 17 00:00:00 2001 From: Lephenixnoir Date: Sun, 24 Sep 2023 11:53:39 +0200 Subject: [PATCH] fxos: create, select, remove, save and load binaries This includes migrating them from legacy vspaces, which currently migrates the bindings but not the symbol definitions. --- CMakeLists.txt | 4 +- include/fxos/binary.h | 51 +++++++-- include/fxos/project.h | 22 +++- include/fxos/util/Buffer.h | 3 + include/fxos/util/bson.h | 14 ++- include/fxos/vspace.h | 28 ++--- lib/binary.cpp | 71 +++++++++++++ lib/project.cpp | 96 ++++++++++++++++- lib/util/Buffer.cpp | 35 +++++- lib/util/bson.cpp | 18 ++++ lib/vspace.cpp | 38 ++++++- shell/b.cpp | 174 ++++++++++++++++++++++++++++++ shell/g.cpp | 31 ------ shell/i.cpp | 136 ++++++++++++++++++++++-- shell/main.cpp | 36 ++++--- shell/p.cpp | 65 ++++++++---- shell/session.cpp | 31 ++++++ shell/session.h | 35 +++++- shell/v.cpp | 212 ------------------------------------- 19 files changed, 775 insertions(+), 325 deletions(-) create mode 100644 lib/binary.cpp create mode 100644 shell/b.cpp delete mode 100644 shell/g.cpp delete mode 100644 shell/v.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index ad55fea..092e48f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -35,6 +35,7 @@ flex_target(LoadAsm lib/load-asm.l "${CMAKE_CURRENT_BINARY_DIR}/load-asm.yy.cpp" COMPILE_FLAGS -s) set(fxos_core_SOURCES + lib/binary.cpp lib/disassembly.cpp lib/lang.cpp lib/memory.cpp @@ -72,15 +73,14 @@ set(fxos_shell_SOURCES shell/theme.cpp shell/dot.cpp shell/a.cpp + shell/b.cpp shell/d.cpp shell/e.cpp - shell/g.cpp shell/h.cpp shell/i.cpp shell/m.cpp shell/p.cpp shell/s.cpp - shell/v.cpp ${FLEX_Lexer_OUTPUTS} ) diff --git a/include/fxos/binary.h b/include/fxos/binary.h index 919f315..57311c5 100644 --- a/include/fxos/binary.h +++ b/include/fxos/binary.h @@ -18,23 +18,25 @@ #include #include -// #include +#include #include #include #include namespace FxOS { -class VirtualSpace; +class OS; +struct BinaryObject; struct Mark; struct Variable; struct Function; struct Binary { - Binary(VirtualSpace &vpsace); - - // TODO: Constructors to load project files into a binary + /* Empty binary with an empty virtual space. */ + Binary() = default; + BSON serialize() const; + void deserialize(BSON const &); VirtualSpace &vspace() { @@ -45,11 +47,27 @@ struct Binary return m_vspace; } - // TODO: OS analysis in a binary + /* OS analysis (performed on-demand). Returns the new or cached OS + analysis results, nullptr if analysis failed. */ + OS *OSAnalysis(bool force = false); + // TODO: Platform information in a binary + // TODO: Implement OS analysis + // TODO: Add and manage objects + + std::map> const &objects() const + { + return m_objects; + } private: - VirtualSpace &m_vspace; + VirtualSpace m_vspace; + + /* OS analysis results */ + std::unique_ptr m_os; + + /* All binary objects */ + std::map> m_objects; }; /* Base structure for all /binary objets/, ie. program objects that can be @@ -170,7 +188,24 @@ struct Mark: public BinaryObject /* Binary object representing a non-empty piece of data. */ struct Variable: public BinaryObject { - // TODO: Variable types + /* Create a new variable at the given location with the given name. The + type defaults to an unsigned machine word (u32). */ + Variable(Binary &binary, u32 address, std::string const &name); + +#if 0 + DataType const *type() const + { + return m_type; + } + void setType(DataType const &type) + { + m_type = type; + BinaryObject::setSize(m_type.size()); + } + +private: + DataType const *m_type; +#endif }; } /* namespace FxOS */ diff --git a/include/fxos/project.h b/include/fxos/project.h index 8b6f1ba..f00f56d 100644 --- a/include/fxos/project.h +++ b/include/fxos/project.h @@ -53,6 +53,20 @@ struct Project /* Load from a folder. */ bool load(std::string const &path); + /* Create a binary based on the provided name. If there is a conflict, the + name will get a suffix like "_0", "_1", etc. Final name is returned. */ + std::string createBinary(std::string const &name = ""); + /* Get a binary by name. */ + Binary *getBinary(std::string const &name); + Binary const *getBinary(std::string const &name) const; + /* List of all binaries. */ + std::map const &binaries() const + { + return m_binaries; + } + /* Remove a binary from the project. */ + void removeBinary(std::string const &name); + private: /* Project name (no constraints but typically an identifier) */ std::string m_name; @@ -60,10 +74,14 @@ private: std::string m_path; /* Whether project needs a confirmation/save before closing */ bool m_dirty; - - // TODO: List of binaries + /* List of binaries in the project */ + std::map m_binaries; BSON serializeMetadata() const; + + /* Generate a new binary name. If there is a collision, adds a suffix like + "_0", "_1", etc. until a fresh name is found. */ + std::string generateBinaryName(std::string const &base = "") const; }; struct RecentProjects diff --git a/include/fxos/util/Buffer.h b/include/fxos/util/Buffer.h index f30109a..08ce09f 100644 --- a/include/fxos/util/Buffer.h +++ b/include/fxos/util/Buffer.h @@ -15,6 +15,7 @@ #ifndef FXOS_UTIL_BUFFER_H #define FXOS_UTIL_BUFFER_H +#include #include #include #include @@ -24,6 +25,8 @@ struct Buffer { /* Empty buffer with size 0 and no pointer */ Buffer(); + BSON serialize() const; + void deserialize(BSON const &); /* Empty buffer initialized with given byte */ Buffer(size_t size, int fill = 0x00); diff --git a/include/fxos/util/bson.h b/include/fxos/util/bson.h index 0f27dda..7ac5f71 100644 --- a/include/fxos/util/bson.h +++ b/include/fxos/util/bson.h @@ -235,7 +235,7 @@ struct BSON assert(isArray() && "wrong BSON accessor: getArrayElements"); return m_value.values; } - u8 *getBinary(size_t *size, int *subtype) const + u8 const *getBinary(size_t *size, int *subtype) const { assert(isBinary() && "wrong BSON accessor: getBinary"); if(size) @@ -284,6 +284,15 @@ struct BSON assert(isBinary() && "BSON::binarySubtype: not a Binary"); return m_subtype; } + /* Move binary data out of a value */ + u8 *moveBinary(size_t *size, int *subtype) + { + getBinary(size, subtype); + u8 *data = m_value.binary; + m_type = Type::Null; + m_value._i64 = 0; + return data; + } /* Get n-th element of array; must be in-bounds (or assertion failure) */ BSON &operator[](int i); @@ -375,6 +384,9 @@ struct BSONField /* Get a NUL-terminated heap copy of the name. */ char *getNameCopy() const; + /* Get a copy of the name. */ + std::string getName() const; + /* Dump field recursively to stream (for debugging purposes). */ void dump(FILE *fp, int depth = 0) const; diff --git a/include/fxos/vspace.h b/include/fxos/vspace.h index a2d74fb..e0cbbf0 100644 --- a/include/fxos/vspace.h +++ b/include/fxos/vspace.h @@ -28,6 +28,7 @@ #include #include #include +#include #include #include @@ -90,6 +91,9 @@ struct Binding: public AbstractMemory can be constructed with the required size. */ Binding(MemoryRegion region, Buffer const &buffer); + BSON serialize() const; + Binding(BSON const &); + /* Targeted region, might overlap with other bindings */ MemoryRegion region; /* Underlying buffer (copy of the original one) */ @@ -101,14 +105,14 @@ struct Binding: public AbstractMemory /* A composite space where regions can be bound dynamically */ // TODO: Use a class interface for VirtualSpace +// TODO: Move non-memory-management members to Binary class VirtualSpace: public AbstractMemory { public: /* Create an empty space with no regions */ VirtualSpace(); - - /* MPU used by this target, or an empty string if unspecified */ - std::string mpu; + BSON serialize() const; + void deserialize(BSON const &); /* List of bindings (most recent first) */ std::vector bindings; @@ -119,26 +123,16 @@ public: region, it is 0-padded to the proper size. */ void bind_region(MemoryRegion const ®ion, Buffer const &buffer); - /* Cursor position, used by the interactive shell */ - uint32_t cursor; - - /* Symbol table */ - SymbolTable symbols; - // - AbstractMemory interface char const *translate_dynamic(uint32_t addr, int *size) override; - // Analysis tools and data - - /* Main disassembly; holds disassembled code for large-scale analyses. */ + // TODO: Remove these + std::string mpu; + SymbolTable symbols; + uint32_t cursor; Disassembly disasm; - - /* OS analysis; created on-demand. Returns the new or cached OS analysis, - nullptr OS analysis fails. */ OS *os_analysis(bool force = false); - private: - /* OS analysis results */ std::unique_ptr m_os; }; diff --git a/lib/binary.cpp b/lib/binary.cpp new file mode 100644 index 0000000..8b41bf4 --- /dev/null +++ b/lib/binary.cpp @@ -0,0 +1,71 @@ +//---------------------------------------------------------------------------// +// 1100101 |_ mov #0, r4 __ // +// 11 |_ <0xb380 %5c4> / _|_ _____ ___ // +// 0110 |_ 3.50 -> 3.60 | _\ \ / _ (_-< // +// |_ base# + offset |_| /_\_\___/__/ // +//---------------------------------------------------------------------------// + +#include +using namespace FxOS; + +//=== Binary ===// + +BSON Binary::serialize() const +{ + BSONField *fields = (BSONField *)malloc(m_objects.size() * sizeof *fields); + int i = 0; + for(auto const &[address, obj]: m_objects) { + char str[32]; + sprintf(str, "%08x", address); + // TODO: Serialize BinaryObjects + // new(&fields[i]) BSONField(str, obj->serialize()); + new(&fields[i]) BSONField(str, BSON::mkNull()); + i++; + } + + return BSON::mkDocument({ + {"*", BSON::mkString("Binary")}, + {"vspace", m_vspace.serialize()}, + {"objects", BSON::mkDocumentFromFieldArray(fields, m_objects.size())}, + }); +} + +void Binary::deserialize(BSON const &b) +{ + assert(b.isDocument() && b["*"].getString() == "Binary"); + m_vspace.deserialize(b["vspace"]); + + BSONField const *fields = b["objects"].getDocumentFields(); + int N = b["objects"].size(); + + for(int i = 0; i < N; i++) { + uint32_t address = std::stoul(fields[i].getName(), nullptr, 16); + // TODO: Deserialize BinaryObject from fields[i].value() + } +} + +//=== BinaryObject ===// + +bool BinaryObject::intersects(BinaryObject const &other) const +{ + uint32_t inter_start = std::max(m_address, other.address()); + uint32_t inter_end + = std::min(m_address + m_size, other.address() + other.size()); + + return inter_start < inter_end; +} + +bool BinaryObject::contains(BinaryObject const &other) const +{ + return m_address <= other.address() + && m_address + m_size >= other.address() + other.size(); +} + +//=== Variable ===// + +Variable::Variable(Binary &binary, u32 address, std::string const &name): + BinaryObject(binary, BinaryObject::Variable, address, 4) +{ + setName(name); + // m_type = IntegerType(4, false); +} diff --git a/lib/project.cpp b/lib/project.cpp index 2d622c5..42389ed 100644 --- a/lib/project.cpp +++ b/lib/project.cpp @@ -3,6 +3,7 @@ #include #include #include +#include using namespace std::literals; namespace fs = std::filesystem; @@ -18,6 +19,7 @@ Project::Project() void Project::setName(std::string const &new_name) { m_name = new_name; + setDirty(); } void Project::setPath(std::string const &new_path) @@ -40,11 +42,64 @@ void Project::setDirty() m_dirty = true; } +std::string Project::generateBinaryName(std::string const &base) const +{ + if(base != "" && !m_binaries.count(base)) + return base; + + std::string const &prefix = (base == "") ? "unnamed" : base; + + int counter = 0; + while(true) { + std::string name = fmt::format("{}_{}", prefix, counter); + if(!m_binaries.count(name)) + return name; + counter++; + } +} + +std::string Project::createBinary(std::string const &name) +{ + std::string trueName = generateBinaryName(name); + + /* Create a new default-constructed entry in the map. */ + m_binaries[trueName]; + + setDirty(); + return trueName; +} + +Binary *Project::getBinary(std::string const &name) +{ + auto it = m_binaries.find(name); + return (it == m_binaries.end()) ? nullptr : &it->second; +} + +Binary const *Project::getBinary(std::string const &name) const +{ + auto it = m_binaries.find(name); + return (it == m_binaries.end()) ? nullptr : &it->second; +} + +void Project::removeBinary(std::string const &name) +{ + if(m_binaries.erase(name)) + setDirty(); +} + BSON Project::serializeMetadata() const { + BSON binaries = BSON::mkArray(m_binaries.size()); + int i = 0; + for(auto const &[name, _]: m_binaries) { + binaries[i] = BSON::mkString(name); + i++; + } + return BSON::mkDocument({ - {"*", BSON::mkString("Project")}, {"name", BSON::mkString(m_name)}, - // TODO: List of binaries by name + {"*", BSON::mkString("Project")}, + {"name", BSON::mkString(m_name)}, + {"binaries", std::move(binaries)}, }); } @@ -68,13 +123,30 @@ bool Project::save() fs::path metadata_path = path / "project"; FILE *metadata = fopen(metadata_path.c_str(), "w"); if(!metadata) { - FxOS_log(ERR, "cannot write `%s': %m", metadata_path.c_str()); + FxOS_log(ERR, "cannot write '%s': %m", metadata_path.c_str()); return false; } - serializeMetadata().serialize(metadata); fclose(metadata); + /* Remove old binaries' folders. */ + for(auto const &entry: fs::directory_iterator(path)) { + fs::path const &path = entry.path(); + if(path.extension() == ".bin" && m_binaries.count(path.stem()) == 0) + fs::remove(path); + } + + for(auto const &[name, binary]: m_binaries) { + fs::path bin_path = path / (name + ".bin"); + FILE *bin = fopen(bin_path.c_str(), "w"); + if(!bin) { + FxOS_log(ERR, "cannot write '%s': %m", bin_path.c_str()); + continue; + } + binary.serialize().serialize(bin); + fclose(bin); + } + m_dirty = false; return true; } @@ -91,6 +163,22 @@ bool Project::load(std::string const &path0) m_path = path; m_name = metadata["name"].getString(); m_dirty = false; + + if(!metadata.hasField("binaries")) + return true; + + for(size_t i = 0; i < metadata["binaries"].size(); i++) { + std::string name = metadata["binaries"][i].getString(); + BSON bin = BSON::loadDocumentFromFile( + path / (name + ".bin"), true, true, "Binary"); + if(bin.isNull()) + continue; + + assert(m_binaries.count(name) == 0); + m_binaries[name]; + m_binaries[name].deserialize(bin); + } + return true; } diff --git a/lib/util/Buffer.cpp b/lib/util/Buffer.cpp index c451126..5d856d1 100644 --- a/lib/util/Buffer.cpp +++ b/lib/util/Buffer.cpp @@ -21,13 +21,46 @@ Buffer::Buffer(): size {0}, data {nullptr}, path {"(none)"} { } +BSON Buffer::serialize() const +{ + if(this->path != "" && this->path != "(none)") { + return BSON::mkDocument({ + {"*", BSON::mkString("Buffer")}, + {"size", BSON::mkI32(this->size)}, + {"path", BSON::mkString(this->path)}, + }); + } + else { + return BSON::mkDocument({ + {"*", BSON::mkString("Buffer")}, + {"size", BSON::mkI32(this->size)}, + {"data", BSON::mkBinaryCopy(0, (u8 *)this->data.get(), this->size)}, + }); + } +} + +void Buffer::deserialize(BSON const &b) +{ + assert(b.isDocument() && b["*"].getString() == "Buffer"); + this->size = b["size"].getI32(); + + // TODO: Serialized Buffer (file mode) does not preserve fill setting! + if(b.hasField("path")) + this->loadFromFile(b["path"].getString(), this->size, 0); + else { + this->path = ""; + this->data = std::make_unique(this->size); + memcpy(this->data.get(), b.getBinary(nullptr, nullptr), this->size); + } +} + /* Empty buffer initialized with given byte */ Buffer::Buffer(size_t bufsize, int fill) { this->size = bufsize; this->data = std::make_unique(bufsize); memset(this->data.get(), fill, bufsize); - this->path = "(anonymous)"; + this->path = "(none)"; } /* Buffer initialized from file */ diff --git a/lib/util/bson.cpp b/lib/util/bson.cpp index 7ea3f3e..7e99e4e 100644 --- a/lib/util/bson.cpp +++ b/lib/util/bson.cpp @@ -29,10 +29,13 @@ #include #include +#include #include #include #include +namespace fs = std::filesystem; + /* Number of bytes available in a value after the type/subtype attributes */ #define SSO_MAXLEN (sizeof(BSON) - 2) @@ -449,6 +452,14 @@ BSON BSON::parseDocumentFromFile(FILE *fp, bool *error, bool log) BSON BSON::loadDocumentFromFile( std::string path, bool log, bool mustExist, char const *expectedType) { + std::error_code rc; + fs::file_status st = fs::status(path, rc); + if(st.type() != fs::file_type::regular) { + if(log && (mustExist || st.type() != fs::file_type::not_found)) + FxOS_log(ERR, "'%s' is not a regular file", path.c_str()); + return mkNull(); + } + FILE *fp = fopen(path.c_str(), "r"); if(!fp) { if(mustExist && log) @@ -769,6 +780,13 @@ char *BSONField::getNameCopy() const return strdup(m_name); } +std::string BSONField::getName() const +{ + size_t len; + char const *str = getNameReadOnly(&len); + return std::string(str, len); +} + void BSONField::dump(FILE *fp, int depth) const { fprintf(fp, "%*s", 2 * depth, ""); diff --git a/lib/vspace.cpp b/lib/vspace.cpp index 9de0816..3b179b3 100644 --- a/lib/vspace.cpp +++ b/lib/vspace.cpp @@ -111,6 +111,27 @@ Binding::Binding(MemoryRegion source_region, Buffer const &source_buffer): { } +BSON Binding::serialize() const +{ + return BSON::mkDocument({ + {"*", BSON::mkString("Binding")}, + {"start", BSON::mkI32(region.start)}, + {"end", BSON::mkI32(region.end)}, + {"buffer", buffer.serialize()}, + }); +} + +Binding::Binding(BSON const &b) +{ + assert(b.isDocument() && b["*"].getString() == "Binding"); + + u32 start = b["start"].getI32(); + u32 end = b["end"].getI32(); + region = MemoryRegion("", start, end, false); + + buffer.deserialize(b["buffer"]); +} + char const *Binding::translate_dynamic(uint32_t addr, int *size) { if(addr >= region.start && addr < region.end) { @@ -123,10 +144,25 @@ char const *Binding::translate_dynamic(uint32_t addr, int *size) //=== VirtualSpace ===// VirtualSpace::VirtualSpace(): - mpu {}, bindings {}, cursor {0}, disasm {*this}, m_os {nullptr} + bindings {}, mpu {}, cursor {0}, disasm {*this}, m_os {nullptr} { } +BSON VirtualSpace::serialize() const +{ + BSON b = BSON::mkArray(this->bindings.size()); + for(uint i = 0; i < this->bindings.size(); i++) + b[i] = this->bindings[i].serialize(); + return b; +} + +void VirtualSpace::deserialize(BSON const &b) +{ + assert(b.isArray()); + for(uint i = 0; i < b.size(); i++) + this->bindings.push_back(Binding(b[i])); +} + OS *VirtualSpace::os_analysis(bool force) { if(!m_os || force) { diff --git a/shell/b.cpp b/shell/b.cpp new file mode 100644 index 0000000..baa28ad --- /dev/null +++ b/shell/b.cpp @@ -0,0 +1,174 @@ +#include "shell.h" +#include "theme.h" +#include "parser.h" +#include "commands.h" +#include "errors.h" + +#include +#include + +#include +#include + +using namespace FxOS; + +//--- +// bs +//--- + +static std::string parse_bs(Session &, Parser &parser) +{ + std::string name = parser.symbol("binary_name"); + parser.end(); + return name; +} + +void _bs(Session &session, std::string const &name) +{ + Binary *b = session.project().getBinary(name); + if(!b) { + FxOS_log(ERR, "No binary “%s” in current project", name.c_str()); + return; + } + session.selectBinary(name); +} + +//--- +// bc +//--- + +static std::string parse_bc(Session &, Parser &parser) +{ + std::string name = ""; + if(!parser.at_end()) + name = parser.symbol(); + parser.end(); + return name; +} + +void _bc(Session &session, std::string name) +{ + /* Create an empty binary and select it */ + name = session.project().createBinary(name); + fmt::print("Selecting a new binary “{}”\n", name); + session.selectBinary(name); +} + +//--- +// bm +//--- + +struct _bm_args +{ + std::string path; + std::vector regions; +}; + +static _bm_args parse_bm(Session &session, Parser &parser) +{ + _bm_args args {}; + args.path = parser.str(); + + /* TODO: bm: 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 _bm(Session &session, std::string file, std::vector regions) +{ + Binary *b = session.currentBinary(); + if(!b) { + FxOS_log(ERR, "No current binary"); + return; + } + + Buffer contents(file); + for(auto &r: regions) + b->vspace().bind_region(r, contents); + + session.project().setDirty(); +} + +//--- +// brm +//--- + +static std::string parse_brm(Session &, Parser &parser) +{ + std::string name = parser.symbol("binary_name"); + parser.end(); + return name; +} + +void _brm(Session &session, std::string name) +{ + Project &p = session.project(); + Binary *b = p.getBinary(name); + if(!b) { + FxOS_log(ERR, "No binary “%s” in project!", name.c_str()); + return; + } + + fmt::print("Removing binary “{}”.\n", name); + p.removeBinary(name); + + /* Select another binary if the current one is gone */ + if(session.currentBinaryName() == name) { + auto const &binaries = p.binaries(); + session.selectBinary(binaries.size() ? binaries.begin()->first : ""); + } +} + +//--- +// Command registration +//--- + +static ShellCommand _bs_cmd( + "bs", [](Session &s, Parser &p) { _bs(s, parse_bs(s, p)); }, + [](Session &s, Parser &p) { parse_bs(s, p); }, "Binary Select", R"( +bs + +Selects the specified binary from the current project. +)"); + +static ShellCommand _bc_cmd( + "bc", [](Session &s, Parser &p) { _bc(s, parse_bc(s, p)); }, + [](Session &s, Parser &p) { parse_bc(s, p); }, "Binary Create", R"( +bc [] + +Creates a new binary in the current project and selects it. If the name +collides, a suffix like "_0" will be added. +)"); + +static ShellCommand _bm_cmd( + "bm", + [](Session &s, Parser &p) { + auto const &args = parse_bm(s, p); + _bm(s, args.path, args.regions); + }, + [](Session &s, Parser &p) { parse_bm(s, p); }, "Binary Map file", R"( +bm "" ... + +Maps the named file into all the specified regions of the current binary. If +the file is smaller than the region, it is zero-padded; if the region is +smaller, the extra data is ignored. The amount of data mapped is always exactly +the size of the requested region. + +bm "/os/fx/3.10/3.10.bin" ROM ROM_P2 + Maps a binary file 3.10.bin to ROM, through both P1 and P2. +)"); + +static ShellCommand _brm_cmd( + "brm", [](Session &s, Parser &p) { _brm(s, parse_brm(s, p)); }, + [](Session &s, Parser &p) { parse_brm(s, p); }, "Binary Remove", R"( +brm + +Removes the specified binary from the current project. + +WARNING: There is no undo (yet). If you mistakenly delete a binary, do not save +the project. Backup the project folder and exit fxos without saving. +)"); diff --git a/shell/g.cpp b/shell/g.cpp deleted file mode 100644 index 8de0bce..0000000 --- a/shell/g.cpp +++ /dev/null @@ -1,31 +0,0 @@ -#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) -{ - if(!session.current_space) - return; - session.current_space->cursor = (value & 0xffffffff); -} - -static ShellCommand _g_cmd( - "g", [](Session &s, Parser &p) { _g(s, parse_g(s, p)); }, - [](Session &s, Parser &p) { parse_g(s, p); }, "Goto address", R"( -g
- -Moves the cursor of the current virtual space to the specified address. -)"); diff --git a/shell/i.cpp b/shell/i.cpp index e14378b..ceb83aa 100644 --- a/shell/i.cpp +++ b/shell/i.cpp @@ -9,6 +9,107 @@ #include #include +//--- +// ib +//--- + +static void show_vspace(VirtualSpace const &s) +{ + fmt::print(" Region Start End File\n "); + for(int i = 0; i < 70; i++) + fmt::print("─"); + fmt::print("\n"); + + if(s.bindings.size() == 0) { + fmt::print(" (no bindings)\n"); + return; + } + 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"); + } +} + +static void show_binary_short( + std::string const &name, bool current, Binary const &b) +{ + auto const &objects = b.objects(); + u32 total_size = 0; + for(auto const &[_, obj]: objects) + total_size += obj->size(); + + if(current) + fmt::print("* "); + fmt::print(theme(11), "{}\n", name); + fmt::print( + " {} objects (totaling {} bytes)\n", objects.size(), total_size); + fmt::print("\n"); + show_vspace(b.vspace()); +} + + +void _ib(Session &session) +{ + // TODO: Factor these errors into Session. + Binary *b = session.currentBinary(); + if(!b) { + FxOS_log(ERR, "No current binary!"); + return; + } + + show_binary_short(session.currentBinaryName(), true, *b); + // TODO: Show more binary information +} + +//--- +// ibs +//--- + +std::vector parse_ibs(Session &, Parser &parser) +{ + std::vector args; + + while(!parser.at_end()) + args.push_back(parser.symbol("binary_name")); + + parser.end(); + return args; +} + +void _ibs(Session &session, std::vector const &args) +{ + bool first = true; + + for(auto const &name: args) { + if(!first) + fmt::print("\n"); + else + first = false; + + Binary *b = session.project().getBinary(name); + if(b) + show_binary_short(name, name == session.currentBinaryName(), *b); + else + FxOS_log( + ERR, "No binary named “%s” in current project!", name.c_str()); + } + + if(!args.size()) { + for(auto const &[name, b]: session.project().binaries()) { + if(!first) + fmt::print("\n"); + else + first = false; + + show_binary_short(name, name == session.currentBinaryName(), b); + } + } +} + //--- // ic //--- @@ -194,16 +295,22 @@ void _io(Session &session, std::string name) void _ip(Session &session) { - Project *p = session.project(); - if(!p) + if(!session.hasProject()) fmt::print("No current project o(x_x)o\n"); else { - fmt::print( - "Current project: {}{}\n", p->name(), p->isDirty() ? "*" : ""); - if(p->path() != "") - fmt::print("Path: {}\n", p->path()); + Project &p = session.project(); + fmt::print("Current project: {}{}\n", p.name(), p.isDirty() ? "*" : ""); + if(p.path() != "") + fmt::print("Path: {}\n", p.path()); else fmt::print("No path yet (use ps to assign one)\n"); + + fmt::print("Binaries:"); + if(!p.binaries().size()) + fmt::print(" (none)"); + for(auto const &[name, _]: p.binaries()) + fmt::print(" {}", name); + fmt::print("\n"); } auto const &entries = session.recentProjects().entries(); @@ -475,6 +582,23 @@ void _is(Session &session, std::string vspace_name, // Command registration //--- +static ShellCommand _ib_cmd( + "ib", [](Session &s, Parser &p) { p.end(), _ib(s); }, + [](Session &, Parser &p) { p.end(); }, "Info Binary (current)", R"( +ib + +Prints detailed information about the currently-selected binary in the project. +)"); + +static ShellCommand _ibs_cmd( + "ibs", [](Session &s, Parser &p) { _ibs(s, parse_ibs(s, p)); }, + [](Session &s, Parser &p) { parse_ibs(s, p); }, "Info Binaries", R"( +ibs [...] + +Prints short information about named binaries in the current project. If no +argument is specified, prints information about all binaries. +)"); + static ShellCommand _ic_cmd( "ic", [](Session &s, Parser &p) { _ic(s, parse_ic(s, p)); }, [](Session &s, Parser &p) { parse_ic(s, p); }, "Info Claims", R"( diff --git a/shell/main.cpp b/shell/main.cpp index c99b7d0..774b6a0 100644 --- a/shell/main.cpp +++ b/shell/main.cpp @@ -129,6 +129,8 @@ char *autocomplete(char const *text, int state) global_session, rl_line_buffer, rl_point); if(r.category == "command") options = complete_command(text); + // TODO: Replace vspace_name with binary_name in autocomplete + // TODO: Add legacy_vspace_name in autocomplete else if(r.category == "vspace_name") options = complete_vspace(text, global_session); else if(r.category == "memory_region") @@ -156,17 +158,15 @@ static bool read_interactive(Session &s, std::string &cmdline) { std::string prompt; - Project *p = s.project(); - if(p) - prompt += p->name() + (p->isDirty() ? "*" : ""); - - std::string vspace_name = "(none)"; - for(auto &it: s.spaces) { - if(it.second.get() == s.current_space) - vspace_name = it.first; + if(s.hasProject()) { + Project &p = s.project(); + prompt += p.name() + (p.isDirty() ? "*" : ""); } - prompt += fmt::format("|{}> ", vspace_name); + std::string binary_name = s.currentBinaryName(); + if(binary_name == "") + binary_name = "(none)"; + prompt += fmt::format("|{}> ", binary_name); /* We need to insert RL_PROMPT_{START,END}_IGNORE into the color formatting, so we trick a little bit by using a space */ @@ -348,6 +348,12 @@ static void command_help(std::string const &cmd) commands[cmd]->long_description); } +static void debug_dump_bson(std::string const &path) +{ + BSON b = BSON::loadDocumentFromFile(path, true, true); + b.dump(stdout); +} + int main(int argc, char **argv) { /* Parse command-line options first */ @@ -407,13 +413,13 @@ int main(int argc, char **argv) /* Load a project as specified by command-line arguments */ load_initial_project(s, opts.load, opts.load_is_by_name); /* If none was given or it failed, load the most recent project */ - if(!s.project()) { + if(!s.hasProject()) { fs::path const &p = s.recentProjects().mostRecentPath(); if(p != "") s.loadProject(p); } /* If that failed too, create a blank project */ - if(!s.project()) + if(!s.hasProject()) s.switchToNewProject(); /* Load fxosrc files from all library folders if wanted */ @@ -448,6 +454,7 @@ int main(int argc, char **argv) /* Or read from the command line */ if(lex_idle()) { while(sigsetjmp(sigint_buf, 1) != 0) {} + cmdline = ""; if(read_interactive(s, cmdline) && s.confirmProjectUnload()) break; lex_repl(cmdline); @@ -473,9 +480,14 @@ int main(int argc, char **argv) if(cmd == "q" && parser.lookahead().type != '?' && s.confirmProjectUnload()) break; - if(cmd == "_p") { + else if(cmd == "_p") { parser.dump_command(); } + else if(cmd == "_b") { + std::string path = parser.str(); + parser.end(); + debug_dump_bson(path); + } else if(!has_idled && legacy_command(s, cmd, parser)) { continue; } diff --git a/shell/p.cpp b/shell/p.cpp index aee0b82..f4a6ee0 100644 --- a/shell/p.cpp +++ b/shell/p.cpp @@ -35,8 +35,8 @@ void _pn(Session &session, std::string const &name, std::string const &path) session.switchToNewProject(name, path); if(path != "") { - session.project()->save(); - session.recentProjects().touch(*session.project()); + session.project().save(); + session.recentProjects().touch(session.project()); } } @@ -53,11 +53,9 @@ static std::string parse_pr(Session &, Parser &p) void _pr(Session &session, std::string const &new_name) { - Project *p = session.project(); - if(!p) - return; - p->setName(new_name); - p->setDirty(); + Project &p = session.project(); + p.setName(new_name); + p.setDirty(); } //--- @@ -77,23 +75,19 @@ static std::string parse_ps(Session &, Parser &p) void _ps(Session &session, std::string const &new_path) { - Project *p = session.project(); - if(!p) { - FxOS_log(ERR, "No current project o(x_x)o"); - return; - } + Project &p = session.project(); - if(new_path == "" && !p->canSave()) { + if(new_path == "" && !p.canSave()) { FxOS_log(ERR, "Project “%s” has no path; use ps with a path.", - p->name().c_str()); + p.name().c_str()); } if(new_path != "") - p->setPath(new_path); - if(!p->save()) + p.setPath(new_path); + if(!p.save()) return; - session.recentProjects().touch(*p); + session.recentProjects().touch(p); } //--- @@ -109,8 +103,35 @@ static std::string parse_pm(Session &, Parser &p) void _pm(Session &session, std::string const &legacy_vspace_name) { - // TODO: pm - fmt::print("TODO: migrate {} to current project", legacy_vspace_name); + if(!session.legacySpaces.count(legacy_vspace_name)) { + FxOS_log(ERR, "No legacy virtual space with name “%s”", + legacy_vspace_name.c_str()); + return; + } + + Project &p = session.project(); + LegacyVspace &lvs = session.legacySpaces[legacy_vspace_name]; + std::string binaryName = p.createBinary(legacy_vspace_name); + p.setDirty(); + + Binary *b = p.getBinary(binaryName); + + fmt::print( + "Migrating legacy vspace ”{}” to new binary ”{}” in current project\n", + legacy_vspace_name, binaryName); + + for(LegacyBinding &binding: lvs.bindings) { + std::string path = session.file(binding.path); + Buffer contents(path); + for(auto &r: binding.regions) + b->vspace().bind_region(r, contents); + } + + // TODO: pm: Migrate symbols and syscalls + + /* Switch to this binary if none was selected previously */ + fmt::print("Selecting new binary “{}”\n", binaryName); + session.selectBinary(binaryName); } //--- @@ -152,12 +173,10 @@ void _pl(Session &session, bool isRecent, std::string const &source) ERR, "No recent project named “%s”; see ip.", source.c_str()); return; } - if(session.loadProject(path)) - session.recentProjects().touch(*session.project()); + session.loadProject(path); } else { - if(session.loadProject(source)) - session.recentProjects().touch(*session.project()); + session.loadProject(source); } } diff --git a/shell/session.cpp b/shell/session.cpp index f4a157c..c10b738 100644 --- a/shell/session.cpp +++ b/shell/session.cpp @@ -43,6 +43,27 @@ void Session::saveConfig() const fclose(fp); } +Binary *Session::currentBinary() +{ + if(!m_project || m_currentBinaryName == "") + return nullptr; + return m_project->getBinary(m_currentBinaryName); +} + +Binary const *Session::currentBinary() const +{ + if(!m_project || m_currentBinaryName == "") + return nullptr; + return m_project->getBinary(m_currentBinaryName); +} + +void Session::selectBinary(std::string const &name) +{ + if(name != "" && (!m_project || !m_project->getBinary(name))) + return; + m_currentBinaryName = name; +} + bool Session::isProjectDirty() const { Project *p = m_project.get(); @@ -82,6 +103,8 @@ void Session::switchToNewProject( /* Unload current project - any unsaved data is lost. */ m_project.reset(); m_project = std::move(p); + + selectBinary(""); } bool Session::loadProject(std::string const &path) @@ -93,6 +116,14 @@ bool Session::loadProject(std::string const &path) /* Unload current project - any unsaved data is lost. */ m_project.reset(); m_project = std::move(p); + + recentProjects().touch(project()); + + /* Select the first binary in the project, if there is one. */ + // TODO: Save the last binary in use + auto const &binaries = project().binaries(); + selectBinary(binaries.size() ? binaries.begin()->first : ""); + return true; } diff --git a/shell/session.h b/shell/session.h index 77c9fbf..f0d1ffd 100644 --- a/shell/session.h +++ b/shell/session.h @@ -38,14 +38,37 @@ struct Session //=== Projects ===// - /* Get the current project. This can only be null during startup and while - switching projects. It is an application invariant that there is always - a project open (even if temporary/unsaved). */ - Project *project() + /* Whether there is currently a project loaded. This can only be false + during startup and while switching: it is an application invariant that + there is always exactly one project open (even if temporary/unsaved). */ + bool hasProject() { - return m_project.get(); + return m_project.get() != nullptr; } + /* Get the current project. An exception is raised if there's none. */ + Project &project() + { + if(!hasProject()) + throw std::runtime_error("No current project! o(x_x)o"); + return *m_project.get(); + } + + /* Get the current binary name within the current project. This can be an + empty string if the project has no binaries. */ + std::string const ¤tBinaryName() const + { + return m_currentBinaryName; + } + + /* Get the current binary within the current project. This can be null if + the project has no binaries. */ + Binary *currentBinary(); + Binary const *currentBinary() const; + + /* Select binary by name. */ + void selectBinary(std::string const &name); + /* Whether the current project is open. This can be used to show warnings when attempting to close the program or load another project. */ bool isProjectDirty() const; @@ -106,6 +129,8 @@ private: RecentProjects m_recent; /* Current project. */ std::unique_ptr m_project; + /* Current binary name within the project. */ + std::string m_currentBinaryName; }; #endif /* FXOS_SESSION_H */ diff --git a/shell/v.cpp b/shell/v.cpp deleted file mode 100644 index 5d91073..0000000 --- a/shell/v.cpp +++ /dev/null @@ -1,212 +0,0 @@ -#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); - - int total_claim_size = 0; - for(Claim const &c: s.disasm.claims) - total_claim_size += c.size; - - if(is_current) - fmt::print("* "); - fmt::print(theme(11), "{}\n", name); - - fmt::print(" Symbol table: {} symbols\n", s.symbols.symbols.size()); - fmt::print( - " Main disassembly: {} instructions\n", s.disasm.instructions.size()); - fmt::print(" Functions: {}\n", s.disasm.functions.size()); - fmt::print(" Claims: {} (totalling {} bytes)\n", s.disasm.claims.size(), - total_claim_size); - - fmt::print(" Region--Start---------End---------File------------------\n"); - if(s.bindings.size() == 0) { - fmt::print(" (no bindings)\n"); - return; - } - 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) { - VirtualSpace *s = session.get_space(name); - if(s != nullptr) - show_vspace(name, *session.spaces[name], session); - else - fmt::print("Virtual space '{}' does not exist", name); - } -} - -//--- -// 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) -{ - VirtualSpace *s = session.get_space(name); - if(!s) - return; - session.current_space = session.spaces[name].get(); -} - -//--- -// vc -//--- - -static std::string parse_vc(Session &, Parser &parser) -{ - std::string name = ""; - if(!parser.at_end()) - name = parser.symbol(); - parser.end(); - return name; -} - -void _vc(Session &session, std::string name) -{ - if(name == "") - name = session.generate_space_name("space", true); - else - name = session.generate_space_name(name, false); - - /* Create an empty space and select it */ - std::unique_ptr space = std::make_unique(); - session.spaces.emplace(name, std::move(space)); - _vs(session, name); - _g(session, 0x80000000); -} - -//--- -// 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) -{ - if(!session.current_space) - return; - - 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.current_space->cursor = regions[0].start; - - for(auto &r: regions) - session.current_space->bind_region(r, contents); -} - -//--- -// Command registration -//--- - -static ShellCommand _vl_cmd( - "vl", [](Session &s, Parser &p) { _vl(s, parse_vl(s, p)); }, - [](Session &s, Parser &p) { parse_vl(s, p); }, "Virtual space List", R"( -vl [...] - -Shows the bound regions of each specified virtual space. If none is specified, -shows all the virtual spaces. -)"); - -static ShellCommand _vs_cmd( - "vs", [](Session &s, Parser &p) { _vs(s, parse_vs(s, p)); }, - [](Session &s, Parser &p) { parse_vs(s, p); }, "Virtual space Select", R"( -vs - -Selects the specified virtual space. -)"); - -static ShellCommand _vc_cmd( - "vc", [](Session &s, Parser &p) { _vc(s, parse_vc(s, p)); }, - [](Session &s, Parser &p) { parse_vc(s, p); }, "Virtual space Create", R"( -vc [] - -Creates a new virtual space under the specified name. If such a space already -exists, adds a numerical suffix like _0 until a fresh name is found. The space -is created with no bindings and automatically selected. -)"); - -static ShellCommand _vm_cmd( - "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); }, "Virtual space Map file", R"( -vm "" ... - -Maps the named file into all the specified regions of the current virtual -space. If the file is smaller than the region, it is zero-padded; if the region -is smaller, the extra data is ignored. The amount of data mapped is always -exactly the size of the requested region. - -vm "/os/fx/3.10/3.10.bin" ROM ROM_P2 - Maps a binary file 3.10.bin to ROM, through both P1 and P2. -)");