fxos/lib/analysis.cpp

Ignoring revisions in .git-blame-ignore-revs. Click here to bypass and see the normal blame view.

413 lines
12 KiB
C++
Raw Normal View History

2023-11-13 23:41:27 +01:00
//---------------------------------------------------------------------------//
// 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>
2023-11-13 23:41:27 +01:00
#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();
}
2023-11-13 23:41:27 +01:00
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;
2023-11-13 23:41:27 +01:00
}
}
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;
2023-11-13 23:41:27 +01:00
}
}
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;
}
2023-11-13 23:41:27 +01:00
/* 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();
}
}
2023-11-13 23:41:27 +01:00
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;
2023-11-13 23:41:27 +01:00
}
static void interpretBlock(BasicBlock const &bb, BlockStates &states)
{
2023-11-29 17:33:22 +01:00
ProgramState PS {states.entry};
2023-11-13 23:41:27 +01:00
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);
}
2023-11-13 23:41:27 +01:00
}
states.exit = PS;
}
std::unique_ptr<StaticFunctionAnalysis> interpretFunction(Function const &f)
2023-11-13 23:41:27 +01:00
{
std::vector<BlockStates> VBS;
/* Initialize all blocks' entry states */
for(uint i = 0; i < f.blockCount(); i++) {
BlockStates BS;
2023-11-29 17:33:22 +01:00
if(i == f.entryBlockIndex())
2023-11-13 23:41:27 +01:00
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;
2023-11-13 23:41:27 +01:00
2023-11-29 17:33:22 +01:00
for(int predIndex: bb.predecessorsByIndex())
VBS[i].nextEntry.joinWith(VBS[predIndex].exit);
2023-11-13 23:41:27 +01:00
}
/* 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 */