fxos/shell/i.cpp

578 lines
16 KiB
C++

#include "shell.h"
#include "parser.h"
#include "commands.h"
#include "errors.h"
#include "theme.h"
#include <algorithm>
#include <fmt/core.h>
#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);
}
}
}
//---
// if
//---
struct _if_args
{
std::vector<uint32_t> addresses;
};
static struct _if_args parse_if(Session &session, Parser &parser)
{
_if_args args;
while(!parser.at_end())
args.addresses.push_back(parser.expr(session.currentBinary()));
parser.end();
return args;
}
void _if(Session &session, struct _if_args const &args)
{
if(!session.currentBinary())
return;
Disassembly &disasm = session.currentBinary()->vspace().disasm;
if(!args.addresses.size()) {
fmt::print("{} functions\n", disasm.functions.size());
}
for(uint32_t address: args.addresses) {
OldFunction *func = disasm.getFunctionAt(address);
if(!func) {
FxOS_log(ERR, "no function at 0x{:08x}", address);
continue;
}
// TODO: Promote address to syscall, name, etc.
fmt::print("0x{:08x}:\n", address);
fmt::print(" callTargets:\n");
auto &ct = func->callTargets;
// TODO: Promote address to syscall, name, etc.
for(uint32_t pc: ct)
fmt::print(" 0x{:08x}\n", pc);
if(!ct.size())
fmt::print(" (none)\n");
}
}
//---
// io
//---
struct _io_args
{
Binary *binary;
std::vector<u32> addresses;
};
static struct _io_args parse_io(Session &session, Parser &parser)
{
_io_args args;
std::string binname = session.currentBinaryName();
parser.option(
"binary", [&binname](std::string const &value) { binname = value; });
parser.accept_options();
args.binary = session.project().getBinary(binname);
if(!args.binary) {
std::string msg = fmt::format("No binary “{}” in project!", binname);
if(parser.completing())
throw Parser::CompletionRequest("_error", msg);
else
FxOS_log(ERR, "%s", msg.c_str());
}
while(!parser.at_end())
args.addresses.push_back(parser.expr(args.binary));
parser.end();
return args;
}
static void print_object(BinaryObject &obj)
{
fmt::print(" 0x{:08x} ({}) ", obj.address(), obj.size());
if(obj.name() != "")
fmt::print("{} ", obj.name());
if(obj.isFunction())
fmt::print("<function> (TODO)\n");
else if(obj.isVariable())
fmt::print("<variable> (TODO)\n");
else if(obj.isMark())
fmt::print("<mark> (TODO)\n");
else
fmt::print("<unknown object kind> o(x_x)o\n");
if(obj.comment() != "")
fmt::print(" {}\n", obj.comment());
}
void _io(Session &, Binary *binary, std::vector<u32> addresses)
{
if(!addresses.size()) {
/* This is always sorted */
for(auto const &[_, object]: binary->objects())
print_object(*object);
return;
}
/* Find all objects related to provided addresses. Each object is indexed
not by its own address but by the address of interest that caused it to
be added to the list. */
std::multimap<u32, BinaryObject *> objects;
for(u32 address: addresses) {
auto [it, end] = binary->objects().equal_range(address);
for(; it != end; ++it)
objects.insert({address, it->second.get()});
auto const &covering = binary->objectsCovering(address);
for(BinaryObject *obj: covering)
objects.insert({address, obj});
}
for(auto [interest, obj]: objects) {
if(obj->address() == interest)
fmt::print("Object defined at {:08x}:\n", interest);
else
fmt::print("Object that covers {:08x}:\n", interest);
print_object(*obj);
}
}
//---
// ios
//---
static char const *info_str
= "OS: type %s version %02d.%02d.%04d\n"
"\n"
"Header information:\n"
" Bootcode timestamp (DateA) (0x%000008x) : %s\n"
" Bootcode checksum (0x%000008x) : 0x%08x\n"
" Serial number (0x%000008x) : %s\n"
" OS version (0x%000008x) : %s\n";
static char const *footer_str
= "\nFooter information:\n"
" Detected footer address : 0x%08x\n"
" Langdata entries found : %d\n"
" OS date (DateO) (0x%000008x) : %s\n"
" OS checksum (0x%000008x) : 0x%08x\n"
" Computed OS checksum : 0x%08x\n";
static char const *syscall_str
= "\nSyscall information:\n"
" Syscall table address (0x%000008x) : 0x%08x\n"
" Entries that point to valid memory : 0x%x\n"
" First seemingly invalid entry : 0x%08x\n"
" Syscall entries outside ROM:\n";
static char const *syscall_nonrom_str = " %%%04x -> %08x (%s memory)\n";
static std::string parse_ios(Session &, Parser &parser)
{
std::string name = parser.at_end() ? "" : parser.symbol("binary_name");
parser.end();
return name;
}
void _ios(Session &session, std::string name)
{
Binary *binary = session.currentBinary();
if(name != "")
binary = session.project().getBinary(name);
if(!binary)
return;
VirtualSpace &vspace = binary->vspace();
OS *os = binary->OSAnalysis();
if(!os)
throw CommandError("os analysis on '{}' failed", name);
printf(info_str, (os->type == OS::FX ? "FX" : "CG"), os->version_major,
os->version_minor, os->version_patch, os->bootcode_timestamp.address,
os->bootcode_timestamp.value.c_str(), os->bootcode_checksum.address,
os->bootcode_checksum, os->serial_number.address,
os->serial_number.value.c_str(), os->version.address,
os->version.value.c_str());
if(os->footer == (uint32_t)-1) {
printf("\nFooter could not be found.\n");
}
else {
printf(footer_str, os->footer, os->langdata, os->timestamp.address,
os->timestamp.value.c_str(), os->checksum.address, os->checksum,
os->computed_checksum);
}
uint32_t syscall_table = os->syscall_table_address();
uint32_t first_noncall
= vspace.read_u32(syscall_table + 4 * os->syscall_count());
printf(syscall_str, (os->type == OS::FX ? 0x8001007c : 0x8002007c),
syscall_table, os->syscall_count(), first_noncall);
int total = 0;
for(int i = 0; i < os->syscall_count(); i++) {
uint32_t e = os->syscall(i);
MemoryRegion const *r = MemoryRegion::region_for(e);
if(!r || r->name == "ROM" || r->name == "ROM_P2")
continue;
printf(syscall_nonrom_str, i, e, r->name.c_str());
total++;
}
if(!total)
printf(" (none)\n");
}
//---
// ip
//---
void _ip(Session &session)
{
if(!session.hasProject())
fmt::print("No current project o(x_x)o\n");
else {
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();
fmt::print("\nRecent projects:\n");
if(!entries.size())
fmt::print(" (none)\n");
for(auto &e: entries)
fmt::print(" {} ({}, last used {:%c})\n", e.name, e.path,
fmt::localtime(e.utime));
if(session.legacySpaces.size() != 0) {
fmt::print("\nLegacy vspaces:\n");
for(auto const &[name, lvs]: session.legacySpaces) {
fmt::print(" {} ({} symbols, {} syscalls)\n", name,
lvs.symbols.size(), lvs.syscalls.size());
for(auto const &b: lvs.bindings) {
fmt::print(" ");
for(auto const &r: b.regions)
fmt::print("0x{:08x} ", r.start);
fmt::print("'{}'\n", b.path);
}
}
}
}
//---
// isc
//---
struct _isc_args
{
std::string binary_name;
bool sort = false;
std::vector<uint32_t> addresses;
};
static struct _isc_args parse_isc(Session &session, Parser &parser)
{
_isc_args args;
parser.option("sort",
[&args](std::string const &value) { args.sort = (value == "true"); });
parser.option("binary",
[&args](std::string const &value) { args.binary_name = value; });
parser.accept_options();
Binary *binary = session.currentBinary();
if(!args.binary_name.empty()) {
binary = session.project().getBinary(args.binary_name);
if(!binary) {
std::string msg
= format("No binary “%s” in project!", args.binary_name);
if(parser.completing())
throw Parser::CompletionRequest("_error", msg);
else
FxOS_log(ERR, "%s", msg.c_str());
}
}
while(!parser.at_end())
args.addresses.push_back(parser.expr(binary));
parser.end();
return args;
}
struct SyscallInfo
{
uint32_t address;
int id;
};
bool operator<(const SyscallInfo &left, const SyscallInfo &right)
{
return (left.address < right.address) || (left.id < right.id);
}
void _isc(Session &session, std::string binary_name, bool sort,
std::vector<uint32_t> addresses)
{
Binary *binary = session.currentBinary();
if(!binary) {
FxOS_log(ERR, "No current binary!");
return;
}
if(!binary_name.empty())
binary = session.project().getBinary(binary_name);
if(!binary) {
FxOS_log(ERR, "No binary “%s” in project!", binary_name.c_str());
return;
}
OS *os = binary->OSAnalysis();
if(!os) {
if(!binary_name.empty())
FxOS_log(ERR, "OS analysis on '%s' failed", binary_name);
else
FxOS_log(ERR, "OS analysis failed");
return;
}
if(!addresses.empty()) {
if(sort)
std::sort(&addresses[0], &addresses[addresses.size()]);
for(uint32_t address: addresses) {
int syscall = os->find_syscall(address);
if(syscall == -1)
continue;
fmt::print(theme(3), " 0x{:08x}", address);
fmt::print(theme(10), " %{:04x}", syscall);
fmt::print("\n");
}
return;
}
int total = os->syscall_count();
auto info = std::make_unique<SyscallInfo[]>(total);
for(int i = 0; i < total; i++)
info[i] = (SyscallInfo) {.address = os->syscall(i), .id = i};
if(sort)
std::sort(&info[0], &info[total]);
for(int i = 0; i < total; i++) {
fmt::print(theme(3), " 0x{:08x}", info[i].address);
fmt::print(theme(10), " %{:04x}", info[i].id);
fmt::print("\n");
}
}
//---
// 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 _if_cmd(
"if",
[](Session &s, Parser &p) {
auto args = parse_if(s, p);
_if(s, args);
},
[](Session &s, Parser &p) { parse_if(s, p); }, "Info Function", R"(
if [<function>...]
Prints information about functions. Without arguments, prints vspace-level
statistics. With arguments, prints detailed function info.
)");
static ShellCommand _io_cmd(
"io",
[](Session &s, Parser &p) {
auto args = parse_io(s, p);
_io(s, args.binary, args.addresses);
},
[](Session &s, Parser &p) { parse_io(s, p); }, "Info Objects", R"(
io [binary=<binary_name>] [<expr>...]
Lists all objects in the specified binary (default: the current one). If
expressions are provided, instead list objects defined at these addresses or
that cover these addresses. With sort=true, sorts by increasing addresses.
)");
static ShellCommand _ios_cmd(
"ios", [](Session &s, Parser &p) { _ios(s, parse_ios(s, p)); },
[](Session &s, Parser &p) { parse_ios(s, p); }, "Info OS", R"(
ios [<binary_name>]
Prints information about the OS mapped in the named virtual space (defaults to
the current one). This usually requires an OS binary to be mapped to ROM.
)");
static ShellCommand _ip_cmd(
"ip", [](Session &s, Parser &) { _ip(s); },
[](Session &, Parser &p) { p.end(); }, "Info Projects", R"(
ip
Projects information about the currently-loaded project, and paths and names of
recent projects;
)");
static ShellCommand _isc_cmd(
"isc",
[](Session &s, Parser &p) {
auto args = parse_isc(s, p);
_isc(s, args.binary_name, args.sort, args.addresses);
},
[](Session &s, Parser &p) { parse_isc(s, p); }, "Info Syscalls", R"(
isc [sort=true] [binary=<binary_name>] [<address>...]
Prints the syscall table for the specified binary (default: the current one).
By default, syscalls are enumerated by syscall number. If sort=true is
specified, they are instead sorted by address.
)");