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.
This commit is contained in:
parent
9d7c87ac3d
commit
fcdcdba423
|
@ -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}
|
||||
)
|
||||
|
||||
|
|
|
@ -18,23 +18,25 @@
|
|||
|
||||
#include <fxos/util/types.h>
|
||||
#include <fxos/util/bson.h>
|
||||
// #include <fxos/vspace.h>
|
||||
#include <fxos/vspace.h>
|
||||
#include <cassert>
|
||||
#include <string>
|
||||
#include <map>
|
||||
|
||||
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<u32, std::unique_ptr<BinaryObject>> const &objects() const
|
||||
{
|
||||
return m_objects;
|
||||
}
|
||||
|
||||
private:
|
||||
VirtualSpace &m_vspace;
|
||||
VirtualSpace m_vspace;
|
||||
|
||||
/* OS analysis results */
|
||||
std::unique_ptr<OS> m_os;
|
||||
|
||||
/* All binary objects */
|
||||
std::map<u32, std::unique_ptr<BinaryObject>> 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 */
|
||||
|
|
|
@ -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<std::string, Binary> 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<std::string, Binary> 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
|
||||
|
|
|
@ -15,6 +15,7 @@
|
|||
#ifndef FXOS_UTIL_BUFFER_H
|
||||
#define FXOS_UTIL_BUFFER_H
|
||||
|
||||
#include <fxos/util/bson.h>
|
||||
#include <string>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -28,6 +28,7 @@
|
|||
#include <fxos/disassembly.h>
|
||||
#include <fxos/util/Buffer.h>
|
||||
#include <fxos/util/Addressable.h>
|
||||
#include <fxos/util/bson.h>
|
||||
|
||||
#include <optional>
|
||||
#include <vector>
|
||||
|
@ -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<Binding> 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<OS> m_os;
|
||||
};
|
||||
|
||||
|
|
|
@ -0,0 +1,71 @@
|
|||
//---------------------------------------------------------------------------//
|
||||
// 1100101 |_ mov #0, r4 __ //
|
||||
// 11 |_ <0xb380 %5c4> / _|_ _____ ___ //
|
||||
// 0110 |_ 3.50 -> 3.60 | _\ \ / _ (_-< //
|
||||
// |_ base# + offset |_| /_\_\___/__/ //
|
||||
//---------------------------------------------------------------------------//
|
||||
|
||||
#include <fxos/binary.h>
|
||||
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);
|
||||
}
|
|
@ -3,6 +3,7 @@
|
|||
#include <filesystem>
|
||||
#include <algorithm>
|
||||
#include <string>
|
||||
#include <fmt/core.h>
|
||||
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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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<char[]>(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<char[]>(bufsize);
|
||||
memset(this->data.get(), fill, bufsize);
|
||||
this->path = "(anonymous)";
|
||||
this->path = "(none)";
|
||||
}
|
||||
|
||||
/* Buffer initialized from file */
|
||||
|
|
|
@ -29,10 +29,13 @@
|
|||
|
||||
#include <fxos/util/bson.h>
|
||||
#include <fxos/util/log.h>
|
||||
#include <filesystem>
|
||||
#include <vector>
|
||||
#include <cstring>
|
||||
#include <cstdio>
|
||||
|
||||
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, "");
|
||||
|
|
|
@ -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("<anonymous>", 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) {
|
||||
|
|
|
@ -0,0 +1,174 @@
|
|||
#include "shell.h"
|
||||
#include "theme.h"
|
||||
#include "parser.h"
|
||||
#include "commands.h"
|
||||
#include "errors.h"
|
||||
|
||||
#include <fmt/core.h>
|
||||
#include <fmt/color.h>
|
||||
|
||||
#include <fxos/memory.h>
|
||||
#include <fxos/util/log.h>
|
||||
|
||||
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<MemoryRegion> 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<MemoryRegion> 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 <binary_name>
|
||||
|
||||
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 [<name>]
|
||||
|
||||
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 "<file>" <region>...
|
||||
|
||||
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 <binary_name>
|
||||
|
||||
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.
|
||||
)");
|
31
shell/g.cpp
31
shell/g.cpp
|
@ -1,31 +0,0 @@
|
|||
#include "shell.h"
|
||||
#include "parser.h"
|
||||
#include "commands.h"
|
||||
|
||||
#include <fmt/core.h>
|
||||
|
||||
//---
|
||||
// g
|
||||
//---
|
||||
|
||||
static long parse_g(Session &session, Parser &parser)
|
||||
{
|
||||
long addr = parser.expr(session.current_space);
|
||||
parser.end();
|
||||
return addr;
|
||||
}
|
||||
|
||||
void _g(Session &session, long value)
|
||||
{
|
||||
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 <address>
|
||||
|
||||
Moves the cursor of the current virtual space to the specified address.
|
||||
)");
|
136
shell/i.cpp
136
shell/i.cpp
|
@ -9,6 +9,107 @@
|
|||
#include <fmt/chrono.h>
|
||||
#include <fxos/util/log.h>
|
||||
|
||||
//---
|
||||
// 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<std::string> parse_ibs(Session &, Parser &parser)
|
||||
{
|
||||
std::vector<std::string> args;
|
||||
|
||||
while(!parser.at_end())
|
||||
args.push_back(parser.symbol("binary_name"));
|
||||
|
||||
parser.end();
|
||||
return args;
|
||||
}
|
||||
|
||||
void _ibs(Session &session, std::vector<std::string> 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 [<binary>...]
|
||||
|
||||
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"(
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
65
shell/p.cpp
65
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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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<Project> m_project;
|
||||
/* Current binary name within the project. */
|
||||
std::string m_currentBinaryName;
|
||||
};
|
||||
|
||||
#endif /* FXOS_SESSION_H */
|
||||
|
|
212
shell/v.cpp
212
shell/v.cpp
|
@ -1,212 +0,0 @@
|
|||
#include "shell.h"
|
||||
#include "theme.h"
|
||||
#include "parser.h"
|
||||
#include "commands.h"
|
||||
#include "errors.h"
|
||||
|
||||
#include <fmt/core.h>
|
||||
#include <fmt/color.h>
|
||||
|
||||
#include <fxos/memory.h>
|
||||
|
||||
using namespace FxOS;
|
||||
|
||||
//---
|
||||
// vl
|
||||
//---
|
||||
|
||||
static std::vector<std::string> parse_vl(Session &, Parser &parser)
|
||||
{
|
||||
std::vector<std::string> spaces;
|
||||
|
||||
while(!parser.at_end())
|
||||
spaces.push_back(parser.symbol("vspace_name"));
|
||||
|
||||
parser.end();
|
||||
return spaces;
|
||||
}
|
||||
|
||||
static void show_vspace(std::string name, VirtualSpace &s, Session &session)
|
||||
{
|
||||
bool is_current = (&s == session.current_space);
|
||||
|
||||
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<std::string> 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<VirtualSpace> space = std::make_unique<VirtualSpace>();
|
||||
session.spaces.emplace(name, std::move(space));
|
||||
_vs(session, name);
|
||||
_g(session, 0x80000000);
|
||||
}
|
||||
|
||||
//---
|
||||
// vm
|
||||
//---
|
||||
|
||||
struct _vm_args
|
||||
{
|
||||
std::string path;
|
||||
std::vector<MemoryRegion> regions;
|
||||
};
|
||||
|
||||
static _vm_args parse_vm(Session &session, Parser &parser)
|
||||
{
|
||||
_vm_args args {};
|
||||
args.path = parser.str();
|
||||
|
||||
/* TODO: vm: Allow specifying address without a size */
|
||||
do
|
||||
args.regions.push_back(parser.region(session.current_space));
|
||||
while(!parser.at_end());
|
||||
|
||||
parser.end();
|
||||
return args;
|
||||
}
|
||||
|
||||
void _vm(Session &session, std::string file, std::vector<MemoryRegion> regions)
|
||||
{
|
||||
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 [<space_name>...]
|
||||
|
||||
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 <space_name>
|
||||
|
||||
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 [<name>]
|
||||
|
||||
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 "<file>" <region>...
|
||||
|
||||
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.
|
||||
)");
|
Loading…
Reference in New Issue