fxos/shell/s.cpp

583 lines
16 KiB
C++

#include "shell.h"
#include "parser.h"
#include "commands.h"
#include "errors.h"
#include "theme.h"
#include <regex>
#include <fxos/symbols.h>
#include <fmt/core.h>
#include <endian.h>
#include <fmt/printf.h>
#include <fxos/util/log.h>
#include <fxos/passes/print.h>
#include <fxos/passes/syscall.h>
#include <fxos/passes/pcrel.h>
//---
// sh
//---
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 _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;
};
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");
}
//---
// s4
//---
struct _s4_args
{
uint32_t value;
std::vector<MemoryRegion> regions;
};
static _s4_args parse_s4(Session &session, Parser &parser)
{
_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;
}
void _s4(Session &session, uint32_t value, std::vector<MemoryRegion> &regions)
{
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);
}
//---
// ssd
//---
struct _ssd_args
{
/* String to find */
std::string pattern;
/* Custom virtual space */
std::string vspace_name;
/* Required alignment for matches to be used */
bool identify_claims;
};
static _ssd_args parse_ssd(Session &session, Parser &parser)
{
_ssd_args args;
args.identify_claims = true;
parser.option("claims", [&args](std::string const &value) {
args.identify_claims = (value == "true");
});
parser.option("vspace",
[&args](std::string const &value) { args.vspace_name = value; });
parser.accept_options();
args.pattern = parser.str();
/* Check the structure of the needle */
if(args.pattern.size() == 0)
throw CommandError(
"search pattern '{}' should be non-zero "
"size",
args.pattern);
/* Check virtual space name */
VirtualSpace *space = session.current_space;
if(!args.vspace_name.empty()) {
space = session.get_space(args.vspace_name);
if(!space) {
std::string msg
= format("virtual space '%s' does not exist", args.vspace_name);
if(parser.completing())
throw Parser::CompletionRequest("_error", msg);
else
FxOS_log(ERR, "%s", msg);
}
}
parser.accept_options();
parser.end();
return args;
}
void _ssd(Session &session, char const *pattern, bool identify_claims,
std::string vspace_name)
{
VirtualSpace *space = session.current_space;
if(!vspace_name.empty()) {
space = session.get_space(vspace_name);
if(!space) {
FxOS_log(ERR, "no virtual space selected");
return;
}
}
if(!space) {
FxOS_log(ERR, "virtual space '%s' does not exist", vspace_name);
return;
}
OS *os = space->os_analysis();
if(!os) {
if(!vspace_name.empty())
FxOS_log(ERR, "OS analysis on '%s' failed", vspace_name);
else
FxOS_log(ERR, "OS analysis failed");
return;
}
if(space->disasm.instructions.size() == 0) {
if(vspace_name.empty())
FxOS_log(ERR, "current virtual space has no main disassembly");
else
FxOS_log(
ERR, "virtual space '%s' has no main disassembly", vspace_name);
return;
}
/* Address of the last disassembled instruction, i.e. where we should stop searching */
uint32_t end_address = (++space->disasm.instructions.rend())->first;
if(space->cursor >= end_address)
return;
/* Create local disassembly information */
FxOS::Disassembly disasm(*space);
/* Load the block into memory */
for(uint32_t pc = space->cursor; pc < end_address; pc += 2)
disasm.getInstructionAt(pc, true);
/* To determine if passes fail */
bool ok = true;
/* Analyse pcrel */
PcrelPass pcrel(disasm);
ok = pcrel.analyzeAllInstructions();
if(!ok) {
FxOS_log(ERR, "ss pcrel pass failed");
return;
}
/* Analyse syscalls */
SyscallPass syscall(disasm, os);
ok = syscall.analyzeAllInstructions();
if(!ok) {
FxOS_log(ERR, "ss syscall pass failed");
return;
}
/* Create string output from disassembly */
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;
/* Stores disassembly output */
std::string disasm_str;
for(auto &pair: disasm.instructions)
ok &= p.analyzeInstructionFull(
pair.first, pair.second, &disasm_str, false);
if(!ok) {
FxOS_log(ERR, "ss print pass failed");
return;
}
if(disasm_str.empty()) {
FxOS_log(ERR, "no disassembly found in virtual space");
return;
}
/* Used for std::getline */
std::istringstream iss(disasm_str);
/* Store number of matches */
int match_count = 0;
/* Store regex results */
std::smatch match;
/* Store claim */
Claim const *claim;
/* Map of symbols -> matches */
std::map<std::tuple<FxOS::Symbol::Type, uint32_t>, std::string> matches;
/* Unknown matches to print */
std::string unknown_matches;
/* Do a regex search on each line */
for(std::string line; std::getline(iss, line);)
if(std::regex_search(
line, match, std::regex(pattern, std::regex::extended))) {
if(identify_claims) {
/* Search for the address, so that match.str(1) has the addr */
std::regex_search(line, match,
std::regex("^ (.+): .+$", std::regex::extended));
std::istringstream address_str(match.str(1));
uint32_t address;
address_str >> std::hex >> address;
claim = space->disasm.getClaimAt(address);
if(claim) {
int syscall = os->find_syscall(claim->address);
if(syscall == -1)
syscall = os->find_syscall(claim->owner);
if(syscall == -1)
matches[{FxOS::Symbol::Address, claim->address}]
+= line + "\n";
else
matches[{FxOS::Symbol::Syscall, syscall}]
+= line + "\n";
}
else
unknown_matches += line + "\n";
}
else
unknown_matches += line + "\n";
// fmt::print("{}\n", line);
match_count++;
}
if(identify_claims) {
for(auto [location, lines]: matches) {
auto [type, value] = location;
if(type == FxOS::Symbol::Address) {
fmt::print(theme(10), "{:#08x} ", value);
std::optional<std::string> name
= space->symbols.query(FxOS::Symbol::Address, value);
if(name)
fmt::print(theme(3), "{}", *name);
fmt::print(":\n");
}
else {
fmt::print("<");
fmt::print(theme(10), "%{:04x}", value);
std::optional<std::string> name
= space->symbols.query(FxOS::Symbol::Syscall, value);
if(name)
fmt::print(theme(3), " {}", *name);
fmt::print(">\n");
}
fmt::print("{}\n", lines);
}
if(unknown_matches.size() > 0) {
fmt::print(theme(10), "Unknown");
fmt::print(":\n");
}
}
fmt::print("{}", unknown_matches);
if(match_count == 0)
fmt::print("\nNo occurrence found.\n");
else
fmt::print("\n{} occurences found.\n", match_count);
}
//---
// Command registration
//---
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>...]
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 _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>...]
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.
)");
static ShellCommand _ssd_cmd(
"ssd",
[](Session &s, Parser &p) {
auto args = parse_ssd(s, p);
_ssd(s, args.pattern.c_str(), args.identify_claims, args.vspace_name);
},
[](Session &s, Parser &p) { parse_ssd(s, p); }, "Search string in disassembly",
R"(
ssd [claims=true] [vspace=<virtual_space>] "<pattern>"
Searches disassembly from $ to the last analysed instruction for a string
pattern. If claims=true, the resulting addresses are then checked for claims so
that the command can output whether it is within a syscall, and what the names
of those syscalls are.
ssd "%12a8"
Searches for all usages of syscall 0x12a8 and print which syscalls they are
within.
ssd claims=false "%12a8"
Searches for all usages of syscall 0x12a8.
ssd claims=false vspace=cg_2.00 "%12a8"
Searches for all usages of syscall 0x12a8 within the 'cg_2.00' virtual space.
)");