fxos: assembly visualization for new functions
This commit is contained in:
parent
bbfb96bb1a
commit
1df2a14c06
|
@ -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:
|
||||
|
|
|
@ -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 */
|
||||
|
|
|
@ -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
|
||||
|
|
36
lib/lang.cpp
36
lib/lang.cpp
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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 */
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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) {
|
||||
|
|
Loading…
Reference in New Issue