Compare commits

...

12 Commits

Author SHA1 Message Date
Lephenixnoir 0373ae50fe
_ic: also print claims owned by provided address 2023-08-20 20:27:52 +02:00
Lephenixnoir 2dbd910379
fxos, _if: add insufficient call analysis
We look for constants in call instruction parameters, but this only
works for jsr because the register argument in [jmp @rn] is not known to
be a constant yet (some static analysis required).
2023-08-20 20:23:30 +02:00
Lephenixnoir df4bba2c1a
fxos: analyze pcrel in cfg (for future call analysis)
This will not cover advanced stuff that relies on static analysis, but
we don't care at the moment.
2023-08-20 19:42:06 +02:00
Lephenixnoir 12e6cd45a4
_am: add search for OS main menu function 2023-08-20 18:52:34 +02:00
Lephenixnoir 0f23fec85d
shell: fix double syntax errors aborting program
Type e.g. "e 1 + 2 * 3". The first "+" throws a syntax error
(parentheses are needed around expressions) and the "*" another one
during the final exhaust_until_separator phase, which wasn't protected
by a try/catch.
2023-08-20 18:31:30 +02:00
Lephenixnoir 44babe3baf
lib: allow declaring exclusive claims multiple times 2023-08-20 18:31:30 +02:00
Lephenixnoir 97029d4f3e
clang-format and other minor changes 2023-08-20 17:54:03 +02:00
Lephenixnoir a399ed31d7
relconst: fix a printing bug causing some constants to show as 0
Constants (no base) that fit on a single byte would print as 0 due to
flawed logic.
2022-12-29 22:16:47 +01:00
Dr-Carlos 5e20cbe805 Merge pull request 'Add sort option to `is`' (#13) from Dr-Carlos/fxos:sort-is into master
Reviewed-on: https://gitea.planet-casio.com/Lephenixnoir/fxos/pulls/13
2022-12-23 21:30:26 +01:00
Dr-Carlos efaad5b980 add extra symbol comparison methods 2022-12-23 09:51:00 +11:00
Dr-Carlos b494a30404 _is: add sort option 2022-12-23 07:11:16 +11:00
Dr-Carlos f16ecc370c _e: print large demical representations correctly 2022-12-22 09:58:52 +11:00
17 changed files with 388 additions and 59 deletions

View File

@ -108,6 +108,10 @@ struct Instruction
struct Function
{
/* Create a bare function with no detailed information */
Function(uint32_t pc);
/* Function's entry point */
uint32_t address;
/* List of subfunctions called. TODO: Not yet populated by anyone */
@ -144,6 +148,9 @@ struct Claim
/* Utility to check for intersections */
bool intersects(Claim const &other) const;
/* Check equality of claims (raw equality) */
bool operator==(Claim const &other) const;
/* String representation */
std::string str() const;
};
@ -185,6 +192,8 @@ struct Disassembly
bool hasFunctionAt(uint32_t pc);
/* Find a function by address; returns nullptr if not yet defined */
Function *getFunctionAt(uint32_t pc);
/* Find a function and create it empty if it's not yet defined */
Function *getOrCreateFunctionAt(uint32_t pc);
// Claim information
@ -194,13 +203,20 @@ struct Disassembly
/* Access the claim that owns the address, if there is one */
Claim const *getClaimAt(uint32_t address);
/* Access the first claim that overlaps this region, if any */
/* Find the first claim that overlaps this region, if any */
Claim const *findClaimConflict(uint32_t address, int size);
/* Find all (or up to max ≥ 0) claims that overlaps this region */
std::vector<Claim const *> findClaimConflicts(
uint32_t address, int size, int max = -1);
/* Add a new exclusive claim. If there is any intersection with previous
claims, this fails. */
claims which do not compare equal to c, this fails. */
bool addExclusiveClaim(Claim const &c);
/* Get all claims owned by a certain address. */
std::vector<Claim const *> findClaimsOwnedBy(uint32_t address);
// TODO: Add non-exclusive claims/handle collisions

View File

@ -175,6 +175,8 @@ struct AsmInstruction
bool isjump() const noexcept;
/* Check whether it's a conditional jump */
bool iscondjump() const noexcept;
/* Check whether instruction is a function call */
bool iscall() const noexcept;
/* Check whether instruction has a delay slot */
bool isdelayed() const noexcept;
/* Check whether instruction can be used in a delay slot */

View File

@ -32,7 +32,6 @@ namespace FxOS {
struct MemoryArea
{
public:
/* Userspace seen from user and privileged mode */
static MemoryArea U0, P0;
/* Second half of memory, only for privileged mode */
@ -98,7 +97,7 @@ struct MemoryRegion
/* Returns the size of the region */
uint32_t size() const noexcept;
/* Returns the area associated to the region (assuming it is fully
/* Returns the area associated with the region (assuming it is fully
contained in one, which should always be the case) */
MemoryArea area() const noexcept;

View File

@ -34,8 +34,8 @@ class OS
{
public:
/* Create an OS interface for this virtual space. If there is no data
loaded in ROM or the OS can't be identified, the type os OS is set to
UNKNOWN and no information is provided. */
loaded in ROM or the OS can't be identified, the type os OS is set to
UNKNOWN and no information is provided. */
OS(VirtualSpace &space);
/* Type of OS, determined at construction */

View File

@ -45,6 +45,7 @@
#define FXOS_PASSES_CFG_H
#include <fxos/disassembly.h>
#include <fxos/passes/pcrel.h>
#include <set>
namespace FxOS {
@ -69,6 +70,8 @@ private:
uint32_t m_lastFunction;
/* Set of instructions in a function, used to generate new claims */
std::set<uint32_t> m_claimedInstructions;
/* pcrel pass used to find call to other functions */
PcrelPass m_pcrel;
};
} /* namespace FxOS */

View File

@ -21,6 +21,7 @@
#include <optional>
#include <string>
#include <vector>
#include <cstdint>
namespace FxOS {
@ -34,6 +35,40 @@ struct Symbol
/* Symbol name, no particular conventions */
std::string name;
bool operator<(const FxOS::Symbol &right) const
{
return (type < right.type)
|| (value < right.value && type == right.type);
}
bool operator>(const FxOS::Symbol &right) const
{
return (type > right.type)
|| (value > right.value && type == right.type);
}
bool operator==(const FxOS::Symbol &right) const
{
return value == right.value && type == right.type;
}
bool operator!=(const FxOS::Symbol &right) const
{
return value != right.value || type != right.type;
}
bool operator>=(const FxOS::Symbol &right) const
{
return (type > right.type)
|| (value >= right.value && type == right.type);
}
bool operator<=(const FxOS::Symbol &right) const
{
return (type < right.type)
|| (value <= right.value && type == right.type);
}
};
/* A symbol table, usually the set of symbols of a virtual space */

View File

@ -4,7 +4,10 @@
// 0110 |_ 3.50 -> 3.60 | _\ \ / _ (_-< //
// |_ base# + offset |_| /_\_\___/__/ //
//---------------------------------------------------------------------------//
// fxos/util/Queue: Simple queue that handles recursivity
// fxos/util/Queue: Simple queue that handles recursion
//
// Can be instantiated for any T for which std::set<T> is valid and properly
// checks objects' identity.
//---
#ifndef FXOS_UTIL_QUEUE_H

View File

@ -300,12 +300,17 @@ std::string RelConst::str() const noexcept
if(ival >= -256 && ival < 256) {
uint32_t v = 0;
if(str.size() && ival > 0)
str += "+", v = ival;
if(str.size() && ival < 0)
str += "-", v = -ival;
if(ival >= 0) {
if(str.size())
str += "+";
v = ival;
}
else {
str += "-";
v = -ival;
}
return str + format("%d", v);
return str + format("%d (0x%08x)", v, uval);
}
else {
return str + format("0x%08x", uval);

View File

@ -51,6 +51,14 @@ Instruction::Instruction(uint16_t opcode):
{
}
//---
// Function information
//---
Function::Function(uint32_t pc): address {pc}
{
}
//---
// Dynamic claims
//---
@ -65,6 +73,12 @@ bool Claim::intersects(Claim const &other) const
return inter_start < inter_end;
}
bool Claim::operator==(Claim const &other) const
{
return this->address == other.address && this->size == other.size
&& this->type == other.type && this->owner == other.owner;
}
std::string Claim::str() const
{
std::string details = format(" (claim 0x%08x:%d)", address, size);
@ -140,7 +154,17 @@ Function *Disassembly::getFunctionAt(uint32_t pc)
return &it->second;
}
Claim const *Disassembly::findClaimConflict(uint32_t address, int size)
Function *Disassembly::getOrCreateFunctionAt(uint32_t pc)
{
if(!this->hasFunctionAt(pc)) {
Function f(pc);
this->functions.insert({pc, f});
}
return this->getFunctionAt(pc);
}
std::vector<Claim const *> Disassembly::findClaimConflicts(
uint32_t address, int size, int max)
{
Claim fake_claim = {
.address = address,
@ -148,6 +172,7 @@ Claim const *Disassembly::findClaimConflict(uint32_t address, int size)
.type = 0,
.owner = 0,
};
std::vector<Claim const *> conflicts;
/* Find the first claim whose start is [> address] */
auto it = this->claims.upper_bound(fake_claim);
@ -155,19 +180,26 @@ Claim const *Disassembly::findClaimConflict(uint32_t address, int size)
if(it != this->claims.begin())
it--;
while(it != this->claims.end()) {
while(it != this->claims.end()
&& (max < 0 || conflicts.size() < (size_t)max)) {
/* We completely passed address+size, no conflict found */
if(it->address >= address + size)
return nullptr;
break;
/* There is an intersection */
if(it->intersects(fake_claim))
return &*it;
conflicts.push_back(&*it);
it++;
}
return nullptr;
return conflicts;
}
Claim const *Disassembly::findClaimConflict(uint32_t address, int size)
{
auto claims = findClaimConflicts(address, size, 1);
return claims.size() > 0 ? claims[0] : nullptr;
}
Claim const *Disassembly::getClaimAt(uint32_t address)
@ -177,15 +209,35 @@ Claim const *Disassembly::getClaimAt(uint32_t address)
bool Disassembly::addExclusiveClaim(Claim const &c)
{
Claim const *conflict = this->findClaimConflict(c.address, c.size);
if(conflict) {
auto conflicts = this->findClaimConflicts(c.address, c.size);
bool exclusive = true;
for(auto conflict: conflicts) {
/* Allow declaring the same claim twice */
if(*conflict == c)
continue;
FxOS_log(ERR, "exclusive claim for %s conflicts with %s", c.str(),
conflict->str());
return false;
conflicts[0]->str());
exclusive = false;
}
this->claims.insert(c);
return true;
if(exclusive)
this->claims.insert(c);
return exclusive;
}
std::vector<Claim const *> Disassembly::findClaimsOwnedBy(uint32_t address)
{
std::vector<Claim const *> claims;
/* Since we don't order by owner we have to tank the linear search */
for(auto const &c: this->claims) {
if(c.owner == address)
claims.push_back(&c);
}
return claims;
}

View File

@ -261,6 +261,12 @@ bool AsmInstruction::iscondjump() const noexcept
return false;
}
bool AsmInstruction::iscall() const noexcept
{
return !strcmp(mnemonic, "jsr") || !strcmp(mnemonic, "bsr")
|| !strcmp(mnemonic, "bsrf");
}
bool AsmInstruction::isdelayed() const noexcept
{
char const *v[] = {
@ -288,6 +294,7 @@ bool AsmInstruction::isdelayed() const noexcept
bool AsmInstruction::isvaliddelayslot() const noexcept
{
// TODO: PC-relative move is a valid delay slot but it doesn't work
return !isdelayed() && !isterminal() && !isjump() && !iscondjump()
&& strcmp(this->mnemonic, "mova") != 0;
}

View File

@ -13,7 +13,7 @@
namespace FxOS {
CfgPass::CfgPass(Disassembly &disasm):
InstructionPass(disasm), m_claimedInstructions {}
InstructionPass(disasm), m_claimedInstructions {}, m_pcrel {disasm}
{
this->setAllowDiscovery(true);
}
@ -94,6 +94,7 @@ bool CfgPass::analyzeInstruction(uint32_t pc, Instruction &i)
i.jmptarget = jmptarget;
}
m_pcrel.analyzeInstruction(pc, i);
return true;
}
@ -102,13 +103,35 @@ bool CfgPass::exploreFunction(uint32_t pc)
m_lastFunction = pc;
m_claimedInstructions.clear();
if(!m_disasm.hasFunctionAt(pc)) {
// TODO: Have proper function creation methods in Disassembly
Function func = {.address = pc, .callTargets = {}};
m_disasm.functions[pc] = func;
Function *func = m_disasm.getOrCreateFunctionAt(pc);
if(!this->analyzeFunction(pc))
return false;
RelConstDomain RCD;
/* Look for call targets */
for(uint32_t pc: m_claimedInstructions) {
Instruction const *ci = m_disasm.getInstructionAt(pc);
if(!ci)
continue;
AsmInstruction const &i = *ci->inst;
/* Find function call instructions */
if(i.isterminal() || !i.iscall() || i.arg_count < 1)
continue;
/* The target must be known */
if(!RCD.is_constant(ci->args[0].location))
continue;
uint32_t target = RCD.constant_value(ci->args[0].location);
auto &v = func->callTargets;
if(std::find(v.begin(), v.end(), target) == v.end())
func->callTargets.push_back(target);
}
return this->analyzeFunction(pc);
return true;
}
std::set<Claim> CfgPass::resultClaims()

View File

@ -50,20 +50,6 @@ static void ad_disassemble_all(
printf("\n");
FxOS_log(LOG, "Finished pass <cfg> in %s", timer.format_time());
/* Annotate all decoded instructions with pcrel/syscall
TODO: analyze only the functions, if possible */
printr("[pcrel] Resolving PC-relative addressing modes...");
timer.restart();
PcrelPass pcrel_pass(space.disasm);
if(!pcrel_pass.analyzeAllInstructions()) {
errors++;
if(!force)
return;
}
timer.stop();
printf("\n");
FxOS_log(LOG, "Finished pass <pcrel> in %s", timer.format_time());
printr("[syscall] Finding syscall references...");
timer.restart();
OS *os = space.os_analysis();
@ -142,6 +128,103 @@ void _ads(Session &session)
ad_disassemble_all(space, addresses, true);
}
//---
// am
//---
static void _am_cg_main_menu_function(VirtualSpace &vspace)
{
OS *os = vspace.os_analysis();
if(!os) {
FxOS_log(ERR, "no OS analysis");
return;
}
if(os->syscall_count() < 0x1e58) {
FxOS_log(ERR, "less than 0x1e58 syscalls");
return;
}
uint32_t sc_addr = os->syscall(0x1e58);
fmt::print("syscall %%1e58 found at 0x{:08x}\n", sc_addr);
/* Check up to 150 instructions to find the call to the internal function
SaveAndOpenMainMenu(). This call is in a widget of the shape
mov.l GetkeyToMainFunctionReturnFlag, rX
mov #3, rY
bsr SaveAndOpenMainMenu
mov.b rY, @rX
bra <start of widget>
nop */
for(int i = 0; i < 150; i++) {
uint16_t i0 = vspace.read_u16(sc_addr + 2 * (i + 0));
uint16_t i1 = vspace.read_u16(sc_addr + 2 * (i + 1));
uint16_t i2 = vspace.read_u16(sc_addr + 2 * (i + 2));
uint16_t i3 = vspace.read_u16(sc_addr + 2 * (i + 3));
uint16_t i4 = vspace.read_u16(sc_addr + 2 * (i + 4));
uint16_t i5 = vspace.read_u16(sc_addr + 2 * (i + 5));
/* Match: mov.l @(disp, pc), rX */
if((i0 & 0xf000) != 0xd000)
continue;
int rX = (i0 >> 8) & 0x0f;
/* Match: mov #3, rY */
if((i1 & 0xf0ff) != 0xe003)
continue;
int rY = (i1 >> 8) & 0x0f;
/* Match: bsr @(disp, pc) */
if((i2 & 0xf000) != 0xb000)
continue;
int disp = (i2 & 0x0fff);
/* Match: mov.b rX, @rY */
if((i3 != 0x2000 + (rX << 8) + (rY << 4)))
continue;
/* Match: bra @(_, pc) */
if((i4 & 0xf000) != 0xa000)
continue;
/* Match: nop */
if(i5 != 0x0009)
continue;
/* Return the target of the bsr instruction */
uint32_t fun_addr = sc_addr + 2 * (i + 2) + 4 + disp * 2;
fmt::print("found widget at 0x{:08x}\n", sc_addr + 2 * i);
fmt::print("rX = r{}, rY = r{}, disp = {}\n", rX, rY, disp);
fmt::print("main menu function address: 0x{:08x}\n", fun_addr);
}
}
static std::string parse_am(Session &session, Parser &parser)
{
if(!session.current_space)
return "";
std::string name = parser.symbol();
parser.end();
return name;
}
void _am(Session &session, std::string name)
{
if(!session.current_space) {
FxOS_log(ERR, "am: no virtual space");
return;
}
if(name == "cg_main_menu_function")
_am_cg_main_menu_function(*session.current_space);
else
FxOS_log(ERR, "am: unknown misc. command '%s'", name);
}
//---
// Command definitions
//---
@ -174,3 +257,16 @@ Disassembles all syscalls entries using ad, which stores the results in the
current virtual space's main disassembly. Unlike ad, this commands continues
even if some syscalls fail to disassemble.
)");
static ShellCommand _am_cmd(
"am",
[](Session &s, Parser &p) {
auto name = parse_am(s, p);
_am(s, name);
},
[](Session &s, Parser &p) { parse_am(s, p); }, "Analysis: Misc functions",
R"(
am <name>
Runs miscellaneous analysis functions; commonly used for prototyping.
)");

View File

@ -128,8 +128,9 @@ void _d(Session &session, std::variant<long, Range> location)
address++;
}
/* cfg implicitly does pcrel */
disassemble(session, disasm,
{"cfg", "pcrel", /*"constprop",*/ "syscall", "print"}, address);
{"cfg", /*"constprop",*/ "syscall", "print"}, address);
}
}

