fxos: assembly visualization for new functions

This commit is contained in:
Lephenixnoir 2023-11-04 11:01:46 +01:00
parent bbfb96bb1a
commit 1df2a14c06
Signed by: Lephenixnoir
GPG Key ID: 1BBA026E13FC0495
7 changed files with 187 additions and 46 deletions

View File

@ -80,7 +80,7 @@ struct Function: public BinaryObject
/* Construction functions to be used only by the cfg pass. */
void exploreFunctionAt(u32 address);
void addBasicBlock(BasicBlock &&bb);
BasicBlock &addBasicBlock(BasicBlock &&bb);
void updateFunctionSize();
private:

View File

@ -127,6 +127,10 @@ struct AsmArgument
/* Immediate value. Valid for Imm */
int imm;
};
/* Get the PC-relative target, assuming the instruction is at the provided
address, for arguments with PC-relative offsets. */
u32 getPCRelativeTarget(u32 pc, int size) const;
};
/* AsmArgument constructors */

View File

@ -19,13 +19,14 @@ Function::Function(Binary &binary, u32 address):
/* Size is not determined at first. */
/* Default unambiguous name */
setName(format("FUN_%08x", address));
setName(format("fun.%08x", address));
}
/* Add a basic block to the function. The entry block must be added first. */
void Function::addBasicBlock(BasicBlock &&bb)
BasicBlock &Function::addBasicBlock(BasicBlock &&bb)
{
m_blocks.push_back(bb);
return m_blocks.back();
}
/* Update the function's BinaryObject size by finding the last address covered
@ -181,11 +182,12 @@ void Function::exploreFunctionAt(u32 functionAddress)
for(u32 _: b.leaders) {
(void)_;
BasicBlock bb(*this, *it, *it == functionAddress);
BasicBlock bb0(*this, *it, *it == functionAddress);
BasicBlock &bb = addBasicBlock(std::move(bb0));
do {
// TODO: Support 32-bit instructions
u32 opcode = parentBinary().vspace().read_u16(opcode);
u32 opcode = parentBinary().vspace().read_u16(*it);
Instruction ins(*this, *it, opcode);
bb.addInstruction(std::move(ins));
it++;
@ -193,7 +195,6 @@ void Function::exploreFunctionAt(u32 functionAddress)
while(it != b.addresses.end() && !b.leaders.count(*it));
bb.finalizeBlock();
addBasicBlock(std::move(bb));
}
}
@ -216,7 +217,7 @@ uint BasicBlock::blockIndex() const
if(&bb == this)
return i;
}
assert(false && "blockIndex of block not in its own parent");
assert(false && "blockIndex: block not in its own parent");
}
bool BasicBlock::mayFallthrough() const

View File

@ -173,6 +173,24 @@ std::string AsmArgument::str() const
}
}
u32 AsmArgument::getPCRelativeTarget(u32 pc, int size) const
{
size = size + (size == 0);
if(this->kind == AsmArgument::PcRel)
return (pc & -size) + 4 + this->disp;
if(this->kind == AsmArgument::PcJump)
return pc + 4 + this->disp;
if(this->kind == AsmArgument::PcAddr)
return (pc & -4) + 4 + this->disp;
/* SH3 manual says that mova uses the target address of the jump when
in a delay slot. SH4AL-DSP makes it invalid. Supporting this would
be very tricky since the target PC is often dynamic (eg. rts). */
return 0xffffffff;
}
//---
// Instruction management
//---
@ -219,20 +237,10 @@ AsmInstruction::AsmInstruction(
u32 AsmInstruction::getPCRelativeTarget(u32 pc) const
{
for(int i = 0; i < arg_count; i++) {
AsmArgument const &arg = args[i];
int size = opsize + (opsize == 0);
if(arg.kind == AsmArgument::PcRel)
return (pc & -size) + 4 + arg.disp;
if(arg.kind == AsmArgument::PcJump)
return pc + 4 + arg.disp;
if(arg.kind == AsmArgument::PcAddr)
return (pc & -4) + 4 + arg.disp;
/* SH3 manual says that mova uses the target address of the jump when
in a delay slot. SH4AL-DSP makes it invalid. Supporting this would
be very tricky since the target PC is often dynamic (eg. rts). */
for(int i = 0; i < this->arg_count; i++) {
u32 target = this->args[i].getPCRelativeTarget(pc, this->opsize);
if(target != 0xffffffff)
return target;
}
return 0xffffffff;
}

View File

@ -7,11 +7,13 @@
#include <fxos/view/assembly.h>
#include <fxos/binary.h>
#include <fxos/function.h>
#include <fxos/util/format.h>
#include <fxos/util/Queue.h>
#include <vector>
#include <cstdio>
#include <cstring>
#include <numeric>
#include <fmt/color.h>
namespace FxOS {
@ -32,7 +34,8 @@ static inline bool output(ArgumentOutput &out, ViewAssemblyOptions::Promotion p,
return false;
}
static void renderArgument(AsmArgument const &arg, Argument const &a,
// TODO: Take advantage of Instruction's info
static void renderArgument(AsmArgument const &arg, u32 pc, int opsize,
ArgumentOutput &out, ViewAssemblyOptions const &opts)
{
out.push_back({{}, arg.str()});
@ -50,30 +53,51 @@ static void renderArgument(AsmArgument const &arg, Argument const &a,
else if(arg.kind == AsmArgument::PcAddr)
type = PCAddr;
u32 value;
bool hasValue = false;
OS *os = opts.binary ? opts.binary->OSAnalysis() : nullptr;
VirtualSpace *vspace = opts.binary ? &opts.binary->vspace() : nullptr;
if(type == PCJump || type == PCRelative || type == PCAddr) {
auto p = (type == PCJump) ? opts.promotions.PCJump_to_Location
: (type == PCAddr) ? opts.promotions.PCAddr_to_Location
: opts.promotions.PCRelative_to_Location;
if(!RelConstDomain().is_constant(a.location))
u32 location = arg.getPCRelativeTarget(pc, opsize);
if(location == 0xffffffff)
return;
if(output(out, p, {}, format("<%s>", a.location.str())))
if(output(out, p, {}, format("<%08x>", location)))
return;
type = (type == PCRelative) ? Location : Constant;
// TODO: Check that this is a read operation!
if(opsize != 0 && vspace && vspace->covers(location, opsize)) {
if(opsize == 1) {
value = vspace->read_i8(location);
hasValue = true;
}
if(opsize == 2) {
value = vspace->read_i16(location);
hasValue = true;
}
if(opsize == 4) {
value = vspace->read_i32(location);
hasValue = true;
}
}
}
if(type == Location) {
// TODO: Check that this is a read operation!
auto p = opts.promotions.ReadLocation_to_Constant;
if(!a.value || output(out, p, {}, a.value.str()))
auto rc = RelConstDomain().constant(value);
if(!hasValue || output(out, p, {}, rc.str()))
return;
type = Constant;
}
/* Promote to object name first if available... */
if(type == Constant && a.value && opts.binary) {
if(type == Constant && hasValue && opts.binary) {
auto p = opts.promotions.Constant_to_ObjectName;
u32 address = RelConstDomain().constant_value(a.value);
BinaryObject *obj = opts.binary->objectAt(address);
BinaryObject *obj = opts.binary->objectAt(value);
if(obj) {
if(output(out, p, {}, obj->name()))
@ -82,11 +106,14 @@ static void renderArgument(AsmArgument const &arg, Argument const &a,
}
}
/* ... or, as a default, a syscall number */
if(type == Constant && a.value && a.syscall_id >= 0) {
auto p = opts.promotions.Constant_to_SyscallNumber;
if(output(out, p, {}, format("%%%04x", a.syscall_id)))
return;
type = SyscallNumber;
if(type == Constant && hasValue && os) {
int syscall_id = os->find_syscall(value);
if(syscall_id >= 0) {
auto p = opts.promotions.Constant_to_SyscallNumber;
if(output(out, p, {}, format("%%%04x", syscall_id)))
return;
type = SyscallNumber;
}
}
}
@ -133,7 +160,7 @@ static void doOldInst(u32 pc, OldInstruction &i,
if(n)
printf(", ");
renderArgument(i.inst->args[n], i.args[n], argout, opts);
renderArgument(i.inst->args[n], pc, i.inst->opsize, argout, opts);
for(size_t i = 0; i < argout.size(); i++) {
if(i != 0)
@ -201,11 +228,109 @@ void viewAssemblyLegacyAddress(
//=== Binary-API assembly printer ===//
void viewAssemblyInstruction(
Instruction const &inst, ViewAssemblyOptions *opts);
static ViewAssemblyOptions defaultOptions {};
void viewAssemblyBasicBlock(BasicBlock const &bb, ViewAssemblyOptions *opts);
void viewAssemblyInstruction(Instruction const &ins, ViewAssemblyOptions *opts)
{
opts = opts ? opts : &defaultOptions;
void viewAssemblyFunction(Function const &fun, ViewAssemblyOptions *opts);
AsmInstruction opcode = ins.opcode();
ArgumentOutput argout;
u32 pc = ins.address();
printf(" %08x: %04x", pc, opcode.opcode);
/* Mnemonic */
static char const *suffixes[5] = {"", ".b", ".w", "", ".l"};
char const *suffix = suffixes[(opcode.opsize <= 4) ? opcode.opsize : 0];
int spacing
= opcode.arg_count ? 8 - strlen(opcode.mnemonic) - strlen(suffix) : 0;
printf(" %s%s%*s", opcode.mnemonic, suffix, spacing, "");
/* Arguments */
for(size_t n = 0; n < opcode.arg_count; n++) {
if(n)
printf(", ");
renderArgument(opcode.args[n], pc, opcode.opsize, argout, *opts);
for(size_t i = 0; i < argout.size(); i++) {
if(i != 0)
printf(" ");
printf("%s", argout[i].second.c_str());
}
argout.clear();
}
printf("\n");
}
static std::string objectsAt(
Binary const &binary, u32 address, BinaryObject const *except = nullptr)
{
std::vector<std::string> objects;
OS *os = binary.OSAnalysis();
if(os) {
int index = os->find_syscall(address);
if(index >= 0)
objects.push_back(fmt::format("%{:04x}", index));
}
int unnamed = 0;
for(BinaryObject const *obj: binary.objectsAt(address)) {
if(obj == except)
continue;
if(obj->name() == "")
unnamed++;
else
objects.push_back(obj->name());
}
if(unnamed > 0)
objects.push_back(fmt::format("+{}", unnamed));
return std::accumulate(objects.begin(), objects.end(), std::string {},
[](auto &l, auto const r) { return l + (l.empty() ? "" : " ") + r; });
}
void viewAssemblyBasicBlock(BasicBlock const &bb, ViewAssemblyOptions *opts)
{
opts = opts ? opts : &defaultOptions;
printf(" bb.%08x", bb.address());
if(bb.address() != bb.parentFunction().address()) {
std::string others = objectsAt(bb.parentBinary(), bb.address());
if(others != "")
printf(" (%s)", others.c_str());
}
printf(":\n");
for(Instruction const &ins: bb)
viewAssemblyInstruction(ins, opts);
printf("\n");
}
void viewAssemblyFunction(Function const &fun, ViewAssemblyOptions *opts)
{
opts = opts ? opts : &defaultOptions;
/* Note that the Function constructor sets "fun.%08x" as name by default */
if(fun.name() != "")
printf("%s", fun.name().c_str());
else
printf("fun.%08x", fun.address());
std::string others = objectsAt(fun.parentBinary(), fun.address(), &fun);
if(others != "")
printf(" (%s)", others.c_str());
printf(":\n");
for(BasicBlock const &bb: fun)
viewAssemblyBasicBlock(bb, opts);
}
} /* namespace FxOS */

View File

@ -39,7 +39,7 @@ static void disassemble(
}
}
else if(pass == "print" && address + 1) {
viewAssemblyLegacyAddress(binary, address);
// viewAssemblyLegacyAddress(binary, address);
}
else {
FxOS_log(ERR, "unknown pass <%s>", pass);
@ -56,9 +56,11 @@ static void disassemble(
}
if(address + 1) {
printf("&<<<< function test >>>>&\n");
Function f(binary, address);
f.exploreFunctionAt(address);
ViewAssemblyOptions opts;
opts.binary = &binary;
viewAssemblyFunction(f, &opts);
}
}

View File

@ -294,7 +294,7 @@ void Parser::accept_options()
if(m_options.count(opt))
m_options[opt](*this);
else if(!m_complete)
FxOS_log(ERR, "unrecognized option %s", opt);
lex_err("unrecognized option %s", opt.c_str());
}
}
@ -316,11 +316,12 @@ long Parser::atom()
if(opt)
val = *opt;
else if(!m_complete)
FxOS_log(ERR, "symbol '%s' is undefined", t.value.STRING);
lex_err("symbol '%s' is undefined", t.value.STRING.c_str());
}
else if(!m_complete) {
lex_err("cannot query symbol '%s' (no virtual space)",
t.value.STRING.c_str());
}
else if(!m_complete)
FxOS_log(ERR, "cannot query symbol '%s' (no virtual space)",
t.value.STRING);
return val;
}
else if(t.type == T::SYSCALL) {