fxos/lib/analysis.cpp

413 lines
12 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

//---------------------------------------------------------------------------//
// 1100101 |_ mov #0, r4 __ //
// 11 |_ <0xb380 %5c4> / _|_ _____ ___ //
// 0110 |_ 3.50 -> 3.60 | _\ \ / _ (_-< //
// |_ base# + offset |_| /_\_\___/__/ //
//---------------------------------------------------------------------------//
#include <fxos/analysis.h>
#include <fxos/util/log.h>
#include <fmt/core.h>
#include <cassert>
namespace FxOS {
void ProgramState::setFunctionInit()
{
// TODO: Analysis: Set symbolic parameters at function entry
for(int i = 0; i < 16; i++)
m_regs[i] = RelConstDomain().top();
}
void ProgramState::setBottom()
{
for(int i = 0; i < 16; i++)
m_regs[i] = RelConstDomain().bottom();
}
void ProgramState::setTop()
{
for(int i = 0; i < 16; i++)
m_regs[i] = RelConstDomain().top();
}
void ProgramState::applyDiff(ProgramStateDiff const &diff)
{
RelConstDomain RCD;
switch(diff.baseType()) {
case ProgramStateDiff::BaseType::Anything:
setTop();
break;
case ProgramStateDiff::BaseType::NoOp:
break;
case ProgramStateDiff::BaseType::RegisterUpdate: {
CpuRegister reg = diff.registerName();
if(reg.getR() >= 0)
m_regs[reg.getR()] = diff.registerValue();
break;
}
}
switch(diff.branchType()) {
case ProgramStateDiff::BranchType::None:
case ProgramStateDiff::BranchType::Branch:
case ProgramStateDiff::BranchType::Rte:
break;
case ProgramStateDiff::BranchType::CallStandard:
for(int i = 0; i < 7; i++)
m_regs[i] = RCD.top();
break;
}
}
void ProgramState::joinWith(ProgramState const &other)
{
RelConstDomain RCD;
for(int i = 0; i < 16; i++) {
m_regs[i] = RCD.join(m_regs[i], other.getRegister(i));
}
}
bool ProgramState::le(ProgramState const &other) const
{
RelConstDomain RCD;
for(int i = 0; i < 16; i++) {
if(!RCD.le(m_regs[i], other.getRegister(i)))
return false;
}
return true;
}
void ProgramStateDiff::mergeWithDelaySlot(ProgramStateDiff const &slotDiff)
{
assert(baseType() == BaseType::NoOp && "merge diff from not a branch");
assert(slotDiff.branchType() == BranchType::None
&& "merge diff from not a delay slot");
auto bt = branchType();
*this = slotDiff;
m_branchType = bt;
}
std::string ProgramStateDiff::baseStr() const
{
switch(baseType()) {
case BaseType::Anything:
return "";
case BaseType::NoOp:
return "";
case BaseType::RegisterUpdate:
return fmt::format("{} ← {}", m_register.str(), m_value.str(false));
}
return "???";
}
std::string ProgramStateDiff::branchStr() const
{
switch(branchType()) {
case BranchType::None:
case BranchType::Branch:
return "";
case BranchType::Rte:
return "rte";
case BranchType::CallStandard:
return "stdcall";
}
return "???";
}
std::string ProgramStateDiff::str(bool optional) const
{
std::string base = baseStr();
std::string branch = branchStr();
if(!base.empty() && !branch.empty())
return base + " | " + branch;
if(base.empty() && branch.empty() && !optional)
return "()";
return base.empty() ? branch : base;
}
/* Information stored for each block during the fixpoint iteration */
struct BlockStates
{
ProgramState entry;
std::vector<ProgramStateDiff> diffs;
ProgramState exit;
ProgramState nextEntry;
};
static u32 computeConstantOperand(Instruction const &ins, AsmOperand const &op)
{
Binary const &binary = ins.parentBinary();
u32 target;
assert(op.isConstant() && "analysis of constant operands is out of sync");
switch(op.kind()) {
case AsmOperand::PcRel:
target = op.getPCRelativeTarget(ins.address());
if(!binary.vspace().covers(target, op.opsize())) {
FxOS_log(ERR, "constant operand reads out of vspace bounds");
return -1;
}
if(op.opsize() == 1)
return binary.vspace().read_i8(target);
if(op.opsize() == 2)
return binary.vspace().read_i16(target);
if(op.opsize() == 4)
return binary.vspace().read_i32(target);
FxOS_log(ERR, "PcRel operand with no opsize");
return -1;
case AsmOperand::PcJump:
case AsmOperand::PcAddr:
return op.getPCRelativeTarget(ins.address());
case AsmOperand::Imm:
return op.imm();
default:
assert(false && "not a constant operand");
__builtin_unreachable();
}
}
static ProgramStateDiff interpretInstruction(
Instruction const &ins, ProgramState const &PS)
{
RelConstDomain RCD;
ProgramStateDiff diff;
diff.setAnything();
AsmInstruction asmins = ins.opcode();
switch(asmins.operation()) {
/* Moves */
case AsmInstruction::SH_mov:
case AsmInstruction::SH_ldc:
case AsmInstruction::SH_lds:
case AsmInstruction::SH_stc:
case AsmInstruction::SH_sts:
case AsmInstruction::SH_mova: {
AsmOperand src = asmins.operand(0);
AsmOperand dst = asmins.operand(1);
if(!dst.isReg())
diff.setNoOp();
else if(src.isConstant()) {
RelConst c = RCD.constant(computeConstantOperand(ins, src));
diff.setRegisterUpdate(dst.base(), c);
}
else
diff.setRegisterTouched(dst.base());
break;
}
/* Opaque instructions with one operand and one output */
case AsmInstruction::SH_dt:
case AsmInstruction::SH_movt:
case AsmInstruction::SH_rotl:
case AsmInstruction::SH_rotr:
case AsmInstruction::SH_rotcl:
case AsmInstruction::SH_rotcr:
case AsmInstruction::SH_shal:
case AsmInstruction::SH_shar:
case AsmInstruction::SH_shll:
case AsmInstruction::SH_shlr:
case AsmInstruction::SH_shll2:
case AsmInstruction::SH_shlr2:
case AsmInstruction::SH_shll8:
case AsmInstruction::SH_shlr8:
case AsmInstruction::SH_shll16:
case AsmInstruction::SH_shlr16: {
AsmOperand op = asmins.operand(0);
assert(op.isReg());
diff.setRegisterTouched(op.base());
break;
}
/* Opaque instructions with two operands and one output */
case AsmInstruction::SH_add:
case AsmInstruction::SH_addc:
case AsmInstruction::SH_addv:
case AsmInstruction::SH_and:
case AsmInstruction::SH_div1:
case AsmInstruction::SH_exts:
case AsmInstruction::SH_extu:
case AsmInstruction::SH_neg:
case AsmInstruction::SH_negc:
case AsmInstruction::SH_not:
case AsmInstruction::SH_or:
case AsmInstruction::SH_shad:
case AsmInstruction::SH_shld:
case AsmInstruction::SH_sub:
case AsmInstruction::SH_subc:
case AsmInstruction::SH_subv:
case AsmInstruction::SH_swap:
case AsmInstruction::SH_xor:
case AsmInstruction::SH_xtrct: {
AsmOperand op = asmins.operand(1);
diff.setRegisterTouched(op.base());
break;
}
/* No-op instructions that affect state not modeled by the analysis */
case AsmInstruction::SH_clrs:
case AsmInstruction::SH_clrt:
case AsmInstruction::SH_clrmac:
case AsmInstruction::SH_div0u:
case AsmInstruction::SH_ldtlb:
case AsmInstruction::SH_nop:
case AsmInstruction::SH_rte:
case AsmInstruction::SH_rts:
case AsmInstruction::SH_sets:
case AsmInstruction::SH_sett:
case AsmInstruction::SH_sleep:
case AsmInstruction::SH_cmp_pl:
case AsmInstruction::SH_cmp_pz:
case AsmInstruction::SH_cmp_eq:
case AsmInstruction::SH_cmp_hs:
case AsmInstruction::SH_cmp_ge:
case AsmInstruction::SH_cmp_hi:
case AsmInstruction::SH_cmp_gt:
case AsmInstruction::SH_cmp_str:
case AsmInstruction::SH_div0s:
case AsmInstruction::SH_dmuls:
case AsmInstruction::SH_dmulu:
case AsmInstruction::SH_mul:
case AsmInstruction::SH_muls:
case AsmInstruction::SH_mulu:
case AsmInstruction::SH_tst:
case AsmInstruction::SH_jmp:
case AsmInstruction::SH_pref:
case AsmInstruction::SH_tas:
case AsmInstruction::SH_mac:
case AsmInstruction::SH_braf:
case AsmInstruction::SH_bf:
case AsmInstruction::SH_bf_s:
case AsmInstruction::SH_bt:
case AsmInstruction::SH_bt_s:
case AsmInstruction::SH_bra:
case AsmInstruction::SH_trapa:
case AsmInstruction::SH_icbi:
case AsmInstruction::SH_ocbi:
case AsmInstruction::SH_ocbp:
case AsmInstruction::SH_ocbwb:
case AsmInstruction::SH_prefi:
case AsmInstruction::SH_synco:
diff.setNoOp();
break;
case AsmInstruction::SH_bsr:
case AsmInstruction::SH_bsrf:
case AsmInstruction::SH_jsr:
diff.setNoOp();
diff.setCallStandard();
break;
case AsmInstruction::SH_movco:
case AsmInstruction::SH_movli:
case AsmInstruction::SH_movua:
case AsmInstruction::SH_movca:
diff.setAnything();
break;
}
for(auto op: ins.opcode().operands()) {
/* TODO: Properly handle pre-decr/post-dec */
if(op.kind() == AsmOperand::PreDec || op.kind() == AsmOperand::PostInc)
diff.setAnything();
}
return diff;
}
static void interpretBlock(BasicBlock const &bb, BlockStates &states)
{
ProgramState PS {states.entry};
states.diffs.clear();
// TODO: Fix that, use delay slots
for(auto const &[ins, delaySlot]: bb.instructionsAndDelaySlots()) {
ProgramStateDiff diff = interpretInstruction(ins, PS);
if(delaySlot) {
ProgramStateDiff diff2 = interpretInstruction(*delaySlot, PS);
diff.mergeWithDelaySlot(diff2);
states.diffs.push_back(diff);
states.diffs.emplace_back();
PS.applyDiff(diff);
}
else {
states.diffs.push_back(diff);
PS.applyDiff(diff);
}
}
states.exit = PS;
}
std::unique_ptr<StaticFunctionAnalysis> interpretFunction(Function const &f)
{
std::vector<BlockStates> VBS;
/* Initialize all blocks' entry states */
for(uint i = 0; i < f.blockCount(); i++) {
BlockStates BS;
if(i == f.entryBlockIndex())
BS.entry.setFunctionInit();
else
BS.entry.setBottom();
VBS.push_back(BS);
}
/* The naive iteration strategy */
while(true) {
/* Interpret all blocks on their current states */
for(uint i = 0; i < f.blockCount(); i++)
interpretBlock(f.basicBlockByIndex(i), VBS[i]);
/* Compute the next entry state for each block */
for(uint i = 0; i < f.blockCount(); i++) {
BasicBlock const &bb = f.basicBlockByIndex(i);
VBS[i].nextEntry = VBS[i].entry;
for(int predIndex: bb.predecessorsByIndex())
VBS[i].nextEntry.joinWith(VBS[predIndex].exit);
}
/* Determine whether a fixpoint has been reached yet */
bool pfp = std::all_of(VBS.begin(), VBS.end(),
[](BlockStates &BS) { return BS.nextEntry.le(BS.entry); });
if(pfp)
break;
/* Switch to next state */
for(uint i = 0; i < f.blockCount(); i++)
VBS[i].entry = VBS[i].nextEntry;
}
auto an = std::make_unique<StaticFunctionAnalysis>();
for(uint i = 0; i < f.blockCount(); i++) {
StaticFunctionAnalysis::Block B;
B.entry = VBS[i].entry;
B.diffs = std::move(VBS[i].diffs);
an->blocks.push_back(std::move(B));
}
return an;
}
} /* namespace FxOS */