View File

@ -30,8 +30,8 @@ static _e_args parse_e(Session &session, Parser &parser)
if(!args.space_name.empty()) {
space = session.get_space(args.space_name);
if(!space) {
std::string msg =
format("virtual space '%s' does not exist", args.space_name);
std::string msg
= format("virtual space '%s' does not exist", args.space_name);
if(parser.completing())
throw Parser::CompletionRequest("_error", msg);
else
@ -49,14 +49,15 @@ static _e_args parse_e(Session &session, Parser &parser)
void _e(Session &, std::string, std::vector<long> const &values)
{
for(long value: values) {
long print_val = labs(value);
/* Hexa format */
int length = (labs(value) <= (1ll << 32) ? 8 : 16) + 2 + (value < 0);
int length = (print_val <= (1ll << 32) ? 8 : 16) + 2 + (value < 0);
std::string format = fmt::format("{{:#0{}x}}", length);
fmt::print(format, value);
long a = abs(value);
if(a <= 100 || a % 100 <= 1 || a % 100 >= 99)
fmt::print(" = {}", a);
if(print_val <= 100 || print_val % 100 <= 1 || print_val % 100 >= 99)
fmt::print(" = {}", print_val);
fmt::print("\n");
}

View File

@ -34,11 +34,69 @@ void _ic(Session &session, struct _ic_args const &args)
return;
for(uint32_t address: args.addresses) {
fmt::print("Claim over 0x{:08x}:\n", address);
Claim const *claim = session.current_space->disasm.getClaimAt(address);
if(claim)
fmt::print("0x{:08x} is claimed by {}\n", address, claim->str());
fmt::print(" 0x{:08x} is claimed by {}\n", address, claim->str());
else
fmt::print("0x{:08x} is not claimed\n", address);
fmt::print(" 0x{:08x} is not claimed\n", address);
auto dep = session.current_space->disasm.findClaimsOwnedBy(address);
fmt::print("Claims owned by 0x{:08x}:\n", address);
for(Claim const *c: dep)
fmt::print(" - {}\n", c->str());
if(!dep.size())
fmt::print(" (none)\n");
}
}
//---
// 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.current_space));
parser.end();
return args;
}
void _if(Session &session, struct _if_args const &args)
{
if(!session.current_space)
return;
Disassembly &disasm = session.current_space->disasm;
if(!args.addresses.size()) {
fmt::print("{} functions\n", disasm.functions.size());
}
for(uint32_t address: args.addresses) {
Function *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");
}
}
@ -249,6 +307,7 @@ struct _is_args
{
std::string vspace_name;
std::optional<FxOS::Symbol> symbol;
bool sort;
};
static struct _is_args parse_is(Session &session, Parser &parser)
@ -258,6 +317,9 @@ static struct _is_args parse_is(Session &session, Parser &parser)
parser.option("vspace",
[&args](std::string const &value) { args.vspace_name = value; });
parser.option("sort",
[&args](std::string const &value) { args.sort = (value == "true"); });
parser.accept_options();
FxOS::Symbol s;
@ -296,7 +358,7 @@ static struct _is_args parse_is(Session &session, Parser &parser)
}
void _is(Session &session, std::string vspace_name,
std::optional<FxOS::Symbol> symbol)
std::optional<FxOS::Symbol> symbol, bool sort)
{
VirtualSpace *space = session.current_space;
if(!space) {
@ -351,6 +413,9 @@ void _is(Session &session, std::string vspace_name,
symbols = {s};
}
if(sort)
std::sort(&symbols[0], &symbols[symbols.size()]);
for(auto const &s: symbols) {
if(s.type == FxOS::Symbol::Syscall) {
fmt::print(theme(10), " %{:04x}", s.value);
@ -377,6 +442,18 @@ usually generated by analysis commands and allow sections of the OS to be
marked as part of functions, data, interrupt handlers, etc.
)");
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) { _io(s, parse_io(s, p)); },
[](Session &s, Parser &p) { parse_io(s, p); }, "Info OS", R"(
@ -404,12 +481,13 @@ static ShellCommand _is_cmd(
"is",
[](Session &s, Parser &p) {
auto args = parse_is(s, p);
_is(s, args.vspace_name, args.symbol);
_is(s, args.vspace_name, args.symbol, args.sort);
},
[](Session &s, Parser &p) { parse_is(s, p); }, "Info Symbols", R"(
is [vspace=<virtual_space>] [<address|syscall>]
is [sort=true] [vspace=<virtual_space>] [<address|syscall>]
Lists symbols in the specified virtual space (defaults to the current
one). By default, all symbols are listed, but if an address or syscall is
provided, the symbol associated with it will be printed instead.
provided, the symbol associated with it will be printed instead. If sort=true,
symbols will be sorted by syscall number and then address.
)");

View File

@ -424,7 +424,15 @@ int main(int argc, char **argv)
}
/* Exhaust command input (if not all used by the command) */
parser.exhaust_until_separator();
while(true) {
try {
parser.exhaust_until_separator();
break;
}
catch(std::exception &e) {
FxOS_log(ERR, "%s", e.what());
}
}
}
/* Save command history */

View File

@ -249,12 +249,12 @@ std::variant<long, Range> Parser::expr_or_range(VirtualSpace *space)
if(m_la.type == ':') {
expect(':');
long length = expr(space);
return (Range){start, start + length};
return (Range) {start, start + length};
}
else if(m_la.type == '.') {
expect('.');
long end = expr(space);
return (Range){start, end};
return (Range) {start, end};
}
else {
return (long)start;