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:
Lephenixnoir 2023-09-24 11:53:39 +02:00
parent 9d7c87ac3d
commit fcdcdba423
Signed by: Lephenixnoir
GPG Key ID: 1BBA026E13FC0495
19 changed files with 775 additions and 325 deletions

View File

@ -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}
)

View File

@ -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 */

View File

@ -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

View File

@ -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);

View File

@ -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;

View File

@ -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 &region, 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;
};

71
lib/binary.cpp Normal file
View File

@ -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);
}

View File

@ -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;
}

View File

@ -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 */

View 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, "");

View File

@ -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) {

174
shell/b.cpp Normal file
View File

@ -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.
)");

View File

@ -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.
)");

View File

@ -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"(

View File

@ -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;
}

View File

@ -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);
}
}

View File

@ -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;
}

View File

@ -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 &currentBinaryName() 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 */

View File

@ -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.
)");