Improve command classification
This commit is contained in:
parent
3a9a622ee3
commit
12845a1675
|
@ -0,0 +1,114 @@
|
|||
AccessModifierOffset: -4
|
||||
AlignAfterOpenBracket: DontAlign
|
||||
AlignArrayOfStructures: None
|
||||
AlignConsecutiveAssignments: None
|
||||
AlignConsecutiveBitFields: AcrossEmptyLinesAndComments
|
||||
AlignConsecutiveDeclarations: None
|
||||
AlignConsecutiveMacros: Consecutive
|
||||
AlignEscapedNewlines: DontAlign
|
||||
AlignOperands: Align
|
||||
AlignTrailingComments: true
|
||||
AllowAllArgumentsOnNextLine: true
|
||||
AllowAllParametersOfDeclarationOnNextLine: true
|
||||
AllowShortBlocksOnASingleLine: Empty
|
||||
AllowShortCaseLabelsOnASingleLine: false
|
||||
AllowShortEnumsOnASingleLine: true
|
||||
AllowShortFunctionsOnASingleLine: false
|
||||
AllowShortIfStatementsOnASingleLine: false
|
||||
AllowShortLambdasOnASingleLine: All
|
||||
AllowShortLoopsOnASingleLine: false
|
||||
AlwaysBreakAfterReturnType: None
|
||||
AlwaysBreakBeforeMultilineStrings: true
|
||||
AlwaysBreakTemplateDeclarations: Yes
|
||||
AttributeMacros: []
|
||||
BinPackArguments: true
|
||||
BinPackParameters: true
|
||||
BitFieldColonSpacing: Before
|
||||
BreakBeforeBraces: Custom
|
||||
BraceWrapping:
|
||||
AfterCaseLabel: false
|
||||
AfterClass: true
|
||||
AfterControlStatement: Never
|
||||
AfterEnum: false
|
||||
AfterFunction: true
|
||||
AfterNamespace: false
|
||||
AfterStruct: true
|
||||
AfterUnion: true
|
||||
AfterExternBlock: false
|
||||
BeforeCatch: true
|
||||
BeforeElse: true
|
||||
BeforeLambdaBody: false
|
||||
BeforeWhile: true
|
||||
IndentBraces: false
|
||||
SplitEmptyFunction: true
|
||||
SplitEmptyRecord: true
|
||||
SplitEmptyNamespace: true
|
||||
BreakBeforeBinaryOperators: All
|
||||
BreakBeforeTernaryOperators: true
|
||||
BreakConstructorInitializers: AfterColon
|
||||
BreakInheritanceList: AfterColon
|
||||
BreakStringLiterals: false
|
||||
ColumnLimit: 80
|
||||
CompactNamespaces: false
|
||||
ConstructorInitializerIndentWidth: 4
|
||||
ContinuationIndentWidth: 4
|
||||
Cpp11BracedListStyle: true
|
||||
DeriveLineEnding: false
|
||||
DerivePointerAlignment: false
|
||||
DisableFormat: false
|
||||
EmptyLineAfterAccessModifier: Leave
|
||||
EmptyLineBeforeAccessModifier: Leave
|
||||
FixNamespaceComments: true
|
||||
IncludeBlocks: Regroup
|
||||
IndentAccessModifiers: false
|
||||
IndentCaseBlocks: false
|
||||
IndentCaseLabels: false
|
||||
IndentExternBlock: NoIndent
|
||||
IndentGotoLabels: false
|
||||
IndentPPDirectives: None
|
||||
IndentWidth: 4
|
||||
IndentWrappedFunctionNames: false
|
||||
KeepEmptyLinesAtTheStartOfBlocks: true
|
||||
LambdaBodyIndentation: Signature
|
||||
Language: Cpp
|
||||
MaxEmptyLinesToKeep: 5
|
||||
NamespaceIndentation: None
|
||||
PPIndentWidth: 4
|
||||
# PackConstructorInitializers: BinPack
|
||||
PointerAlignment: Right
|
||||
# QualifierAlignment: Custom
|
||||
# QualifierOrder: ['constexpr', 'inline', 'static', 'type', 'const', 'volatile', 'restrict']
|
||||
ReferenceAlignment: Right
|
||||
ReflowComments: false
|
||||
ShortNamespaceLines: 1
|
||||
SortIncludes: Never
|
||||
SortUsingDeclarations: true
|
||||
SpaceAfterCStyleCast: false
|
||||
SpaceAfterLogicalNot: false
|
||||
SpaceAfterTemplateKeyword: false
|
||||
SpaceAroundPointerQualifiers: Both
|
||||
SpaceBeforeAssignmentOperators: true
|
||||
SpaceBeforeCaseColon: false
|
||||
SpaceBeforeCpp11BracedList: true
|
||||
SpaceBeforeCtorInitializerColon: false
|
||||
SpaceBeforeInheritanceColon: false
|
||||
SpaceBeforeParens: Never
|
||||
SpaceBeforeRangeBasedForLoopColon: false
|
||||
SpaceBeforeSquareBrackets: false
|
||||
SpaceInEmptyBlock: false
|
||||
SpaceInEmptyParentheses: false
|
||||
SpacesBeforeTrailingComments: 2
|
||||
SpacesInAngles: false
|
||||
SpacesInCStyleCastParentheses: false
|
||||
SpacesInConditionalStatement: false
|
||||
SpacesInContainerLiterals: false
|
||||
SpacesInLineCommentPrefix:
|
||||
Minimum: 1
|
||||
Maximum: -1
|
||||
SpacesInParentheses: false
|
||||
SpacesInSquareBrackets: false
|
||||
Standard: Latest
|
||||
StatementMacros: ['__attribute__']
|
||||
TabWidth: 4
|
||||
UseCRLF: false
|
||||
UseTab: Never
|
|
@ -47,12 +47,14 @@ set(fxos_shell_SOURCES
|
|||
shell/parser.cpp
|
||||
shell/session.cpp
|
||||
shell/theme.cpp
|
||||
shell/dot.cpp
|
||||
shell/a.cpp
|
||||
shell/d.cpp
|
||||
shell/e.cpp
|
||||
shell/g.cpp
|
||||
shell/h.cpp
|
||||
shell/i.cpp
|
||||
shell/m.cpp
|
||||
shell/s.cpp
|
||||
shell/v.cpp
|
||||
${FLEX_Lexer_OUTPUTS}
|
||||
|
|
|
@ -36,14 +36,14 @@ class AbstractMemory
|
|||
{
|
||||
public:
|
||||
/* Checks if an address or interval is simulated (in its entirety) */
|
||||
bool covers(uint32_t addr, int size=1);
|
||||
bool covers(uint32_t addr, int size = 1);
|
||||
|
||||
/* Check if a full region is simulated */
|
||||
bool covers(MemoryRegion const ®ion);
|
||||
|
||||
/* Returns the data located at the provided virtual address, nullptr if it
|
||||
is not entirely covered. */
|
||||
char const *translate(uint32_t addr, int size=1);
|
||||
char const *translate(uint32_t addr, int size = 1);
|
||||
|
||||
/* Returns the data located at the provided virtual address, and indicates
|
||||
how much is available in *size. The pointer is null if [addr] itself is
|
||||
|
@ -65,11 +65,11 @@ public:
|
|||
data type, see <fxos/util/Addressable.h>. */
|
||||
|
||||
/* Various sizes of integers with sign-extension or zero-extension. */
|
||||
Addressable<int8_t> read_i8 (uint32_t addr);
|
||||
Addressable<uint8_t> read_u8 (uint32_t addr);
|
||||
Addressable<int16_t> read_i16(uint32_t addr);
|
||||
Addressable<int8_t> read_i8(uint32_t addr);
|
||||
Addressable<uint8_t> read_u8(uint32_t addr);
|
||||
Addressable<int16_t> read_i16(uint32_t addr);
|
||||
Addressable<uint16_t> read_u16(uint32_t addr);
|
||||
Addressable<int32_t> read_i32(uint32_t addr);
|
||||
Addressable<int32_t> read_i32(uint32_t addr);
|
||||
Addressable<uint32_t> read_u32(uint32_t addr);
|
||||
|
||||
/* Read a non-NUL-terminated string */
|
||||
|
|
|
@ -29,36 +29,36 @@ class AbstractDomain
|
|||
{
|
||||
public:
|
||||
/* Bottom and Top constants */
|
||||
virtual T bottom() const noexcept = 0;
|
||||
virtual T top() const noexcept = 0;
|
||||
virtual T bottom() const noexcept = 0;
|
||||
virtual T top() const noexcept = 0;
|
||||
|
||||
/* Construct abstract value from integer constant */
|
||||
virtual T constant(uint32_t value) const noexcept = 0;
|
||||
virtual T constant(uint32_t value) const noexcept = 0;
|
||||
/* Check if value is constant */
|
||||
virtual bool is_constant(T) const noexcept = 0;
|
||||
virtual bool is_constant(T) const noexcept = 0;
|
||||
/* Unpack a constant. May return anything if is_constant() is false */
|
||||
virtual uint32_t constant_value(T) const noexcept = 0;
|
||||
virtual uint32_t constant_value(T) const noexcept = 0;
|
||||
|
||||
/* Basic arithmetic. Division and modulo are both non-trivial
|
||||
instruction sequences usually isolated in easily-identifiable
|
||||
subroutines, so we don't care about them. */
|
||||
virtual T minus(T) const noexcept = 0;
|
||||
virtual T add(T, T) const noexcept = 0;
|
||||
virtual T sub(T, T) const noexcept = 0;
|
||||
virtual T smul(T, T) const noexcept = 0;
|
||||
virtual T umul(T, T) const noexcept = 0;
|
||||
virtual T minus(T) const noexcept = 0;
|
||||
virtual T add(T, T) const noexcept = 0;
|
||||
virtual T sub(T, T) const noexcept = 0;
|
||||
virtual T smul(T, T) const noexcept = 0;
|
||||
virtual T umul(T, T) const noexcept = 0;
|
||||
|
||||
/* Sign extensions */
|
||||
virtual T extub(T) const noexcept = 0;
|
||||
virtual T extsb(T) const noexcept = 0;
|
||||
virtual T extuw(T) const noexcept = 0;
|
||||
virtual T extsw(T) const noexcept = 0;
|
||||
virtual T extub(T) const noexcept = 0;
|
||||
virtual T extsb(T) const noexcept = 0;
|
||||
virtual T extuw(T) const noexcept = 0;
|
||||
virtual T extsw(T) const noexcept = 0;
|
||||
|
||||
/* Logical operations */
|
||||
virtual T lnot(T) const noexcept = 0;
|
||||
virtual T land(T, T) const noexcept = 0;
|
||||
virtual T lor(T, T) const noexcept = 0;
|
||||
virtual T lxor(T, T) const noexcept = 0;
|
||||
virtual T lnot(T) const noexcept = 0;
|
||||
virtual T land(T, T) const noexcept = 0;
|
||||
virtual T lor(T, T) const noexcept = 0;
|
||||
virtual T lxor(T, T) const noexcept = 0;
|
||||
|
||||
/* Comparisons. This operation proceeds in two steps:
|
||||
* First call cmp(x, y) to check if the values are comparable. If
|
||||
|
@ -66,9 +66,9 @@ public:
|
|||
* If the values are comparable, call cmpu(x, y) or cmps(x, y), which
|
||||
returns a negative number if x < y, 0 if x == y, and a positive
|
||||
number if x > y. */
|
||||
virtual bool cmp(T, T) const noexcept = 0;
|
||||
virtual int cmpu(T, T) const noexcept = 0;
|
||||
virtual int cmps(T, T) const noexcept = 0;
|
||||
virtual bool cmp(T, T) const noexcept = 0;
|
||||
virtual int cmpu(T, T) const noexcept = 0;
|
||||
virtual int cmps(T, T) const noexcept = 0;
|
||||
};
|
||||
|
||||
} /* namespace FxOS */
|
||||
|
|
|
@ -33,7 +33,7 @@ namespace FxOS {
|
|||
/* The lattice of relative constants (base + offset) */
|
||||
struct RelConst
|
||||
{
|
||||
enum { Bottom=1, Top=2 };
|
||||
enum { Bottom = 1, Top = 2 };
|
||||
|
||||
/* The following fields concurrently indicate the base. The order of
|
||||
resolution is as follows (non-trivial types in parentheses):
|
||||
|
@ -46,8 +46,10 @@ struct RelConst
|
|||
For efficiency, checking [base==0] will tell apart plain old
|
||||
constants from values with bases (and specials but these are usually
|
||||
handled first). */
|
||||
union {
|
||||
struct {
|
||||
union
|
||||
{
|
||||
struct
|
||||
{
|
||||
uint8_t spe;
|
||||
uint8_t arg;
|
||||
uint8_t org;
|
||||
|
@ -65,7 +67,8 @@ struct RelConst
|
|||
non-trivial effect on signs such as multiplication are not
|
||||
supported with bases.
|
||||
* For zero bases, the interpretation is instruction-dependent. */
|
||||
union {
|
||||
union
|
||||
{
|
||||
int32_t ival;
|
||||
uint32_t uval;
|
||||
};
|
||||
|
@ -79,7 +82,7 @@ struct RelConst
|
|||
|
||||
/* Evaluates to true if the location is non-trivial, ie. if it is
|
||||
neither Top nor Bottom. */
|
||||
operator bool () const noexcept;
|
||||
operator bool() const noexcept;
|
||||
|
||||
/* String representation */
|
||||
std::string str() const noexcept;
|
||||
|
@ -93,32 +96,32 @@ public:
|
|||
|
||||
/* Implementation of the AbstractDomain specification */
|
||||
|
||||
RelConst bottom() const noexcept override;
|
||||
RelConst top() const noexcept override;
|
||||
RelConst bottom() const noexcept override;
|
||||
RelConst top() const noexcept override;
|
||||
|
||||
RelConst constant(uint32_t value) const noexcept override;
|
||||
bool is_constant(RelConst) const noexcept override;
|
||||
uint32_t constant_value(RelConst) const noexcept override;
|
||||
RelConst constant(uint32_t value) const noexcept override;
|
||||
bool is_constant(RelConst) const noexcept override;
|
||||
uint32_t constant_value(RelConst) const noexcept override;
|
||||
|
||||
RelConst minus(RelConst) const noexcept override;
|
||||
RelConst add(RelConst, RelConst) const noexcept override;
|
||||
RelConst sub(RelConst, RelConst) const noexcept override;
|
||||
RelConst smul(RelConst, RelConst) const noexcept override;
|
||||
RelConst umul(RelConst, RelConst) const noexcept override;
|
||||
RelConst minus(RelConst) const noexcept override;
|
||||
RelConst add(RelConst, RelConst) const noexcept override;
|
||||
RelConst sub(RelConst, RelConst) const noexcept override;
|
||||
RelConst smul(RelConst, RelConst) const noexcept override;
|
||||
RelConst umul(RelConst, RelConst) const noexcept override;
|
||||
|
||||
RelConst extub(RelConst) const noexcept override;
|
||||
RelConst extsb(RelConst) const noexcept override;
|
||||
RelConst extuw(RelConst) const noexcept override;
|
||||
RelConst extsw(RelConst) const noexcept override;
|
||||
RelConst extub(RelConst) const noexcept override;
|
||||
RelConst extsb(RelConst) const noexcept override;
|
||||
RelConst extuw(RelConst) const noexcept override;
|
||||
RelConst extsw(RelConst) const noexcept override;
|
||||
|
||||
RelConst lnot(RelConst) const noexcept override;
|
||||
RelConst land(RelConst, RelConst) const noexcept override;
|
||||
RelConst lor(RelConst, RelConst) const noexcept override;
|
||||
RelConst lxor(RelConst, RelConst) const noexcept override;
|
||||
RelConst lnot(RelConst) const noexcept override;
|
||||
RelConst land(RelConst, RelConst) const noexcept override;
|
||||
RelConst lor(RelConst, RelConst) const noexcept override;
|
||||
RelConst lxor(RelConst, RelConst) const noexcept override;
|
||||
|
||||
bool cmp(RelConst, RelConst) const noexcept override;
|
||||
int cmpu(RelConst, RelConst) const noexcept override;
|
||||
int cmps(RelConst, RelConst) const noexcept override;
|
||||
bool cmp(RelConst, RelConst) const noexcept override;
|
||||
int cmpu(RelConst, RelConst) const noexcept override;
|
||||
int cmps(RelConst, RelConst) const noexcept override;
|
||||
};
|
||||
|
||||
} /* namespace FxOS */
|
||||
|
|
|
@ -148,7 +148,8 @@ struct Claim
|
|||
std::string str() const;
|
||||
};
|
||||
|
||||
constexpr bool operator < (Claim const &c1, Claim const &c2) {
|
||||
constexpr bool operator<(Claim const &c1, Claim const &c2)
|
||||
{
|
||||
return c1.address < c2.address;
|
||||
}
|
||||
|
||||
|
@ -173,7 +174,7 @@ struct Disassembly
|
|||
/* Find an instruction by address. If the instruction is not loaded,
|
||||
returns nullptr, unless [allowDiscovery] is set, in which case it's
|
||||
loaded normally. */
|
||||
Instruction *getInstructionAt(uint32_t pc, bool allowDiscovery=false);
|
||||
Instruction *getInstructionAt(uint32_t pc, bool allowDiscovery = false);
|
||||
|
||||
|
||||
// Function information
|
||||
|
|
|
@ -38,26 +38,58 @@ namespace FxOS {
|
|||
class CpuRegister
|
||||
{
|
||||
public:
|
||||
enum CpuRegisterName: int8_t {
|
||||
enum CpuRegisterName : int8_t {
|
||||
/* Value 0 is reserved for special purposes such as "no register" */
|
||||
UNDEFINED = 0,
|
||||
/* Caller-saved general-purpose registers */
|
||||
R0, R1, R2, R3, R4, R5, R6, R7,
|
||||
R0,
|
||||
R1,
|
||||
R2,
|
||||
R3,
|
||||
R4,
|
||||
R5,
|
||||
R6,
|
||||
R7,
|
||||
/* Banked general-purpose registers. fxos does not account for
|
||||
banking identities, these are just for naming and output. */
|
||||
R0B, R1B, R2B, R3B, R4B, R5B, R6B, R7B,
|
||||
R0B,
|
||||
R1B,
|
||||
R2B,
|
||||
R3B,
|
||||
R4B,
|
||||
R5B,
|
||||
R6B,
|
||||
R7B,
|
||||
/* Callee-saved general-purpose registers */
|
||||
R8, R9, R10, R11, R12, R13, R14, R15,
|
||||
R8,
|
||||
R9,
|
||||
R10,
|
||||
R11,
|
||||
R12,
|
||||
R13,
|
||||
R14,
|
||||
R15,
|
||||
/* System registers */
|
||||
MACH, MACL, PR, PC,
|
||||
MACH,
|
||||
MACL,
|
||||
PR,
|
||||
PC,
|
||||
/* Control registers */
|
||||
SR, SSR, SPC, GBR, VBR, DBR, SGR,
|
||||
SR,
|
||||
SSR,
|
||||
SPC,
|
||||
GBR,
|
||||
VBR,
|
||||
DBR,
|
||||
SGR,
|
||||
};
|
||||
|
||||
CpuRegister() = default;
|
||||
|
||||
/* Construction from CpuRegisterName */
|
||||
constexpr CpuRegister(CpuRegisterName name): m_name(name) {}
|
||||
constexpr CpuRegister(CpuRegisterName name): m_name(name)
|
||||
{
|
||||
}
|
||||
|
||||
/* Construction from string */
|
||||
CpuRegister(std::string register_name);
|
||||
|
@ -66,13 +98,18 @@ public:
|
|||
std::string str() const noexcept;
|
||||
|
||||
/* Conversion to CpuRegisterName for switch statements */
|
||||
constexpr operator CpuRegisterName() noexcept { return m_name; }
|
||||
constexpr operator CpuRegisterName() noexcept
|
||||
{
|
||||
return m_name;
|
||||
}
|
||||
|
||||
/* Comparison operators */
|
||||
constexpr bool operator==(CpuRegister r) const {
|
||||
constexpr bool operator==(CpuRegister r) const
|
||||
{
|
||||
return m_name == r.m_name;
|
||||
}
|
||||
constexpr bool operator!=(CpuRegister r) const {
|
||||
constexpr bool operator!=(CpuRegister r) const
|
||||
{
|
||||
return m_name != r.m_name;
|
||||
}
|
||||
|
||||
|
@ -84,17 +121,17 @@ private:
|
|||
struct AsmArgument
|
||||
{
|
||||
/* Various addressing modes in the language */
|
||||
enum Kind: int8_t {
|
||||
Reg, /* rn */
|
||||
Deref, /* @rn */
|
||||
PostInc, /* @rn+ */
|
||||
PreDec, /* @-rn */
|
||||
StructDeref, /* @(disp,rn) or @(disp,gbr) */
|
||||
ArrayDeref, /* @(r0,rn) or @(r0,gbr) */
|
||||
PcRel, /* @(disp,pc) with 4-alignment correction */
|
||||
PcJump, /* pc+disp */
|
||||
PcAddr, /* pc+disp (the address itself, for mova) */
|
||||
Imm, /* #imm */
|
||||
enum Kind : int8_t {
|
||||
Reg, /* rn */
|
||||
Deref, /* @rn */
|
||||
PostInc, /* @rn+ */
|
||||
PreDec, /* @-rn */
|
||||
StructDeref, /* @(disp,rn) or @(disp,gbr) */
|
||||
ArrayDeref, /* @(r0,rn) or @(r0,gbr) */
|
||||
PcRel, /* @(disp,pc) with 4-alignment correction */
|
||||
PcJump, /* pc+disp */
|
||||
PcAddr, /* pc+disp (the address itself, for mova) */
|
||||
Imm, /* #imm */
|
||||
};
|
||||
|
||||
AsmArgument() = default;
|
||||
|
@ -111,7 +148,8 @@ struct AsmArgument
|
|||
/* Operation size (0, 1, 2 or 4). Generally a multiplier for disp */
|
||||
int8_t opsize;
|
||||
|
||||
union {
|
||||
union
|
||||
{
|
||||
/* Displacement in bytes. For StructDeref, PcRel, PcJump, and PcAddr */
|
||||
int disp;
|
||||
/* Immediate value. Valid for Imm */
|
||||
|
|
|
@ -41,13 +41,15 @@ public:
|
|||
/* Start and end of area (both included) */
|
||||
uint32_t start, end;
|
||||
/* Size of area */
|
||||
uint32_t size() const {
|
||||
uint32_t size() const
|
||||
{
|
||||
return this->end - this->start + 1;
|
||||
}
|
||||
/* Name ("U0", "P0", "P1", "P2", "P3", or "P4") */
|
||||
char const *name;
|
||||
|
||||
constexpr bool operator == (MemoryArea const &other) const {
|
||||
constexpr bool operator==(MemoryArea const &other) const
|
||||
{
|
||||
return this->start == other.start && this->end == other.end;
|
||||
}
|
||||
};
|
||||
|
@ -74,8 +76,7 @@ struct MemoryRegion
|
|||
/* Empty region at 0 */
|
||||
MemoryRegion();
|
||||
/* Short constructor which calls guess_flags() */
|
||||
MemoryRegion(std::string name, uint32_t start, uint32_t end,
|
||||
bool writable);
|
||||
MemoryRegion(std::string name, uint32_t start, uint32_t end, bool writable);
|
||||
/* Short constructor for standard regions only */
|
||||
MemoryRegion(std::string standard_region_name);
|
||||
|
||||
|
|
|
@ -103,7 +103,7 @@ private:
|
|||
/* Syscall table, in order of syscall IDs */
|
||||
std::vector<uint32_t> m_syscall_table;
|
||||
/* Bimap converse, syscalls sorted by address */
|
||||
std::map<uint32_t,int> m_syscall_addresses;
|
||||
std::map<uint32_t, int> m_syscall_addresses;
|
||||
};
|
||||
|
||||
} /* namespace FxOS */
|
||||
|
|
|
@ -57,11 +57,11 @@ public:
|
|||
/* Promotion parameters. Default is always to append. */
|
||||
enum Promotion {
|
||||
/* Never promote */
|
||||
Never=1,
|
||||
Never = 1,
|
||||
/* Promote but keep the lower-level information */
|
||||
Append=0,
|
||||
Append = 0,
|
||||
/* Promote and hide the lower-level information */
|
||||
Promote=2,
|
||||
Promote = 2,
|
||||
};
|
||||
|
||||
/** In the following, promote_x always means promote *to x* **/
|
||||
|
|
|
@ -57,7 +57,8 @@ struct IntegerType: public BaseType
|
|||
{
|
||||
static DataType const *u8, *i8, *u16, *i16, *u32, *i32;
|
||||
|
||||
IntegerType(size_t _size, bool _issigned) {
|
||||
IntegerType(size_t _size, bool _issigned)
|
||||
{
|
||||
size = align = _size;
|
||||
issigned = _issigned;
|
||||
}
|
||||
|
@ -113,7 +114,13 @@ class DataType
|
|||
{
|
||||
public:
|
||||
/* Variant identifier (think of it as a named sum type) */
|
||||
enum DataKind { Integer=0, Bitfield=1, Array=2, String=3, Struct=4 };
|
||||
enum DataKind {
|
||||
Integer = 0,
|
||||
Bitfield = 1,
|
||||
Array = 2,
|
||||
String = 3,
|
||||
Struct = 4
|
||||
};
|
||||
DataKind kind() const noexcept;
|
||||
|
||||
/* Common properties */
|
||||
|
@ -131,14 +138,25 @@ public:
|
|||
|
||||
/* Converting constructors from any of these types */
|
||||
|
||||
DataType(IntegerType t): v(t) {}
|
||||
DataType(BitfieldType t): v(t) {}
|
||||
DataType(ArrayType t): v(t) {}
|
||||
DataType(StringType t): v(t) {}
|
||||
DataType(StructType t): v(t) {}
|
||||
DataType(IntegerType t): v(t)
|
||||
{
|
||||
}
|
||||
DataType(BitfieldType t): v(t)
|
||||
{
|
||||
}
|
||||
DataType(ArrayType t): v(t)
|
||||
{
|
||||
}
|
||||
DataType(StringType t): v(t)
|
||||
{
|
||||
}
|
||||
DataType(StructType t): v(t)
|
||||
{
|
||||
}
|
||||
|
||||
private:
|
||||
std::variant<IntegerType,BitfieldType,ArrayType,StringType,StructType> v;
|
||||
std::variant<IntegerType, BitfieldType, ArrayType, StringType, StructType>
|
||||
v;
|
||||
};
|
||||
|
||||
//---
|
||||
|
@ -161,10 +179,12 @@ struct DataValue
|
|||
DataValue(DataType const *type);
|
||||
|
||||
/* Check whether the value is fully defined and initialized */
|
||||
bool defined() const {
|
||||
bool defined() const
|
||||
{
|
||||
return std::find(mem.begin(), mem.end(), -1) == mem.end();
|
||||
}
|
||||
operator bool() const {
|
||||
operator bool() const
|
||||
{
|
||||
return defined();
|
||||
}
|
||||
|
||||
|
|
|
@ -27,7 +27,7 @@ namespace FxOS {
|
|||
/* A named symbol that can be substituted to literal values in the code. */
|
||||
struct Symbol
|
||||
{
|
||||
enum Type { Syscall=1, Address=2 };
|
||||
enum Type { Syscall = 1, Address = 2 };
|
||||
|
||||
enum Type type;
|
||||
uint32_t value;
|
||||
|
|
|
@ -30,11 +30,18 @@ struct Addressable
|
|||
uint32_t address;
|
||||
|
||||
Addressable() = default;
|
||||
Addressable(T value): value(value), address(-1) {}
|
||||
Addressable(uint32_t addr, T value): value(value), address(addr) {}
|
||||
Addressable(T value): value(value), address(-1)
|
||||
{
|
||||
}
|
||||
Addressable(uint32_t addr, T value): value(value), address(addr)
|
||||
{
|
||||
}
|
||||
|
||||
/* Implicitly decay to the base type */
|
||||
operator T () const { return value; }
|
||||
operator T() const
|
||||
{
|
||||
return value;
|
||||
}
|
||||
};
|
||||
|
||||
#endif /* FXOS_UTIL_ADDRESSABLE_H */
|
||||
|
|
|
@ -26,22 +26,22 @@ struct Buffer
|
|||
Buffer();
|
||||
|
||||
/* Empty buffer initialized with given byte */
|
||||
Buffer(size_t size, int fill=0x00);
|
||||
Buffer(size_t size, int fill = 0x00);
|
||||
|
||||
/* Buffer initialized from file, reading the given size from the
|
||||
beginning of the file. If the file is smaller than the specified
|
||||
size, the buffer is padded.
|
||||
|
||||
If this constructor is used, the file path is remembered. */
|
||||
Buffer(std::string const &filepath, ssize_t size=-1, int fill=0x00);
|
||||
Buffer(std::string const &filepath, ssize_t size = -1, int fill = 0x00);
|
||||
|
||||
/* Buffer initialized from file by looking in one of the specified
|
||||
directories only. */
|
||||
Buffer(std::string filepath, std::vector<std::string> const &folders,
|
||||
ssize_t size=-1, int fill=0x00);
|
||||
ssize_t size = -1, int fill = 0x00);
|
||||
|
||||
/* Create a buffer by copying and resizing another buffer */
|
||||
Buffer(Buffer const &other, size_t new_size, int fill=0x00);
|
||||
Buffer(Buffer const &other, size_t new_size, int fill = 0x00);
|
||||
|
||||
/* Buffer size */
|
||||
size_t size;
|
||||
|
|
|
@ -16,30 +16,37 @@
|
|||
template<typename T>
|
||||
struct Queue
|
||||
{
|
||||
Queue(): pending {}, seen {} {}
|
||||
Queue(): pending {}, seen {}
|
||||
{
|
||||
}
|
||||
|
||||
bool empty() const {
|
||||
bool empty() const
|
||||
{
|
||||
return pending.empty();
|
||||
}
|
||||
T &pop() {
|
||||
T &pop()
|
||||
{
|
||||
T &object = pending.front();
|
||||
pending.pop();
|
||||
return object;
|
||||
}
|
||||
void clear() {
|
||||
void clear()
|
||||
{
|
||||
pending.clear();
|
||||
seen.clear();
|
||||
}
|
||||
|
||||
/* Enqueue an object to visit later (if not already visited) */
|
||||
void enqueue(T object) {
|
||||
void enqueue(T object)
|
||||
{
|
||||
if(!seen.count(object)) {
|
||||
pending.push(object);
|
||||
seen.insert(object);
|
||||
}
|
||||
}
|
||||
/* Enqueue an object even for later update, regardless of loops */
|
||||
void update(T object) {
|
||||
void update(T object)
|
||||
{
|
||||
pending.push(object);
|
||||
seen.insert(object);
|
||||
}
|
||||
|
|
|
@ -20,14 +20,14 @@
|
|||
#include <cstdio>
|
||||
|
||||
/* Format a string with printf() syntax */
|
||||
template<typename ... Args>
|
||||
std::string _format_do(std::string const &format, Args && ... args)
|
||||
template<typename... Args>
|
||||
std::string _format_do(std::string const &format, Args &&...args)
|
||||
{
|
||||
/* Reserve space for snprintf() to put its NUL */
|
||||
size_t size = snprintf(nullptr, 0, format.c_str(), args ...) + 1;
|
||||
size_t size = snprintf(nullptr, 0, format.c_str(), args...) + 1;
|
||||
|
||||
std::unique_ptr<char[]> buf(new char[size]);
|
||||
snprintf(buf.get(), size, format.c_str(), args ...);
|
||||
snprintf(buf.get(), size, format.c_str(), args...);
|
||||
|
||||
/* Remove the NUL from the string */
|
||||
return std::string(buf.get(), buf.get() + size - 1);
|
||||
|
@ -35,18 +35,18 @@ std::string _format_do(std::string const &format, Args && ... args)
|
|||
|
||||
/* Convert std::string to char * when printing (by @Zitrax on Github) */
|
||||
template<typename T>
|
||||
auto _format_convert(T&& t)
|
||||
auto _format_convert(T &&t)
|
||||
{
|
||||
if constexpr(std::is_same<std::remove_cv_t<std::remove_reference_t<T>>,
|
||||
std::string>::value)
|
||||
std::string>::value)
|
||||
return std::forward<T>(t).c_str();
|
||||
else
|
||||
return std::forward<T>(t);
|
||||
}
|
||||
|
||||
/* String formatting with std::string support in %s */
|
||||
template<typename ... Args>
|
||||
std::string format(std::string const &format, Args && ... args)
|
||||
template<typename... Args>
|
||||
std::string format(std::string const &format, Args &&...args)
|
||||
{
|
||||
return _format_do(format, _format_convert(std::forward<Args>(args))...);
|
||||
}
|
||||
|
|
|
@ -24,7 +24,7 @@
|
|||
|
||||
#include <fxos/util/format.h>
|
||||
|
||||
namespace FxOS {
|
||||
namespace FxOS {
|
||||
|
||||
/* Message levels, for masking */
|
||||
constexpr int LOG_LEVEL_LOG = 0;
|
||||
|
@ -44,7 +44,7 @@ void logmsg(int level, char const *file, int line, char const *func,
|
|||
} /* namespace FxOS */
|
||||
|
||||
#define FxOS_log(level, fmt, ...) \
|
||||
FxOS::logmsg(FxOS::LOG_LEVEL_ ## level, __FILE__, __LINE__, __func__, \
|
||||
FxOS::logmsg(FxOS::LOG_LEVEL_##level, __FILE__, __LINE__, __func__, \
|
||||
format(fmt, ##__VA_ARGS__))
|
||||
|
||||
#endif /* FXOS_UTIL_LOG_H */
|
||||
|
|
|
@ -86,7 +86,7 @@ public:
|
|||
|
||||
/* OS analysis; created on-demand. Returns the new or cached OS analysis,
|
||||
nullptr OS analysis fails. */
|
||||
OS *os_analysis(bool force=false);
|
||||
OS *os_analysis(bool force = false);
|
||||
|
||||
private:
|
||||
/* OS analysis results */
|
||||
|
|
|
@ -87,10 +87,10 @@ Addressable<std::string> AbstractMemory::read_str(uint32_t addr, size_t len)
|
|||
return Addressable(addr, std::string(str, len));
|
||||
}
|
||||
|
||||
uint32_t AbstractMemory::search(uint32_t start, uint32_t end,
|
||||
void const *pattern, int size)
|
||||
uint32_t AbstractMemory::search(
|
||||
uint32_t start, uint32_t end, void const *pattern, int size)
|
||||
{
|
||||
void const *data = translate(start, end-start);
|
||||
void const *data = translate(start, end - start);
|
||||
if(!data)
|
||||
return end;
|
||||
|
||||
|
|
|
@ -20,12 +20,13 @@ auto constexpr Top = RelConst::Top;
|
|||
auto constexpr Bottom = RelConst::Bottom;
|
||||
|
||||
/* General prelude that propagates Top, then Bottom */
|
||||
#define special(r1, r2) { \
|
||||
if((r1).spe == Top || (r2).spe == Top) \
|
||||
return top(); \
|
||||
if((r1).spe || (r2).spe) \
|
||||
return bottom(); \
|
||||
}
|
||||
#define special(r1, r2) \
|
||||
{ \
|
||||
if((r1).spe == Top || (r2).spe == Top) \
|
||||
return top(); \
|
||||
if((r1).spe || (r2).spe) \
|
||||
return bottom(); \
|
||||
}
|
||||
|
||||
inline RelConst RelConstDomain::bottom() const noexcept
|
||||
{
|
||||
|
@ -270,7 +271,7 @@ int RelConstDomain::cmps(RelConst r1, RelConst r2) const noexcept
|
|||
// Other functions
|
||||
//---
|
||||
|
||||
RelConst::operator bool () const noexcept
|
||||
RelConst::operator bool() const noexcept
|
||||
{
|
||||
return !spe;
|
||||
}
|
||||
|
@ -287,17 +288,22 @@ std::string RelConst::str() const noexcept
|
|||
return "Top";
|
||||
|
||||
std::string str;
|
||||
if(arg) str = format("arg%d", arg);
|
||||
if(org) str = format("org_%s", CpuRegister((RegName)org).str());
|
||||
if(reg) str = CpuRegister((RegName)org).str();
|
||||
if(arg)
|
||||
str = format("arg%d", arg);
|
||||
if(org)
|
||||
str = format("org_%s", CpuRegister((RegName)org).str());
|
||||
if(reg)
|
||||
str = CpuRegister((RegName)org).str();
|
||||
|
||||
if(!uval)
|
||||
return str;
|
||||
|
||||
if(ival >= -256 && ival < 256) {
|
||||
uint32_t v = 0;
|
||||
if(str.size() && ival > 0) str += "+", v = ival;
|
||||
if(str.size() && ival < 0) str += "-", v = -ival;
|
||||
if(str.size() && ival > 0)
|
||||
str += "+", v = ival;
|
||||
if(str.size() && ival < 0)
|
||||
str += "-", v = -ival;
|
||||
|
||||
return str + format("%d", v);
|
||||
}
|
||||
|
|
|
@ -39,16 +39,15 @@ Argument::Argument()
|
|||
}
|
||||
|
||||
Instruction::Instruction(AsmInstruction const *inst):
|
||||
inst {inst}, args {}, opcode {inst->opcode},
|
||||
leader {false}, delayslot {false}, terminal {false}, jump {false},
|
||||
condjump {false}, jmptarget {0xffffffff}
|
||||
inst {inst}, args {}, opcode {inst->opcode}, leader {false},
|
||||
delayslot {false}, terminal {false}, jump {false}, condjump {false},
|
||||
jmptarget {0xffffffff}
|
||||
{
|
||||
}
|
||||
|
||||
Instruction::Instruction(uint16_t opcode):
|
||||
inst {nullptr}, args {}, opcode {opcode},
|
||||
leader {false}, delayslot {false}, terminal {false}, jump {false},
|
||||
condjump {false}, jmptarget {0xffffffff}
|
||||
inst {nullptr}, args {}, opcode {opcode}, leader {false}, delayslot {false},
|
||||
terminal {false}, jump {false}, condjump {false}, jmptarget {0xffffffff}
|
||||
{
|
||||
}
|
||||
|
||||
|
@ -60,8 +59,8 @@ bool Claim::intersects(Claim const &other) const
|
|||
{
|
||||
/* Compute the actual intersection */
|
||||
uint32_t inter_start = std::max(this->address, other.address);
|
||||
uint32_t inter_end = std::min(this->address + this->size,
|
||||
other.address + other.size);
|
||||
uint32_t inter_end
|
||||
= std::min(this->address + this->size, other.address + other.size);
|
||||
|
||||
return inter_start < inter_end;
|
||||
}
|
||||
|
@ -145,9 +144,9 @@ Claim const *Disassembly::findClaimConflict(uint32_t address, int size)
|
|||
{
|
||||
Claim fake_claim = {
|
||||
.address = address,
|
||||
.size = (uint16_t)size,
|
||||
.type = 0,
|
||||
.owner = 0,
|
||||
.size = (uint16_t)size,
|
||||
.type = 0,
|
||||
.owner = 0,
|
||||
};
|
||||
|
||||
/* Find the first claim whose start is [> address] */
|
||||
|
@ -180,8 +179,8 @@ bool Disassembly::addExclusiveClaim(Claim const &c)
|
|||
{
|
||||
Claim const *conflict = this->findClaimConflict(c.address, c.size);
|
||||
if(conflict) {
|
||||
FxOS_log(ERR, "exclusive claim for %s conflicts with %s",
|
||||
c.str(), conflict->str());
|
||||
FxOS_log(ERR, "exclusive claim for %s conflicts with %s", c.str(),
|
||||
conflict->str());
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -194,8 +193,7 @@ bool Disassembly::addExclusiveClaim(Claim const &c)
|
|||
// DisassemblyPass
|
||||
//---
|
||||
|
||||
DisassemblyPass::DisassemblyPass(Disassembly &disasm):
|
||||
m_disasm {disasm}
|
||||
DisassemblyPass::DisassemblyPass(Disassembly &disasm): m_disasm {disasm}
|
||||
{
|
||||
}
|
||||
|
||||
|
@ -203,8 +201,7 @@ DisassemblyPass::DisassemblyPass(Disassembly &disasm):
|
|||
// FunctionPass
|
||||
//---
|
||||
|
||||
FunctionPass::FunctionPass(Disassembly &disasm):
|
||||
DisassemblyPass(disasm)
|
||||
FunctionPass::FunctionPass(Disassembly &disasm): DisassemblyPass(disasm)
|
||||
{
|
||||
}
|
||||
|
||||
|
@ -243,7 +240,8 @@ bool FunctionPass::analyzeFunctionRecursively(uint32_t pc)
|
|||
Function *next = m_disasm.getFunctionAt(pc);
|
||||
if(this->analyzeFunction(*next))
|
||||
this->enqueueSubfunctions(*next);
|
||||
else ok = false;
|
||||
else
|
||||
ok = false;
|
||||
}
|
||||
|
||||
return ok;
|
||||
|
@ -303,7 +301,8 @@ bool InstructionPass::analyzeAnonymousFunction(uint32_t pc)
|
|||
|
||||
if(i != nullptr && this->analyzeInstruction(pc, *i))
|
||||
this->enqueueSuccessors(pc, *i);
|
||||
else ok = false;
|
||||
else
|
||||
ok = false;
|
||||
}
|
||||
|
||||
return ok;
|
||||
|
|
53
lib/lang.cpp
53
lib/lang.cpp
|
@ -16,14 +16,10 @@ namespace FxOS {
|
|||
// CPU registers
|
||||
//---
|
||||
|
||||
char const *regnames[] = {
|
||||
"r0", "r1", "r2", "r3", "r4", "r5", "r6", "r7",
|
||||
"r0_bank", "r1_bank", "r2_bank", "r3_bank",
|
||||
"r4_bank", "r5_bank", "r6_bank", "r7_bank",
|
||||
"r8", "r9", "r10", "r11", "r12", "r13", "r14", "r15",
|
||||
"mach", "macl", "pr", "pc",
|
||||
"sr", "ssr", "spc", "gbr", "vbr", "dbr", "sgr"
|
||||
};
|
||||
char const *regnames[] = {"r0", "r1", "r2", "r3", "r4", "r5", "r6", "r7",
|
||||
"r0_bank", "r1_bank", "r2_bank", "r3_bank", "r4_bank", "r5_bank", "r6_bank",
|
||||
"r7_bank", "r8", "r9", "r10", "r11", "r12", "r13", "r14", "r15", "mach",
|
||||
"macl", "pr", "pc", "sr", "ssr", "spc", "gbr", "vbr", "dbr", "sgr"};
|
||||
|
||||
/* Construction from string */
|
||||
CpuRegister::CpuRegister(std::string name)
|
||||
|
@ -33,7 +29,7 @@ CpuRegister::CpuRegister(std::string name)
|
|||
|
||||
for(int i = 0; i < regcount; i++) {
|
||||
if(!strcmp(regnames[i], name_c)) {
|
||||
m_name = CpuRegisterName(i+1);
|
||||
m_name = CpuRegisterName(i + 1);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
@ -48,7 +44,7 @@ std::string CpuRegister::str() const noexcept
|
|||
int i = m_name - 1;
|
||||
|
||||
if(i < 0 || i >= regcount)
|
||||
return format("<Register%d>", i+1);
|
||||
return format("<Register%d>", i + 1);
|
||||
return regnames[i];
|
||||
}
|
||||
|
||||
|
@ -145,8 +141,7 @@ AsmArgument AsmArgument_Imm(int imm)
|
|||
/* String representation */
|
||||
std::string AsmArgument::str() const
|
||||
{
|
||||
switch(kind)
|
||||
{
|
||||
switch(kind) {
|
||||
case AsmArgument::Reg:
|
||||
return base.str();
|
||||
case AsmArgument::Deref:
|
||||
|
@ -158,8 +153,7 @@ std::string AsmArgument::str() const
|
|||
case AsmArgument::StructDeref:
|
||||
return format("@(%d,%s)", disp, base.str().c_str());
|
||||
case AsmArgument::ArrayDeref:
|
||||
return format("@(%s,%s)", index.str().c_str(),
|
||||
base.str().c_str());
|
||||
return format("@(%s,%s)", index.str().c_str(), base.str().c_str());
|
||||
case AsmArgument::PcRel:
|
||||
return format("@(%d,pc)", disp);
|
||||
case AsmArgument::PcJump:
|
||||
|
@ -208,8 +202,8 @@ AsmInstruction::AsmInstruction(char const *mn, AsmArgument arg):
|
|||
arg_count = 1;
|
||||
}
|
||||
|
||||
AsmInstruction::AsmInstruction(char const *mn, AsmArgument arg1,
|
||||
AsmArgument arg2):
|
||||
AsmInstruction::AsmInstruction(
|
||||
char const *mn, AsmArgument arg1, AsmArgument arg2):
|
||||
AsmInstruction(mn)
|
||||
{
|
||||
args[0] = arg1;
|
||||
|
@ -227,7 +221,7 @@ bool AsmInstruction::isterminal() const noexcept
|
|||
return true;
|
||||
|
||||
/* Also jmp @rn which is regarded as a terminal call */
|
||||
if(!strcmp(mnemonic,"jmp") && args[0].kind == AsmArgument::Deref)
|
||||
if(!strcmp(mnemonic, "jmp") && args[0].kind == AsmArgument::Deref)
|
||||
return true;
|
||||
|
||||
/* Same for braf because we can't analyse further */
|
||||
|
@ -245,7 +239,13 @@ bool AsmInstruction::isjump() const noexcept
|
|||
bool AsmInstruction::iscondjump() const noexcept
|
||||
{
|
||||
char const *v[] = {
|
||||
"bf", "bf.s", "bf/s", "bt", "bt.s", "bt/s", NULL,
|
||||
"bf",
|
||||
"bf.s",
|
||||
"bf/s",
|
||||
"bt",
|
||||
"bt.s",
|
||||
"bt/s",
|
||||
NULL,
|
||||
};
|
||||
|
||||
for(int i = 0; v[i]; i++) {
|
||||
|
@ -258,8 +258,19 @@ bool AsmInstruction::iscondjump() const noexcept
|
|||
bool AsmInstruction::isdelayed() const noexcept
|
||||
{
|
||||
char const *v[] = {
|
||||
"rte", "rts", "jmp", "jsr", "bra", "braf", "bsr", "bsrf",
|
||||
"bf.s", "bf/s", "bt.s", "bt/s", NULL,
|
||||
"rte",
|
||||
"rts",
|
||||
"jmp",
|
||||
"jsr",
|
||||
"bra",
|
||||
"braf",
|
||||
"bsr",
|
||||
"bsrf",
|
||||
"bf.s",
|
||||
"bf/s",
|
||||
"bt.s",
|
||||
"bt/s",
|
||||
NULL,
|
||||
};
|
||||
|
||||
for(int i = 0; v[i]; i++) {
|
||||
|
@ -272,7 +283,7 @@ bool AsmInstruction::isdelayed() const noexcept
|
|||
bool AsmInstruction::isvaliddelayslot() const noexcept
|
||||
{
|
||||
return !isdelayed() && !isterminal() && !isjump() && !iscondjump()
|
||||
&& strcmp(this->mnemonic, "mova") != 0;
|
||||
&& strcmp(this->mnemonic, "mova") != 0;
|
||||
}
|
||||
|
||||
} /* namespace FxOS */
|
||||
|
|
|
@ -11,12 +11,12 @@
|
|||
|
||||
namespace FxOS {
|
||||
|
||||
MemoryArea MemoryArea::U0 = { 0x00000000, 0x7fffffff, "U0" };
|
||||
MemoryArea MemoryArea::P0 = { 0x00000000, 0x7fffffff, "P0" };
|
||||
MemoryArea MemoryArea::P1 = { 0x80000000, 0x9fffffff, "P1" };
|
||||
MemoryArea MemoryArea::P2 = { 0xa0000000, 0xbfffffff, "P2" };
|
||||
MemoryArea MemoryArea::P3 = { 0xc0000000, 0xdfffffff, "P3" };
|
||||
MemoryArea MemoryArea::P4 = { 0xe0000000, 0xffffffff, "P4" };
|
||||
MemoryArea MemoryArea::U0 = {0x00000000, 0x7fffffff, "U0"};
|
||||
MemoryArea MemoryArea::P0 = {0x00000000, 0x7fffffff, "P0"};
|
||||
MemoryArea MemoryArea::P1 = {0x80000000, 0x9fffffff, "P1"};
|
||||
MemoryArea MemoryArea::P2 = {0xa0000000, 0xbfffffff, "P2"};
|
||||
MemoryArea MemoryArea::P3 = {0xc0000000, 0xdfffffff, "P3"};
|
||||
MemoryArea MemoryArea::P4 = {0xe0000000, 0xffffffff, "P4"};
|
||||
|
||||
//---
|
||||
// Fine memory region management
|
||||
|
@ -27,9 +27,10 @@ MemoryRegion::MemoryRegion():
|
|||
{
|
||||
}
|
||||
|
||||
MemoryRegion::MemoryRegion(std::string name, uint32_t start, uint32_t end,
|
||||
bool writable):
|
||||
name {name}, start {start}, end {end}, writable {writable}
|
||||
MemoryRegion::MemoryRegion(
|
||||
std::string name, uint32_t start, uint32_t end, bool writable):
|
||||
name {name},
|
||||
start {start}, end {end}, writable {writable}
|
||||
{
|
||||
this->guess_flags();
|
||||
}
|
||||
|
@ -42,11 +43,11 @@ uint32_t MemoryRegion::size() const noexcept
|
|||
MemoryArea MemoryRegion::area() const noexcept
|
||||
{
|
||||
using Area = MemoryArea;
|
||||
static Area areas[5]={ Area::P4, Area::P3, Area::P2, Area::P1, Area::P0 };
|
||||
static Area areas[5] = {Area::P4, Area::P3, Area::P2, Area::P1, Area::P0};
|
||||
|
||||
for(int i = 0; i < 5; i++) {
|
||||
if(start >= areas[i].start)
|
||||
return areas[i];
|
||||
if(start >= areas[i].start)
|
||||
return areas[i];
|
||||
}
|
||||
|
||||
return Area::P0;
|
||||
|
@ -56,7 +57,8 @@ void MemoryRegion::guess_flags() noexcept
|
|||
{
|
||||
MemoryArea area = this->area();
|
||||
|
||||
if(area==MemoryArea::U0 || area==MemoryArea::P0 || area==MemoryArea::P3) {
|
||||
if(area == MemoryArea::U0 || area == MemoryArea::P0
|
||||
|| area == MemoryArea::P3) {
|
||||
cacheable = true;
|
||||
mappable = true;
|
||||
}
|
||||
|
@ -77,21 +79,28 @@ void MemoryRegion::guess_flags() noexcept
|
|||
using R = MemoryRegion;
|
||||
|
||||
/* Basic memory regions */
|
||||
R const R::ROM ("ROM", 0x80000000, 0x81ffffff, false);
|
||||
R const R::ROM_P2 ("ROM_P2", 0xa0000000, 0xa07fffff, false);
|
||||
R const R::RAM ("RAM", 0x88000000, 0x881fffff, true);
|
||||
R const R::RAM_P2 ("RAM_P2", 0xa8000000, 0xa81fffff, true);
|
||||
R const R::RAM_8C ("RAM_8C", 0x8c000000, 0x8c7fffff, true);
|
||||
R const R::ROM("ROM", 0x80000000, 0x81ffffff, false);
|
||||
R const R::ROM_P2("ROM_P2", 0xa0000000, 0xa07fffff, false);
|
||||
R const R::RAM("RAM", 0x88000000, 0x881fffff, true);
|
||||
R const R::RAM_P2("RAM_P2", 0xa8000000, 0xa81fffff, true);
|
||||
R const R::RAM_8C("RAM_8C", 0x8c000000, 0x8c7fffff, true);
|
||||
R const R::RAM_8C_P2("RAM_8C_P2", 0xac000000, 0xac7fffff, true);
|
||||
R const R::RS ("RS", 0xfd800000, 0xfd8007ff, true);
|
||||
R const R::ILRAM ("ILRAM", 0xe5200000, 0xe5203fff, true);
|
||||
R const R::XRAM ("XRAM", 0xe5007000, 0xe5008fff, true);
|
||||
R const R::YRAM ("YRAM", 0xe5017000, 0xe5018fff, true);
|
||||
R const R::RS("RS", 0xfd800000, 0xfd8007ff, true);
|
||||
R const R::ILRAM("ILRAM", 0xe5200000, 0xe5203fff, true);
|
||||
R const R::XRAM("XRAM", 0xe5007000, 0xe5008fff, true);
|
||||
R const R::YRAM("YRAM", 0xe5017000, 0xe5018fff, true);
|
||||
|
||||
std::array<MemoryRegion const *, 10> const MemoryRegion::m_all = {
|
||||
&R::ROM, &R::ROM_P2,
|
||||
&R::RAM, &R::RAM_P2, &R::RAM_8C, &R::RAM_8C_P2,
|
||||
&R::RS, &R::ILRAM, &R::XRAM, &R::YRAM,
|
||||
&R::ROM,
|
||||
&R::ROM_P2,
|
||||
&R::RAM,
|
||||
&R::RAM_P2,
|
||||
&R::RAM_8C,
|
||||
&R::RAM_8C_P2,
|
||||
&R::RS,
|
||||
&R::ILRAM,
|
||||
&R::XRAM,
|
||||
&R::YRAM,
|
||||
};
|
||||
|
||||
std::array<MemoryRegion const *, 10> const &MemoryRegion::all()
|
||||
|
@ -117,8 +126,7 @@ MemoryRegion const *MemoryRegion::region_for(MemoryRegion region)
|
|||
return nullptr;
|
||||
}
|
||||
|
||||
MemoryRegion::MemoryRegion(std::string name):
|
||||
MemoryRegion()
|
||||
MemoryRegion::MemoryRegion(std::string name): MemoryRegion()
|
||||
{
|
||||
for(auto r: MemoryRegion::all()) {
|
||||
if(r->name == name) {
|
||||
|
|
68
lib/os.cpp
68
lib/os.cpp
|
@ -8,10 +8,11 @@
|
|||
|
||||
namespace FxOS {
|
||||
|
||||
OS::OS(VirtualSpace &space): type{UNKNOWN}, m_space{space}
|
||||
OS::OS(VirtualSpace &space): type {UNKNOWN}, m_space {space}
|
||||
{
|
||||
if(!space.covers(0x80000000, (256 << 10))) {
|
||||
FxOS_log(ERR, "OS analysis failed: space doesn't have at least 256 kB "
|
||||
FxOS_log(ERR,
|
||||
"OS analysis failed: space doesn't have at least 256 kB "
|
||||
"of ROM to analyze from");
|
||||
return;
|
||||
}
|
||||
|
@ -57,37 +58,53 @@ void OS::parse_header()
|
|||
|
||||
bool OS::version_lt(int major, int minor, int patch) const noexcept
|
||||
{
|
||||
if(this->version_major < major) return true;
|
||||
if(this->version_major > major) return false;
|
||||
if(this->version_minor < minor) return true;
|
||||
if(this->version_minor > minor) return false;
|
||||
if(this->version_major < major)
|
||||
return true;
|
||||
if(this->version_major > major)
|
||||
return false;
|
||||
if(this->version_minor < minor)
|
||||
return true;
|
||||
if(this->version_minor > minor)
|
||||
return false;
|
||||
return (this->version_patch < patch);
|
||||
}
|
||||
|
||||
bool OS::version_le(int major, int minor, int patch) const noexcept
|
||||
{
|
||||
if(this->version_major < major) return true;
|
||||
if(this->version_major > major) return false;
|
||||
if(this->version_minor < minor) return true;
|
||||
if(this->version_minor > minor) return false;
|
||||
if(this->version_major < major)
|
||||
return true;
|
||||
if(this->version_major > major)
|
||||
return false;
|
||||
if(this->version_minor < minor)
|
||||
return true;
|
||||
if(this->version_minor > minor)
|
||||
return false;
|
||||
return (this->version_patch <= patch);
|
||||
}
|
||||
|
||||
bool OS::version_gt(int major, int minor, int patch) const noexcept
|
||||
{
|
||||
if(this->version_major < major) return false;
|
||||
if(this->version_major > major) return true;
|
||||
if(this->version_minor < minor) return false;
|
||||
if(this->version_minor > minor) return true;
|
||||
if(this->version_major < major)
|
||||
return false;
|
||||
if(this->version_major > major)
|
||||
return true;
|
||||
if(this->version_minor < minor)
|
||||
return false;
|
||||
if(this->version_minor > minor)
|
||||
return true;
|
||||
return (this->version_patch > patch);
|
||||
}
|
||||
|
||||
bool OS::version_ge(int major, int minor, int patch) const noexcept
|
||||
{
|
||||
if(this->version_major < major) return false;
|
||||
if(this->version_major > major) return true;
|
||||
if(this->version_minor < minor) return false;
|
||||
if(this->version_minor > minor) return true;
|
||||
if(this->version_major < major)
|
||||
return false;
|
||||
if(this->version_major > major)
|
||||
return true;
|
||||
if(this->version_minor < minor)
|
||||
return false;
|
||||
if(this->version_minor > minor)
|
||||
return true;
|
||||
return (this->version_patch >= patch);
|
||||
}
|
||||
|
||||
|
@ -131,7 +148,8 @@ void OS::parse_syscall_table()
|
|||
uint32_t entry = m_space.read_u32(syscall_table + 4 * id);
|
||||
|
||||
MemoryRegion const *r = MemoryRegion::region_for(entry);
|
||||
if(!r) break;
|
||||
if(!r)
|
||||
break;
|
||||
|
||||
m_syscall_table.push_back(entry);
|
||||
m_syscall_addresses[entry] = id;
|
||||
|
@ -173,7 +191,7 @@ void OS::parse_footer()
|
|||
}
|
||||
|
||||
this->timestamp = s.read_str(addr, 14);
|
||||
this->checksum = s.read_u32(addr + 0x18);
|
||||
this->checksum = s.read_u32(addr + 0x18);
|
||||
this->computed_checksum = this->compute_checksum();
|
||||
}
|
||||
|
||||
|
@ -181,8 +199,8 @@ void OS::parse_footer()
|
|||
// Checksum
|
||||
//---
|
||||
|
||||
static uint32_t accumulate_range(VirtualSpace &m_space,
|
||||
uint32_t start, uint32_t end)
|
||||
static uint32_t accumulate_range(
|
||||
VirtualSpace &m_space, uint32_t start, uint32_t end)
|
||||
{
|
||||
uint32_t sum = 0;
|
||||
|
||||
|
@ -213,12 +231,12 @@ uint32_t OS::compute_checksum() const
|
|||
else if(this->type == CG) {
|
||||
if(this->version_lt(2, 2, 0)) {
|
||||
return accumulate_range(m_space, 0x80020000, 0x80b5feb0)
|
||||
+ accumulate_range(m_space,0x80b5fef0, 0x80b5fff8);
|
||||
+ accumulate_range(m_space, 0x80b5fef0, 0x80b5fff8);
|
||||
}
|
||||
else {
|
||||
return accumulate_range(m_space, 0x80020000, 0x80b20000)
|
||||
+ accumulate_range(m_space, this->footer+8,
|
||||
this->checksum.address);
|
||||
+ accumulate_range(
|
||||
m_space, this->footer + 8, this->checksum.address);
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
|
|
|
@ -42,7 +42,7 @@ bool CfgPass::analyzeInstruction(uint32_t pc, Instruction &i)
|
|||
return false;
|
||||
}
|
||||
|
||||
jmptarget = (pc+4) + args[0].disp;
|
||||
jmptarget = (pc + 4) + args[0].disp;
|
||||
|
||||
/* Make the target of the jump a leader */
|
||||
Instruction &target = *m_disasm.getInstructionAt(jmptarget, true);
|
||||
|
@ -50,9 +50,11 @@ bool CfgPass::analyzeInstruction(uint32_t pc, Instruction &i)
|
|||
|
||||
/* Check that it's not in a delay slot */
|
||||
if(target.delayslot)
|
||||
throw std::logic_error(format("0x%08x jumps into 0x%08x, which is "
|
||||
throw std::logic_error(format(
|
||||
"0x%08x jumps into 0x%08x, which is "
|
||||
"a delay slot - this is unsupported by fxos and will produce "
|
||||
"garbage analysis! (x_x)", pc, jmptarget));
|
||||
"garbage analysis! (x_x)",
|
||||
pc, jmptarget));
|
||||
}
|
||||
|
||||
/* If this instruction is in a delay slot, check its type. A valid
|
||||
|
@ -66,13 +68,15 @@ bool CfgPass::analyzeInstruction(uint32_t pc, Instruction &i)
|
|||
}
|
||||
/* If it has a delay slot, create it at the next instruction */
|
||||
else if(i.inst->isdelayed()) {
|
||||
Instruction &slot = *m_disasm.getInstructionAt(pc+2, true);
|
||||
Instruction &slot = *m_disasm.getInstructionAt(pc + 2, true);
|
||||
if(slot.leader)
|
||||
throw std::logic_error(format("0x%08x is a leader and also a delay"
|
||||
throw std::logic_error(format(
|
||||
"0x%08x is a leader and also a delay"
|
||||
" slot - this is unsupported by fxos and will produce garbage "
|
||||
"analysis! (x_x)", pc+2));
|
||||
"analysis! (x_x)",
|
||||
pc + 2));
|
||||
if(!slot.inst->isvaliddelayslot()) {
|
||||
FxOS_log(ERR, "invalid delay slot at 0x%08x", pc+2);
|
||||
FxOS_log(ERR, "invalid delay slot at 0x%08x", pc + 2);
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -100,7 +104,7 @@ bool CfgPass::exploreFunction(uint32_t pc)
|
|||
|
||||
if(!m_disasm.hasFunctionAt(pc)) {
|
||||
// TODO: Have proper function creation methods in Disassembly
|
||||
Function func = { .address=pc, .callTargets={} };
|
||||
Function func = {.address = pc, .callTargets = {}};
|
||||
m_disasm.functions[pc] = func;
|
||||
}
|
||||
|
||||
|
|
|
@ -10,8 +10,7 @@
|
|||
|
||||
namespace FxOS {
|
||||
|
||||
PcrelPass::PcrelPass(Disassembly &disasm):
|
||||
InstructionPass(disasm)
|
||||
PcrelPass::PcrelPass(Disassembly &disasm): InstructionPass(disasm)
|
||||
{
|
||||
}
|
||||
|
||||
|
@ -25,7 +24,8 @@ bool PcrelPass::analyzeInstruction(uint32_t pc, Instruction &ci)
|
|||
AsmArgument const &arg = i->args[n];
|
||||
Argument &a = ci.args[n];
|
||||
|
||||
if(arg.kind == AsmArgument::PcRel && (i->opsize==2 || i->opsize==4)) {
|
||||
if(arg.kind == AsmArgument::PcRel
|
||||
&& (i->opsize == 2 || i->opsize == 4)) {
|
||||
uint32_t addr = (pc & ~(arg.opsize - 1)) + 4 + arg.disp;
|
||||
a.location = RelConstDomain().constant(addr);
|
||||
|
||||
|
@ -48,8 +48,7 @@ bool PcrelPass::analyzeInstruction(uint32_t pc, Instruction &ci)
|
|||
a.location = RelConstDomain().constant(addr);
|
||||
a.value = RelConstDomain().constant(addr);
|
||||
}
|
||||
else if(arg.kind == AsmArgument::PcAddr)
|
||||
{
|
||||
else if(arg.kind == AsmArgument::PcAddr) {
|
||||
uint32_t addr = (pc & ~3) + 4 + arg.disp;
|
||||
|
||||
/* SH3 manual says that mova uses the target address of the jump
|
||||
|
|
|
@ -30,7 +30,7 @@ bool PrintPass::analyzeInstruction(uint32_t pc, Instruction &i)
|
|||
{
|
||||
/* Ellipsis if there is a gap since last instruction */
|
||||
|
||||
if(m_last_address+1 != 0 && pc != m_last_address+2)
|
||||
if(m_last_address + 1 != 0 && pc != m_last_address + 2)
|
||||
printf(" ...\n");
|
||||
|
||||
/* Preliminary syscall number */
|
||||
|
@ -55,12 +55,11 @@ bool PrintPass::analyzeInstruction(uint32_t pc, Instruction &i)
|
|||
|
||||
/* Mnemonic */
|
||||
|
||||
static char const *suffixes[5] = { "", ".b", ".w", "", ".l" };
|
||||
static char const *suffixes[5] = {"", ".b", ".w", "", ".l"};
|
||||
char const *suffix = suffixes[(i.inst->opsize <= 4) ? i.inst->opsize : 0];
|
||||
|
||||
int spacing = i.inst->arg_count
|
||||
? 8 - strlen(i.inst->mnemonic) - strlen(suffix)
|
||||
: 0;
|
||||
int spacing
|
||||
= i.inst->arg_count ? 8 - strlen(i.inst->mnemonic) - strlen(suffix) : 0;
|
||||
printf(" %s%s%*s", i.inst->mnemonic, suffix, spacing, "");
|
||||
|
||||
/* Arguments */
|
||||
|
@ -69,7 +68,8 @@ bool PrintPass::analyzeInstruction(uint32_t pc, Instruction &i)
|
|||
AsmArgument const &arg = i.inst->args[n];
|
||||
Argument const &a = i.args[n];
|
||||
|
||||
if(n) printf(", ");
|
||||
if(n)
|
||||
printf(", ");
|
||||
|
||||
queue(arg.str());
|
||||
if(arg.kind == AsmArgument::PcJump)
|
||||
|
@ -86,8 +86,8 @@ bool PrintPass::analyzeInstruction(uint32_t pc, Instruction &i)
|
|||
return true;
|
||||
}
|
||||
|
||||
std::optional<std::string> PrintPass::symquery(Symbol::Type type,
|
||||
uint32_t value)
|
||||
std::optional<std::string> PrintPass::symquery(
|
||||
Symbol::Type type, uint32_t value)
|
||||
{
|
||||
for(int i = m_symtables.size() - 1; i >= 0; i--) {
|
||||
SymbolTable const &st = m_symtables[i];
|
||||
|
@ -121,34 +121,41 @@ void PrintPass::queue_flush()
|
|||
|
||||
void PrintPass::pcjumploc(Argument const &a)
|
||||
{
|
||||
if(!RelConstDomain().is_constant(a.location)) return;
|
||||
if(promote_pcjump_loc == Never) return;
|
||||
if(!RelConstDomain().is_constant(a.location))
|
||||
return;
|
||||
if(promote_pcjump_loc == Never)
|
||||
return;
|
||||
|
||||
queue(format("<%s>", a.location.str()), promote_pcjump_loc==Promote);
|
||||
queue(format("<%s>", a.location.str()), promote_pcjump_loc == Promote);
|
||||
syscall(a);
|
||||
}
|
||||
|
||||
void PrintPass::pcrelloc(Argument const &a)
|
||||
{
|
||||
if(!RelConstDomain().is_constant(a.location)) return;
|
||||
if(promote_pcrel_loc == Never) return;
|
||||
if(!RelConstDomain().is_constant(a.location))
|
||||
return;
|
||||
if(promote_pcrel_loc == Never)
|
||||
return;
|
||||
|
||||
queue(format("<%s>", a.location.str()), promote_pcrel_loc==Promote);
|
||||
queue(format("<%s>", a.location.str()), promote_pcrel_loc == Promote);
|
||||
pcrelval(a);
|
||||
}
|
||||
|
||||
void PrintPass::pcrelval(Argument const &a)
|
||||
{
|
||||
if(!a.value) return;
|
||||
if(promote_pcrel_value == Never) return;
|
||||
if(!a.value)
|
||||
return;
|
||||
if(promote_pcrel_value == Never)
|
||||
return;
|
||||
|
||||
queue(a.value.str(), promote_pcrel_value==Promote);
|
||||
queue(a.value.str(), promote_pcrel_value == Promote);
|
||||
syscall(a);
|
||||
}
|
||||
|
||||
void PrintPass::syscall(Argument const &a)
|
||||
{
|
||||
if(!a.value) return;
|
||||
if(!a.value)
|
||||
return;
|
||||
|
||||
/* If this is not a syscall, try to display as a symbol instead */
|
||||
if(promote_syscall == Never || a.syscall_id < 0) {
|
||||
|
@ -156,37 +163,43 @@ void PrintPass::syscall(Argument const &a)
|
|||
return;
|
||||
}
|
||||
|
||||
queue(format("%%%03x", a.syscall_id), promote_syscall==Promote);
|
||||
queue(format("%%%03x", a.syscall_id), promote_syscall == Promote);
|
||||
syscallname(a);
|
||||
}
|
||||
|
||||
void PrintPass::syscallname(Argument const &a)
|
||||
{
|
||||
if(a.syscall_id < 0) return;
|
||||
if(a.syscall_id < 0)
|
||||
return;
|
||||
|
||||
auto maybe_name = symquery(Symbol::Syscall, a.syscall_id);
|
||||
if(!maybe_name) return;
|
||||
if(!maybe_name)
|
||||
return;
|
||||
|
||||
queue(*maybe_name, promote_syscallname==Promote);
|
||||
queue(*maybe_name, promote_syscallname == Promote);
|
||||
}
|
||||
|
||||
void PrintPass::symbol(Argument const &a)
|
||||
{
|
||||
if(!a.value) return;
|
||||
if(!a.value)
|
||||
return;
|
||||
|
||||
auto maybe_name = symquery(Symbol::Address,
|
||||
RelConstDomain().constant_value(a.value));
|
||||
if(!maybe_name) return;
|
||||
auto maybe_name
|
||||
= symquery(Symbol::Address, RelConstDomain().constant_value(a.value));
|
||||
if(!maybe_name)
|
||||
return;
|
||||
|
||||
queue(*maybe_name, promote_symbol==Promote);
|
||||
queue(*maybe_name, promote_symbol == Promote);
|
||||
}
|
||||
|
||||
void PrintPass::pcaddrloc(Argument const &a)
|
||||
{
|
||||
if(!RelConstDomain().is_constant(a.location)) return;
|
||||
if(promote_pcaddr_loc == Never) return;
|
||||
if(!RelConstDomain().is_constant(a.location))
|
||||
return;
|
||||
if(promote_pcaddr_loc == Never)
|
||||
return;
|
||||
|
||||
queue(format("<%s>", a.location.str()), promote_pcaddr_loc==Promote);
|
||||
queue(format("<%s>", a.location.str()), promote_pcaddr_loc == Promote);
|
||||
}
|
||||
|
||||
} /* namespace FxOS */
|
||||
|
|
|
@ -34,12 +34,12 @@ bool SyscallPass::analyzeInstruction(uint32_t pc, Instruction &ci)
|
|||
uint32_t address;
|
||||
|
||||
if(arg.kind == AsmArgument::PcRel && a.value
|
||||
&& RelConstDomain().is_constant(a.value)) {
|
||||
&& RelConstDomain().is_constant(a.value)) {
|
||||
eligible = true;
|
||||
address = RelConstDomain().constant_value(a.value);
|
||||
}
|
||||
if(arg.kind == AsmArgument::PcJump && a.location
|
||||
&& RelConstDomain().is_constant(a.location)) {
|
||||
&& RelConstDomain().is_constant(a.location)) {
|
||||
eligible = true;
|
||||
address = RelConstDomain().constant_value(a.location);
|
||||
}
|
||||
|
|
|
@ -9,15 +9,15 @@ namespace FxOS {
|
|||
// Data types
|
||||
//---
|
||||
|
||||
static DataType _u8(IntegerType(1, false));
|
||||
static DataType _i8(IntegerType(1, true));
|
||||
static DataType _u8(IntegerType(1, false));
|
||||
static DataType _i8(IntegerType(1, true));
|
||||
static DataType _u16(IntegerType(2, false));
|
||||
static DataType _i16(IntegerType(2, true));
|
||||
static DataType _u32(IntegerType(4, false));
|
||||
static DataType _i32(IntegerType(4, true));
|
||||
|
||||
DataType const *IntegerType::u8 = &_u8;
|
||||
DataType const *IntegerType::i8 = &_i8;
|
||||
DataType const *IntegerType::u8 = &_u8;
|
||||
DataType const *IntegerType::i8 = &_i8;
|
||||
DataType const *IntegerType::u16 = &_u16;
|
||||
DataType const *IntegerType::i16 = &_i16;
|
||||
DataType const *IntegerType::u32 = &_u32;
|
||||
|
@ -41,11 +41,16 @@ DataType::DataKind DataType::kind() const noexcept
|
|||
size_t DataType::size() const noexcept
|
||||
{
|
||||
switch(kind()) {
|
||||
case Integer: return integer().size;
|
||||
case Bitfield: return bitfield().size;
|
||||
case Array: return array().size;
|
||||
case String: return string().size;
|
||||
case Struct: return structure().size;
|
||||
case Integer:
|
||||
return integer().size;
|
||||
case Bitfield:
|
||||
return bitfield().size;
|
||||
case Array:
|
||||
return array().size;
|
||||
case String:
|
||||
return string().size;
|
||||
case Struct:
|
||||
return structure().size;
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
@ -76,8 +81,7 @@ StructType const &DataType::structure() const
|
|||
// Data values
|
||||
//---
|
||||
|
||||
DataValue::DataValue():
|
||||
type {nullptr}
|
||||
DataValue::DataValue(): type {nullptr}
|
||||
{
|
||||
}
|
||||
|
||||
|
@ -136,7 +140,7 @@ std::string DataValue::str() const noexcept
|
|||
switch(type->kind()) {
|
||||
/* Format all integers in hexadecimal */
|
||||
case DataType::Integer:
|
||||
return format("0x%0*x", 2*type->size(), read(0,type->size()));
|
||||
return format("0x%0*x", 2 * type->size(), read(0, type->size()));
|
||||
|
||||
/* TODO: Print data values of complex types */
|
||||
case DataType::Bitfield:
|
||||
|
@ -149,8 +153,10 @@ std::string DataValue::str() const noexcept
|
|||
for(size_t i = 0; i < mem.size(); i++) {
|
||||
int16_t byte = mem[i];
|
||||
|
||||
if(byte == -1) result += " UND";
|
||||
else result += format(" %02x", byte);
|
||||
if(byte == -1)
|
||||
result += " UND";
|
||||
else
|
||||
result += format(" %02x", byte);
|
||||
}
|
||||
|
||||
result[0] = '{';
|
||||
|
|
|
@ -19,8 +19,8 @@ void SymbolTable::add(Symbol s)
|
|||
symbols.push_back(s);
|
||||
}
|
||||
|
||||
std::optional<std::string> SymbolTable::query(Symbol::Type type,
|
||||
uint32_t value) const
|
||||
std::optional<std::string> SymbolTable::query(
|
||||
Symbol::Type type, uint32_t value) const
|
||||
{
|
||||
for(auto &sym: symbols) {
|
||||
if(sym.type == type && sym.value == value)
|
||||
|
|
|
@ -17,8 +17,7 @@
|
|||
#include <filesystem>
|
||||
namespace fs = std::filesystem;
|
||||
|
||||
Buffer::Buffer():
|
||||
size {0}, data {nullptr}, path {"(none)"}
|
||||
Buffer::Buffer(): size {0}, data {nullptr}, path {"(none)"}
|
||||
{
|
||||
}
|
||||
|
||||
|
@ -32,13 +31,14 @@ Buffer::Buffer(size_t bufsize, int fill)
|
|||
}
|
||||
|
||||
/* Buffer initialized from file */
|
||||
void Buffer::loadFromFile(std::string const &filepath, ssize_t bufsize,
|
||||
int fill)
|
||||
void Buffer::loadFromFile(
|
||||
std::string const &filepath, ssize_t bufsize, int fill)
|
||||
{
|
||||
char const *path = filepath.c_str();
|
||||
|
||||
int fd = open(path, O_RDONLY);
|
||||
if(!fd) throw std::runtime_error(format("cannot open '%s'", path));
|
||||
if(!fd)
|
||||
throw std::runtime_error(format("cannot open '%s'", path));
|
||||
|
||||
struct stat statbuf;
|
||||
if(fstat(fd, &statbuf) < 0) {
|
||||
|
@ -62,14 +62,13 @@ void Buffer::loadFromFile(std::string const &filepath, ssize_t bufsize,
|
|||
}
|
||||
|
||||
/* Buffer initialized from file */
|
||||
Buffer::Buffer(std::string const &filepath, ssize_t bufsize, int fill):
|
||||
Buffer()
|
||||
Buffer::Buffer(std::string const &filepath, ssize_t bufsize, int fill): Buffer()
|
||||
{
|
||||
this->loadFromFile(filepath, bufsize, fill);
|
||||
}
|
||||
|
||||
Buffer::Buffer(std::string filepath, std::vector<std::string> const &folders,
|
||||
ssize_t size, int fill):
|
||||
ssize_t size, int fill):
|
||||
Buffer()
|
||||
{
|
||||
for(auto const &f: folders) {
|
||||
|
|
|
@ -8,8 +8,7 @@
|
|||
#include <fxos/util/Timer.h>
|
||||
#include <fxos/util/format.h>
|
||||
|
||||
Timer::Timer():
|
||||
time_ns {0}, running {false}, start_time {}, depth {0}
|
||||
Timer::Timer(): time_ns {0}, running {false}, start_time {}, depth {0}
|
||||
{
|
||||
}
|
||||
|
||||
|
@ -29,7 +28,7 @@ void Timer::stop(void)
|
|||
clock_gettime(CLOCK_REALTIME, &end);
|
||||
|
||||
this->time_ns += 1000000000 * (end.tv_sec - this->start_time.tv_sec)
|
||||
+ (end.tv_nsec - this->start_time.tv_nsec);
|
||||
+ (end.tv_nsec - this->start_time.tv_nsec);
|
||||
|
||||
this->start_time.tv_sec = 0;
|
||||
this->start_time.tv_nsec = 0;
|
||||
|
@ -49,11 +48,14 @@ void Timer::restart(void)
|
|||
|
||||
std::string Timer::format_time(uint64_t time_ns)
|
||||
{
|
||||
if(time_ns < 2000) return format("%lld ns", time_ns);
|
||||
if(time_ns < 2000)
|
||||
return format("%lld ns", time_ns);
|
||||
time_ns /= 1000;
|
||||
if(time_ns < 2000) return format("%lld us", time_ns);
|
||||
if(time_ns < 2000)
|
||||
return format("%lld us", time_ns);
|
||||
time_ns /= 1000;
|
||||
if(time_ns < 2000) return format("%lld ms", time_ns);
|
||||
if(time_ns < 2000)
|
||||
return format("%lld ms", time_ns);
|
||||
time_ns /= 1000;
|
||||
return format("%lld s", time_ns);
|
||||
}
|
||||
|
|
|
@ -17,8 +17,7 @@ static int loglevel = LOG_LEVEL_WRN;
|
|||
static int lastlevel = -1;
|
||||
|
||||
/* Initialize log level depending on environment variable at startup */
|
||||
__attribute__((constructor))
|
||||
static void init_log_level(void)
|
||||
__attribute__((constructor)) static void init_log_level(void)
|
||||
{
|
||||
char const *FXOS_LOG = std::getenv("FXOS_LOG");
|
||||
if(!FXOS_LOG)
|
||||
|
@ -30,7 +29,8 @@ static void init_log_level(void)
|
|||
loglevel = LOG_LEVEL_WRN;
|
||||
else if(!strcmp(FXOS_LOG, "ERR"))
|
||||
loglevel = LOG_LEVEL_ERR;
|
||||
else FxOS_log(WRN, "invalid FXOS_LOG value: %s", FXOS_LOG);
|
||||
else
|
||||
FxOS_log(WRN, "invalid FXOS_LOG value: %s", FXOS_LOG);
|
||||
}
|
||||
|
||||
void log_setminlevel(int level)
|
||||
|
@ -46,14 +46,17 @@ int log_getminlevel()
|
|||
void logmsg(int level, char const *file, int line, char const *func,
|
||||
std::string message)
|
||||
{
|
||||
if(level < loglevel) return;
|
||||
if(level < loglevel)
|
||||
return;
|
||||
|
||||
bool endline = true;
|
||||
bool prefix = true;
|
||||
|
||||
/* Add a newline if last line was unfinished, but level changed */
|
||||
if(lastlevel >= 0 && lastlevel != level) std::cerr << '\n';
|
||||
else if(lastlevel >= 0) prefix = false;
|
||||
if(lastlevel >= 0 && lastlevel != level)
|
||||
std::cerr << '\n';
|
||||
else if(lastlevel >= 0)
|
||||
prefix = false;
|
||||
|
||||
if(message.size() > 0 && message.back() == '\\') {
|
||||
endline = false;
|
||||
|
@ -70,7 +73,8 @@ void logmsg(int level, char const *file, int line, char const *func,
|
|||
if(level == LOG_LEVEL_ERR)
|
||||
fprintf(stderr, "\x1b[31;1merror:\x1b[0m ");
|
||||
}
|
||||
else fprintf(stderr, " ");
|
||||
else
|
||||
fprintf(stderr, " ");
|
||||
|
||||
fputs(message.c_str(), stderr);
|
||||
|
||||
|
|
448
shell/a.cpp
448
shell/a.cpp
|
@ -14,304 +14,100 @@
|
|||
#include <fmt/core.h>
|
||||
#include <endian.h>
|
||||
|
||||
//---
|
||||
// afh
|
||||
//---
|
||||
|
||||
static bool matches(char const *data, char const *reference,
|
||||
char const *pattern, size_t size)
|
||||
{
|
||||
for(size_t i = 0; i < size; i++) {
|
||||
if(pattern[i] && data[i] != reference[i]) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static int hexa(int c) {
|
||||
if(c >= '0' && c <= '9')
|
||||
return c - '0';
|
||||
return (c | 0x20) - 'a' + 10;
|
||||
}
|
||||
|
||||
struct _afh_args {
|
||||
/* String to find */
|
||||
std::string reference;
|
||||
/* Bytes to match within reference (not all are required) */
|
||||
std::string pattern;
|
||||
/* Size of search */
|
||||
size_t size;
|
||||
/* Regions to search in, may be empty */
|
||||
std::vector<MemoryRegion> regions;
|
||||
/* Distance to show hexump around a match */
|
||||
int distance;
|
||||
/* Required alignment for matches to be used */
|
||||
int align;
|
||||
};
|
||||
|
||||
static _afh_args parse_afh(Session &session, Parser &parser)
|
||||
{
|
||||
_afh_args args;
|
||||
args.distance = -1;
|
||||
args.align = -1;
|
||||
|
||||
parser.option("align", [&args](std::string const &value){
|
||||
args.align = atoi(value.c_str());
|
||||
});
|
||||
parser.option("distance", [&args](std::string const &value){
|
||||
args.distance = atoi(value.c_str());
|
||||
});
|
||||
|
||||
parser.accept_options();
|
||||
std::string needle = parser.str();
|
||||
|
||||
/* Check the structure of the needle */
|
||||
|
||||
if(needle.size() == 0 || needle.size() % 2 != 0)
|
||||
throw CommandError("search pattern '{}' should be of even non-zero "
|
||||
"size", needle);
|
||||
|
||||
size_t bad_index = needle.find_first_not_of("0123456789abcdefABCDEF.");
|
||||
if(bad_index != std::string::npos)
|
||||
throw CommandError("invalid character '{}' in seach pattern",
|
||||
needle[bad_index]);
|
||||
|
||||
for(size_t i = 0; i < needle.size(); i += 2) {
|
||||
char c1 = needle[i], c2 = needle[i+1];
|
||||
if((c1 == '.') != (c2 == '.'))
|
||||
throw CommandError("invalid search byte '{}{}', should be either "
|
||||
"'..' or fully specified", c1, c2);
|
||||
}
|
||||
|
||||
/* Convert into a reference/pattern form */
|
||||
|
||||
args.size = needle.size() / 2;
|
||||
args.reference.reserve(args.size);
|
||||
args.pattern.reserve(args.size);
|
||||
|
||||
for(size_t i = 0; i < args.size; i++) {
|
||||
char c1 = needle[2*i], c2 = needle[2*i+1];
|
||||
if(c1 == '.') {
|
||||
args.reference[i] = 0;
|
||||
args.pattern[i] = 0;
|
||||
}
|
||||
else {
|
||||
args.reference[i] = (hexa(c1) << 4) | hexa(c2);
|
||||
args.pattern[i] = 1;
|
||||
}
|
||||
}
|
||||
|
||||
while(!parser.at_end()) {
|
||||
parser.accept_options();
|
||||
args.regions.push_back(parser.region(session.current_space));
|
||||
}
|
||||
|
||||
parser.accept_options();
|
||||
parser.end();
|
||||
|
||||
return args;
|
||||
}
|
||||
|
||||
void _afh(Session &session, char const *reference, char const *pattern,
|
||||
size_t size, int align, int distance, std::vector<MemoryRegion> ®ions)
|
||||
{
|
||||
if(!session.current_space)
|
||||
return;
|
||||
|
||||
/* Default values */
|
||||
if(distance < 0) distance = 32;
|
||||
if(align <= 0) align = 1;
|
||||
|
||||
VirtualSpace &v = *session.current_space;
|
||||
|
||||
/* If no region is specified, explore the regions for all bindings */
|
||||
if(regions.size() == 0) {
|
||||
for(auto &b: v.bindings)
|
||||
regions.push_back(b.region);
|
||||
}
|
||||
|
||||
int match_count = 0;
|
||||
bool output_started = false;
|
||||
|
||||
/* Matches are not shown right away because if they are close enough a
|
||||
single local hexdump will show several of them */
|
||||
std::vector<std::pair<uint32_t,int>> pending;
|
||||
|
||||
for(auto const &r: regions) {
|
||||
char const *data = v.translate(r.start, r.size());
|
||||
if(!data) throw CommandError("region 0x{:08x} .. 0x{:08x} is not "
|
||||
"fully bound", r.start, r.end);
|
||||
|
||||
/* Reach the required alignemnt */
|
||||
int i = 0;
|
||||
while((r.start + i) % align != 0) i++;
|
||||
|
||||
/* Search patterns for (size) bytes inside (data) */
|
||||
for(; i <= (int)r.size() - (int)size; i += align) {
|
||||
if(!matches(data + i, reference, pattern, size)) continue;
|
||||
|
||||
uint32_t start = r.start + i;
|
||||
|
||||
/* Flush pending matches if this new match is far away */
|
||||
if(pending.size() > 0) {
|
||||
auto const &p = pending[pending.size() - 1];
|
||||
if(p.first + p.second + distance < start) {
|
||||
Range r;
|
||||
r.start = pending[0].first - distance;
|
||||
r.end = p.first + p.second + distance - 1;
|
||||
|
||||
if(output_started) fmt::print("...\n");
|
||||
_h_hexdump(session, r, pending);
|
||||
output_started = true;
|
||||
pending.clear();
|
||||
}
|
||||
}
|
||||
|
||||
pending.emplace_back(start, size);
|
||||
match_count++;
|
||||
start += size;
|
||||
|
||||
if(match_count >= 128) break;
|
||||
}
|
||||
if(match_count >= 128) break;
|
||||
|
||||
/* Print the last pending elements */
|
||||
if(pending.size()) {
|
||||
auto const &p = pending[pending.size() - 1];
|
||||
Range r = { pending[0].first-distance, p.first+p.second+distance };
|
||||
if(output_started) fmt::print("...\n");
|
||||
_h_hexdump(session, r, pending);
|
||||
}
|
||||
pending.clear();
|
||||
}
|
||||
|
||||
if(match_count == 0)
|
||||
fmt::print("No occurrence found.\n");
|
||||
else if(match_count < 128)
|
||||
fmt::print("{} occurences found.\n", match_count);
|
||||
else
|
||||
fmt::print("Stopped after 128 occurrences.\n");
|
||||
}
|
||||
|
||||
//---
|
||||
// af4
|
||||
//---
|
||||
|
||||
struct _af4_args {
|
||||
uint32_t value;
|
||||
std::vector<MemoryRegion> regions;
|
||||
};
|
||||
|
||||
static _af4_args parse_af4(Session &session, Parser &parser)
|
||||
{
|
||||
_af4_args args;
|
||||
args.value = parser.expr(session.current_space);
|
||||
|
||||
while(!parser.at_end()) {
|
||||
args.regions.push_back(parser.region(session.current_space));
|
||||
}
|
||||
|
||||
parser.end();
|
||||
return args;
|
||||
}
|
||||
|
||||
void _af4(Session &session, uint32_t value, std::vector<MemoryRegion> ®ions)
|
||||
{
|
||||
uint32_t value_big_endian = htobe32(value);
|
||||
char pattern[4] = { 1, 1, 1, 1 };
|
||||
_afh(session, (char *)&value_big_endian, pattern, 4, 4, -1, regions);
|
||||
}
|
||||
|
||||
//---
|
||||
// ad
|
||||
//---
|
||||
|
||||
static void ad_disassemble_all(VirtualSpace &space,
|
||||
std::vector<uint32_t> const &addresses, bool force)
|
||||
static void ad_disassemble_all(
|
||||
VirtualSpace &space, std::vector<uint32_t> const &addresses, bool force)
|
||||
{
|
||||
int successes=0, errors=0;
|
||||
Timer timer;
|
||||
int successes = 0, errors = 0;
|
||||
Timer timer;
|
||||
|
||||
/* Analyze the CFGs of all functions */
|
||||
/* Analyze the CFGs of all functions */
|
||||
|
||||
timer.start();
|
||||
CfgPass cfg_pass(space.disasm);
|
||||
timer.start();
|
||||
CfgPass cfg_pass(space.disasm);
|
||||
|
||||
/* We collect subfunction addresses while running the pass */
|
||||
for(int i = 0; i < (int)addresses.size(); i++) {
|
||||
uint32_t entry = addresses[i];
|
||||
printr("[cfg %d/%zu] Disassembling 0x%08x...",
|
||||
i+1, addresses.size(), entry);
|
||||
if(!cfg_pass.exploreFunction(entry)) {
|
||||
FxOS_log(ERR, "while processing 0x%08x", entry);
|
||||
errors++;
|
||||
if(!force) return;
|
||||
}
|
||||
else {
|
||||
for(Claim const &c: cfg_pass.resultClaims())
|
||||
space.disasm.addExclusiveClaim(c);
|
||||
successes++;
|
||||
}
|
||||
}
|
||||
timer.stop();
|
||||
printf("\n");
|
||||
FxOS_log(LOG, "Finished pass <cfg> in %s", timer.format_time());
|
||||
/* We collect subfunction addresses while running the pass */
|
||||
for(int i = 0; i < (int)addresses.size(); i++) {
|
||||
uint32_t entry = addresses[i];
|
||||
printr("[cfg %d/%zu] Disassembling 0x%08x...", i + 1, addresses.size(),
|
||||
entry);
|
||||
if(!cfg_pass.exploreFunction(entry)) {
|
||||
FxOS_log(ERR, "while processing 0x%08x", entry);
|
||||
errors++;
|
||||
if(!force)
|
||||
return;
|
||||
}
|
||||
else {
|
||||
for(Claim const &c: cfg_pass.resultClaims())
|
||||
space.disasm.addExclusiveClaim(c);
|
||||
successes++;
|
||||
}
|
||||
}
|
||||
timer.stop();
|
||||
printf("\n");
|
||||
FxOS_log(LOG, "Finished pass <cfg> in %s", timer.format_time());
|
||||
|
||||
/* Annotate all decoded instructions with pcrel/syscall
|
||||
/* Annotate all decoded instructions with pcrel/syscall
|
||||
TODO: analyze only the functions, if possible */
|
||||
printr("[pcrel] Resolving PC-relative addressing modes...");
|
||||
timer.restart();
|
||||
PcrelPass pcrel_pass(space.disasm);
|
||||
if(!pcrel_pass.analyzeAllInstructions()) {
|
||||
errors++;
|
||||
if(!force) return;
|
||||
}
|
||||
timer.stop();
|
||||
printf("\n");
|
||||
FxOS_log(LOG, "Finished pass <pcrel> in %s", timer.format_time());
|
||||
printr("[pcrel] Resolving PC-relative addressing modes...");
|
||||
timer.restart();
|
||||
PcrelPass pcrel_pass(space.disasm);
|
||||
if(!pcrel_pass.analyzeAllInstructions()) {
|
||||
errors++;
|
||||
if(!force)
|
||||
return;
|
||||
}
|
||||
timer.stop();
|
||||
printf("\n");
|
||||
FxOS_log(LOG, "Finished pass <pcrel> in %s", timer.format_time());
|
||||
|
||||
printr("[syscall] Finding syscall references...");
|
||||
timer.restart();
|
||||
OS *os = space.os_analysis();
|
||||
if(os) {
|
||||
SyscallPass syscall_pass(space.disasm, os);
|
||||
if(!syscall_pass.analyzeAllInstructions()) {
|
||||
errors++;
|
||||
if(!force) return;
|
||||
}
|
||||
}
|
||||
timer.stop();
|
||||
printf("\n");
|
||||
FxOS_log(LOG, "Finished pass <syscall> in %s", timer.format_time());
|
||||
printr("[syscall] Finding syscall references...");
|
||||
timer.restart();
|
||||
OS *os = space.os_analysis();
|
||||
if(os) {
|
||||
SyscallPass syscall_pass(space.disasm, os);
|
||||
if(!syscall_pass.analyzeAllInstructions()) {
|
||||
errors++;
|
||||
if(!force)
|
||||
return;
|
||||
}
|
||||
}
|
||||
timer.stop();
|
||||
printf("\n");
|
||||
FxOS_log(LOG, "Finished pass <syscall> in %s", timer.format_time());
|
||||
|
||||
printf("Successfully analyzed %d functions (%d errors)\n",
|
||||
successes, errors);
|
||||
printf(
|
||||
"Successfully analyzed %d functions (%d errors)\n", successes, errors);
|
||||
|
||||
/* TODO: Get subfunction addresses by abstract interpretation and keep
|
||||
/* TODO: Get subfunction addresses by abstract interpretation and keep
|
||||
going recursively */
|
||||
}
|
||||
|
||||
static std::vector<uint32_t> parse_ad(Session &session, Parser &parser)
|
||||
{
|
||||
if(!session.current_space)
|
||||
return std::vector<uint32_t>();
|
||||
if(!session.current_space)
|
||||
return std::vector<uint32_t>();
|
||||
|
||||
std::vector<uint32_t> addresses;
|
||||
do {
|
||||
addresses.push_back(parser.expr(session.current_space));
|
||||
}
|
||||
while(!parser.at_end());
|
||||
std::vector<uint32_t> addresses;
|
||||
do {
|
||||
addresses.push_back(parser.expr(session.current_space));
|
||||
}
|
||||
while(!parser.at_end());
|
||||
|
||||
parser.end();
|
||||
return addresses;
|
||||
parser.end();
|
||||
return addresses;
|
||||
}
|
||||
|
||||
void _ad(Session &session, std::vector<uint32_t> const &addresses)
|
||||
{
|
||||
if(!session.current_space)
|
||||
return;
|
||||
if(!session.current_space)
|
||||
return;
|
||||
|
||||
VirtualSpace &space = *session.current_space;
|
||||
ad_disassemble_all(space, addresses, false);
|
||||
VirtualSpace &space = *session.current_space;
|
||||
ad_disassemble_all(space, addresses, false);
|
||||
}
|
||||
|
||||
//--
|
||||
|
@ -320,91 +116,43 @@ void _ad(Session &session, std::vector<uint32_t> const &addresses)
|
|||
|
||||
static void parse_ads(Session &session, Parser &parser)
|
||||
{
|
||||
if(!session.current_space)
|
||||
return;
|
||||
if(!session.current_space)
|
||||
return;
|
||||
|
||||
parser.end();
|
||||
parser.end();
|
||||
}
|
||||
|
||||
void _ads(Session &session)
|
||||
{
|
||||
if(!session.current_space)
|
||||
return;
|
||||
if(!session.current_space)
|
||||
return;
|
||||
|
||||
VirtualSpace &space = *session.current_space;
|
||||
OS *os = space.os_analysis();
|
||||
VirtualSpace &space = *session.current_space;
|
||||
OS *os = space.os_analysis();
|
||||
|
||||
if(!os) {
|
||||
printf("ads: OS analysis failed, cannot enumerate syscalls");
|
||||
return;
|
||||
}
|
||||
if(!os) {
|
||||
printf("ads: OS analysis failed, cannot enumerate syscalls");
|
||||
return;
|
||||
}
|
||||
|
||||
std::vector<uint32_t> addresses;
|
||||
for(int i = 0; i < os->syscall_count(); i++)
|
||||
addresses.push_back(os->syscall(i));
|
||||
std::vector<uint32_t> addresses;
|
||||
for(int i = 0; i < os->syscall_count(); i++)
|
||||
addresses.push_back(os->syscall(i));
|
||||
|
||||
ad_disassemble_all(space, addresses, true);
|
||||
ad_disassemble_all(space, addresses, true);
|
||||
}
|
||||
|
||||
//---
|
||||
// Command definitions
|
||||
//---
|
||||
|
||||
static ShellCommand _af4_cmd("af4",
|
||||
[](Session &s, Parser &p){
|
||||
auto args = parse_af4(s, p);
|
||||
_af4(s, args.value, args.regions); },
|
||||
[](Session &s, Parser &p){ parse_af4(s, p); },
|
||||
"Analysis: Find 4-aligned value", R"(
|
||||
af4 <value> [<regions>...]
|
||||
|
||||
Searches mapped memory for a 4-aligned 32-bit value. If regions are specified
|
||||
(by name or the <start>:<size> syntax), searches these regions. Otherwise,
|
||||
searches every binding in the current virtual space.
|
||||
|
||||
af4 0xb4000000 ROM 0x88000000:512
|
||||
Searches occurrences of 0xb4000000, 4-aligned, within mapped ROM and within
|
||||
the first 512 bytes of RAM.
|
||||
|
||||
af4 0x43415349
|
||||
Seacrhes for the 4-aligned string "CASI" in all currently bound regions.
|
||||
)");
|
||||
|
||||
static ShellCommand _afh_cmd("afh",
|
||||
[](Session &s, Parser &p) {
|
||||
auto args = parse_afh(s, p);
|
||||
_afh(s, args.reference.c_str(), args.pattern.c_str(), args.size,
|
||||
args.align, args.distance, args.regions); },
|
||||
[](Session &s, Parser &p){ parse_afh(s, p); },
|
||||
"Analysis: Find Hexadecimal pattern", R"(
|
||||
afh [align=<value>] "<pattern>" [<regions>...]
|
||||
|
||||
Searches mapped memory for a hexadecimal pattern with hole bytes. The pattern
|
||||
should be a hexadecimal string like "0123..6789..cd", consisting either of
|
||||
explicit hexadecimal values or hole bytes marked by "..". The holes must start
|
||||
and end at byte boundaries.
|
||||
|
||||
An occurrence of the pattern is any sequence of bytes that matches the explicit
|
||||
bytes exactly, with no constraint on the hole bytes (ie. any value matches).
|
||||
|
||||
If regions are specified, searches these regions. Otherwise, searches every
|
||||
binding in the current virtual space. If align=<align> is specified, only
|
||||
considers addresses that are multiples of the specified alignment value.
|
||||
|
||||
afh "434153494f......00" ROM
|
||||
Searches strings ending with "CASIO" followed by up to 3 characters in ROM.
|
||||
|
||||
afh align=4 "b4..0000"
|
||||
Searches 4-aligned values close to the display interface 0xb4000000 within
|
||||
all currently bound regions.
|
||||
)");
|
||||
|
||||
static ShellCommand _ad_cmd("ad",
|
||||
[](Session &s, Parser &p) {
|
||||
auto addresses = parse_ad(s, p);
|
||||
_ad(s, addresses); },
|
||||
[](Session &s, Parser &p) { parse_ad(s, p); },
|
||||
"Analysis: Disassemble", R"(
|
||||
static ShellCommand _ad_cmd(
|
||||
"ad",
|
||||
[](Session &s, Parser &p) {
|
||||
auto addresses = parse_ad(s, p);
|
||||
_ad(s, addresses);
|
||||
},
|
||||
[](Session &s, Parser &p) { parse_ad(s, p); }, "Analysis: Disassemble", R"(
|
||||
ad [<addresses>...]
|
||||
|
||||
Disassemble the given set of addresses into the current virtual space's main
|
||||
|
@ -412,12 +160,14 @@ disassembly. The main disassembly is used for OS-wide features like cross-
|
|||
reference search or call graphs.
|
||||
)");
|
||||
|
||||
static ShellCommand _ads_cmd("ads",
|
||||
[](Session &s, Parser &p) {
|
||||
parse_ads(s, p);
|
||||
_ads(s); },
|
||||
[](Session &s, Parser &p) { parse_ads(s, p); },
|
||||
"Analysis: Disassemble all Syscalls", R"(
|
||||
static ShellCommand _ads_cmd(
|
||||
"ads",
|
||||
[](Session &s, Parser &p) {
|
||||
parse_ads(s, p);
|
||||
_ads(s);
|
||||
},
|
||||
[](Session &s, Parser &p) { parse_ads(s, p); },
|
||||
"Analysis: Disassemble all Syscalls", R"(
|
||||
ads
|
||||
|
||||
Disassembles all syscalls entries using ad, which stores the results in the
|
||||
|
|
|
@ -10,9 +10,10 @@
|
|||
|
||||
#include <fxos/memory.h>
|
||||
#include "session.h"
|
||||
#include "parser.h"
|
||||
|
||||
//---
|
||||
// Meta commands
|
||||
// Include commands
|
||||
//---
|
||||
|
||||
/* Include the specified list of files *AFTER* the current command ends */
|
||||
|
@ -31,9 +32,6 @@ void _vs(Session &s, std::string const &name);
|
|||
/* Create a new virtual space */
|
||||
void _vc(Session &s, std::string name);
|
||||
|
||||
/* Create a new virtual space from a target */
|
||||
void _vct(Session &s, std::string filename, std::string space="");
|
||||
|
||||
/* Map a file into one or more regions inside the current virtual space */
|
||||
void _vm(Session &s, std::string filename, std::vector<MemoryRegion> regions);
|
||||
|
||||
|
@ -41,11 +39,8 @@ void _vm(Session &s, std::string filename, std::vector<MemoryRegion> regions);
|
|||
// Evaluation
|
||||
//---
|
||||
|
||||
/* Evaluate a numerical input in the current virtual space */
|
||||
void _e(Session &s, std::vector<long> const &values);
|
||||
|
||||
/* Evaluate a numerical input in another virtual space */
|
||||
void _ev(Session &s, std::string space_name, std::vector<long> const &values);
|
||||
/* Evaluate a numerical input in the specified virtual space */
|
||||
void _e(Session &s, std::string space_name, std::vector<long> const &values);
|
||||
|
||||
//---
|
||||
// Goto
|
||||
|
|
220
shell/d.cpp
220
shell/d.cpp
|
@ -1,74 +1,68 @@
|
|||
#include "shell.h"
|
||||
#include "parser.h"
|
||||
#include "commands.h"
|
||||
#include "errors.h"
|
||||
#include "parser.h"
|
||||
#include "shell.h"
|
||||
|
||||
#include <fmt/core.h>
|
||||
|
||||
#include <fxos/disassembly.h>
|
||||
#include <fxos/util/Timer.h>
|
||||
#include <fxos/util/log.h>
|
||||
#include <fxos/passes/cfg.h>
|
||||
#include <fxos/passes/pcrel.h>
|
||||
#include <fxos/passes/syscall.h>
|
||||
#include <fxos/passes/print.h>
|
||||
#include <fxos/passes/syscall.h>
|
||||
#include <fxos/util/Timer.h>
|
||||
#include <fxos/util/log.h>
|
||||
|
||||
static void disassemble(Session &session, Disassembly &disasm,
|
||||
std::vector<std::string> const &passes, uint32_t address)
|
||||
std::vector<std::string> const &passes, uint32_t address)
|
||||
{
|
||||
for(auto pass: passes)
|
||||
{
|
||||
Timer timer;
|
||||
timer.start();
|
||||
for(auto pass: passes) {
|
||||
Timer timer;
|
||||
timer.start();
|
||||
|
||||
bool ok;
|
||||
bool ok;
|
||||
|
||||
if(pass == "cfg")
|
||||
{
|
||||
CfgPass p(disasm);
|
||||
ok = p.analyzeAnonymousFunction(address);
|
||||
}
|
||||
else if(pass == "pcrel")
|
||||
{
|
||||
PcrelPass p(disasm);
|
||||
ok = p.analyzeAllInstructions();
|
||||
}
|
||||
else if(pass == "syscall")
|
||||
{
|
||||
OS *os = session.current_space->os_analysis();
|
||||
if(os) {
|
||||
SyscallPass p(disasm, os);
|
||||
ok = p.analyzeAllInstructions();
|
||||
}
|
||||
}
|
||||
else if(pass == "print")
|
||||
{
|
||||
PrintPass p(disasm);
|
||||
if(pass == "cfg") {
|
||||
CfgPass p(disasm);
|
||||
ok = p.analyzeAnonymousFunction(address);
|
||||
}
|
||||
else if(pass == "pcrel") {
|
||||
PcrelPass p(disasm);
|
||||
ok = p.analyzeAllInstructions();
|
||||
}
|
||||
else if(pass == "syscall") {
|
||||
OS *os = session.current_space->os_analysis();
|
||||
if(os) {
|
||||
SyscallPass p(disasm, os);
|
||||
ok = p.analyzeAllInstructions();
|
||||
}
|
||||
}
|
||||
else if(pass == "print") {
|
||||
PrintPass p(disasm);
|
||||
|
||||
p.promote_pcjump_loc = PrintPass::Promote;
|
||||
p.promote_pcrel_loc = PrintPass::Promote;
|
||||
p.promote_pcrel_value = PrintPass::Promote;
|
||||
p.promote_syscall = PrintPass::Promote;
|
||||
p.promote_syscallname = PrintPass::Append;
|
||||
p.promote_symbol = PrintPass::Append;
|
||||
p.promote_pcaddr_loc = PrintPass::Promote;
|
||||
p.promote_pcjump_loc = PrintPass::Promote;
|
||||
p.promote_pcrel_loc = PrintPass::Promote;
|
||||
p.promote_pcrel_value = PrintPass::Promote;
|
||||
p.promote_syscall = PrintPass::Promote;
|
||||
p.promote_syscallname = PrintPass::Append;
|
||||
p.promote_symbol = PrintPass::Append;
|
||||
p.promote_pcaddr_loc = PrintPass::Promote;
|
||||
|
||||
ok = p.analyzeAllInstructions();
|
||||
}
|
||||
else
|
||||
{
|
||||
FxOS_log(ERR, "unknown pass <%s>", pass);
|
||||
ok = false;
|
||||
}
|
||||
ok = p.analyzeAllInstructions();
|
||||
}
|
||||
else {
|
||||
FxOS_log(ERR, "unknown pass <%s>", pass);
|
||||
ok = false;
|
||||
}
|
||||
|
||||
timer.stop();
|
||||
FxOS_log(LOG, "Finished pass <%s> in %s", pass, timer.format_time());
|
||||
timer.stop();
|
||||
FxOS_log(LOG, "Finished pass <%s> in %s", pass, timer.format_time());
|
||||
|
||||
if(!ok) {
|
||||
FxOS_log(ERR, "pass <%s> failed", pass);
|
||||
break;
|
||||
}
|
||||
}
|
||||
if(!ok) {
|
||||
FxOS_log(ERR, "pass <%s> failed", pass);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//---
|
||||
|
@ -77,31 +71,31 @@ static void disassemble(Session &session, Disassembly &disasm,
|
|||
|
||||
static uint32_t parse_d(Session &session, Parser &parser)
|
||||
{
|
||||
if(!session.current_space)
|
||||
return 0;
|
||||
uint32_t address = session.current_space->cursor;
|
||||
if(!session.current_space)
|
||||
return 0;
|
||||
uint32_t address = session.current_space->cursor;
|
||||
|
||||
if(!parser.at_end())
|
||||
address = parser.expr(session.current_space);
|
||||
if(!parser.at_end())
|
||||
address = parser.expr(session.current_space);
|
||||
|
||||
parser.end();
|
||||
return address;
|
||||
parser.end();
|
||||
return address;
|
||||
}
|
||||
|
||||
void _d(Session &session, uint32_t address)
|
||||
{
|
||||
if(!session.current_space)
|
||||
return;
|
||||
FxOS::Disassembly disasm(*session.current_space);
|
||||
if(!session.current_space)
|
||||
return;
|
||||
FxOS::Disassembly disasm(*session.current_space);
|
||||
|
||||
if(address & 1) {
|
||||
fmt::print("address 0x{:08x} is odd, starting at 0x{:08x}\n",
|
||||
address, address+1);
|
||||
address++;
|
||||
}
|
||||
if(address & 1) {
|
||||
fmt::print("address 0x{:08x} is odd, starting at 0x{:08x}\n", address,
|
||||
address + 1);
|
||||
address++;
|
||||
}
|
||||
|
||||
disassemble(session, disasm,
|
||||
{ "cfg", "pcrel", /*"constprop",*/ "syscall", "print" }, address);
|
||||
disassemble(session, disasm,
|
||||
{"cfg", "pcrel", /*"constprop",*/ "syscall", "print"}, address);
|
||||
}
|
||||
|
||||
//---
|
||||
|
@ -110,63 +104,46 @@ void _d(Session &session, uint32_t address)
|
|||
|
||||
static Range parse_dr(Session &session, Parser &parser)
|
||||
{
|
||||
Range range = parser.range(session.current_space);
|
||||
parser.end();
|
||||
return range;
|
||||
Range range = parser.range(session.current_space);
|
||||
parser.end();
|
||||
return range;
|
||||
}
|
||||
|
||||
void _dr(Session &session, Range range)
|
||||
{
|
||||
if(!session.current_space)
|
||||
return;
|
||||
FxOS::Disassembly disasm(*session.current_space);
|
||||
if(!session.current_space)
|
||||
return;
|
||||
FxOS::Disassembly disasm(*session.current_space);
|
||||
|
||||
if(range.start & 1) {
|
||||
fmt::print("address 0x{:08x} is odd, starting at 0x{:08x}\n",
|
||||
range.start, range.start+1);
|
||||
range.start++;
|
||||
}
|
||||
if(range.end & 1) {
|
||||
fmt::print("address 0x{:08x} is odd, ending at 0x{:08x}\n",
|
||||
range.end, range.end-1);
|
||||
range.end--;
|
||||
}
|
||||
if(range.start & 1) {
|
||||
fmt::print("address 0x{:08x} is odd, starting at 0x{:08x}\n",
|
||||
range.start, range.start + 1);
|
||||
range.start++;
|
||||
}
|
||||
if(range.end & 1) {
|
||||
fmt::print("address 0x{:08x} is odd, ending at 0x{:08x}\n", range.end,
|
||||
range.end - 1);
|
||||
range.end--;
|
||||
}
|
||||
|
||||
if(range.start >= range.end) return;
|
||||
if(range.start >= range.end)
|
||||
return;
|
||||
|
||||
/* Load the block into memory */
|
||||
for(uint32_t pc = range.start; pc < range.end; pc += 2)
|
||||
disasm.getInstructionAt(pc, true);
|
||||
/* Load the block into memory */
|
||||
for(uint32_t pc = range.start; pc < range.end; pc += 2)
|
||||
disasm.getInstructionAt(pc, true);
|
||||
|
||||
disassemble(session, disasm, { "pcrel", /*"constprop",*/ "syscall",
|
||||
"print" }, -1);
|
||||
}
|
||||
|
||||
//---
|
||||
// dtl
|
||||
//---
|
||||
|
||||
static std::string parse_dtl(Session &session, Parser &parser)
|
||||
{
|
||||
std::string filename = parser.str();
|
||||
parser.end();
|
||||
return session.file(filename);
|
||||
}
|
||||
|
||||
void _dtl(Session &, std::string filename)
|
||||
{
|
||||
Buffer buf(filename);
|
||||
FxOS::load_instructions(buf);
|
||||
disassemble(
|
||||
session, disasm, {"pcrel", /*"constprop",*/ "syscall", "print"}, -1);
|
||||
}
|
||||
|
||||
//---
|
||||
// Command registration
|
||||
//---
|
||||
|
||||
static ShellCommand _d_cmd("d",
|
||||
[](Session &s, Parser &p){ _d(s, parse_d(s, p)); },
|
||||
[](Session &s, Parser &p){ parse_d(s, p); },
|
||||
"Disassemble", R"(
|
||||
static ShellCommand _d_cmd(
|
||||
"d", [](Session &s, Parser &p) { _d(s, parse_d(s, p)); },
|
||||
[](Session &s, Parser &p) { parse_d(s, p); }, "Disassemble", R"(
|
||||
d [<address>]
|
||||
|
||||
Disassembles code starting at the specified address, exploring branches until
|
||||
|
@ -183,10 +160,9 @@ The following disassembler passes are run:
|
|||
syscall Annotates uses of syscall table entries with the syscall number
|
||||
)");
|
||||
|
||||
static ShellCommand _dr_cmd("dr",
|
||||
[](Session &s, Parser &p){ _dr(s, parse_dr(s, p)); },
|
||||
[](Session &s, Parser &p){ parse_dr(s, p); },
|
||||
"Disassemble Range", R"(
|
||||
static ShellCommand _dr_cmd(
|
||||
"dr", [](Session &s, Parser &p) { _dr(s, parse_dr(s, p)); },
|
||||
[](Session &s, Parser &p) { parse_dr(s, p); }, "Disassemble Range", R"(
|
||||
dr [<range>]
|
||||
|
||||
Disassembles an explicit region of memory. This is similar to d, except that
|
||||
|
@ -195,13 +171,3 @@ by the cfg pass. See d? for more information.
|
|||
|
||||
Like d, this command does not extend the virtual space's main disassembly.
|
||||
)");
|
||||
|
||||
static ShellCommand _dtl_cmd("dtl",
|
||||
[](Session &s, Parser &p){ _dtl(s, parse_dtl(s, p)); },
|
||||
[](Session &s, Parser &p){ parse_dtl(s, p); },
|
||||
"Disassembly Table Load", R"(
|
||||
dtl "<file>"
|
||||
|
||||
Loads a disassembly table from the specified file. This command is mostly
|
||||
designed to be used in startup scripts.
|
||||
)");
|
||||
|
|
|
@ -0,0 +1,66 @@
|
|||
#include "shell.h"
|
||||
#include "parser.h"
|
||||
|
||||
//---
|
||||
// .
|
||||
//---
|
||||
|
||||
static std::vector<std::string> parse_dot(Session &, Parser &parser)
|
||||
{
|
||||
std::vector<std::string> files;
|
||||
|
||||
while(!parser.at_end())
|
||||
files.push_back(parser.str());
|
||||
|
||||
return files;
|
||||
}
|
||||
|
||||
void _dot(Session &s, std::vector<std::string> const &files, bool absolute)
|
||||
{
|
||||
std::vector<std::string> paths;
|
||||
for(auto const &file: files) {
|
||||
paths.push_back(absolute ? file : s.file(file).string());
|
||||
}
|
||||
lex_include(paths);
|
||||
}
|
||||
|
||||
//---
|
||||
// .dt
|
||||
//---
|
||||
|
||||
static std::string parse_dot_dt(Session &session, Parser &parser)
|
||||
{
|
||||
std::string filename = parser.str();
|
||||
parser.end();
|
||||
return session.file(filename);
|
||||
}
|
||||
|
||||
void _dot_dt(Session &, std::string filename)
|
||||
{
|
||||
Buffer buf(filename);
|
||||
FxOS::load_instructions(buf);
|
||||
}
|
||||
|
||||
//---
|
||||
// Command registration
|
||||
//---
|
||||
|
||||
static ShellCommand _dot_cmd(
|
||||
".", [](Session &s, Parser &p) { _dot(s, parse_dot(s, p), false); },
|
||||
[](Session &s, Parser &p) { parse_dot(s, p); }, "Include scripts", R"(
|
||||
. "<script_1>" "<script_2>"...
|
||||
|
||||
Reads file paths from its string arguments, and executes each of them as a
|
||||
sequence of commands in the order of the command line.
|
||||
)");
|
||||
|
||||
static ShellCommand _dot_dt_cmd(
|
||||
".dt", [](Session &s, Parser &p) { _dot_dt(s, parse_dot_dt(s, p)); },
|
||||
[](Session &s, Parser &p) { parse_dot_dt(s, p); },
|
||||
"Include Disassembly Table",
|
||||
R"(
|
||||
.dt "<file>"
|
||||
|
||||
Loads a disassembly table from the specified file. This command is mostly
|
||||
designed to be used in startup scripts.
|
||||
)");
|
131
shell/e.cpp
131
shell/e.cpp
|
@ -9,100 +9,54 @@
|
|||
// e
|
||||
//---
|
||||
|
||||
static std::vector<long> parse_e(Session &session, Parser &parser)
|
||||
struct _e_args
|
||||
{
|
||||
std::vector<long> values;
|
||||
|
||||
while(!parser.at_end()) {
|
||||
values.push_back(parser.expr(session.current_space));
|
||||
}
|
||||
|
||||
parser.end();
|
||||
return values;
|
||||
}
|
||||
|
||||
void _e(Session &session, std::vector<long> const &values)
|
||||
{
|
||||
for(long value: values) {
|
||||
/* Hexa format */
|
||||
int length = (labs(value) <= (1ll << 32) ? 8 : 16) + 2 + (value < 0);
|
||||
std::string format = fmt::format("{{:#0{}x}}", length);
|
||||
fmt::print(format, value);
|
||||
|
||||
long a = abs(value);
|
||||
if(a <= 100 || a % 100 <= 1 || a % 100 >= 99)
|
||||
fmt::print(" = {}", a);
|
||||
|
||||
VirtualSpace *space = session.current_space;
|
||||
|
||||
if(space) {
|
||||
std::optional<std::string> opt;
|
||||
|
||||
/* Promote to syscall ID */
|
||||
OS *os = space->os_analysis();
|
||||
int syscall_id;
|
||||
|
||||
if(os && (syscall_id = os->find_syscall(value)) != -1) {
|
||||
fmt::print(" = %{:03x}", syscall_id);
|
||||
opt = space->symbols.query(Symbol::Syscall, syscall_id);
|
||||
if(opt) fmt::print(" = {}", *opt);
|
||||
}
|
||||
|
||||
opt = space->symbols.query(Symbol::Address, value);
|
||||
if(opt) fmt::print(" = {}", *opt);
|
||||
}
|
||||
|
||||
fmt::print("\n");
|
||||
}
|
||||
}
|
||||
|
||||
//---
|
||||
// ev
|
||||
//---
|
||||
|
||||
struct _ev_args {
|
||||
std::string space_name;
|
||||
std::vector<long> values;
|
||||
std::string space_name;
|
||||
std::vector<long> values;
|
||||
};
|
||||
|
||||
static _ev_args parse_ev(Session &session, Parser &parser)
|
||||
static _e_args parse_e(Session &session, Parser &parser)
|
||||
{
|
||||
_ev_args args {};
|
||||
args.space_name = parser.symbol("vspace_name");
|
||||
_e_args args {};
|
||||
parser.option("vspace",
|
||||
[&args](std::string const &value) { args.space_name = value; });
|
||||
|
||||
VirtualSpace *space = session.get_space(args.space_name);
|
||||
if(space) {
|
||||
while(!parser.at_end()) {
|
||||
args.values.push_back(parser.expr(space));
|
||||
}
|
||||
}
|
||||
// TODO: Error message when session specified in _ev does not exist
|
||||
VirtualSpace *space = session.get_space(args.space_name);
|
||||
if(space) {
|
||||
while(!parser.at_end()) {
|
||||
args.values.push_back(parser.expr(space));
|
||||
}
|
||||
}
|
||||
// TODO: Error message when session specified in _e does not exist
|
||||
|
||||
parser.end();
|
||||
return args;
|
||||
parser.end();
|
||||
return args;
|
||||
}
|
||||
|
||||
void _ev(Session &, std::string, std::vector<long> const &values)
|
||||
void _e(Session &, std::string, std::vector<long> const &values)
|
||||
{
|
||||
for(long value: values) {
|
||||
/* Hexa format */
|
||||
int length = (labs(value) <= (1ll << 32) ? 8 : 16) + 2 + (value < 0);
|
||||
std::string format = fmt::format("{{:#0{}x}}", length);
|
||||
fmt::print(format, value);
|
||||
for(long value: values) {
|
||||
/* Hexa format */
|
||||
int length = (labs(value) <= (1ll << 32) ? 8 : 16) + 2 + (value < 0);
|
||||
std::string format = fmt::format("{{:#0{}x}}", length);
|
||||
fmt::print(format, value);
|
||||
|
||||
long a = abs(value);
|
||||
if(a <= 100 || a % 100 <= 1 || a % 100 >= 99)
|
||||
fmt::print(" = {}", a);
|
||||
long a = abs(value);
|
||||
if(a <= 100 || a % 100 <= 1 || a % 100 >= 99)
|
||||
fmt::print(" = {}", a);
|
||||
|
||||
fmt::print("\n");
|
||||
}
|
||||
fmt::print("\n");
|
||||
}
|
||||
}
|
||||
|
||||
static ShellCommand _e_cmd("e",
|
||||
[](Session &s, Parser &p){ _e(s, parse_e(s, p)); },
|
||||
[](Session &s, Parser &p){ parse_e(s, p); },
|
||||
"Evaluate expression", R"(
|
||||
e [<expression>...]
|
||||
static ShellCommand _e_cmd(
|
||||
"e",
|
||||
[](Session &s, Parser &p) {
|
||||
auto const &args = parse_e(s, p);
|
||||
_e(s, args.space_name, args.values);
|
||||
},
|
||||
[](Session &s, Parser &p) { parse_e(s, p); }, "Evaluate expression", R"(
|
||||
e [vspace=<virtual_space>] [<expression>...]
|
||||
|
||||
Evaluates the specified expressions. The expressions may include syscall
|
||||
references (%0ab), named symbols (TRA), the current cursor ($), and
|
||||
|
@ -112,21 +66,10 @@ The parser doesn't accept arithmetic expressions directly on the command-line;
|
|||
they must be placed within parentheses.
|
||||
|
||||
The resulting values undergo a simple analysis which recovers symbol names and
|
||||
syscall addresses.
|
||||
syscall addresses within the named virtual space, or using the current virtual
|
||||
space if not provided.
|
||||
|
||||
e TRA ($+2)
|
||||
Evaluate the address of the TRA register, and the address of the next
|
||||
instruction.
|
||||
)");
|
||||
|
||||
static ShellCommand _ev_cmd("ev",
|
||||
[](Session &s, Parser &p){
|
||||
auto const &args = parse_ev(s, p);
|
||||
_ev(s, args.space_name, args.values); },
|
||||
[](Session &s, Parser &p){ parse_ev(s, p); },
|
||||
"Evaluate expression in Virtual space", R"(
|
||||
ev <virtual_space> [<expression>...]
|
||||
|
||||
Same as e, but resolves symbols within the named virtual space rather than the
|
||||
current one. See e? for more information.
|
||||
)");
|
||||
|
|
19
shell/g.cpp
19
shell/g.cpp
|
@ -10,22 +10,21 @@
|
|||
|
||||
static long parse_g(Session &session, Parser &parser)
|
||||
{
|
||||
long addr = parser.expr(session.current_space);
|
||||
parser.end();
|
||||
return addr;
|
||||
long addr = parser.expr(session.current_space);
|
||||
parser.end();
|
||||
return addr;
|
||||
}
|
||||
|
||||
void _g(Session &session, long value)
|
||||
{
|
||||
if(!session.current_space)
|
||||
return;
|
||||
session.current_space->cursor = (value & 0xffffffff);
|
||||
if(!session.current_space)
|
||||
return;
|
||||
session.current_space->cursor = (value & 0xffffffff);
|
||||
}
|
||||
|
||||
static ShellCommand _g_cmd("g",
|
||||
[](Session &s, Parser &p){ _g(s, parse_g(s, p)); },
|
||||
[](Session &s, Parser &p){ parse_g(s, p); },
|
||||
"Goto address", R"(
|
||||
static ShellCommand _g_cmd(
|
||||
"g", [](Session &s, Parser &p) { _g(s, parse_g(s, p)); },
|
||||
[](Session &s, Parser &p) { parse_g(s, p); }, "Goto address", R"(
|
||||
g <address>
|
||||
|
||||
Moves the cursor of the current virtual space to the specified address.
|
||||
|
|
100
shell/h.cpp
100
shell/h.cpp
|
@ -5,61 +5,64 @@
|
|||
|
||||
#include <fmt/core.h>
|
||||
|
||||
using Selections = std::vector<std::pair<uint32_t,int>>;
|
||||
using Selections = std::vector<std::pair<uint32_t, int>>;
|
||||
|
||||
static bool is_selected(uint32_t address, Selections const &sel)
|
||||
{
|
||||
for(auto &p: sel) {
|
||||
if(address >= p.first && address < p.first + p.second) return true;
|
||||
}
|
||||
return false;
|
||||
for(auto &p: sel) {
|
||||
if(address >= p.first && address < p.first + p.second)
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void _h_hexdump(Session &session, Range r, Selections sel)
|
||||
{
|
||||
if(!session.current_space)
|
||||
return;
|
||||
VirtualSpace &v = *session.current_space;
|
||||
if(!session.current_space)
|
||||
return;
|
||||
VirtualSpace &v = *session.current_space;
|
||||
|
||||
uint32_t start = r.start & ~0xf;
|
||||
uint32_t end = (r.end + 15) & ~0xf;
|
||||
uint32_t start = r.start & ~0xf;
|
||||
uint32_t end = (r.end + 15) & ~0xf;
|
||||
|
||||
for(uint32_t pos = start; pos != end; pos += 16) {
|
||||
fmt::print(theme(3), " 0x{:08x} ", pos);
|
||||
for(uint32_t pos = start; pos != end; pos += 16) {
|
||||
fmt::print(theme(3), " 0x{:08x} ", pos);
|
||||
|
||||
/* Hexdump */
|
||||
for(int offset = 0; offset < 16; offset++) {
|
||||
if(!(offset % 4)) fmt::print(" ");
|
||||
/* Hexdump */
|
||||
for(int offset = 0; offset < 16; offset++) {
|
||||
if(!(offset % 4))
|
||||
fmt::print(" ");
|
||||
|
||||
uint32_t addr = pos + offset;
|
||||
if(addr < r.start || addr >= r.end) {
|
||||
fmt::print(" ");
|
||||
}
|
||||
else if(is_selected(addr, sel)) {
|
||||
fmt::print(theme(13), "{:02x}", v.read_u8(addr));
|
||||
}
|
||||
else {
|
||||
fmt::print("{:02x}", v.read_u8(addr));
|
||||
}
|
||||
}
|
||||
uint32_t addr = pos + offset;
|
||||
if(addr < r.start || addr >= r.end) {
|
||||
fmt::print(" ");
|
||||
}
|
||||
else if(is_selected(addr, sel)) {
|
||||
fmt::print(theme(13), "{:02x}", v.read_u8(addr));
|
||||
}
|
||||
else {
|
||||
fmt::print("{:02x}", v.read_u8(addr));
|
||||
}
|
||||
}
|
||||
|
||||
/* ASCII */
|
||||
fmt::print(" ");
|
||||
for(int offset = 0; offset < 16; offset++) {
|
||||
if(!(offset % 4)) fmt::print(" ");
|
||||
/* ASCII */
|
||||
fmt::print(" ");
|
||||
for(int offset = 0; offset < 16; offset++) {
|
||||
if(!(offset % 4))
|
||||
fmt::print(" ");
|
||||
|
||||
uint32_t addr = pos + offset;
|
||||
if(addr < r.start || addr >= r.end) {
|
||||
fmt::print(" ");
|
||||
}
|
||||
else {
|
||||
int c = v.read_u8(addr);
|
||||
fmt::print(theme(11), "{:c}", isprint(c) ? c : '.');
|
||||
}
|
||||
}
|
||||
uint32_t addr = pos + offset;
|
||||
if(addr < r.start || addr >= r.end) {
|
||||
fmt::print(" ");
|
||||
}
|
||||
else {
|
||||
int c = v.read_u8(addr);
|
||||
fmt::print(theme(11), "{:c}", isprint(c) ? c : '.');
|
||||
}
|
||||
}
|
||||
|
||||
fmt::print("\n");
|
||||
}
|
||||
fmt::print("\n");
|
||||
}
|
||||
}
|
||||
|
||||
//---
|
||||
|
@ -68,20 +71,19 @@ void _h_hexdump(Session &session, Range r, Selections sel)
|
|||
|
||||
static Range parse_h(Session &session, Parser &parser)
|
||||
{
|
||||
Range r = parser.range(session.current_space, 0, 128);
|
||||
parser.end();
|
||||
return r;
|
||||
Range r = parser.range(session.current_space, 0, 128);
|
||||
parser.end();
|
||||
return r;
|
||||
}
|
||||
|
||||
void _h(Session &session, Range r)
|
||||
{
|
||||
_h_hexdump(session, r, {});
|
||||
_h_hexdump(session, r, {});
|
||||
}
|
||||
|
||||
static ShellCommand _h_cmd("h",
|
||||
[](Session &s, Parser &p){ _h(s, parse_h(s, p)); },
|
||||
[](Session &s, Parser &p){ parse_h(s, p); },
|
||||
"Hexdump", R"(
|
||||
static ShellCommand _h_cmd(
|
||||
"h", [](Session &s, Parser &p) { _h(s, parse_h(s, p)); },
|
||||
[](Session &s, Parser &p) { parse_h(s, p); }, "Hexdump", R"(
|
||||
h (<address>|<range>)
|
||||
|
||||
Dumps the specified range into hexadecimal and ASCII representations. When an
|
||||
|
|
341
shell/i.cpp
341
shell/i.cpp
|
@ -11,196 +11,229 @@
|
|||
// ic
|
||||
//---
|
||||
|
||||
struct _ic_args {
|
||||
std::vector<uint32_t> addresses;
|
||||
struct _ic_args
|
||||
{
|
||||
std::vector<uint32_t> addresses;
|
||||
};
|
||||
|
||||
static struct _ic_args parse_ic(Session &session, Parser &parser)
|
||||
{
|
||||
_ic_args args;
|
||||
_ic_args args;
|
||||
|
||||
while(!parser.at_end())
|
||||
args.addresses.push_back(parser.expr(session.current_space));
|
||||
while(!parser.at_end())
|
||||
args.addresses.push_back(parser.expr(session.current_space));
|
||||
|
||||
parser.end();
|
||||
return args;
|
||||
parser.end();
|
||||
return args;
|
||||
}
|
||||
|
||||
void _ic(Session &session, struct _ic_args const &args)
|
||||
{
|
||||
if(!session.current_space)
|
||||
return;
|
||||
if(!session.current_space)
|
||||
return;
|
||||
|
||||
for(uint32_t address: args.addresses) {
|
||||
Claim const *claim = session.current_space->disasm.getClaimAt(address);
|
||||
if(claim)
|
||||
fmt::print("0x{:08x} is claimed by {}\n", address, claim->str());
|
||||
else
|
||||
fmt::print("0x{:08x} is not claimed\n", address);
|
||||
}
|
||||
for(uint32_t address: args.addresses) {
|
||||
Claim const *claim = session.current_space->disasm.getClaimAt(address);
|
||||
if(claim)
|
||||
fmt::print("0x{:08x} is claimed by {}\n", address, claim->str());
|
||||
else
|
||||
fmt::print("0x{:08x} is not claimed\n", address);
|
||||
}
|
||||
}
|
||||
|
||||
//---
|
||||
// io
|
||||
//---
|
||||
|
||||
static char const *info_str =
|
||||
"OS: type %s version %02d.%02d.%04d\n"
|
||||
"\n"
|
||||
"Header information:\n"
|
||||
" Bootcode timestamp (DateA) (0x%000008x) : %s\n"
|
||||
" Bootcode checksum (0x%000008x) : 0x%08x\n"
|
||||
" Serial number (0x%000008x) : %s\n"
|
||||
" OS version (0x%000008x) : %s\n";
|
||||
static char const *info_str
|
||||
= "OS: type %s version %02d.%02d.%04d\n"
|
||||
"\n"
|
||||
"Header information:\n"
|
||||
" Bootcode timestamp (DateA) (0x%000008x) : %s\n"
|
||||
" Bootcode checksum (0x%000008x) : 0x%08x\n"
|
||||
" Serial number (0x%000008x) : %s\n"
|
||||
" OS version (0x%000008x) : %s\n";
|
||||
|
||||
static char const *footer_str =
|
||||
"\nFooter information:\n"
|
||||
" Detected footer address : 0x%08x\n"
|
||||
" Langdata entries found : %d\n"
|
||||
" OS date (DateO) (0x%000008x) : %s\n"
|
||||
" OS checksum (0x%000008x) : 0x%08x\n"
|
||||
" Computed OS checksum : 0x%08x\n";
|
||||
static char const *footer_str
|
||||
= "\nFooter information:\n"
|
||||
" Detected footer address : 0x%08x\n"
|
||||
" Langdata entries found : %d\n"
|
||||
" OS date (DateO) (0x%000008x) : %s\n"
|
||||
" OS checksum (0x%000008x) : 0x%08x\n"
|
||||
" Computed OS checksum : 0x%08x\n";
|
||||
|
||||
static char const *syscall_str =
|
||||
"\nSyscall information:\n"
|
||||
" Syscall table address (0x%000008x) : 0x%08x\n"
|
||||
" Entries that point to valid memory : 0x%x\n"
|
||||
" First seemingly invalid entry : 0x%08x\n"
|
||||
" Syscall entries outside ROM:\n";
|
||||
static char const *syscall_str
|
||||
= "\nSyscall information:\n"
|
||||
" Syscall table address (0x%000008x) : 0x%08x\n"
|
||||
" Entries that point to valid memory : 0x%x\n"
|
||||
" First seemingly invalid entry : 0x%08x\n"
|
||||
" Syscall entries outside ROM:\n";
|
||||
|
||||
static char const *syscall_nonrom_str =
|
||||
" %%%03x -> %08x (%s memory)\n";
|
||||
static char const *syscall_nonrom_str = " %%%03x -> %08x (%s memory)\n";
|
||||
|
||||
static std::string parse_io(Session &, Parser &parser)
|
||||
{
|
||||
std::string name = parser.at_end() ? "" : parser.symbol("vspace_name");
|
||||
parser.end();
|
||||
return name;
|
||||
std::string name = parser.at_end() ? "" : parser.symbol("vspace_name");
|
||||
parser.end();
|
||||
return name;
|
||||
}
|
||||
|
||||
void _io(Session &session, std::string name)
|
||||
{
|
||||
VirtualSpace *space = session.current_space;
|
||||
if(name != "")
|
||||
space = session.get_space(name);
|
||||
if(!space)
|
||||
return;
|
||||
VirtualSpace *space = session.current_space;
|
||||
if(name != "")
|
||||
space = session.get_space(name);
|
||||
if(!space)
|
||||
return;
|
||||
|
||||
OS *os = space->os_analysis();
|
||||
if(!os) throw CommandError("os analysis on '{}' failed", name);
|
||||
OS *os = space->os_analysis();
|
||||
if(!os)
|
||||
throw CommandError("os analysis on '{}' failed", name);
|
||||
|
||||
printf(info_str, (os->type == OS::FX ? "FX" : "CG"),
|
||||
os->version_major, os->version_minor, os->version_patch,
|
||||
os->bootcode_timestamp.address, os->bootcode_timestamp.value.c_str(),
|
||||
os->bootcode_checksum.address, os->bootcode_checksum,
|
||||
os->serial_number.address, os->serial_number.value.c_str(),
|
||||
os->version.address, os->version.value.c_str());
|
||||
printf(info_str, (os->type == OS::FX ? "FX" : "CG"), os->version_major,
|
||||
os->version_minor, os->version_patch, os->bootcode_timestamp.address,
|
||||
os->bootcode_timestamp.value.c_str(), os->bootcode_checksum.address,
|
||||
os->bootcode_checksum, os->serial_number.address,
|
||||
os->serial_number.value.c_str(), os->version.address,
|
||||
os->version.value.c_str());
|
||||
|
||||
if(os->footer == (uint32_t)-1)
|
||||
{
|
||||
printf("\nFooter could not be found.\n");
|
||||
}
|
||||
else
|
||||
{
|
||||
printf(footer_str, os->footer, os->langdata,
|
||||
os->timestamp.address, os->timestamp.value.c_str(),
|
||||
os->checksum.address, os->checksum, os->computed_checksum);
|
||||
}
|
||||
if(os->footer == (uint32_t)-1) {
|
||||
printf("\nFooter could not be found.\n");
|
||||
}
|
||||
else {
|
||||
printf(footer_str, os->footer, os->langdata, os->timestamp.address,
|
||||
os->timestamp.value.c_str(), os->checksum.address, os->checksum,
|
||||
os->computed_checksum);
|
||||
}
|
||||
|
||||
uint32_t syscall_table = os->syscall_table_address();
|
||||
uint32_t first_noncall = space->read_u32(syscall_table +
|
||||
4 * os->syscall_count());
|
||||
uint32_t syscall_table = os->syscall_table_address();
|
||||
uint32_t first_noncall
|
||||
= space->read_u32(syscall_table + 4 * os->syscall_count());
|
||||
|
||||
printf(syscall_str, (os->type == OS::FX ? 0x8001007c : 0x8002007c),
|
||||
syscall_table, os->syscall_count(), first_noncall);
|
||||
printf(syscall_str, (os->type == OS::FX ? 0x8001007c : 0x8002007c),
|
||||
syscall_table, os->syscall_count(), first_noncall);
|
||||
|
||||
int total = 0;
|
||||
for(int i = 0; i < os->syscall_count(); i++)
|
||||
{
|
||||
uint32_t e = os->syscall(i);
|
||||
MemoryRegion const *r = MemoryRegion::region_for(e);
|
||||
if(!r || r->name == "ROM" || r->name == "ROM_P2") continue;
|
||||
int total = 0;
|
||||
for(int i = 0; i < os->syscall_count(); i++) {
|
||||
uint32_t e = os->syscall(i);
|
||||
MemoryRegion const *r = MemoryRegion::region_for(e);
|
||||
if(!r || r->name == "ROM" || r->name == "ROM_P2")
|
||||
continue;
|
||||
|
||||
printf(syscall_nonrom_str, i, e, r->name.c_str());
|
||||
total++;
|
||||
}
|
||||
printf(syscall_nonrom_str, i, e, r->name.c_str());
|
||||
total++;
|
||||
}
|
||||
|
||||
if(!total) printf(" (none)\n");
|
||||
if(!total)
|
||||
printf(" (none)\n");
|
||||
}
|
||||
|
||||
//---
|
||||
// isc
|
||||
//---
|
||||
|
||||
struct _isc_args
|
||||
{
|
||||
std::string vspace_name;
|
||||
bool sort;
|
||||
};
|
||||
|
||||
static struct _isc_args parse_isc(Session &, Parser &parser)
|
||||
{
|
||||
struct _isc_args args
|
||||
{
|
||||
};
|
||||
|
||||
parser.option("sort",
|
||||
[&args](std::string const &value) { args.sort = (value == "true"); });
|
||||
|
||||
parser.accept_options();
|
||||
args.vspace_name = parser.at_end() ? "" : parser.symbol("vspace_name");
|
||||
parser.accept_options();
|
||||
|
||||
parser.end();
|
||||
return args;
|
||||
}
|
||||
|
||||
struct SyscallInfo
|
||||
{
|
||||
uint32_t address;
|
||||
int id;
|
||||
};
|
||||
|
||||
bool operator<(const SyscallInfo &left, const SyscallInfo &right)
|
||||
{
|
||||
return (left.address < right.address) || (left.id < right.id);
|
||||
}
|
||||
|
||||
void _isc(Session &session, std::string vspace_name, bool sort)
|
||||
{
|
||||
VirtualSpace *space = session.current_space;
|
||||
if(vspace_name != "")
|
||||
space = session.get_space(vspace_name);
|
||||
if(!space)
|
||||
return;
|
||||
|
||||
// TODO: is <vspace_name> doesn't work
|
||||
|
||||
OS *os = space->os_analysis();
|
||||
if(!os)
|
||||
throw CommandError("os analysis on '{}' failed", vspace_name);
|
||||
|
||||
int total = os->syscall_count();
|
||||
auto info = std::make_unique<SyscallInfo[]>(total);
|
||||
|
||||
for(int i = 0; i < total; i++) {
|
||||
info[i] = (SyscallInfo) {.address = os->syscall(i), .id = i};
|
||||
}
|
||||
|
||||
if(sort)
|
||||
std::sort(&info[0], &info[total]);
|
||||
|
||||
for(int i = 0; i < total; i++) {
|
||||
fmt::print(theme(3), " 0x{:08x}", info[i].address);
|
||||
fmt::print(theme(10), (total >= 0x1000 ? " %{:04x}" : " %{:03x}"),
|
||||
info[i].id);
|
||||
fmt::print("\n");
|
||||
}
|
||||
}
|
||||
|
||||
//---
|
||||
// is
|
||||
//---
|
||||
|
||||
struct _is_args {
|
||||
std::string vspace_name;
|
||||
bool sort;
|
||||
};
|
||||
|
||||
static struct _is_args parse_is(Session &, Parser &parser)
|
||||
static void parse_is(Session &, Parser &parser)
|
||||
{
|
||||
struct _is_args args {};
|
||||
|
||||
parser.option("sort", [&args](std::string const &value){
|
||||
args.sort = (value == "true");
|
||||
});
|
||||
|
||||
parser.accept_options();
|
||||
args.vspace_name = parser.at_end() ? "" : parser.symbol("vspace_name");
|
||||
parser.accept_options();
|
||||
|
||||
parser.end();
|
||||
return args;
|
||||
parser.end();
|
||||
}
|
||||
|
||||
struct SyscallInfo {
|
||||
uint32_t address;
|
||||
int id;
|
||||
};
|
||||
|
||||
bool operator < (const SyscallInfo &left, const SyscallInfo &right)
|
||||
void _is(Session &session)
|
||||
{
|
||||
return (left.address < right.address) || (left.id < right.id);
|
||||
}
|
||||
if(!session.current_space)
|
||||
return;
|
||||
|
||||
void _is(Session &session, std::string vspace_name, bool sort)
|
||||
{
|
||||
VirtualSpace *space = session.current_space;
|
||||
if(vspace_name != "")
|
||||
space = session.get_space(vspace_name);
|
||||
if(!space)
|
||||
return;
|
||||
for(auto const &s: session.current_space->symbols.symbols) {
|
||||
if(s.type == FxOS::Symbol::Syscall && s.value < 0x1000) {
|
||||
fmt::print(theme(10), " %{:03x}", s.value);
|
||||
}
|
||||
else if(s.type == FxOS::Symbol::Syscall) {
|
||||
fmt::print(theme(10), " %{:04x}", s.value);
|
||||
}
|
||||
else {
|
||||
fmt::print(" 0x{:08x}", s.value);
|
||||
}
|
||||
|
||||
// TODO: is <vspace_name> doesn't work
|
||||
|
||||
OS *os = space->os_analysis();
|
||||
if(!os) throw CommandError("os analysis on '{}' failed", vspace_name);
|
||||
|
||||
int total = os->syscall_count();
|
||||
auto info = std::make_unique<SyscallInfo[]>(total);
|
||||
|
||||
for(int i = 0; i < total; i++) {
|
||||
info[i] = (SyscallInfo){ .address = os->syscall(i), .id = i };
|
||||
}
|
||||
|
||||
if(sort) std::sort(&info[0], &info[total]);
|
||||
|
||||
for(int i = 0; i < total; i++) {
|
||||
fmt::print(theme(3), " 0x{:08x}", info[i].address);
|
||||
fmt::print(theme(10), (total >= 0x1000 ? " %{:04x}":" %{:03x}"),
|
||||
info[i].id);
|
||||
fmt::print("\n");
|
||||
}
|
||||
fmt::print(" {}\n", s.name);
|
||||
}
|
||||
}
|
||||
|
||||
//---
|
||||
// Command registration
|
||||
//---
|
||||
|
||||
static ShellCommand _ic_cmd("ic",
|
||||
[](Session &s, Parser &p){ _ic(s, parse_ic(s, p)); },
|
||||
[](Session &s, Parser &p){ parse_ic(s, p); },
|
||||
"Info Claims", R"(
|
||||
static ShellCommand _ic_cmd(
|
||||
"ic", [](Session &s, Parser &p) { _ic(s, parse_ic(s, p)); },
|
||||
[](Session &s, Parser &p) { parse_ic(s, p); }, "Info Claims", R"(
|
||||
ic <address>...
|
||||
|
||||
Prints information about claims over the specified addresses. Claims are
|
||||
|
@ -208,25 +241,37 @@ usually generated by analysis commands and allow sections of the OS to be
|
|||
marked as part of functions, data, interrupt handlers, etc.
|
||||
)");
|
||||
|
||||
static ShellCommand _io_cmd("io",
|
||||
[](Session &s, Parser &p){ _io(s, parse_io(s, p)); },
|
||||
[](Session &s, Parser &p){ parse_io(s, p); },
|
||||
"Info OS", R"(
|
||||
static ShellCommand _io_cmd(
|
||||
"io", [](Session &s, Parser &p) { _io(s, parse_io(s, p)); },
|
||||
[](Session &s, Parser &p) { parse_io(s, p); }, "Info OS", R"(
|
||||
io [<vspace_name>]
|
||||
|
||||
Prints information about the OS mapped in the named virtual space (defaults to
|
||||
the current one). This usually requires an OS binary to be mapped to ROM.
|
||||
)");
|
||||
|
||||
static ShellCommand _is_cmd("is",
|
||||
[](Session &s, Parser &p){
|
||||
auto args = parse_is(s, p);
|
||||
_is(s, args.vspace_name, args.sort); },
|
||||
[](Session &s, Parser &p){ parse_is(s, p); },
|
||||
"Info Syscalls", R"(
|
||||
is [sort=true] [<vspace_name>]
|
||||
static ShellCommand _isc_cmd(
|
||||
"isc",
|
||||
[](Session &s, Parser &p) {
|
||||
auto args = parse_isc(s, p);
|
||||
_isc(s, args.vspace_name, args.sort);
|
||||
},
|
||||
[](Session &s, Parser &p) { parse_isc(s, p); }, "Info Syscalls", R"(
|
||||
isc [sort=true] [<vspace_name>]
|
||||
|
||||
Prints the syscall table for the specified virtual space (defaults to the
|
||||
current one). By default, syscalls are enumerated by syscall number. If
|
||||
sort=true is specified, they are instead sorted by address.
|
||||
)");
|
||||
|
||||
static ShellCommand _is_cmd(
|
||||
"is",
|
||||
[](Session &s, Parser &p) {
|
||||
parse_is(s, p);
|
||||
_is(s);
|
||||
},
|
||||
[](Session &s, Parser &p) { parse_is(s, p); }, "Info Symbols", R"(
|
||||
is
|
||||
|
||||
Lists all symbols in the current virtual space.
|
||||
)");
|
||||
|
|
|
@ -0,0 +1,41 @@
|
|||
#include "parser.h"
|
||||
#include "shell.h"
|
||||
|
||||
//---
|
||||
// ma
|
||||
//---
|
||||
|
||||
static FxOS::Symbol parse_ma(Session &, Parser &parser)
|
||||
{
|
||||
Token t = parser.expect({T::NUM, T::SYSCALL});
|
||||
|
||||
FxOS::Symbol s;
|
||||
if(t.type == T::SYSCALL)
|
||||
s.type = FxOS::Symbol::Syscall;
|
||||
else
|
||||
s.type = FxOS::Symbol::Address;
|
||||
|
||||
s.value = t.value.NUM;
|
||||
s.name = parser.symbol();
|
||||
|
||||
parser.end();
|
||||
return s;
|
||||
}
|
||||
|
||||
void _ma(Session &session, Symbol s)
|
||||
{
|
||||
if(!session.current_space)
|
||||
return;
|
||||
session.current_space->symbols.add(s);
|
||||
}
|
||||
|
||||
static ShellCommand _ma_cmd(
|
||||
"ma", [](Session &s, Parser &p) { _ma(s, parse_ma(s, p)); },
|
||||
[](Session &s, Parser &p) { parse_ma(s, p); }, "Metadata Add", R"(
|
||||
ma <address|syscall> <symbol>
|
||||
|
||||
Defines a new symbol at the specified address or syscall number in the current
|
||||
virtual space. If a syscall number is provided, the address is not resolved; the
|
||||
same syscall symbol can be used accross multiple OS versions regardless of where
|
||||
the functions are actually located.
|
||||
)");
|
625
shell/main.cpp
625
shell/main.cpp
|
@ -22,19 +22,19 @@
|
|||
static std::map<std::string, ShellCommand *> commands;
|
||||
|
||||
ShellCommand::ShellCommand(std::string _name, ShellFunction _function,
|
||||
ShellCompleter _completer, std::string _shortd, std::string _longd):
|
||||
function {_function}, completer {_completer},
|
||||
short_description {_shortd}
|
||||
ShellCompleter _completer, std::string _shortd, std::string _longd):
|
||||
function {_function},
|
||||
completer {_completer}, short_description {_shortd}
|
||||
{
|
||||
/* Left trim */
|
||||
_longd.erase(0, _longd.find_first_not_of("\n"));
|
||||
/* Right trim */
|
||||
int r = _longd.size();
|
||||
while(r > 0 && _longd[--r] == '\n') {}
|
||||
_longd.erase(r+1, _longd.size());
|
||||
/* Left trim */
|
||||
_longd.erase(0, _longd.find_first_not_of("\n"));
|
||||
/* Right trim */
|
||||
int r = _longd.size();
|
||||
while(r > 0 && _longd[--r] == '\n') {}
|
||||
_longd.erase(r + 1, _longd.size());
|
||||
|
||||
this->long_description = _longd;
|
||||
commands[_name] = this;
|
||||
this->long_description = _longd;
|
||||
commands[_name] = this;
|
||||
}
|
||||
|
||||
static Session global_session;
|
||||
|
@ -45,167 +45,149 @@ static Session global_session;
|
|||
|
||||
std::vector<std::string> complete_command(char const *text)
|
||||
{
|
||||
std::vector<std::string> options;
|
||||
for(auto const &it: commands) {
|
||||
if(!strncmp(it.first.c_str(), text, strlen(text)))
|
||||
options.push_back(it.first);
|
||||
}
|
||||
return options;
|
||||
std::vector<std::string> options;
|
||||
for(auto const &it: commands) {
|
||||
if(!strncmp(it.first.c_str(), text, strlen(text)))
|
||||
options.push_back(it.first);
|
||||
}
|
||||
return options;
|
||||
}
|
||||
|
||||
std::vector<std::string> complete_vspace(char const *text, Session &session)
|
||||
{
|
||||
std::vector<std::string> options;
|
||||
for(auto const &it: session.spaces) {
|
||||
if(!strncmp(it.first.c_str(), text, strlen(text)))
|
||||
options.push_back(it.first);
|
||||
}
|
||||
return options;
|
||||
std::vector<std::string> options;
|
||||
for(auto const &it: session.spaces) {
|
||||
if(!strncmp(it.first.c_str(), text, strlen(text)))
|
||||
options.push_back(it.first);
|
||||
}
|
||||
return options;
|
||||
}
|
||||
|
||||
std::vector<std::string> complete_region(char const *text)
|
||||
{
|
||||
std::vector<std::string> options;
|
||||
for(auto const &it: MemoryRegion::all()) {
|
||||
if(!strncmp(it->name.c_str(), text, strlen(text)))
|
||||
options.push_back(it->name);
|
||||
}
|
||||
return options;
|
||||
std::vector<std::string> options;
|
||||
for(auto const &it: MemoryRegion::all()) {
|
||||
if(!strncmp(it->name.c_str(), text, strlen(text)))
|
||||
options.push_back(it->name);
|
||||
}
|
||||
return options;
|
||||
}
|
||||
|
||||
std::vector<std::string> complete_symbol(char const *text, VirtualSpace *space)
|
||||
{
|
||||
std::vector<std::string> options;
|
||||
for(auto const &it: space->symbols.symbols) {
|
||||
if(!strncmp(it.name.c_str(), text, strlen(text)))
|
||||
options.push_back(it.name);
|
||||
}
|
||||
return options;
|
||||
std::vector<std::string> options;
|
||||
for(auto const &it: space->symbols.symbols) {
|
||||
if(!strncmp(it.name.c_str(), text, strlen(text)))
|
||||
options.push_back(it.name);
|
||||
}
|
||||
return options;
|
||||
}
|
||||
|
||||
Parser::CompletionRequest parse_until_autocomplete(Session &session,
|
||||
char const *line_buffer, int point)
|
||||
Parser::CompletionRequest parse_until_autocomplete(
|
||||
Session &session, char const *line_buffer, int point)
|
||||
{
|
||||
/* Parse partial input and try to get a category of suggestions */
|
||||
std::string input(line_buffer, point);
|
||||
lex_repl(input);
|
||||
Parser p(true);
|
||||
p.start();
|
||||
/* Parse partial input and try to get a category of suggestions */
|
||||
std::string input(line_buffer, point);
|
||||
lex_repl(input);
|
||||
Parser p(true);
|
||||
p.start();
|
||||
|
||||
/* Read commands until the parser runs out of input and throws a
|
||||
/* Read commands until the parser runs out of input and throws a
|
||||
CompletionRequest */
|
||||
try {
|
||||
do {
|
||||
p.skip_separators();
|
||||
std::string cmd = p.symbol("command");
|
||||
if(commands.count(cmd) && commands[cmd]->completer)
|
||||
commands[cmd]->completer(session, p);
|
||||
} while(!lex_idle());
|
||||
}
|
||||
catch(Parser::CompletionRequest r) {
|
||||
return r;
|
||||
}
|
||||
catch(Parser::SyntaxError &e) {
|
||||
/* Ignore syntax errors, just don't autocomplete */
|
||||
}
|
||||
try {
|
||||
do {
|
||||
p.skip_separators();
|
||||
std::string cmd = p.symbol("command");
|
||||
if(commands.count(cmd) && commands[cmd]->completer)
|
||||
commands[cmd]->completer(session, p);
|
||||
}
|
||||
while(!lex_idle());
|
||||
}
|
||||
catch(Parser::CompletionRequest r) {
|
||||
return r;
|
||||
}
|
||||
catch(Parser::SyntaxError &e) {
|
||||
/* Ignore syntax errors, just don't autocomplete */
|
||||
}
|
||||
|
||||
return Parser::CompletionRequest("", "");
|
||||
return Parser::CompletionRequest("", "");
|
||||
}
|
||||
|
||||
char *autocomplete(char const *text, int state)
|
||||
{
|
||||
static std::vector<std::string> options;
|
||||
static size_t i = 0;
|
||||
static std::vector<std::string> options;
|
||||
static size_t i = 0;
|
||||
|
||||
if(state == 0) {
|
||||
Parser::CompletionRequest const &r = parse_until_autocomplete(
|
||||
global_session, rl_line_buffer, rl_point);
|
||||
if(r.category == "command")
|
||||
options = complete_command(text);
|
||||
else if(r.category == "vspace_name")
|
||||
options = complete_vspace(text, global_session);
|
||||
else if(r.category == "memory_region")
|
||||
options = complete_region(text);
|
||||
else if(r.category == "symbol" && r.space != nullptr)
|
||||
options = complete_symbol(text, r.space);
|
||||
else
|
||||
options.clear();
|
||||
i = 0;
|
||||
}
|
||||
if(state == 0) {
|
||||
Parser::CompletionRequest const &r = parse_until_autocomplete(
|
||||
global_session, rl_line_buffer, rl_point);
|
||||
if(r.category == "command")
|
||||
options = complete_command(text);
|
||||
else if(r.category == "vspace_name")
|
||||
options = complete_vspace(text, global_session);
|
||||
else if(r.category == "memory_region")
|
||||
options = complete_region(text);
|
||||
else if(r.category == "symbol" && r.space != nullptr)
|
||||
options = complete_symbol(text, r.space);
|
||||
else
|
||||
options.clear();
|
||||
i = 0;
|
||||
}
|
||||
|
||||
return (i < options.size()) ? strdup(options[i++].c_str()) : NULL;
|
||||
return (i < options.size()) ? strdup(options[i++].c_str()) : NULL;
|
||||
}
|
||||
|
||||
//---
|
||||
// Shell routine
|
||||
//---
|
||||
|
||||
static std::vector<std::string> parse_dot(Session &, Parser &parser)
|
||||
{
|
||||
std::vector<std::string> files;
|
||||
|
||||
while(!parser.at_end())
|
||||
files.push_back(parser.str());
|
||||
|
||||
return files;
|
||||
}
|
||||
|
||||
void _dot(Session &s, std::vector<std::string> const &files, bool absolute)
|
||||
{
|
||||
std::vector<std::string> paths;
|
||||
for(auto const &file: files) {
|
||||
paths.push_back(absolute ? file : s.file(file).string());
|
||||
}
|
||||
lex_include(paths);
|
||||
}
|
||||
|
||||
static std::string read_interactive(Session const &s, bool &leave)
|
||||
{
|
||||
std::string prompt = "no_vspace> ";
|
||||
if(s.current_space) {
|
||||
std::string name = "(none)";
|
||||
std::string prompt = "no_vspace> ";
|
||||
if(s.current_space) {
|
||||
std::string name = "(none)";
|
||||
|
||||
for(auto &it: s.spaces) {
|
||||
if(it.second.get() == s.current_space)
|
||||
name = it.first;
|
||||
}
|
||||
for(auto &it: s.spaces) {
|
||||
if(it.second.get() == s.current_space)
|
||||
name = it.first;
|
||||
}
|
||||
|
||||
prompt = fmt::format("{} @ 0x{:08x}> ", name, s.current_space->cursor);
|
||||
}
|
||||
prompt = fmt::format("{} @ 0x{:08x}> ", name, s.current_space->cursor);
|
||||
}
|
||||
|
||||
/* We need to insert RL_PROMPT_{START,END}_IGNORE into the color
|
||||
/* We need to insert RL_PROMPT_{START,END}_IGNORE into the color
|
||||
formatting, so we trick a little bit by using a space */
|
||||
std::string color = fmt::format(theme(9), " ");
|
||||
int space_pos = color.find(' ');
|
||||
std::string SC = color.substr(0, space_pos);
|
||||
std::string EC = color.substr(space_pos+1);
|
||||
std::string color = fmt::format(theme(9), " ");
|
||||
int space_pos = color.find(' ');
|
||||
std::string SC = color.substr(0, space_pos);
|
||||
std::string EC = color.substr(space_pos + 1);
|
||||
|
||||
std::string SI(1, RL_PROMPT_START_IGNORE);
|
||||
std::string EI(1, RL_PROMPT_END_IGNORE);
|
||||
std::string SI(1, RL_PROMPT_START_IGNORE);
|
||||
std::string EI(1, RL_PROMPT_END_IGNORE);
|
||||
|
||||
prompt = SI+SC+EI + prompt + SI+EC+EI;
|
||||
prompt = SI + SC + EI + prompt + SI + EC + EI;
|
||||
|
||||
/* Get a command to execute */
|
||||
char *cmd_ptr = readline(prompt.c_str());
|
||||
if(!cmd_ptr) {
|
||||
leave = true;
|
||||
return "";
|
||||
}
|
||||
/* Get a command to execute */
|
||||
char *cmd_ptr = readline(prompt.c_str());
|
||||
if(!cmd_ptr) {
|
||||
leave = true;
|
||||
return "";
|
||||
}
|
||||
|
||||
std::string cmd = cmd_ptr;
|
||||
if(strlen(cmd_ptr) > 0)
|
||||
add_history(cmd_ptr);
|
||||
free(cmd_ptr);
|
||||
return cmd;
|
||||
std::string cmd = cmd_ptr;
|
||||
if(strlen(cmd_ptr) > 0)
|
||||
add_history(cmd_ptr);
|
||||
free(cmd_ptr);
|
||||
return cmd;
|
||||
}
|
||||
|
||||
int norc = 0;
|
||||
std::string execute_arg = "";
|
||||
char* extra_rom = NULL;
|
||||
char *extra_rom = NULL;
|
||||
int log_level = FxOS::LOG_LEVEL_WRN;
|
||||
|
||||
char const *help_message =
|
||||
R"(Usage: fxos [OPTION]... [FILE]
|
||||
R"(Usage: fxos [OPTION]... [FILE]
|
||||
|
||||
Open the fxos disassembler, optionally with [FILE] mapped into a new empty
|
||||
space at ROM and ROM_P2.
|
||||
|
@ -217,242 +199,235 @@ Options:
|
|||
-h, --help display this help and exit
|
||||
)";
|
||||
|
||||
static void parse_options(int argc, char **argv) {
|
||||
/* List of options */
|
||||
static struct option long_options[] = {
|
||||
{"help", no_argument, 0, 'h'},
|
||||
{"execute", required_argument, 0, 'e'},
|
||||
{"norc", no_argument, &norc, 1},
|
||||
{"log", required_argument, 0, 'l'},
|
||||
/* This is here as a fallback if nothing is passed */
|
||||
{0, 0, 0, 0}
|
||||
};
|
||||
static void parse_options(int argc, char **argv)
|
||||
{
|
||||
/* List of options */
|
||||
static struct option long_options[] = {{"help", no_argument, 0, 'h'},
|
||||
{"execute", required_argument, 0, 'e'}, {"norc", no_argument, &norc, 1},
|
||||
{"log", required_argument, 0, 'l'},
|
||||
/* This is here as a fallback if nothing is passed */
|
||||
{0, 0, 0, 0}};
|
||||
|
||||
bool show_help = false;
|
||||
bool show_help = false;
|
||||
|
||||
int opt, option_index;
|
||||
while (1) {
|
||||
/* Get the next command-line option */
|
||||
opt = getopt_long(argc, argv, "he:l:", long_options, &option_index);
|
||||
int opt, option_index;
|
||||
while(1) {
|
||||
/* Get the next command-line option */
|
||||
opt = getopt_long(argc, argv, "he:l:", long_options, &option_index);
|
||||
|
||||
if (opt == -1)
|
||||
break;
|
||||
if(opt == -1)
|
||||
break;
|
||||
|
||||
/* Handle options */
|
||||
switch (opt) {
|
||||
case 0:
|
||||
/* This happens if we store the argument to a variable (e.g. --norc) */
|
||||
break;
|
||||
case 'h':
|
||||
show_help = true;
|
||||
break;
|
||||
case 'e':
|
||||
execute_arg += ";";
|
||||
execute_arg += optarg;
|
||||
break;
|
||||
case 'l':
|
||||
if(!strcmp(optarg, "debug"))
|
||||
log_level = FxOS::LOG_LEVEL_LOG;
|
||||
else if(!strcmp(optarg, "warning"))
|
||||
log_level = FxOS::LOG_LEVEL_LOG;
|
||||
else if(!strcmp(optarg, "error"))
|
||||
log_level = FxOS::LOG_LEVEL_ERR;
|
||||
else {
|
||||
FxOS_log(ERR, "invalid log level '%s'", optarg);
|
||||
printf("Try %s --help for more information.\n", argv[0]);
|
||||
exit(1);
|
||||
}
|
||||
break;
|
||||
case '?':
|
||||
printf("Try %s --help for more information.\n", argv[0]);
|
||||
exit(1);
|
||||
default:
|
||||
printf("Try %s --help for more information.\n", argv[0]);
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
/* Handle options */
|
||||
switch(opt) {
|
||||
case 0:
|
||||
/* This happens if we store the argument to a variable (e.g. --norc) */
|
||||
break;
|
||||
case 'h':
|
||||
show_help = true;
|
||||
break;
|
||||
case 'e':
|
||||
execute_arg += ";";
|
||||
execute_arg += optarg;
|
||||
break;
|
||||
case 'l':
|
||||
if(!strcmp(optarg, "debug"))
|
||||
log_level = FxOS::LOG_LEVEL_LOG;
|
||||
else if(!strcmp(optarg, "warning"))
|
||||
log_level = FxOS::LOG_LEVEL_LOG;
|
||||
else if(!strcmp(optarg, "error"))
|
||||
log_level = FxOS::LOG_LEVEL_ERR;
|
||||
else {
|
||||
FxOS_log(ERR, "invalid log level '%s'", optarg);
|
||||
printf("Try %s --help for more information.\n", argv[0]);
|
||||
exit(1);
|
||||
}
|
||||
break;
|
||||
case '?':
|
||||
printf("Try %s --help for more information.\n", argv[0]);
|
||||
exit(1);
|
||||
default:
|
||||
printf("Try %s --help for more information.\n", argv[0]);
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
/* Print help message if --help is used at least once */
|
||||
if (show_help) {
|
||||
printf("%s", help_message);
|
||||
exit(0);
|
||||
}
|
||||
/* Print help message if --help is used at least once */
|
||||
if(show_help) {
|
||||
printf("%s", help_message);
|
||||
exit(0);
|
||||
}
|
||||
|
||||
/* Handle positional arguments */
|
||||
if (optind < argc) {
|
||||
if ((argc - optind) > 1) {
|
||||
printf("%s only supports 1 positional argument, FILE.\n", argv[0]);
|
||||
printf("Try %s --help for more information.\n", argv[0]);
|
||||
exit(1);
|
||||
}
|
||||
/* Handle positional arguments */
|
||||
if(optind < argc) {
|
||||
if((argc - optind) > 1) {
|
||||
printf("%s only supports 1 positional argument, FILE.\n", argv[0]);
|
||||
printf("Try %s --help for more information.\n", argv[0]);
|
||||
exit(1);
|
||||
}
|
||||
|
||||
extra_rom = argv[optind++];
|
||||
}
|
||||
extra_rom = argv[optind++];
|
||||
}
|
||||
}
|
||||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
/* Parse command-line options first */
|
||||
parse_options(argc, argv);
|
||||
FxOS::log_setminlevel(log_level);
|
||||
/* Parse command-line options first */
|
||||
parse_options(argc, argv);
|
||||
FxOS::log_setminlevel(log_level);
|
||||
|
||||
Session &s = global_session;
|
||||
Session &s = global_session;
|
||||
|
||||
theme_builtin("tomorrow-night");
|
||||
theme_builtin("tomorrow-night");
|
||||
|
||||
rl_completion_entry_function = autocomplete;
|
||||
rl_basic_word_break_characters =
|
||||
" \t\n\"\\'`@$><=;|&{(" /* Readline's default */
|
||||
"+-*%)"; /* Word breaks with special characters in fxos */
|
||||
rl_completion_entry_function = autocomplete;
|
||||
rl_basic_word_break_characters
|
||||
= " \t\n\"\\'`@$><=;|&{(" /* Readline's default */
|
||||
"+-*%)"; /* Word breaks with special characters in fxos */
|
||||
|
||||
/* Load path into the session */
|
||||
char const *fxos_path_env = std::getenv("FXOS_PATH");
|
||||
if(fxos_path_env) {
|
||||
std::string fxos_path = fxos_path_env;
|
||||
/* Load path into the session */
|
||||
char const *fxos_path_env = std::getenv("FXOS_PATH");
|
||||
if(fxos_path_env) {
|
||||
std::string fxos_path = fxos_path_env;
|
||||
|
||||
size_t offset=0, end;
|
||||
while(true) {
|
||||
offset = fxos_path.find_first_not_of(":", offset);
|
||||
if(offset >= fxos_path.size()) break;
|
||||
size_t offset = 0, end;
|
||||
while(true) {
|
||||
offset = fxos_path.find_first_not_of(":", offset);
|
||||
if(offset >= fxos_path.size())
|
||||
break;
|
||||
|
||||
end = fxos_path.find_first_of(":", offset);
|
||||
s.path.push_back(fxos_path.substr(offset, end-offset));
|
||||
offset = end;
|
||||
}
|
||||
}
|
||||
else {
|
||||
fmt::print("warning: no FXOS_PATH in environment, using WD\n");
|
||||
s.path.push_back(fs::current_path());
|
||||
}
|
||||
end = fxos_path.find_first_of(":", offset);
|
||||
s.path.push_back(fxos_path.substr(offset, end - offset));
|
||||
offset = end;
|
||||
}
|
||||
}
|
||||
else {
|
||||
fmt::print("warning: no FXOS_PATH in environment, using WD\n");
|
||||
s.path.push_back(fs::current_path());
|
||||
}
|
||||
|
||||
/* Clear readline input when receiving SIGINT */
|
||||
static sigjmp_buf sigint_buf;
|
||||
std::signal(SIGINT, [](int) {
|
||||
rl_free_line_state();
|
||||
rl_cleanup_after_signal();
|
||||
rl_line_buffer[rl_point = rl_end = rl_mark = 0] = 0;
|
||||
printf("\n");
|
||||
siglongjmp(sigint_buf, 1);
|
||||
});
|
||||
rl_set_signals();
|
||||
/* Clear readline input when receiving SIGINT */
|
||||
static sigjmp_buf sigint_buf;
|
||||
std::signal(SIGINT, [](int) {
|
||||
rl_free_line_state();
|
||||
rl_cleanup_after_signal();
|
||||
rl_line_buffer[rl_point = rl_end = rl_mark = 0] = 0;
|
||||
printf("\n");
|
||||
siglongjmp(sigint_buf, 1);
|
||||
});
|
||||
rl_set_signals();
|
||||
|
||||
/* Load command history */
|
||||
char histfile[128] = "";
|
||||
if(std::getenv("HOME"))
|
||||
snprintf(histfile, 128, "%s/.fxos_history", std::getenv("HOME"));
|
||||
if(histfile[0]) read_history(histfile);
|
||||
/* Load command history */
|
||||
char histfile[128] = "";
|
||||
if(std::getenv("HOME"))
|
||||
snprintf(histfile, 128, "%s/.fxos_history", std::getenv("HOME"));
|
||||
if(histfile[0])
|
||||
read_history(histfile);
|
||||
|
||||
/* Load fxosrc files from all library folders if wanted */
|
||||
if (!norc) {
|
||||
std::vector<std::string> fxosrc_files;
|
||||
for(auto const &path: s.path) {
|
||||
if(fs::exists(path / "fxosrc"))
|
||||
fxosrc_files.push_back(path / "fxosrc");
|
||||
}
|
||||
if(fxosrc_files.size() > 0)
|
||||
_dot(s, fxosrc_files, true);
|
||||
}
|
||||
/* Load fxosrc files from all library folders if wanted */
|
||||
if(!norc) {
|
||||
std::vector<std::string> fxosrc_files;
|
||||
for(auto const &path: s.path) {
|
||||
if(fs::exists(path / "fxosrc"))
|
||||
fxosrc_files.push_back(path / "fxosrc");
|
||||
}
|
||||
if(fxosrc_files.size() > 0)
|
||||
_dot(s, fxosrc_files, true);
|
||||
}
|
||||
|
||||
|
||||
/* Stores whether we have already idled once, handling extra rom
|
||||
/* Stores whether we have already idled once, handling extra rom
|
||||
* and execute options */
|
||||
bool has_idled = false;
|
||||
bool has_idled = false;
|
||||
|
||||
/* Shell main loop */
|
||||
while(true) {
|
||||
/* Get a command if there isn't a file being executed */
|
||||
if(lex_idle()) {
|
||||
/* If we passed in FILE and we haven't handled it yet */
|
||||
if (extra_rom && !has_idled) {
|
||||
/* Create new 'file' virtual space and switch to it */
|
||||
_vc(s, "file");
|
||||
_vs(s, "file");
|
||||
/* Shell main loop */
|
||||
while(true) {
|
||||
/* Get a command if there isn't a file being executed */
|
||||
if(lex_idle()) {
|
||||
/* If we passed in FILE and we haven't handled it yet */
|
||||
if(extra_rom && !has_idled) {
|
||||
/* Create new 'file' virtual space and switch to it */
|
||||
_vc(s, "file");
|
||||
_vs(s, "file");
|
||||
|
||||
/* Add FILE as a ROM and ROM_P2 */
|
||||
_vm(s, extra_rom, {MemoryRegion::ROM, MemoryRegion::ROM_P2});
|
||||
}
|
||||
/* Add FILE as a ROM and ROM_P2 */
|
||||
_vm(s, extra_rom, {MemoryRegion::ROM, MemoryRegion::ROM_P2});
|
||||
}
|
||||
|
||||
/* If we need to execute a command */
|
||||
if (!execute_arg.empty()) {
|
||||
if (has_idled)
|
||||
exit(0);
|
||||
else
|
||||
lex_repl(execute_arg);
|
||||
} else {
|
||||
while(sigsetjmp(sigint_buf, 1) != 0);
|
||||
/* If we need to execute a command */
|
||||
if(!execute_arg.empty()) {
|
||||
if(has_idled)
|
||||
exit(0);
|
||||
else
|
||||
lex_repl(execute_arg);
|
||||
}
|
||||
else {
|
||||
while(sigsetjmp(sigint_buf, 1) != 0)
|
||||
;
|
||||
|
||||
bool leave = false;
|
||||
std::string cmdline = read_interactive(s, leave);
|
||||
if(leave) break;
|
||||
lex_repl(cmdline);
|
||||
}
|
||||
bool leave = false;
|
||||
std::string cmdline = read_interactive(s, leave);
|
||||
if(leave)
|
||||
break;
|
||||
lex_repl(cmdline);
|
||||
}
|
||||
|
||||
/* We have already handled FILE and --execute, don't do it again */
|
||||
has_idled = true;
|
||||
}
|
||||
/* We have already handled FILE and --execute, don't do it again */
|
||||
has_idled = true;
|
||||
}
|
||||
|
||||
Parser parser(false);
|
||||
Parser parser(false);
|
||||
|
||||
try {
|
||||
/* Read the next command from the lexer */
|
||||
parser.start();
|
||||
parser.skip_separators();
|
||||
try {
|
||||
/* Read the next command from the lexer */
|
||||
parser.start();
|
||||
parser.skip_separators();
|
||||
|
||||
/* Read the command name */
|
||||
if(parser.lookahead().type == T::END)
|
||||
continue;
|
||||
if(parser.lookahead().type == '?') {
|
||||
for(auto const &it: commands) {
|
||||
fmt::print(" {:5s} {}\n", it.first,
|
||||
it.second->short_description);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
std::string cmd = parser.symbol("command");
|
||||
/* Read the command name */
|
||||
if(parser.lookahead().type == T::END)
|
||||
continue;
|
||||
if(parser.lookahead().type == '?') {
|
||||
for(auto const &it: commands) {
|
||||
fmt::print(
|
||||
" {:5s} {}\n", it.first, it.second->short_description);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
std::string cmd = parser.symbol("command");
|
||||
|
||||
if(cmd == "q" && parser.lookahead().type != '?')
|
||||
break;
|
||||
if(cmd == "p") {
|
||||
parser.dump_command();
|
||||
}
|
||||
else if(commands.count(cmd)) {
|
||||
if(parser.lookahead().type == '?') {
|
||||
fmt::print("{}: {}\n\n{}\n", cmd,
|
||||
commands[cmd]->short_description,
|
||||
commands[cmd]->long_description);
|
||||
continue;
|
||||
}
|
||||
if(commands[cmd]->function)
|
||||
commands[cmd]->function(s, parser);
|
||||
}
|
||||
else {
|
||||
FxOS_log(ERR, "unknown command '%s'", cmd);
|
||||
}
|
||||
}
|
||||
catch(std::exception &e) {
|
||||
FxOS_log(ERR, "%s", e.what());
|
||||
}
|
||||
if(cmd == "q" && parser.lookahead().type != '?')
|
||||
break;
|
||||
if(cmd == "p") {
|
||||
parser.dump_command();
|
||||
}
|
||||
else if(commands.count(cmd)) {
|
||||
if(parser.lookahead().type == '?') {
|
||||
fmt::print("{}: {}\n\n{}\n", cmd,
|
||||
commands[cmd]->short_description,
|
||||
commands[cmd]->long_description);
|
||||
continue;
|
||||
}
|
||||
if(commands[cmd]->function)
|
||||
commands[cmd]->function(s, parser);
|
||||
}
|
||||
else {
|
||||
FxOS_log(ERR, "unknown command '%s'", cmd);
|
||||
}
|
||||
}
|
||||
catch(std::exception &e) {
|
||||
FxOS_log(ERR, "%s", e.what());
|
||||
}
|
||||
|
||||
/* Exhaust command input (if not all used by the command) */
|
||||
parser.exhaust_until_separator();
|
||||
}
|
||||
/* Exhaust command input (if not all used by the command) */
|
||||
parser.exhaust_until_separator();
|
||||
}
|
||||
|
||||
/* Save command history */
|
||||
if(histfile[0]) write_history(histfile);
|
||||
/* Save command history */
|
||||
if(histfile[0])
|
||||
write_history(histfile);
|
||||
|
||||
return 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Register a no-op quit command for auto-completion */
|
||||
static ShellCommand _q_cmd("q", NULL, NULL, "Quit", "Quits fxos.");
|
||||
|
||||
/* Register the include command */
|
||||
static ShellCommand _dot_cmd(".",
|
||||
[](Session &s, Parser &p) { _dot(s, parse_dot(s, p), false); },
|
||||
[](Session &s, Parser &p) { parse_dot(s, p); },
|
||||
"Include scripts", R"(
|
||||
. "<script_1>" "<script_2>"...
|
||||
|
||||
Reads file paths from its string arguments, and executes each of them as a
|
||||
sequence of commands in the order of the command line.
|
||||
)");
|
||||
|
|
474
shell/parser.cpp
474
shell/parser.cpp
|
@ -9,62 +9,62 @@
|
|||
|
||||
std::string T::str() const
|
||||
{
|
||||
switch((int)m_name) {
|
||||
case T::END:
|
||||
return "end of file";
|
||||
case T::SPC:
|
||||
return "whitespace";
|
||||
case T::SEPARATOR:
|
||||
return "end of command";
|
||||
case T::NUM:
|
||||
return "number";
|
||||
case T::SYMBOL:
|
||||
return "symbol";
|
||||
case T::SYSCALL:
|
||||
return "syscall number";
|
||||
case T::OPTION:
|
||||
return "command option";
|
||||
case T::STRING:
|
||||
return "string";
|
||||
case '.':
|
||||
return "'..'";
|
||||
case '<':
|
||||
return "'<<'";
|
||||
case '>':
|
||||
return "'>>'";
|
||||
default:
|
||||
return fmt::format("'{}'", (char)m_name);
|
||||
}
|
||||
switch((int)m_name) {
|
||||
case T::END:
|
||||
return "end of file";
|
||||
case T::SPC:
|
||||
return "whitespace";
|
||||
case T::SEPARATOR:
|
||||
return "end of command";
|
||||
case T::NUM:
|
||||
return "number";
|
||||
case T::SYMBOL:
|
||||
return "symbol";
|
||||
case T::SYSCALL:
|
||||
return "syscall number";
|
||||
case T::OPTION:
|
||||
return "command option";
|
||||
case T::STRING:
|
||||
return "string";
|
||||
case '.':
|
||||
return "'..'";
|
||||
case '<':
|
||||
return "'<<'";
|
||||
case '>':
|
||||
return "'>>'";
|
||||
default:
|
||||
return fmt::format("'{}'", (char)m_name);
|
||||
}
|
||||
}
|
||||
|
||||
std::string Token::str() const
|
||||
{
|
||||
switch((int)this->type) {
|
||||
case T::END:
|
||||
return "end of file";
|
||||
case T::SPC:
|
||||
return "whitespace";
|
||||
case T::SEPARATOR:
|
||||
return "end of command";
|
||||
case T::NUM:
|
||||
return fmt::format("number {}", this->value.NUM);
|
||||
case T::SYMBOL:
|
||||
return fmt::format("symbol '{}'", this->value.STRING);
|
||||
case T::SYSCALL:
|
||||
return fmt::format("syscall number %{:03x}", this->value.NUM);
|
||||
case T::OPTION:
|
||||
return fmt::format("command option '{}'", this->value.STRING);
|
||||
case T::STRING:
|
||||
return fmt::format("string '{}'", this->value.STRING);
|
||||
case '.':
|
||||
return "'..'";
|
||||
case '<':
|
||||
return "'<<'";
|
||||
case '>':
|
||||
return "'>>'";
|
||||
default:
|
||||
return fmt::format("'{}'", (char)this->type);
|
||||
}
|
||||
switch((int)this->type) {
|
||||
case T::END:
|
||||
return "end of file";
|
||||
case T::SPC:
|
||||
return "whitespace";
|
||||
case T::SEPARATOR:
|
||||
return "end of command";
|
||||
case T::NUM:
|
||||
return fmt::format("number {}", this->value.NUM);
|
||||
case T::SYMBOL:
|
||||
return fmt::format("symbol '{}'", this->value.STRING);
|
||||
case T::SYSCALL:
|
||||
return fmt::format("syscall number %{:03x}", this->value.NUM);
|
||||
case T::OPTION:
|
||||
return fmt::format("command option '{}'", this->value.STRING);
|
||||
case T::STRING:
|
||||
return fmt::format("string '{}'", this->value.STRING);
|
||||
case '.':
|
||||
return "'..'";
|
||||
case '<':
|
||||
return "'<<'";
|
||||
case '>':
|
||||
return "'>>'";
|
||||
default:
|
||||
return fmt::format("'{}'", (char)this->type);
|
||||
}
|
||||
}
|
||||
|
||||
//---
|
||||
|
@ -72,84 +72,88 @@ std::string Token::str() const
|
|||
//---
|
||||
|
||||
Parser::Parser(bool complete):
|
||||
m_complete {complete}, m_la {}, m_expr_space {nullptr}
|
||||
m_complete {complete}, m_la {}, m_expr_space {nullptr}
|
||||
{
|
||||
}
|
||||
|
||||
void Parser::start()
|
||||
{
|
||||
feed();
|
||||
feed();
|
||||
}
|
||||
|
||||
Token Parser::feed(bool ignore_spaces)
|
||||
{
|
||||
Token t = m_la;
|
||||
Token t = m_la;
|
||||
|
||||
do m_la = lex_read();
|
||||
while(ignore_spaces && m_la.type == T::SPC);
|
||||
do
|
||||
m_la = lex_read();
|
||||
while(ignore_spaces && m_la.type == T::SPC);
|
||||
|
||||
return t;
|
||||
return t;
|
||||
}
|
||||
|
||||
Token Parser::lookahead() const
|
||||
{
|
||||
return m_la;
|
||||
return m_la;
|
||||
}
|
||||
|
||||
bool Parser::at_end() const
|
||||
{
|
||||
/* When parsing to complete we try to go infinitely far, so we ignore
|
||||
/* When parsing to complete we try to go infinitely far, so we ignore
|
||||
T::END. We supply T::SEPARATOR to complete the compound commands */
|
||||
if(m_complete) return (m_la.type == T::SEPARATOR);
|
||||
return (m_la.type == T::SEPARATOR || m_la.type == T::END);
|
||||
if(m_complete)
|
||||
return (m_la.type == T::SEPARATOR);
|
||||
return (m_la.type == T::SEPARATOR || m_la.type == T::END);
|
||||
}
|
||||
|
||||
void Parser::end()
|
||||
{
|
||||
m_options.clear();
|
||||
if(!at_end())
|
||||
throw SyntaxError("expected end of command");
|
||||
m_options.clear();
|
||||
if(!at_end())
|
||||
throw SyntaxError("expected end of command");
|
||||
}
|
||||
|
||||
void Parser::skip_separators()
|
||||
{
|
||||
while(m_la.type == T::SEPARATOR) feed();
|
||||
while(m_la.type == T::SEPARATOR)
|
||||
feed();
|
||||
}
|
||||
|
||||
void Parser::exhaust_until_separator()
|
||||
{
|
||||
while(!at_end()) {
|
||||
try {
|
||||
Token t = feed();
|
||||
}
|
||||
catch(SyntaxError const &e) {}
|
||||
}
|
||||
while(!at_end()) {
|
||||
try {
|
||||
Token t = feed();
|
||||
}
|
||||
catch(SyntaxError const &e) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Parser::dump_command()
|
||||
{
|
||||
while(!at_end()) {
|
||||
Token t = m_la;
|
||||
while(!at_end()) {
|
||||
Token t = m_la;
|
||||
|
||||
if(t.type == T::NUM)
|
||||
fmt::print("NUM {:#x}\n", t.value.NUM);
|
||||
else if(t.type == T::SYSCALL)
|
||||
fmt::print("SYSCALL %{:03x}\n", t.value.NUM);
|
||||
else if(t.type == T::SYMBOL)
|
||||
fmt::print("SYMBOL '{}'\n", t.value.STRING);
|
||||
else if(t.type == T::OPTION)
|
||||
fmt::print("OPTION '{}'\n", t.value.STRING);
|
||||
else if(t.type == T::STRING)
|
||||
fmt::print("STRING '{}'\n", t.value.STRING);
|
||||
else if(t.type == '>')
|
||||
fmt::print(">>\n");
|
||||
else if(t.type == '<')
|
||||
fmt::print("<<\n");
|
||||
else
|
||||
fmt::print("{}\n", (char)t.type);
|
||||
if(t.type == T::NUM)
|
||||
fmt::print("NUM {:#x}\n", t.value.NUM);
|
||||
else if(t.type == T::SYSCALL)
|
||||
fmt::print("SYSCALL %{:03x}\n", t.value.NUM);
|
||||
else if(t.type == T::SYMBOL)
|
||||
fmt::print("SYMBOL '{}'\n", t.value.STRING);
|
||||
else if(t.type == T::OPTION)
|
||||
fmt::print("OPTION '{}'\n", t.value.STRING);
|
||||
else if(t.type == T::STRING)
|
||||
fmt::print("STRING '{}'\n", t.value.STRING);
|
||||
else if(t.type == '>')
|
||||
fmt::print(">>\n");
|
||||
else if(t.type == '<')
|
||||
fmt::print("<<\n");
|
||||
else
|
||||
fmt::print("{}\n", (char)t.type);
|
||||
|
||||
feed();
|
||||
}
|
||||
feed();
|
||||
}
|
||||
}
|
||||
|
||||
//---
|
||||
|
@ -158,128 +162,132 @@ void Parser::dump_command()
|
|||
|
||||
void Parser::option(std::string name, OptionHandler callback)
|
||||
{
|
||||
m_options.emplace(name, callback);
|
||||
m_options.emplace(name, callback);
|
||||
}
|
||||
|
||||
Token Parser::expect(std::initializer_list<T> types, bool ignore_spaces)
|
||||
{
|
||||
bool correct_type = false;
|
||||
for(T type: types) {
|
||||
if(m_la.type == type) correct_type = true;
|
||||
}
|
||||
bool correct_type = false;
|
||||
for(T type: types) {
|
||||
if(m_la.type == type)
|
||||
correct_type = true;
|
||||
}
|
||||
|
||||
if(!correct_type) {
|
||||
static char err[128];
|
||||
int offset = sprintf(err, "expected ");
|
||||
if(!correct_type) {
|
||||
static char err[128];
|
||||
int offset = sprintf(err, "expected ");
|
||||
|
||||
for(auto it = types.begin(); it != types.end(); it++) {
|
||||
offset += sprintf(err + offset, "%s%s%s",
|
||||
(it != types.begin() && it + 1 == types.end() ? "or " : ""),
|
||||
(*it).str().c_str(),
|
||||
(it + 1 == types.end() ? "; " : ", "));
|
||||
}
|
||||
for(auto it = types.begin(); it != types.end(); it++) {
|
||||
offset += sprintf(err + offset, "%s%s%s",
|
||||
(it != types.begin() && it + 1 == types.end() ? "or " : ""),
|
||||
(*it).str().c_str(), (it + 1 == types.end() ? "; " : ", "));
|
||||
}
|
||||
|
||||
sprintf(err + offset, "instead found %s", m_la.str().c_str());
|
||||
throw SyntaxError(err);
|
||||
}
|
||||
sprintf(err + offset, "instead found %s", m_la.str().c_str());
|
||||
throw SyntaxError(err);
|
||||
}
|
||||
|
||||
Token t = feed(ignore_spaces);
|
||||
Token t = feed(ignore_spaces);
|
||||
|
||||
return t;
|
||||
return t;
|
||||
}
|
||||
|
||||
Token Parser::expect(T type, bool ignore_spaces)
|
||||
{
|
||||
return expect({ type }, ignore_spaces);
|
||||
return expect({type}, ignore_spaces);
|
||||
}
|
||||
|
||||
std::string Parser::symbol(std::string category)
|
||||
{
|
||||
/* Auto-complete a symbol which has not been typed yet */
|
||||
if(m_complete && m_la.type == T::END)
|
||||
throw CompletionRequest(category, "");
|
||||
/* Auto-complete a symbol which has not been typed yet */
|
||||
if(m_complete && m_la.type == T::END)
|
||||
throw CompletionRequest(category, "");
|
||||
|
||||
if(!m_complete)
|
||||
return expect(T::SYMBOL).value.STRING;
|
||||
if(!m_complete)
|
||||
return expect(T::SYMBOL).value.STRING;
|
||||
|
||||
/* When completing, we have to know whether the symbol is finished (ie.
|
||||
/* When completing, we have to know whether the symbol is finished (ie.
|
||||
there is another token after, including a space) or not */
|
||||
Token t = expect(T::SYMBOL, false);
|
||||
std::string sym = t.value.STRING;
|
||||
Token t = expect(T::SYMBOL, false);
|
||||
std::string sym = t.value.STRING;
|
||||
|
||||
/* This will throw only if there is no token after, not even spaces */
|
||||
if(m_complete && m_la.type == T::END)
|
||||
throw CompletionRequest(category, sym);
|
||||
/* This will throw only if there is no token after, not even spaces */
|
||||
if(m_complete && m_la.type == T::END)
|
||||
throw CompletionRequest(category, sym);
|
||||
|
||||
/* If a space is found, get rid of it */
|
||||
if(m_la.type == T::SPC) feed();
|
||||
/* If a space is found, get rid of it */
|
||||
if(m_la.type == T::SPC)
|
||||
feed();
|
||||
|
||||
return sym;
|
||||
return sym;
|
||||
}
|
||||
|
||||
std::string Parser::str()
|
||||
{
|
||||
Token t = expect(T::STRING);
|
||||
std::string str = t.value.STRING;
|
||||
return str;
|
||||
Token t = expect(T::STRING);
|
||||
std::string str = t.value.STRING;
|
||||
return str;
|
||||
}
|
||||
|
||||
long Parser::num()
|
||||
{
|
||||
return expect(T::NUM).value.NUM;
|
||||
return expect(T::NUM).value.NUM;
|
||||
}
|
||||
|
||||
Range Parser::range(VirtualSpace *space, long before, long after)
|
||||
{
|
||||
long start = expr(space);
|
||||
long start = expr(space);
|
||||
|
||||
/* Accept non-rangs if (before) and (after) are provided */
|
||||
if(m_la.type != ':' && m_la.type != '.' && before >= 0 && after >= 0)
|
||||
return { start - before, start + after };
|
||||
/* Accept non-rangs if (before) and (after) are provided */
|
||||
if(m_la.type != ':' && m_la.type != '.' && before >= 0 && after >= 0)
|
||||
return {start - before, start + after};
|
||||
|
||||
Token t = expect({ ':', '.' });
|
||||
long other = expr(space);
|
||||
Token t = expect({':', '.'});
|
||||
long other = expr(space);
|
||||
|
||||
Range r = { start, (t.type == ':' ? start + other : other) };
|
||||
if(r.start > r.end) std::swap(r.start, r.end);
|
||||
return r;
|
||||
Range r = {start, (t.type == ':' ? start + other : other)};
|
||||
if(r.start > r.end)
|
||||
std::swap(r.start, r.end);
|
||||
return r;
|
||||
}
|
||||
|
||||
FxOS::MemoryRegion Parser::region(VirtualSpace *space, long before, long after)
|
||||
{
|
||||
if(m_la.type == '$' || m_la.type == '(' || m_la.type == '-'
|
||||
|| m_la.type == T::NUM || m_la.type == T::SYSCALL) {
|
||||
Range r = range(space, before, after);
|
||||
return FxOS::MemoryRegion("<anonymous>", r.start, r.end-1, false);
|
||||
}
|
||||
if(m_la.type == '$' || m_la.type == '(' || m_la.type == '-'
|
||||
|| m_la.type == T::NUM || m_la.type == T::SYSCALL) {
|
||||
Range r = range(space, before, after);
|
||||
return FxOS::MemoryRegion("<anonymous>", r.start, r.end - 1, false);
|
||||
}
|
||||
|
||||
/* Return symbol by default so that an empty input autocompletes to a
|
||||
/* Return symbol by default so that an empty input autocompletes to a
|
||||
memory region name */
|
||||
try {
|
||||
return FxOS::MemoryRegion(symbol("memory_region"));
|
||||
}
|
||||
catch(std::invalid_argument const &e) {
|
||||
/* Ignore nonexisting regions when autocompleting */
|
||||
if(m_complete) return FxOS::MemoryRegion("<anonymous>", 0, 1, false);
|
||||
else throw e;
|
||||
}
|
||||
try {
|
||||
return FxOS::MemoryRegion(symbol("memory_region"));
|
||||
}
|
||||
catch(std::invalid_argument const &e) {
|
||||
/* Ignore nonexisting regions when autocompleting */
|
||||
if(m_complete)
|
||||
return FxOS::MemoryRegion("<anonymous>", 0, 1, false);
|
||||
else
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
void Parser::accept_options()
|
||||
{
|
||||
while(m_la.type == T::OPTION) {
|
||||
Token t = expect(T::OPTION);
|
||||
while(m_la.type == T::OPTION) {
|
||||
Token t = expect(T::OPTION);
|
||||
|
||||
std::string opt = t.value.STRING;
|
||||
std::string name = opt.substr(0, opt.find('='));
|
||||
std::string opt = t.value.STRING;
|
||||
std::string name = opt.substr(0, opt.find('='));
|
||||
|
||||
if(!m_options.count(name)) {
|
||||
throw CommandError("unrecognized option {}", name);
|
||||
}
|
||||
if(!m_options.count(name)) {
|
||||
throw CommandError("unrecognized option {}", name);
|
||||
}
|
||||
|
||||
std::string value = opt.substr(opt.find('=')+1);
|
||||
m_options[name](value);
|
||||
}
|
||||
std::string value = opt.substr(opt.find('=') + 1);
|
||||
m_options[name](value);
|
||||
}
|
||||
}
|
||||
|
||||
//---
|
||||
|
@ -288,93 +296,95 @@ void Parser::accept_options()
|
|||
|
||||
long Parser::atom()
|
||||
{
|
||||
Token t = expect({ '$', '(', '-', T::SYMBOL, T::NUM, T::SYSCALL });
|
||||
Token t = expect({'$', '(', '-', T::SYMBOL, T::NUM, T::SYSCALL});
|
||||
|
||||
if(t.type == T::SYMBOL) {
|
||||
/* TODO: Specify the space that symbols are taken from */
|
||||
if(m_complete && m_la.type == T::END)
|
||||
throw CompletionRequest("symbol", t.value.STRING, m_expr_space);
|
||||
if(t.type == T::SYMBOL) {
|
||||
/* TODO: Specify the space that symbols are taken from */
|
||||
if(m_complete && m_la.type == T::END)
|
||||
throw CompletionRequest("symbol", t.value.STRING, m_expr_space);
|
||||
|
||||
long val = 0;
|
||||
if(m_expr_space) {
|
||||
auto const &opt = m_expr_space->symbols.lookup(t.value.STRING);
|
||||
if(opt && opt->type == FxOS::Symbol::Address) {
|
||||
val = opt->value;
|
||||
}
|
||||
else if(opt && opt->type == FxOS::Symbol::Syscall) {
|
||||
OS *os = m_expr_space->os_analysis();
|
||||
if(os && (int)opt->value < os->syscall_count())
|
||||
val = os->syscall(opt->value);
|
||||
}
|
||||
else {
|
||||
throw CommandError("symbol '{}' is undefined", t.value.STRING);
|
||||
}
|
||||
}
|
||||
else throw CommandError("cannot query symbol '{}', no virtual space",
|
||||
t.value.STRING);
|
||||
return val;
|
||||
}
|
||||
else if(t.type == T::SYSCALL) {
|
||||
if(!m_expr_space) return 0;
|
||||
OS *os = m_expr_space->os_analysis();
|
||||
if(!os || t.value.NUM < 0 || t.value.NUM > os->syscall_count())
|
||||
return 0;
|
||||
return os->syscall(t.value.NUM);
|
||||
}
|
||||
else if(t.type == '$') {
|
||||
return (m_expr_space ? m_expr_space->cursor : 0);
|
||||
}
|
||||
else if(t.type == '-') {
|
||||
return -atom();
|
||||
}
|
||||
else if(t.type == T::NUM) {
|
||||
return t.value.NUM;
|
||||
}
|
||||
else {
|
||||
long v = term();
|
||||
expect(')');
|
||||
return v;
|
||||
}
|
||||
long val = 0;
|
||||
if(m_expr_space) {
|
||||
auto const &opt = m_expr_space->symbols.lookup(t.value.STRING);
|
||||
if(opt && opt->type == FxOS::Symbol::Address) {
|
||||
val = opt->value;
|
||||
}
|
||||
else if(opt && opt->type == FxOS::Symbol::Syscall) {
|
||||
OS *os = m_expr_space->os_analysis();
|
||||
if(os && (int)opt->value < os->syscall_count())
|
||||
val = os->syscall(opt->value);
|
||||
}
|
||||
else {
|
||||
throw CommandError("symbol '{}' is undefined", t.value.STRING);
|
||||
}
|
||||
}
|
||||
else
|
||||
throw CommandError(
|
||||
"cannot query symbol '{}', no virtual space", t.value.STRING);
|
||||
return val;
|
||||
}
|
||||
else if(t.type == T::SYSCALL) {
|
||||
if(!m_expr_space)
|
||||
return 0;
|
||||
OS *os = m_expr_space->os_analysis();
|
||||
if(!os || t.value.NUM < 0 || t.value.NUM > os->syscall_count())
|
||||
return 0;
|
||||
return os->syscall(t.value.NUM);
|
||||
}
|
||||
else if(t.type == '$') {
|
||||
return (m_expr_space ? m_expr_space->cursor : 0);
|
||||
}
|
||||
else if(t.type == '-') {
|
||||
return -atom();
|
||||
}
|
||||
else if(t.type == T::NUM) {
|
||||
return t.value.NUM;
|
||||
}
|
||||
else {
|
||||
long v = term();
|
||||
expect(')');
|
||||
return v;
|
||||
}
|
||||
}
|
||||
|
||||
long Parser::factor()
|
||||
{
|
||||
long v = atom();
|
||||
long v = atom();
|
||||
|
||||
while(m_la.type == '*' || m_la.type == '/' || m_la.type == '%') {
|
||||
int op = expect({ '*', '/', '%' }).type;
|
||||
while(m_la.type == '*' || m_la.type == '/' || m_la.type == '%') {
|
||||
int op = expect({'*', '/', '%'}).type;
|
||||
|
||||
if(op == '*')
|
||||
v *= atom();
|
||||
else if(op == '/')
|
||||
v /= atom();
|
||||
else if(op == '%')
|
||||
v %= atom();
|
||||
}
|
||||
if(op == '*')
|
||||
v *= atom();
|
||||
else if(op == '/')
|
||||
v /= atom();
|
||||
else if(op == '%')
|
||||
v %= atom();
|
||||
}
|
||||
|
||||
return v;
|
||||
return v;
|
||||
}
|
||||
|
||||
long Parser::term()
|
||||
{
|
||||
long v = factor();
|
||||
long v = factor();
|
||||
|
||||
while(m_la.type == '+' || m_la.type == '-') {
|
||||
int op = expect({ '+', '-' }).type;
|
||||
while(m_la.type == '+' || m_la.type == '-') {
|
||||
int op = expect({'+', '-'}).type;
|
||||
|
||||
if(op == '+')
|
||||
v += factor();
|
||||
else if(op == '-')
|
||||
v -= factor();
|
||||
}
|
||||
if(op == '+')
|
||||
v += factor();
|
||||
else if(op == '-')
|
||||
v -= factor();
|
||||
}
|
||||
|
||||
return v;
|
||||
return v;
|
||||
}
|
||||
|
||||
long Parser::expr(VirtualSpace *space)
|
||||
{
|
||||
m_expr_space = space;
|
||||
long val = atom();
|
||||
m_expr_space = nullptr;
|
||||
return val;
|
||||
m_expr_space = space;
|
||||
long val = atom();
|
||||
m_expr_space = nullptr;
|
||||
return val;
|
||||
}
|
||||
|
|
325
shell/s.cpp
325
shell/s.cpp
|
@ -8,107 +8,284 @@
|
|||
#include <fmt/core.h>
|
||||
|
||||
//---
|
||||
// sl
|
||||
// sh
|
||||
//---
|
||||
|
||||
static void parse_sl(Session &, Parser &parser)
|
||||
static bool matches(
|
||||
char const *data, char const *reference, char const *pattern, size_t size)
|
||||
{
|
||||
parser.end();
|
||||
for(size_t i = 0; i < size; i++) {
|
||||
if(pattern[i] && data[i] != reference[i])
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void _sl(Session &session)
|
||||
static int hexa(int c)
|
||||
{
|
||||
if(!session.current_space)
|
||||
return;
|
||||
if(c >= '0' && c <= '9')
|
||||
return c - '0';
|
||||
return (c | 0x20) - 'a' + 10;
|
||||
}
|
||||
|
||||
for(auto const &s: session.current_space->symbols.symbols) {
|
||||
if(s.type == FxOS::Symbol::Syscall && s.value < 0x1000) {
|
||||
fmt::print(theme(10), " %{:03x}", s.value);
|
||||
}
|
||||
else if(s.type == FxOS::Symbol::Syscall) {
|
||||
fmt::print(theme(10), " %{:04x}", s.value);
|
||||
}
|
||||
else {
|
||||
fmt::print(" 0x{:08x}", s.value);
|
||||
}
|
||||
struct _sh_args
|
||||
{
|
||||
/* String to find */
|
||||
std::string reference;
|
||||
/* Bytes to match within reference (not all are required) */
|
||||
std::string pattern;
|
||||
/* Size of search */
|
||||
size_t size;
|
||||
/* Regions to search in, may be empty */
|
||||
std::vector<MemoryRegion> regions;
|
||||
/* Distance to show hexump around a match */
|
||||
int distance;
|
||||
/* Required alignment for matches to be used */
|
||||
int align;
|
||||
};
|
||||
|
||||
fmt::print(" {}\n", s.name);
|
||||
}
|
||||
static _sh_args parse_sh(Session &session, Parser &parser)
|
||||
{
|
||||
_sh_args args;
|
||||
args.distance = -1;
|
||||
args.align = -1;
|
||||
|
||||
parser.option("align", [&args](std::string const &value) {
|
||||
args.align = atoi(value.c_str());
|
||||
});
|
||||
parser.option("distance", [&args](std::string const &value) {
|
||||
args.distance = atoi(value.c_str());
|
||||
});
|
||||
|
||||
parser.accept_options();
|
||||
std::string needle = parser.str();
|
||||
|
||||
/* Check the structure of the needle */
|
||||
|
||||
if(needle.size() == 0 || needle.size() % 2 != 0)
|
||||
throw CommandError(
|
||||
"search pattern '{}' should be of even non-zero "
|
||||
"size",
|
||||
needle);
|
||||
|
||||
size_t bad_index = needle.find_first_not_of("0123456789abcdefABCDEF.");
|
||||
if(bad_index != std::string::npos)
|
||||
throw CommandError(
|
||||
"invalid character '{}' in seach pattern", needle[bad_index]);
|
||||
|
||||
for(size_t i = 0; i < needle.size(); i += 2) {
|
||||
char c1 = needle[i], c2 = needle[i + 1];
|
||||
if((c1 == '.') != (c2 == '.'))
|
||||
throw CommandError(
|
||||
"invalid search byte '{}{}', should be either "
|
||||
"'..' or fully specified",
|
||||
c1, c2);
|
||||
}
|
||||
|
||||
/* Convert into a reference/pattern form */
|
||||
|
||||
args.size = needle.size() / 2;
|
||||
args.reference.reserve(args.size);
|
||||
args.pattern.reserve(args.size);
|
||||
|
||||
for(size_t i = 0; i < args.size; i++) {
|
||||
char c1 = needle[2 * i], c2 = needle[2 * i + 1];
|
||||
if(c1 == '.') {
|
||||
args.reference[i] = 0;
|
||||
args.pattern[i] = 0;
|
||||
}
|
||||
else {
|
||||
args.reference[i] = (hexa(c1) << 4) | hexa(c2);
|
||||
args.pattern[i] = 1;
|
||||
}
|
||||
}
|
||||
|
||||
while(!parser.at_end()) {
|
||||
parser.accept_options();
|
||||
args.regions.push_back(parser.region(session.current_space));
|
||||
}
|
||||
|
||||
parser.accept_options();
|
||||
parser.end();
|
||||
|
||||
return args;
|
||||
}
|
||||
|
||||
void _sh(Session &session, char const *reference, char const *pattern,
|
||||
size_t size, int align, int distance, std::vector<MemoryRegion> ®ions)
|
||||
{
|
||||
if(!session.current_space)
|
||||
return;
|
||||
|
||||
/* Default values */
|
||||
if(distance < 0)
|
||||
distance = 32;
|
||||
if(align <= 0)
|
||||
align = 1;
|
||||
|
||||
VirtualSpace &v = *session.current_space;
|
||||
|
||||
/* If no region is specified, explore the regions for all bindings */
|
||||
if(regions.size() == 0) {
|
||||
for(auto &b: v.bindings)
|
||||
regions.push_back(b.region);
|
||||
}
|
||||
|
||||
int match_count = 0;
|
||||
bool output_started = false;
|
||||
|
||||
/* Matches are not shown right away because if they are close enough a
|
||||
single local hexdump will show several of them */
|
||||
std::vector<std::pair<uint32_t, int>> pending;
|
||||
|
||||
for(auto const &r: regions) {
|
||||
char const *data = v.translate(r.start, r.size());
|
||||
if(!data)
|
||||
throw CommandError(
|
||||
"region 0x{:08x} .. 0x{:08x} is not "
|
||||
"fully bound",
|
||||
r.start, r.end);
|
||||
|
||||
/* Reach the required alignemnt */
|
||||
int i = 0;
|
||||
while((r.start + i) % align != 0)
|
||||
i++;
|
||||
|
||||
/* Search patterns for (size) bytes inside (data) */
|
||||
for(; i <= (int)r.size() - (int)size; i += align) {
|
||||
if(!matches(data + i, reference, pattern, size))
|
||||
continue;
|
||||
|
||||
uint32_t start = r.start + i;
|
||||
|
||||
/* Flush pending matches if this new match is far away */
|
||||
if(pending.size() > 0) {
|
||||
auto const &p = pending[pending.size() - 1];
|
||||
if(p.first + p.second + distance < start) {
|
||||
Range r;
|
||||
r.start = pending[0].first - distance;
|
||||
r.end = p.first + p.second + distance - 1;
|
||||
|
||||
if(output_started)
|
||||
fmt::print("...\n");
|
||||
_h_hexdump(session, r, pending);
|
||||
output_started = true;
|
||||
pending.clear();
|
||||
}
|
||||
}
|
||||
|
||||
pending.emplace_back(start, size);
|
||||
match_count++;
|
||||
start += size;
|
||||
|
||||
if(match_count >= 128)
|
||||
break;
|
||||
}
|
||||
if(match_count >= 128)
|
||||
break;
|
||||
|
||||
/* Print the last pending elements */
|
||||
if(pending.size()) {
|
||||
auto const &p = pending[pending.size() - 1];
|
||||
Range r
|
||||
= {pending[0].first - distance, p.first + p.second + distance};
|
||||
if(output_started)
|
||||
fmt::print("...\n");
|
||||
_h_hexdump(session, r, pending);
|
||||
}
|
||||
pending.clear();
|
||||
}
|
||||
|
||||
if(match_count == 0)
|
||||
fmt::print("No occurrence found.\n");
|
||||
else if(match_count < 128)
|
||||
fmt::print("{} occurences found.\n", match_count);
|
||||
else
|
||||
fmt::print("Stopped after 128 occurrences.\n");
|
||||
}
|
||||
|
||||
//---
|
||||
// sa
|
||||
// s4
|
||||
//---
|
||||
|
||||
static FxOS::Symbol parse_sa(Session &session, Parser &parser)
|
||||
struct _s4_args
|
||||
{
|
||||
FxOS::Symbol s;
|
||||
s.type = FxOS::Symbol::Address;
|
||||
s.value = parser.expr(session.current_space);
|
||||
s.name = parser.symbol();
|
||||
uint32_t value;
|
||||
std::vector<MemoryRegion> regions;
|
||||
};
|
||||
|
||||
parser.end();
|
||||
return s;
|
||||
}
|
||||
|
||||
void _sa(Session &session, Symbol s)
|
||||
static _s4_args parse_s4(Session &session, Parser &parser)
|
||||
{
|
||||
if(!session.current_space)
|
||||
return;
|
||||
session.current_space->symbols.add(s);
|
||||
_s4_args args;
|
||||
args.value = parser.expr(session.current_space);
|
||||
|
||||
while(!parser.at_end()) {
|
||||
args.regions.push_back(parser.region(session.current_space));
|
||||
}
|
||||
|
||||
parser.end();
|
||||
return args;
|
||||
}
|
||||
|
||||
//---
|
||||
// ss
|
||||
//---
|
||||
|
||||
static FxOS::Symbol parse_ss(Session &, Parser &parser)
|
||||
void _s4(Session &session, uint32_t value, std::vector<MemoryRegion> ®ions)
|
||||
{
|
||||
FxOS::Symbol s;
|
||||
s.type = FxOS::Symbol::Syscall;
|
||||
s.value = parser.expect({ T::SYSCALL }).value.NUM;
|
||||
s.name = parser.symbol();
|
||||
|
||||
parser.end();
|
||||
return s;
|
||||
}
|
||||
|
||||
void _ss(Session &session, Symbol s)
|
||||
{
|
||||
if(!session.current_space)
|
||||
return;
|
||||
session.current_space->symbols.add(s);
|
||||
uint32_t value_big_endian = htobe32(value);
|
||||
char pattern[4] = {1, 1, 1, 1};
|
||||
_sh(session, (char *)&value_big_endian, pattern, 4, 4, -1, regions);
|
||||
}
|
||||
|
||||
//---
|
||||
// Command registration
|
||||
//---
|
||||
|
||||
static ShellCommand _sl_cmd("sl",
|
||||
[](Session &s, Parser &p){ parse_sl(s, p); _sl(s); },
|
||||
[](Session &s, Parser &p){ parse_sl(s, p); },
|
||||
"Symbol List", R"(
|
||||
sl
|
||||
static ShellCommand _s4_cmd(
|
||||
"s4",
|
||||
[](Session &s, Parser &p) {
|
||||
auto args = parse_s4(s, p);
|
||||
_s4(s, args.value, args.regions);
|
||||
},
|
||||
[](Session &s, Parser &p) { parse_s4(s, p); }, "Search 4-aligned value", R"(
|
||||
s4 <value> [<regions>...]
|
||||
|
||||
Lists all symbols in the current virtual space.
|
||||
Searches mapped memory for a 4-aligned 32-bit value. If regions are specified
|
||||
(by name or the <start>:<size> syntax), searches these regions. Otherwise,
|
||||
searches every binding in the current virtual space.
|
||||
|
||||
s4 0xb4000000 ROM 0x88000000:512
|
||||
Searches occurrences of 0xb4000000, 4-aligned, within mapped ROM and within
|
||||
the first 512 bytes of RAM.
|
||||
|
||||
s4 0x43415349
|
||||
Seacrhes for the 4-aligned string "CASI" in all currently bound regions.
|
||||
)");
|
||||
|
||||
static ShellCommand _sa_cmd("sa",
|
||||
[](Session &s, Parser &p){ _sa(s, parse_sa(s, p)); },
|
||||
[](Session &s, Parser &p){ parse_sa(s, p); },
|
||||
"Symbol at Address", R"(
|
||||
sa <address> <symbol>
|
||||
static ShellCommand _sh_cmd(
|
||||
"sh",
|
||||
[](Session &s, Parser &p) {
|
||||
auto args = parse_sh(s, p);
|
||||
_sh(s, args.reference.c_str(), args.pattern.c_str(), args.size,
|
||||
args.align, args.distance, args.regions);
|
||||
},
|
||||
[](Session &s, Parser &p) { parse_sh(s, p); }, "Search Hexadecimal pattern",
|
||||
R"(
|
||||
sh [align=<value>] "<pattern>" [<regions>...]
|
||||
|
||||
Defines a new symbol at the specified address in the current virtual space.
|
||||
)");
|
||||
|
||||
static ShellCommand _ss_cmd("ss",
|
||||
[](Session &s, Parser &p){ _ss(s, parse_ss(s, p)); },
|
||||
[](Session &s, Parser &p){ parse_ss(s, p); },
|
||||
"Symbol at Syscall", R"(
|
||||
ss %<hex> <symbol>
|
||||
|
||||
Defines a new symbol at the specified syscall number in the current virtual
|
||||
space. The address is not resolved; the same syscall symbol can be used accross
|
||||
multiple OS versions regardless of where the functions are actually located.
|
||||
Searches mapped memory for a hexadecimal pattern with hole bytes. The pattern
|
||||
should be a hexadecimal string like "0123..6789..cd", consisting either of
|
||||
explicit hexadecimal values or hole bytes marked by "..". The holes must start
|
||||
and end at byte boundaries.
|
||||
|
||||
An occurrence of the pattern is any sequence of bytes that matches the explicit
|
||||
bytes exactly, with no constraint on the hole bytes (ie. any value matches).
|
||||
|
||||
If regions are specified, searches these regions. Otherwise, searches every
|
||||
binding in the current virtual space. If align=<align> is specified, only
|
||||
considers addresses that are multiples of the specified alignment value.
|
||||
|
||||
sh "434153494f......00" ROM
|
||||
Searches strings ending with "CASIO" followed by up to 3 characters in ROM.
|
||||
|
||||
sh align=4 "b4..0000"
|
||||
Searches 4-aligned values close to the display interface 0xb4000000 within
|
||||
all currently bound regions.
|
||||
)");
|
||||
|
|
|
@ -3,59 +3,59 @@
|
|||
#include "errors.h"
|
||||
#include <fmt/core.h>
|
||||
|
||||
Session::Session():
|
||||
spaces {}
|
||||
Session::Session(): spaces {}
|
||||
{
|
||||
this->current_space = nullptr;
|
||||
this->pc = -1;
|
||||
this->current_space = nullptr;
|
||||
this->pc = -1;
|
||||
}
|
||||
|
||||
VirtualSpace *Session::get_space(std::string name)
|
||||
{
|
||||
auto const &it = this->spaces.find(name);
|
||||
return it == this->spaces.end() ? nullptr : it->second.get();
|
||||
auto const &it = this->spaces.find(name);
|
||||
return it == this->spaces.end() ? nullptr : it->second.get();
|
||||
}
|
||||
|
||||
std::string Session::generate_space_name(std::string prefix, bool force_suffix)
|
||||
{
|
||||
if(!force_suffix && this->spaces.count(prefix) == 0)
|
||||
return prefix;
|
||||
if(!force_suffix && this->spaces.count(prefix) == 0)
|
||||
return prefix;
|
||||
|
||||
int counter = 0;
|
||||
int counter = 0;
|
||||
|
||||
while(1) {
|
||||
std::string name = fmt::format("{}_{}", prefix, counter);
|
||||
if(!this->spaces.count(name))
|
||||
return name;
|
||||
counter++;
|
||||
}
|
||||
while(1) {
|
||||
std::string name = fmt::format("{}_{}", prefix, counter);
|
||||
if(!this->spaces.count(name))
|
||||
return name;
|
||||
counter++;
|
||||
}
|
||||
}
|
||||
|
||||
fs::path Session::file(std::string name)
|
||||
{
|
||||
#define err(...) std::runtime_error(fmt::format(__VA_ARGS__))
|
||||
fs::path relative_to = lex_last_used_file();
|
||||
#define err(...) std::runtime_error(fmt::format(__VA_ARGS__))
|
||||
fs::path relative_to = lex_last_used_file();
|
||||
|
||||
if(name[0] != '/') {
|
||||
fs::path rel = relative_to.parent_path() / name;
|
||||
if(name[0] != '/') {
|
||||
fs::path rel = relative_to.parent_path() / name;
|
||||
|
||||
if(!fs::exists(rel))
|
||||
throw err("cannot find {} in current directory", name);
|
||||
if(fs::symlink_status(rel).type() != fs::file_type::regular)
|
||||
throw err("{} is neither a file nor a symlink to a file", name);
|
||||
return rel;
|
||||
}
|
||||
if(!fs::exists(rel))
|
||||
throw err("cannot find {} in current directory", name);
|
||||
if(fs::symlink_status(rel).type() != fs::file_type::regular)
|
||||
throw err("{} is neither a file nor a symlink to a file", name);
|
||||
return rel;
|
||||
}
|
||||
|
||||
fs::path filepath(name.substr(1));
|
||||
fs::path filepath(name.substr(1));
|
||||
|
||||
for(auto const &p: this->path) {
|
||||
if(!fs::exists(p / filepath)) continue;
|
||||
/* This file exists, it can be the only one selected */
|
||||
if(fs::symlink_status(p / filepath).type() != fs::file_type::regular)
|
||||
throw err("{} is neither a file nor a symlink to a file", name);
|
||||
return p / filepath;
|
||||
}
|
||||
throw err("cannot find {} in library", name);
|
||||
for(auto const &p: this->path) {
|
||||
if(!fs::exists(p / filepath))
|
||||
continue;
|
||||
/* This file exists, it can be the only one selected */
|
||||
if(fs::symlink_status(p / filepath).type() != fs::file_type::regular)
|
||||
throw err("{} is neither a file nor a symlink to a file", name);
|
||||
return p / filepath;
|
||||
}
|
||||
throw err("cannot find {} in library", name);
|
||||
|
||||
#undef err
|
||||
#undef err
|
||||
}
|
||||
|
|
|
@ -7,49 +7,66 @@ uint32_t base16[16];
|
|||
|
||||
bool theme_load(std::string filename)
|
||||
{
|
||||
FILE *fp = fopen(filename.c_str(), "r");
|
||||
if(!fp) return false;
|
||||
FILE *fp = fopen(filename.c_str(), "r");
|
||||
if(!fp)
|
||||
return false;
|
||||
|
||||
for(int i = 0; i < 16; i++) base16[i] = 0xffffff;
|
||||
for(int i = 0; i < 16; i++)
|
||||
base16[i] = 0xffffff;
|
||||
|
||||
char entry[64], value[256];
|
||||
int colors_found = 0;
|
||||
char entry[64], value[256];
|
||||
int colors_found = 0;
|
||||
|
||||
while(fscanf(fp, " %64[^:]: \"%64[^\"\n]\"", entry, value) == 2)
|
||||
{
|
||||
if(strlen(entry) == 6 && !strncmp(entry, "base0", 5))
|
||||
{
|
||||
int index = entry[5]-'0' - 7*(entry[5]>='A') - 32*(entry[5]>='a');
|
||||
colors_found++;
|
||||
sscanf(value, "%x", &base16[index]);
|
||||
}
|
||||
}
|
||||
while(fscanf(fp, " %64[^:]: \"%64[^\"\n]\"", entry, value) == 2) {
|
||||
if(strlen(entry) == 6 && !strncmp(entry, "base0", 5)) {
|
||||
int index = entry[5] - '0' - 7 * (entry[5] >= 'A')
|
||||
- 32 * (entry[5] >= 'a');
|
||||
colors_found++;
|
||||
sscanf(value, "%x", &base16[index]);
|
||||
}
|
||||
}
|
||||
|
||||
fclose(fp);
|
||||
return true;
|
||||
fclose(fp);
|
||||
return true;
|
||||
}
|
||||
|
||||
fmt::text_style theme(int color)
|
||||
{
|
||||
return fg(fmt::rgb(base16[color & 0xf]));
|
||||
return fg(fmt::rgb(base16[color & 0xf]));
|
||||
}
|
||||
|
||||
//---
|
||||
// Built-in themes
|
||||
//---
|
||||
|
||||
static std::map<std::string, std::array<uint32_t,16>> builtin_themes = {
|
||||
/* Tomorrow Night by Chris Kempson (http://chriskempson.com) */
|
||||
{ "tomorrow-night", {
|
||||
0x1d1f21, 0x282a2e, 0x373b41, 0x969896, 0xb4b7b4, 0xc5c8c6,
|
||||
0xe0e0e0, 0xffffff, 0xcc6666, 0xde935f, 0xf0c674, 0xb5bd68,
|
||||
0x8abeb7, 0x81a2be, 0xb294bb, 0xa3685a, }},
|
||||
static std::map<std::string, std::array<uint32_t, 16>> builtin_themes = {
|
||||
/* Tomorrow Night by Chris Kempson (http://chriskempson.com) */
|
||||
{"tomorrow-night",
|
||||
{
|
||||
0x1d1f21,
|
||||
0x282a2e,
|
||||
0x373b41,
|
||||
0x969896,
|
||||
0xb4b7b4,
|
||||
0xc5c8c6,
|
||||
0xe0e0e0,
|
||||
0xffffff,
|
||||
0xcc6666,
|
||||
0xde935f,
|
||||
0xf0c674,
|
||||
0xb5bd68,
|
||||
0x8abeb7,
|
||||
0x81a2be,
|
||||
0xb294bb,
|
||||
0xa3685a,
|
||||
}},
|
||||
};
|
||||
|
||||
bool theme_builtin(std::string name)
|
||||
{
|
||||
if(!builtin_themes.count(name)) return false;
|
||||
for(int i = 0; i < 16; i++)
|
||||
base16[i] = builtin_themes[name][i];
|
||||
return true;
|
||||
if(!builtin_themes.count(name))
|
||||
return false;
|
||||
for(int i = 0; i < 16; i++)
|
||||
base16[i] = builtin_themes[name][i];
|
||||
return true;
|
||||
}
|
||||
|
|
243
shell/v.cpp
243
shell/v.cpp
|
@ -17,63 +17,63 @@ using namespace FxOS;
|
|||
|
||||
static std::vector<std::string> parse_vl(Session &, Parser &parser)
|
||||
{
|
||||
std::vector<std::string> spaces;
|
||||
std::vector<std::string> spaces;
|
||||
|
||||
while(!parser.at_end())
|
||||
spaces.push_back(parser.symbol("vspace_name"));
|
||||
while(!parser.at_end())
|
||||
spaces.push_back(parser.symbol("vspace_name"));
|
||||
|
||||
parser.end();
|
||||
return spaces;
|
||||
parser.end();
|
||||
return spaces;
|
||||
}
|
||||
|
||||
static void show_vspace(std::string name, VirtualSpace &s, Session &session)
|
||||
{
|
||||
bool is_current = (&s == session.current_space);
|
||||
bool is_current = (&s == session.current_space);
|
||||
|
||||
int total_claim_size = 0;
|
||||
for(Claim const &c: s.disasm.claims)
|
||||
total_claim_size += c.size;
|
||||
int total_claim_size = 0;
|
||||
for(Claim const &c: s.disasm.claims)
|
||||
total_claim_size += c.size;
|
||||
|
||||
if(is_current) fmt::print("* ");
|
||||
fmt::print(theme(11), "{}\n", name);
|
||||
if(is_current)
|
||||
fmt::print("* ");
|
||||
fmt::print(theme(11), "{}\n", name);
|
||||
|
||||
fmt::print(" Symbol table: {} symbols\n",
|
||||
s.symbols.symbols.size());
|
||||
fmt::print(" Main disassembly: {} instructions\n",
|
||||
s.disasm.instructions.size());
|
||||
fmt::print(" Functions: {}\n",
|
||||
s.disasm.functions.size());
|
||||
fmt::print(" Claims: {} (totalling {} bytes)\n",
|
||||
s.disasm.claims.size(), total_claim_size);
|
||||
fmt::print(" Symbol table: {} symbols\n", s.symbols.symbols.size());
|
||||
fmt::print(
|
||||
" Main disassembly: {} instructions\n", s.disasm.instructions.size());
|
||||
fmt::print(" Functions: {}\n", s.disasm.functions.size());
|
||||
fmt::print(" Claims: {} (totalling {} bytes)\n", s.disasm.claims.size(),
|
||||
total_claim_size);
|
||||
|
||||
fmt::print(" Region--Start---------End---------File------------------\n");
|
||||
if(s.bindings.size() == 0) {
|
||||
fmt::print(" (no bindings)\n");
|
||||
return;
|
||||
}
|
||||
for(auto &b: s.bindings) {
|
||||
MemoryRegion const *ref = MemoryRegion::region_for(b.region);
|
||||
fmt::print(" {:<7s} 0x{:08x} .. 0x{:08x}", (ref ? ref->name : ""),
|
||||
b.region.start, b.region.end);
|
||||
if(b.buffer.path != "")
|
||||
fmt::print(" {}", b.buffer.path);
|
||||
fmt::print("\n");
|
||||
}
|
||||
fmt::print(" Region--Start---------End---------File------------------\n");
|
||||
if(s.bindings.size() == 0) {
|
||||
fmt::print(" (no bindings)\n");
|
||||
return;
|
||||
}
|
||||
for(auto &b: s.bindings) {
|
||||
MemoryRegion const *ref = MemoryRegion::region_for(b.region);
|
||||
fmt::print(" {:<7s} 0x{:08x} .. 0x{:08x}", (ref ? ref->name : ""),
|
||||
b.region.start, b.region.end);
|
||||
if(b.buffer.path != "")
|
||||
fmt::print(" {}", b.buffer.path);
|
||||
fmt::print("\n");
|
||||
}
|
||||
}
|
||||
|
||||
void _vl(Session &session, std::vector<std::string> const &args)
|
||||
{
|
||||
if(!args.size()) {
|
||||
for(auto &it: session.spaces)
|
||||
show_vspace(it.first, *it.second, session);
|
||||
}
|
||||
else for(auto &name: args) {
|
||||
VirtualSpace *s = session.get_space(name);
|
||||
if(s != nullptr)
|
||||
show_vspace(name, *session.spaces[name], session);
|
||||
else
|
||||
fmt::print("Virtual space '{}' does not exist", name);
|
||||
}
|
||||
if(!args.size()) {
|
||||
for(auto &it: session.spaces)
|
||||
show_vspace(it.first, *it.second, session);
|
||||
}
|
||||
else
|
||||
for(auto &name: args) {
|
||||
VirtualSpace *s = session.get_space(name);
|
||||
if(s != nullptr)
|
||||
show_vspace(name, *session.spaces[name], session);
|
||||
else
|
||||
fmt::print("Virtual space '{}' does not exist", name);
|
||||
}
|
||||
}
|
||||
|
||||
//---
|
||||
|
@ -82,17 +82,17 @@ void _vl(Session &session, std::vector<std::string> const &args)
|
|||
|
||||
static std::string parse_vs(Session &, Parser &parser)
|
||||
{
|
||||
std::string name = parser.symbol("vspace_name");
|
||||
parser.end();
|
||||
return name;
|
||||
std::string name = parser.symbol("vspace_name");
|
||||
parser.end();
|
||||
return name;
|
||||
}
|
||||
|
||||
void _vs(Session &session, std::string const &name)
|
||||
{
|
||||
VirtualSpace *s = session.get_space(name);
|
||||
if(!s)
|
||||
return;
|
||||
session.current_space = session.spaces[name].get();
|
||||
VirtualSpace *s = session.get_space(name);
|
||||
if(!s)
|
||||
return;
|
||||
session.current_space = session.spaces[name].get();
|
||||
}
|
||||
|
||||
//---
|
||||
|
@ -101,120 +101,91 @@ void _vs(Session &session, std::string const &name)
|
|||
|
||||
static std::string parse_vc(Session &, Parser &parser)
|
||||
{
|
||||
std::string name = "";
|
||||
if(!parser.at_end()) name = parser.symbol();
|
||||
parser.end();
|
||||
return name;
|
||||
std::string name = "";
|
||||
if(!parser.at_end())
|
||||
name = parser.symbol();
|
||||
parser.end();
|
||||
return name;
|
||||
}
|
||||
|
||||
void _vc(Session &session, std::string name)
|
||||
{
|
||||
if(name == "") name = session.generate_space_name("space", true);
|
||||
else name = session.generate_space_name(name, false);
|
||||
if(name == "")
|
||||
name = session.generate_space_name("space", true);
|
||||
else
|
||||
name = session.generate_space_name(name, false);
|
||||
|
||||
/* Create an empty space and select it */
|
||||
std::unique_ptr<VirtualSpace> space = std::make_unique<VirtualSpace>();
|
||||
session.spaces.emplace(name, std::move(space));
|
||||
_vs(session, name);
|
||||
_g(session, 0x80000000);
|
||||
}
|
||||
|
||||
//---
|
||||
// vct
|
||||
//---
|
||||
|
||||
struct _vct_args {
|
||||
std::string path;
|
||||
std::string vspace_name;
|
||||
};
|
||||
|
||||
static _vct_args parse_vct(Session &, Parser &parser)
|
||||
{
|
||||
_vct_args args;
|
||||
args.path = parser.str();
|
||||
if(!parser.at_end()) args.vspace_name = parser.symbol();
|
||||
parser.end();
|
||||
return args;
|
||||
}
|
||||
|
||||
void _vct(Session &session, std::string filename, std::string name)
|
||||
{
|
||||
fs::path path = session.file(filename);
|
||||
|
||||
if(name == "")
|
||||
name = session.generate_space_name(path.filename(), false);
|
||||
else if(session.spaces.count(name))
|
||||
throw CommandError("virtual space '{}' already exists", name);
|
||||
|
||||
_vc(session, name);
|
||||
_dot(session, { filename }, false);
|
||||
/* Create an empty space and select it */
|
||||
std::unique_ptr<VirtualSpace> space = std::make_unique<VirtualSpace>();
|
||||
session.spaces.emplace(name, std::move(space));
|
||||
_vs(session, name);
|
||||
_g(session, 0x80000000);
|
||||
}
|
||||
|
||||
//---
|
||||
// vm
|
||||
//---
|
||||
|
||||
struct _vm_args {
|
||||
std::string path;
|
||||
std::vector<MemoryRegion> regions;
|
||||
struct _vm_args
|
||||
{
|
||||
std::string path;
|
||||
std::vector<MemoryRegion> regions;
|
||||
};
|
||||
|
||||
static _vm_args parse_vm(Session &session, Parser &parser)
|
||||
{
|
||||
_vm_args args {};
|
||||
args.path = parser.str();
|
||||
_vm_args args {};
|
||||
args.path = parser.str();
|
||||
|
||||
/* TODO: vm: Allow specifying address without a size */
|
||||
do args.regions.push_back(parser.region(session.current_space));
|
||||
while(!parser.at_end());
|
||||
/* TODO: vm: Allow specifying address without a size */
|
||||
do
|
||||
args.regions.push_back(parser.region(session.current_space));
|
||||
while(!parser.at_end());
|
||||
|
||||
parser.end();
|
||||
return args;
|
||||
parser.end();
|
||||
return args;
|
||||
}
|
||||
|
||||
void _vm(Session &session, std::string file, std::vector<MemoryRegion> regions)
|
||||
{
|
||||
if(!session.current_space)
|
||||
return;
|
||||
if(!session.current_space)
|
||||
return;
|
||||
|
||||
std::string path = session.file(file);
|
||||
Buffer contents(path);
|
||||
std::string path = session.file(file);
|
||||
Buffer contents(path);
|
||||
|
||||
/* If no files are loaded yet, set the PC to the first loaded region */
|
||||
if(!session.current_space->bindings.size())
|
||||
session.pc = regions[0].start;
|
||||
/* If no files are loaded yet, set the PC to the first loaded region */
|
||||
if(!session.current_space->bindings.size())
|
||||
session.pc = regions[0].start;
|
||||
|
||||
for(auto &r: regions)
|
||||
session.current_space->bind_region(r, contents);
|
||||
for(auto &r: regions)
|
||||
session.current_space->bind_region(r, contents);
|
||||
}
|
||||
|
||||
//---
|
||||
// Command registration
|
||||
//---
|
||||
|
||||
static ShellCommand _vl_cmd("vl",
|
||||
[](Session &s, Parser &p) { _vl(s, parse_vl(s, p)); },
|
||||
[](Session &s, Parser &p){ parse_vl(s, p); },
|
||||
"Virtual space List", R"(
|
||||
static ShellCommand _vl_cmd(
|
||||
"vl", [](Session &s, Parser &p) { _vl(s, parse_vl(s, p)); },
|
||||
[](Session &s, Parser &p) { parse_vl(s, p); }, "Virtual space List", R"(
|
||||
vl [<space_name>...]
|
||||
|
||||
Shows the bound regions of each specified virtual space. If none is specified,
|
||||
shows all the virtual spaces.
|
||||
)");
|
||||
|
||||
static ShellCommand _vs_cmd("vs",
|
||||
[](Session &s, Parser &p) { _vs(s, parse_vs(s, p)); },
|
||||
[](Session &s, Parser &p){ parse_vs(s, p); },
|
||||
"Virtual space Select", R"(
|
||||
static ShellCommand _vs_cmd(
|
||||
"vs", [](Session &s, Parser &p) { _vs(s, parse_vs(s, p)); },
|
||||
[](Session &s, Parser &p) { parse_vs(s, p); }, "Virtual space Select", R"(
|
||||
vs <space_name>
|
||||
|
||||
Selects the specified virtual space.
|
||||
)");
|
||||
|
||||
static ShellCommand _vc_cmd("vc",
|
||||
[](Session &s, Parser &p) { _vc(s, parse_vc(s, p)); },
|
||||
[](Session &s, Parser &p){ parse_vc(s, p); },
|
||||
"Virtual space Create", R"(
|
||||
static ShellCommand _vc_cmd(
|
||||
"vc", [](Session &s, Parser &p) { _vc(s, parse_vc(s, p)); },
|
||||
[](Session &s, Parser &p) { parse_vc(s, p); }, "Virtual space Create", R"(
|
||||
vc [<name>]
|
||||
|
||||
Creates a new virtual space under the specified name. If such a space already
|
||||
|
@ -222,25 +193,13 @@ exists, adds a numerical suffix like _0 until a fresh name is found. The space
|
|||
is created with no bindings and automatically selected.
|
||||
)");
|
||||
|
||||
static ShellCommand _vct_cmd("vct",
|
||||
[](Session &s, Parser &p) {
|
||||
auto const &args = parse_vct(s, p);
|
||||
_vct(s, args.path, args.vspace_name); },
|
||||
[](Session &s, Parser &p){ parse_vct(s, p); },
|
||||
"Virtual space Create from Target", R"(
|
||||
vct "<script_file>" [<name>]
|
||||
|
||||
Creates a new virtual space from a script file. If no name is specified, the
|
||||
basename of the script file is used. The new space is created with vc, then the
|
||||
script file is run.
|
||||
)");
|
||||
|
||||
static ShellCommand _vm_cmd("vm",
|
||||
[](Session &s, Parser &p) {
|
||||
auto const &args = parse_vm(s, p);
|
||||
_vm(s, args.path, args.regions); },
|
||||
[](Session &s, Parser &p){ parse_vm(s, p); },
|
||||
"Virtual space Map file", R"(
|
||||
static ShellCommand _vm_cmd(
|
||||
"vm",
|
||||
[](Session &s, Parser &p) {
|
||||
auto const &args = parse_vm(s, p);
|
||||
_vm(s, args.path, args.regions);
|
||||
},
|
||||
[](Session &s, Parser &p) { parse_vm(s, p); }, "Virtual space Map file", R"(
|
||||
vm "<file>" <region>...
|
||||
|
||||
Maps the named file into all the specified regions of the current virtual
|
||||
|
|
Loading…
Reference in New Issue