199 lines
5.7 KiB
C++
199 lines
5.7 KiB
C++
//---------------------------------------------------------------------------//
|
|
// 1100101 |_ mov #0, r4 __ //
|
|
// 11 |_ <0xb380 %5c4> / _|_ _____ ___ //
|
|
// 0110 |_ 3.50 -> 3.60 | _\ \ / _ (_-< //
|
|
// |_ base# + offset |_| /_\_\___/__/ //
|
|
//---------------------------------------------------------------------------//
|
|
// fxos/analysis: Static analysis for assembler programs
|
|
//
|
|
// TODO: Designed to be an abstract interpreter, still WIP
|
|
//---
|
|
|
|
#ifndef FXOS_ANALYSIS_H
|
|
#define FXOS_ANALYSIS_H
|
|
|
|
#include <fxos/util/types.h>
|
|
#include <fxos/ai/RelConst.h>
|
|
#include <fxos/function.h>
|
|
#include <memory>
|
|
#include <vector>
|
|
|
|
namespace FxOS {
|
|
|
|
struct ProgramStateDiff;
|
|
|
|
/* Full description of a program state for the analyzer, at a known PC. */
|
|
struct ProgramState
|
|
{
|
|
ProgramState()
|
|
{
|
|
setBottom();
|
|
}
|
|
|
|
RelConst getRegister(int n) const
|
|
{
|
|
return ((unsigned)n >= 16) ? RelConstDomain().bottom() : m_regs[n];
|
|
}
|
|
|
|
// TODO: More value in program state
|
|
|
|
/* Set to initial program state at entry of function. */
|
|
void setFunctionInit();
|
|
/* Set to initial non-entry-block state at entry of function (all bot). */
|
|
void setBottom();
|
|
/* Set to completely unknown. */
|
|
void setTop();
|
|
/* Apply a diff. */
|
|
void applyDiff(ProgramStateDiff const &diff);
|
|
|
|
/* Join with another program state. */
|
|
void joinWith(ProgramState const &other);
|
|
/* Lattice order. */
|
|
bool le(ProgramState const &other) const;
|
|
|
|
private:
|
|
/* Values for registers r0..r15 */
|
|
RelConst m_regs[16];
|
|
};
|
|
|
|
/* Change in program state. This describes the effect of either:
|
|
- A single instruction;
|
|
- A branch combined with its delay slot;
|
|
- A branch combined with a no-op.
|
|
The single instruction or delay slot is referred to as the "base
|
|
instruction". */
|
|
struct ProgramStateDiff
|
|
{
|
|
enum class BaseType : u8 {
|
|
/* Instruction can do anything, makes the entire state top. */
|
|
Anything,
|
|
/* Instruction has no effect on tracked state. */
|
|
NoOp,
|
|
/* Instruction updates a register. */
|
|
RegisterUpdate,
|
|
};
|
|
enum class BranchType : u8 {
|
|
/* No branch. */
|
|
None,
|
|
/* Branch to a different PC with no other effect on state. */
|
|
Branch,
|
|
/* Branch to SPC and restore SR to SSR. */
|
|
Rte,
|
|
/* Branch is a function call with standard calling convention. */
|
|
CallStandard,
|
|
};
|
|
|
|
/* Type of effect for the base instruction. */
|
|
BaseType baseType() const
|
|
{
|
|
return m_baseType;
|
|
}
|
|
/* Type of effect for the branch (None if there's no branch). */
|
|
BranchType branchType() const
|
|
{
|
|
return m_branchType;
|
|
}
|
|
|
|
/* For BaseType::RegisterUpdate, which register is targeted. */
|
|
CpuRegister registerName() const
|
|
{
|
|
assert(baseType() == BaseType::RegisterUpdate && "wrong accessor");
|
|
return m_register;
|
|
}
|
|
/* For BaseType::RegisterUpdate, new value of the register. */
|
|
RelConst registerValue() const
|
|
{
|
|
assert(baseType() == BaseType::RegisterUpdate && "wrong accessor");
|
|
return m_value;
|
|
}
|
|
|
|
/*** Pseudo-constructors ***/
|
|
|
|
/* Clear the base diff. */
|
|
void clearBase()
|
|
{
|
|
m_baseType = BaseType::NoOp;
|
|
}
|
|
/* Set the base diff to changing register r to new value v. */
|
|
void setRegisterUpdate(CpuRegister reg, RelConst v)
|
|
{
|
|
m_baseType = BaseType::RegisterUpdate;
|
|
m_register = reg;
|
|
m_value = v;
|
|
}
|
|
/* Set the base diff to changing register r to an unknown value. */
|
|
void setRegisterTouched(CpuRegister reg)
|
|
{
|
|
setRegisterUpdate(reg, RelConstDomain().top());
|
|
}
|
|
/* Set the base diff to changing no register state. */
|
|
void setNoOp()
|
|
{
|
|
m_baseType = BaseType::NoOp;
|
|
}
|
|
/* Set the dif to an unknown effect. */
|
|
void setAnything()
|
|
{
|
|
m_baseType = BaseType::Anything;
|
|
}
|
|
|
|
/* Clear the branch diff. */
|
|
void clearBranch()
|
|
{
|
|
m_branchType = BranchType::None;
|
|
}
|
|
/* Set the branch diff to a normal branch. */
|
|
void setBranch()
|
|
{
|
|
m_branchType = BranchType::Branch;
|
|
}
|
|
/* Set the branch diff to the rte instruction. */
|
|
void setRte()
|
|
{
|
|
m_branchType = BranchType::Rte;
|
|
}
|
|
/* Set the branch diff to a call with standard calling convention. */
|
|
void setCallStandard()
|
|
{
|
|
m_branchType = BranchType::CallStandard;
|
|
}
|
|
|
|
/* Merge a branch diff with a delay slot diff. The bound instance should be
|
|
the branch instruction diff and it can't have a base. The other one
|
|
should not have a branch. */
|
|
void mergeWithDelaySlot(ProgramStateDiff const &delaySlotDiff);
|
|
|
|
std::string baseStr() const;
|
|
std::string branchStr() const;
|
|
/* String representation. If optional is true, returns an empty string for
|
|
no-op diffs. Otherwise always returns an unambiguous string. */
|
|
std::string str(bool optional = false) const;
|
|
|
|
private:
|
|
BaseType m_baseType = BaseType::NoOp;
|
|
BranchType m_branchType = BranchType::None;
|
|
CpuRegister m_register;
|
|
RelConst m_value;
|
|
};
|
|
|
|
/* Function's storage of program states at every control point. */
|
|
struct StaticFunctionAnalysis
|
|
{
|
|
/* Information stored for each block */
|
|
struct Block
|
|
{
|
|
ProgramState entry;
|
|
std::vector<ProgramStateDiff> diffs;
|
|
};
|
|
|
|
std::vector<Block> blocks;
|
|
};
|
|
|
|
/* Analyze a function; returns analysis results if successful, a null pointer
|
|
on error. Does not store the results in f itself. */
|
|
std::unique_ptr<StaticFunctionAnalysis> interpretFunction(Function const &f);
|
|
|
|
} // namespace FxOS
|
|
|
|
#endif /* FXOS_ANALYSIS_H */
|