Improve command classification

This commit is contained in:
Dr-Carlos 2022-04-14 21:00:43 +09:30
parent 3a9a622ee3
commit 12845a1675
48 changed files with 2137 additions and 1930 deletions

114
.clang-format Normal file
View File

@ -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

View File

@ -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}

View File

@ -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 &region);
/* 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 */

View File

@ -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 */

View File

@ -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 */

View File

@ -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

View File

@ -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 */

View File

@ -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);

View File

@ -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 */

View File

@ -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* **/

View File

@ -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();
}

View File

@ -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;

View File

@ -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 */

View File

@ -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;

View File

@ -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);
}

View File

@ -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))...);
}

View File

@ -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 */

View File

@ -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 */

View File

@ -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;

View File

@ -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);
}

View File

@ -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;

View File

@ -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 */

View File

@ -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) {

View File

@ -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;

View File

@ -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;
}

View File

@ -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

View File

@ -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 */

View File

@ -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);
}

View File

@ -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] = '{';

View File

@ -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)

View File

@ -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) {

View File

@ -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);
}

View File

@ -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);

View File

@ -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> &regions)
{
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> &regions)
{
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

View File

@ -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

View File

@ -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.
)");

66
shell/dot.cpp Normal file
View File

@ -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.
)");

View File

@ -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.
)");

View File

@ -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.

View File

@ -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

View File

@ -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.
)");

41
shell/m.cpp Normal file
View File

@ -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.
)");

View File

@ -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.
)");

View File

@ -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;
}

View File

@ -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> &regions)
{
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> &regions)
{
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.
)");

View File

@ -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
}

View File

@ -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;
}

View File

@ -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