AnyPS5/core/libs/prx/libSceAgcDriver/tests/StackWalk.cpp

181 lines
6.4 KiB
C++

#include "prx/libSceAgcDriver/Execution/include/StackWalk.hpp"
#include <array>
#include <cstdio>
#include <cstring>
#include <initializer_list>
#include <iostream>
#include <stdexcept>
#include <string>
#include <vector>
namespace {
using AgcDriver::CollapsedStacks;
using AgcDriver::DwarfFrames;
using Byte = DwarfFrames::Byte;
using Word = DwarfFrames::Word;
using Step = DwarfFrames::Step;
constexpr std::size_t FunctionOffset = 0x100;
constexpr std::size_t FunctionSize = 0x40;
void Check(bool condition, const char* message) {
if (!condition) throw std::runtime_error(message);
}
class Section {
public:
explicit Section(Byte returnColumn) : bytes(0x200, 0) {
const std::size_t cie = append32(0);
append32(0);
append({1, 'z', 'R', 0, 1, 0x78, returnColumn, 1, 0x1b, 0x0c, 7, 8, static_cast<Byte>(0x80 | returnColumn), 1});
patch32(cie, static_cast<std::uint32_t>(cursor - cie - 4));
const std::size_t fde = append32(0);
append32(static_cast<std::uint32_t>(cursor - cie));
append32(static_cast<std::uint32_t>(Function() - Address(cursor)));
append32(FunctionSize);
append({0, 0x41, 0x0e, 0x10, 0x86, 0x02, 0x43, 0x0d, 0x06, 0, 0});
patch32(fde, static_cast<std::uint32_t>(cursor - fde - 4));
end = cursor;
append32(0);
}
Word Function() const {
return Address(FunctionOffset);
}
DwarfFrames Frames() const {
return DwarfFrames(Address(0), Address(bytes.size()), bytes.data(), bytes.data() + end + 4);
}
private:
Word Address(std::size_t offset) const {
return reinterpret_cast<Word>(bytes.data() + offset);
}
std::size_t append32(std::uint32_t value) {
const auto at = cursor;
std::memcpy(bytes.data() + cursor, &value, 4);
cursor += 4;
return at;
}
void append(std::initializer_list<Byte> values) {
for (const auto value : values) bytes[cursor++] = value;
}
void patch32(std::size_t at, std::uint32_t value) {
std::memcpy(bytes.data() + at, &value, 4);
}
std::vector<Byte> bytes;
std::size_t cursor = 0;
std::size_t end = 0;
};
struct Stack {
std::array<Word, 64> words{};
Word At(std::size_t index) const {
return reinterpret_cast<Word>(&words[index]);
}
auto Reader() const {
return [this](Word address, Word& value) {
if (address < At(0) || address > At(words.size() - 1)) return false;
std::memcpy(&value, reinterpret_cast<const void*>(address), sizeof(value));
return true;
};
}
};
DwarfFrames::Registers Registers(Word pc, Word sp, Word fp) {
DwarfFrames::Registers registers{};
registers[16] = pc;
registers[7] = sp;
registers[6] = fp;
return registers;
}
void Prologue() {
const Section section(16);
const auto frames = section.Frames();
const auto function = section.Function();
Stack stack;
stack.words[32] = 0x5555;
stack.words[33] = 0x1234;
auto registers = Registers(function, stack.At(33), 0x9999);
Check(frames.Unwind(registers, true, stack.Reader()) == Step::Stepped, "the entry of a function did not step");
Check(registers[16] == 0x1234 && registers[7] == stack.At(34) && registers[6] == 0x9999, "the entry of a function stepped to the wrong frame");
registers = Registers(function + 2, stack.At(32), 0x9999);
Check(frames.Unwind(registers, true, stack.Reader()) == Step::Stepped, "the frame after the push did not step");
Check(registers[16] == 0x1234 && registers[7] == stack.At(34) && registers[6] == 0x5555, "the frame after the push did not restore the frame pointer");
registers = Registers(function + 0x20, stack.At(8), stack.At(32));
Check(frames.Unwind(registers, false, stack.Reader()) == Step::Stepped, "the body of a function did not step");
Check(registers[16] == 0x1234 && registers[7] == stack.At(34) && registers[6] == 0x5555, "the body of a function did not step through the frame pointer");
}
void Outside() {
const Section section(16);
const auto frames = section.Frames();
const auto function = section.Function();
Stack stack;
auto registers = Registers(function + FunctionSize, stack.At(8), 0);
Check(frames.Unwind(registers, true, stack.Reader()) == Step::NoRecord, "a pc after the function had a record");
registers = Registers(function - 1, stack.At(8), 0);
Check(frames.Unwind(registers, true, stack.Reader()) == Step::NoRecord, "a pc before the function had a record");
Check(frames.Covers(function + 0x10) && !frames.Covers(function + FunctionSize), "the record covers the wrong range");
registers = Registers(function, stack.At(63) + 4, 0);
const auto before = registers;
Check(frames.Unwind(registers, true, stack.Reader()) == Step::Failed && registers == before, "a read outside the stack was not refused");
Check(DwarfFrames(0, 0, nullptr, nullptr).Unwind(registers, true, stack.Reader()) == Step::NoRecord, "a module without records stepped");
}
void ReturnColumn32() {
const Section section(32);
const auto frames = section.Frames();
Stack stack;
stack.words[10] = 0x4321;
auto registers = Registers(section.Function(), stack.At(10), 0);
#ifdef _WIN32
Check(frames.Unwind(registers, true, stack.Reader()) == Step::Stepped && registers[16] == 0x4321 && registers[7] == stack.At(11), "the MinGW return column was not read");
#else
Check(frames.Unwind(registers, true, stack.Reader()) == Step::NoRecord, "a return column above 16 was accepted");
#endif
}
void Collapsed() {
CollapsedStacks stacks;
const std::uint64_t full[] = {0x10, 0x20, 0x30};
const std::uint64_t cut[] = {0x10, 0x40};
stacks.Add(full, false);
stacks.Add(full, false);
stacks.Add(cut, true);
const auto text = stacks.Format([](std::uint64_t address) {
char name[32];
std::snprintf(name, sizeof(name), "m+0x%llx", static_cast<unsigned long long>(address));
return std::string(name);
});
Check(text == "m+0x30;m+0x20;m+0x10 2\n[truncated];m+0x40;m+0x10 1\n", "collapsed stacks changed format");
Check(stacks.Truncated() == 1, "the truncated walk was not counted");
}
}
int main() {
try {
Prologue();
Outside();
ReturnColumn32();
Collapsed();
std::cout << "stack walk tests passed\n";
return 0;
} catch (const std::exception& error) {
std::cerr << error.what() << '\n';
return 1;
}
}