305 lines
7.2 KiB
C++
305 lines
7.2 KiB
C++
//---------------------------------------------------------------------------//
|
|
// 1100101 |_ mov #0, r4 __ //
|
|
// 11 |_ <0xb380 %5c4> / _|_ _____ ___ //
|
|
// 0110 |_ 3.50 -> 3.60 | _\ \ / _ (_-< //
|
|
// |_ base# + offset |_| /_\_\___/__/ //
|
|
//---------------------------------------------------------------------------//
|
|
|
|
#include <fxos/disassembly.h>
|
|
#include <fxos/vspace.h>
|
|
#include <fxos/binary.h>
|
|
#include <fxos/util/log.h>
|
|
#include <optional>
|
|
#include <array>
|
|
|
|
namespace FxOS {
|
|
|
|
/* Instruction map */
|
|
std::array<std::optional<AsmInstruction>, 65536> insmap;
|
|
|
|
void register_instruction(AsmInstruction const &ins)
|
|
{
|
|
uint16_t opcode = ins.opcode;
|
|
|
|
if(insmap[opcode])
|
|
FxOS_log(ERR, "opcode collision between a %s and a %s at %04x",
|
|
insmap[opcode]->mnemonic, ins.mnemonic, opcode);
|
|
else
|
|
insmap[opcode] = ins;
|
|
}
|
|
|
|
//---
|
|
// Concrete (instantiated) arguments and instructions
|
|
//---
|
|
|
|
Argument::Argument()
|
|
{
|
|
location = RelConstDomain().bottom();
|
|
value = location;
|
|
syscall_id = -1;
|
|
}
|
|
|
|
OldInstruction::OldInstruction(AsmInstruction const *inst):
|
|
inst {inst}, args {}, opcode {inst->opcode}, leader {false},
|
|
delayslot {false}, terminal {false}, jump {false}, condjump {false},
|
|
jmptarget {0xffffffff}
|
|
{
|
|
}
|
|
|
|
OldInstruction::OldInstruction(uint16_t opcode):
|
|
inst {nullptr}, args {}, opcode {opcode}, leader {false}, delayslot {false},
|
|
terminal {false}, jump {false}, condjump {false}, jmptarget {0xffffffff}
|
|
{
|
|
}
|
|
|
|
//---
|
|
// Function information
|
|
//---
|
|
|
|
OldFunction::OldFunction(uint32_t pc): address {pc}
|
|
{
|
|
}
|
|
|
|
//---
|
|
// Storage for disassembled data
|
|
//---
|
|
|
|
Disassembly::Disassembly(VirtualSpace &_vspace):
|
|
vspace {_vspace}, instructions {}, functions {}
|
|
{
|
|
}
|
|
|
|
bool Disassembly::hasInstructionAt(uint32_t pc)
|
|
{
|
|
return this->instructions.count(pc) > 0;
|
|
}
|
|
|
|
OldInstruction *Disassembly::getInstructionAt(uint32_t pc, bool allowDiscovery)
|
|
{
|
|
if(pc & 1) {
|
|
FxOS_log(ERR, "reading instruction for disassembly at 0x%08x", pc);
|
|
pc &= -2;
|
|
}
|
|
|
|
if(this->hasInstructionAt(pc)) {
|
|
return &this->instructions.at(pc);
|
|
}
|
|
else if(allowDiscovery) {
|
|
uint16_t opcode = this->vspace.read_u16(pc);
|
|
OldInstruction i(opcode);
|
|
|
|
if(insmap[opcode])
|
|
i = OldInstruction(&*insmap[opcode]);
|
|
|
|
this->instructions.emplace(pc, i);
|
|
return &this->instructions.at(pc);
|
|
}
|
|
else {
|
|
FxOS_log(ERR, "reading non-existing instruction at 0x%08x", pc);
|
|
return nullptr;
|
|
}
|
|
}
|
|
|
|
bool Disassembly::hasFunctionAt(uint32_t pc)
|
|
{
|
|
return this->functions.count(pc) > 0;
|
|
}
|
|
|
|
OldFunction *Disassembly::getFunctionAt(uint32_t pc)
|
|
{
|
|
auto it = this->functions.find(pc);
|
|
|
|
if(it == this->functions.end())
|
|
return nullptr;
|
|
else
|
|
return &it->second;
|
|
}
|
|
|
|
OldFunction *Disassembly::getOrCreateFunctionAt(uint32_t pc)
|
|
{
|
|
if(!this->hasFunctionAt(pc)) {
|
|
OldFunction f(pc);
|
|
this->functions.insert({pc, f});
|
|
}
|
|
return this->getFunctionAt(pc);
|
|
}
|
|
|
|
#if 0
|
|
// TODO: Reuse this for object intersections
|
|
std::vector<Claim const *> Disassembly::findClaimConflicts(
|
|
uint32_t address, int size, int max)
|
|
{
|
|
Claim fake_claim = {
|
|
.address = address,
|
|
.size = (uint16_t)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);
|
|
/* Backtrack to find the last claim whose start is [<= address] */
|
|
if(it != this->claims.begin())
|
|
it--;
|
|
|
|
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)
|
|
break;
|
|
|
|
/* There is an intersection */
|
|
if(it->intersects(fake_claim))
|
|
conflicts.push_back(&*it);
|
|
|
|
it++;
|
|
}
|
|
|
|
return conflicts;
|
|
}
|
|
#endif
|
|
|
|
//---
|
|
// DisassemblyPass
|
|
//---
|
|
|
|
DisassemblyPass::DisassemblyPass(Binary &binary): m_binary {binary}
|
|
{
|
|
}
|
|
|
|
//---
|
|
// FunctionPass
|
|
//---
|
|
|
|
FunctionPass::FunctionPass(Binary &binary): DisassemblyPass(binary)
|
|
{
|
|
}
|
|
|
|
bool FunctionPass::analyzeAllFunctions()
|
|
{
|
|
bool ok = true;
|
|
|
|
// TODO: Use Binary's functions
|
|
for(auto &pair: m_binary.vspace().disasm.functions)
|
|
ok &= this->analyzeFunction(pair.second);
|
|
|
|
return ok;
|
|
}
|
|
|
|
bool FunctionPass::analyzeFunction(uint32_t pc)
|
|
{
|
|
// TODO: Use Binary's functions
|
|
OldFunction *func = m_binary.vspace().disasm.getFunctionAt(pc);
|
|
if(!func) {
|
|
FxOS_log(ERR, "no function at 0x%08x", pc);
|
|
return false;
|
|
}
|
|
return this->analyzeFunction(*func);
|
|
}
|
|
|
|
bool FunctionPass::analyzeFunctionRecursively(OldFunction &func)
|
|
{
|
|
return this->analyzeFunctionRecursively(func.address);
|
|
}
|
|
|
|
bool FunctionPass::analyzeFunctionRecursively(uint32_t pc)
|
|
{
|
|
bool ok = true;
|
|
m_queue.enqueue(pc);
|
|
|
|
while(!m_queue.empty()) {
|
|
uint32_t pc = m_queue.pop();
|
|
// TODO: Use Binary's functions
|
|
OldFunction *next = m_binary.vspace().disasm.getFunctionAt(pc);
|
|
if(this->analyzeFunction(*next))
|
|
this->enqueueSubfunctions(*next);
|
|
else
|
|
ok = false;
|
|
}
|
|
|
|
return ok;
|
|
}
|
|
|
|
void FunctionPass::enqueueSubfunctions(OldFunction &func)
|
|
{
|
|
for(uint32_t pc: func.callTargets)
|
|
m_queue.enqueue(pc);
|
|
}
|
|
|
|
void FunctionPass::updateSubfunctions(OldFunction &func)
|
|
{
|
|
for(uint32_t pc: func.callTargets)
|
|
m_queue.update(pc);
|
|
}
|
|
|
|
//---
|
|
// InstructionPass
|
|
//---
|
|
|
|
InstructionPass::InstructionPass(Binary &binary):
|
|
FunctionPass(binary), m_allowDiscovery {false}
|
|
{
|
|
}
|
|
|
|
void InstructionPass::setAllowDiscovery(bool allowDiscovery)
|
|
{
|
|
m_allowDiscovery = allowDiscovery;
|
|
}
|
|
|
|
bool InstructionPass::analyzeAllInstructions()
|
|
{
|
|
bool ok = true;
|
|
|
|
// TODO: Use Binary's instructions
|
|
for(auto &pair: m_binary.vspace().disasm.instructions)
|
|
ok &= this->analyzeInstruction(pair.first, pair.second);
|
|
|
|
return ok;
|
|
}
|
|
|
|
bool InstructionPass::analyzeFunction(OldFunction &func)
|
|
{
|
|
/* We don't have any function-specific information to pass yet, so we can
|
|
fall back to the anonymous version */
|
|
return this->analyzeAnonymousFunction(func.address);
|
|
}
|
|
|
|
bool InstructionPass::analyzeAnonymousFunction(uint32_t pc)
|
|
{
|
|
bool ok = true;
|
|
m_queue.enqueue(pc);
|
|
|
|
while(!m_queue.empty()) {
|
|
uint32_t pc = m_queue.pop();
|
|
// TODO: Use Binary's instructions
|
|
OldInstruction *i
|
|
= m_binary.vspace().disasm.getInstructionAt(pc, m_allowDiscovery);
|
|
|
|
if(i != nullptr && this->analyzeInstruction(pc, *i))
|
|
this->enqueueSuccessors(pc, *i);
|
|
else
|
|
ok = false;
|
|
}
|
|
|
|
return ok;
|
|
}
|
|
|
|
void InstructionPass::enqueueSuccessors(uint32_t pc, OldInstruction &i)
|
|
{
|
|
if(!i.terminal && !i.jump)
|
|
m_queue.enqueue(pc + 2);
|
|
if(i.jump || i.condjump)
|
|
m_queue.enqueue(i.jmptarget);
|
|
}
|
|
|
|
void InstructionPass::updateSuccessors(uint32_t pc, OldInstruction &i)
|
|
{
|
|
if(!i.terminal && !i.jump)
|
|
m_queue.update(pc + 2);
|
|
if(i.jump || i.condjump)
|
|
m_queue.update(i.jmptarget);
|
|
}
|
|
|
|
} /* namespace FxOS */
|