mirror of
https://github.com/reswitched/Mephisto.git
synced 2024-06-16 03:16:50 -04:00
385 lines
10 KiB
C++
385 lines
10 KiB
C++
#include "Ctu.h"
|
|
|
|
void intrHook(uc_engine *uc, uint32_t intNo, void *user_data) {
|
|
((Cpu *) user_data)->interruptHook(intNo);
|
|
}
|
|
|
|
bool unmpdHook(uc_engine *uc, uc_mem_type type, gptr addr, uint32_t size, guint value, void *user_data) {
|
|
return ((Cpu *) user_data)->unmappedHook(type, addr, size, value);
|
|
}
|
|
|
|
void mmioHook(uc_engine *uc, uc_mem_type type, gptr address, uint32_t size, gptr value, void *user_data) {
|
|
auto cpu = (Cpu *) user_data;
|
|
auto physicalAddress = cpu->mmioHandler->getPhysicalAddressFromVirtual(address);
|
|
auto mmio = cpu->mmioHandler->getMMIOFromPhysicalAddress(address);
|
|
assert(mmio != nullptr);
|
|
switch(type) {
|
|
case UC_MEM_READ:
|
|
LOG_DEBUG(Cpu, "MMIO Read at " ADDRFMT " size %x", physicalAddress, size);
|
|
|
|
uint64_t ovalue;
|
|
if(mmio->read(physicalAddress, size, ovalue)) {
|
|
switch(size) {
|
|
case 1:
|
|
cpu->guestptr<uint8_t>(address) = (uint8_t) ovalue;
|
|
break;
|
|
case 2:
|
|
cpu->guestptr<uint16_t>(address) = (uint16_t) ovalue;
|
|
break;
|
|
case 4:
|
|
cpu->guestptr<uint32_t>(address) = (uint32_t) ovalue;
|
|
break;
|
|
case 8:
|
|
cpu->guestptr<uint64_t>(address) = ovalue;
|
|
break;
|
|
}
|
|
}
|
|
|
|
break;
|
|
case UC_MEM_WRITE:
|
|
LOG_DEBUG(Cpu, "MMIO Write at " ADDRFMT " size %x data " LONGFMT, physicalAddress, size, value);
|
|
mmio->write(physicalAddress, size, value);
|
|
break;
|
|
}
|
|
}
|
|
|
|
void codeBpHook(uc_engine *uc, uint64_t address, uint32_t size, void *user_data) {
|
|
auto ctu = (Ctu *) user_data;
|
|
cout << "Hit breakpoint at ... " << hex << address << endl;
|
|
auto thread = ctu->tm.current();
|
|
assert(thread != nullptr);
|
|
ctu->tm.requeue();
|
|
thread->regs.PC = address;
|
|
ctu->cpu.stop();
|
|
ctu->gdbStub._break();
|
|
}
|
|
|
|
void memBpHook(uc_engine *uc, uc_mem_type type, uint64_t address, uint32_t size, uint64_t value, void *user_data) {
|
|
auto ctu = (Ctu *) user_data;
|
|
if(ctu->cpu.hitMemBreakpoint) {
|
|
ctu->cpu.hitMemBreakpoint = false;
|
|
return;
|
|
}
|
|
cout << "Hit memory breakpoint accessing ... " << hex << address << endl;
|
|
auto thread = ctu->tm.current();
|
|
assert(thread != nullptr);
|
|
ctu->tm.requeue();
|
|
ctu->cpu.stop();
|
|
ctu->gdbStub._break(true);
|
|
ctu->cpu.hitMemBreakpoint = true;
|
|
}
|
|
|
|
Cpu::Cpu(Ctu *_ctu) : ctu(_ctu) {
|
|
CHECKED(uc_open(UC_ARCH_ARM64, UC_MODE_ARM, &uc));
|
|
|
|
CHECKED(uc_mem_map(uc, TERMADDR, 0x1000, UC_PROT_ALL));
|
|
guestptr<uint32_t>(TERMADDR) = 0xd40000e1; // svc 0x7 (ExitProcess)
|
|
|
|
auto fpv = 3 << 20;
|
|
CHECKED(uc_reg_write(uc, UC_ARM64_REG_CPACR_EL1, &fpv));
|
|
|
|
uc_hook hookHandle;
|
|
CHECKED(uc_hook_add(uc, &hookHandle, UC_HOOK_INTR, (void *) intrHook, this, 0, -1));
|
|
CHECKED(uc_hook_add(uc, &hookHandle, UC_HOOK_MEM_INVALID, (void *) unmpdHook, this, 0, -1));
|
|
|
|
for(auto i = 0; i < 0x80; ++i)
|
|
svcHandlers[i] = nullptr;
|
|
|
|
hitMemBreakpoint = false;
|
|
}
|
|
|
|
Cpu::~Cpu() {
|
|
CHECKED(uc_close(uc));
|
|
}
|
|
|
|
guint Cpu::call(gptr _pc, guint x0, guint x1, guint x2, guint x3) {
|
|
reg(0, x0);
|
|
reg(1, x1);
|
|
reg(2, x2);
|
|
reg(3, x3);
|
|
reg(30, TERMADDR);
|
|
CHECKED(uc_emu_start(uc, _pc, TERMADDR + 4, 0, 0));
|
|
return reg(0);
|
|
}
|
|
|
|
void Cpu::setMmio(Mmio *_mmioHandler) {
|
|
mmioHandler = _mmioHandler;
|
|
uc_hook hookHandle;
|
|
CHECKED(uc_hook_add(uc, &hookHandle, UC_HOOK_MEM_READ, (void *)mmioHook, this, mmioHandler->GetBase(), mmioHandler->GetBase()+mmioHandler->GetSize() ));
|
|
CHECKED(uc_hook_add(uc, &hookHandle, UC_HOOK_MEM_WRITE, (void *)mmioHook, this, mmioHandler->GetBase(), mmioHandler->GetBase()+mmioHandler->GetSize() ));
|
|
}
|
|
|
|
void Cpu::exec(size_t insnCount) {
|
|
CHECKED(uc_emu_start(uc, pc(), TERMADDR + 4, 0, insnCount));
|
|
}
|
|
|
|
void Cpu::stop() {
|
|
CHECKED(uc_emu_stop(uc));
|
|
}
|
|
|
|
bool Cpu::map(gptr addr, guint size) {
|
|
CHECKED(uc_mem_map(uc, addr, size, UC_PROT_ALL));
|
|
auto temp = new uint8_t[size];
|
|
memset(temp, 0, size);
|
|
writemem(addr, temp, size);
|
|
delete[] temp;
|
|
return true;
|
|
}
|
|
|
|
bool Cpu::unmap(gptr addr, guint size) {
|
|
CHECKED(uc_mem_unmap(uc, addr, size));
|
|
return true;
|
|
}
|
|
|
|
list<tuple<gptr, gptr, int>> Cpu::regions() {
|
|
list<tuple<gptr, gptr, int>> ret;
|
|
|
|
uc_mem_region *regions;
|
|
uint32_t count;
|
|
|
|
CHECKED(uc_mem_regions(uc, ®ions, &count));
|
|
list<tuple<gptr, gptr>> temp;
|
|
for(auto i = 0; i < count; ++i) {
|
|
auto region = regions[i];
|
|
temp.push_back(make_tuple(region.begin, region.end));
|
|
}
|
|
uc_free(regions);
|
|
|
|
temp.sort([](auto a, auto b) { auto [ab, _] = a; auto [bb, __] = b; return ab < bb; });
|
|
|
|
gptr last = 0;
|
|
for(auto [begin, end] : temp) {
|
|
if(last != begin)
|
|
ret.push_back(make_tuple(last, begin - 1, -1));
|
|
ret.push_back(make_tuple(begin, end, 0));
|
|
last = end + 1;
|
|
}
|
|
|
|
if(last != 0xFFFFFFFFFFFFFFFF)
|
|
ret.push_back(make_tuple(last, 0xFFFFFFFFFFFFFFFF, -1));
|
|
|
|
return ret;
|
|
}
|
|
|
|
bool Cpu::readmem(gptr addr, void *dest, guint size) {
|
|
return uc_mem_read(uc, addr, dest, size) == UC_ERR_OK;
|
|
}
|
|
|
|
#pragma clang diagnostic push
|
|
#pragma clang diagnostic ignored "-Wformat-nonliteral"
|
|
void Cpu::dumpmem(gptr addr, guint size) {
|
|
char *data = (char*)malloc(size);
|
|
memset(data, 0, size);
|
|
readmem(addr, data, size);
|
|
|
|
auto hfmt = "%08lx | ";
|
|
if((addr + size) & 0xFFFF000000000000)
|
|
hfmt = "%016lx | ";
|
|
else if((addr + size) & 0xFFFFFFFF00000000)
|
|
hfmt = "%012lx | ";
|
|
|
|
for(uint32_t i = 0; i < size; i += 16) {
|
|
printf(hfmt, addr+i);
|
|
string ascii = "";
|
|
for(uint8_t j = 0; j < 16; j++) {
|
|
if((i+j) < size) {
|
|
printf("%02x ", (uint8_t)data[i+j]);
|
|
if(isprint(data[i+j]))
|
|
ascii += data[i+j];
|
|
else
|
|
ascii += ".";
|
|
} else {
|
|
printf(" ");
|
|
ascii += " ";
|
|
}
|
|
if(j==7) {
|
|
printf(" ");
|
|
ascii += " ";
|
|
}
|
|
}
|
|
printf("| %s\n", ascii.c_str());
|
|
}
|
|
|
|
free(data);
|
|
}
|
|
#pragma clang diagnostic pop
|
|
|
|
guchar Cpu::read8(gptr addr) {
|
|
return *guestptr<guchar>(addr);
|
|
}
|
|
|
|
std::string Cpu::readstring(gptr addr) {
|
|
std::string out;
|
|
uint32_t offset = 0;
|
|
while(guchar c = read8(addr+offset)) {
|
|
if(c == 0)
|
|
break;
|
|
out += c;
|
|
offset++;
|
|
}
|
|
return out;
|
|
}
|
|
|
|
bool Cpu::writemem(gptr addr, void *src, guint size) {
|
|
return uc_mem_write(uc, addr, src, size) == UC_ERR_OK;
|
|
}
|
|
|
|
gptr Cpu::pc() {
|
|
gptr val;
|
|
CHECKED(uc_reg_read(uc, UC_ARM64_REG_PC, &val));
|
|
return val;
|
|
}
|
|
|
|
void Cpu::pc(gptr val) {
|
|
CHECKED(uc_reg_write(uc, UC_ARM64_REG_PC, &val));
|
|
}
|
|
|
|
guint Cpu::reg(uint regn) {
|
|
guint val;
|
|
auto treg = UC_ARM64_REG_SP;
|
|
if(regn <= 28)
|
|
treg = (uc_arm64_reg) (UC_ARM64_REG_X0 + regn);
|
|
else if(regn < 31)
|
|
treg = (uc_arm64_reg) (UC_ARM64_REG_X29 + regn - 29);
|
|
CHECKED(uc_reg_read(uc, treg, &val));
|
|
return val;
|
|
}
|
|
|
|
void Cpu::reg(uint regn, guint val) {
|
|
auto treg = UC_ARM64_REG_SP;
|
|
if(regn <= 28)
|
|
treg = (uc_arm64_reg) (UC_ARM64_REG_X0 + regn);
|
|
else if(regn < 31)
|
|
treg = (uc_arm64_reg) (UC_ARM64_REG_X29 + regn - 29);
|
|
CHECKED(uc_reg_write(uc, treg, &val));
|
|
}
|
|
|
|
void Cpu::loadRegs(ThreadRegisters ®s) {
|
|
int uregs[32];
|
|
void *tregs[32];
|
|
|
|
CHECKED(uc_reg_write(uc, UC_ARM64_REG_SP, ®s.SP));
|
|
CHECKED(uc_reg_write(uc, UC_ARM64_REG_PC, ®s.PC));
|
|
CHECKED(uc_reg_write(uc, UC_ARM64_REG_NZCV, ®s.NZCV));
|
|
|
|
for(auto i = 0; i < 29; ++i) {
|
|
uregs[i] = UC_ARM64_REG_X0 + i;
|
|
tregs[i] = ®s.gprs[i];
|
|
}
|
|
CHECKED(uc_reg_write_batch(uc, uregs, tregs, 29));
|
|
CHECKED(uc_reg_write(uc, UC_ARM64_REG_X29, ®s.X29));
|
|
CHECKED(uc_reg_write(uc, UC_ARM64_REG_X30, ®s.X30));
|
|
|
|
for(auto i = 0; i < 32; ++i) {
|
|
uregs[i] = UC_ARM64_REG_Q0 + i;
|
|
tregs[i] = ®s.fprs[i];
|
|
}
|
|
CHECKED(uc_reg_write_batch(uc, uregs, tregs, 32));
|
|
}
|
|
|
|
void Cpu::storeRegs(ThreadRegisters ®s) {
|
|
int uregs[32];
|
|
void *tregs[32];
|
|
|
|
CHECKED(uc_reg_read(uc, UC_ARM64_REG_SP, ®s.SP));
|
|
CHECKED(uc_reg_read(uc, UC_ARM64_REG_PC, ®s.PC));
|
|
CHECKED(uc_reg_read(uc, UC_ARM64_REG_NZCV, ®s.NZCV));
|
|
|
|
for(auto i = 0; i < 29; ++i) {
|
|
uregs[i] = UC_ARM64_REG_X0 + i;
|
|
tregs[i] = ®s.gprs[i];
|
|
}
|
|
CHECKED(uc_reg_read_batch(uc, uregs, tregs, 29));
|
|
CHECKED(uc_reg_read(uc, UC_ARM64_REG_X29, ®s.X29));
|
|
CHECKED(uc_reg_read(uc, UC_ARM64_REG_X30, ®s.X30));
|
|
|
|
for(auto i = 0; i < 32; ++i) {
|
|
uregs[i] = UC_ARM64_REG_Q0 + i;
|
|
tregs[i] = ®s.fprs[i];
|
|
}
|
|
CHECKED(uc_reg_read_batch(uc, uregs, tregs, 32));
|
|
}
|
|
|
|
gptr Cpu::tlsBase() {
|
|
gptr base;
|
|
CHECKED(uc_reg_read(uc, UC_ARM64_REG_TPIDRRO_EL0, &base));
|
|
return base;
|
|
}
|
|
|
|
void Cpu::tlsBase(gptr base) {
|
|
CHECKED(uc_reg_write(uc, UC_ARM64_REG_TPIDRRO_EL0, &base));
|
|
}
|
|
|
|
void Cpu::interruptHook(uint32_t intNo) {
|
|
uint32_t esr;
|
|
CHECKED(uc_reg_read(uc, UC_ARM64_REG_ESR, &esr));
|
|
auto ec = esr >> 26;
|
|
auto iss = esr & 0xFFFFFF;
|
|
switch(ec) {
|
|
case 0x15: // SVC
|
|
if(iss >= 0x80 || svcHandlers[iss] == nullptr)
|
|
LOG_ERROR(Cpu, "Unhandled SVC 0x%02x", iss);
|
|
svcHandlers[iss](this);
|
|
break;
|
|
}
|
|
}
|
|
|
|
bool Cpu::unmappedHook(uc_mem_type type, gptr addr, int size, guint value) {
|
|
cout << "!!!!!!!!!!!!!!!!!!!!!!!! Unmapped !!!!!!!!!!!!!!!!!!!!!!!" << endl;
|
|
switch(type) {
|
|
case UC_MEM_READ_UNMAPPED:
|
|
case UC_MEM_READ_PROT:
|
|
LOG_INFO(Cpu, "Attempted to read from %s memory at " ADDRFMT " from " ADDRFMT, (type == UC_MEM_READ_UNMAPPED ? "unmapped" : "protected"), addr, pc());
|
|
break;
|
|
case UC_MEM_FETCH_UNMAPPED:
|
|
case UC_MEM_FETCH_PROT:
|
|
LOG_INFO(Cpu, "Attempted to fetch from %s memory at " ADDRFMT " from " ADDRFMT, (type == UC_MEM_READ_UNMAPPED ? "unmapped" : "protected"), addr, pc());
|
|
break;
|
|
case UC_MEM_WRITE_UNMAPPED:
|
|
case UC_MEM_WRITE_PROT:
|
|
LOG_INFO(Cpu, "Attempted to write " LONGFMT " to %s memory at " ADDRFMT " from " ADDRFMT, value, (type == UC_MEM_READ_UNMAPPED ? "unmapped" : "protected"), addr, pc());
|
|
break;
|
|
}
|
|
if(ctu->gdbStub.enabled) {
|
|
auto thread = ctu->tm.current();
|
|
if(thread == nullptr)
|
|
return false;
|
|
ctu->tm.requeue();
|
|
ctu->gdbStub._break(false);
|
|
if(type == UC_MEM_FETCH_PROT || type == UC_MEM_FETCH_UNMAPPED)
|
|
pc(TERMADDR);
|
|
else
|
|
stop();
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void Cpu::registerSvcHandler(int num, std::function<void()> handler) {
|
|
registerSvcHandler(num, [=](auto _) { handler(); });
|
|
}
|
|
|
|
void Cpu::registerSvcHandler(int num, std::function<void(Cpu *)> handler) {
|
|
svcHandlers[num] = handler;
|
|
}
|
|
|
|
hook_t Cpu::addCodeBreakpoint(gptr addr) {
|
|
assert(ctu->gdbStub.enabled);
|
|
|
|
hook_t hookHandle;
|
|
CHECKED(uc_hook_add(uc, &hookHandle, UC_HOOK_CODE, (void *)codeBpHook, ctu, addr, addr + 2));
|
|
return hookHandle;
|
|
}
|
|
|
|
hook_t Cpu::addMemoryBreakpoint(gptr addr, guint len, BreakpointType type) {
|
|
assert(ctu->gdbStub.enabled);
|
|
|
|
hook_t hookHandle;
|
|
CHECKED(uc_hook_add(uc, &hookHandle, ((type == BreakpointType::Read || type == BreakpointType::Access) ? UC_HOOK_MEM_READ : 0) | ((type == BreakpointType::Write || type == BreakpointType::Access) ? UC_HOOK_MEM_WRITE : 0), (void *)memBpHook, ctu, addr, addr + len));
|
|
return hookHandle;
|
|
}
|
|
|
|
void Cpu::removeBreakpoint(hook_t hook) {
|
|
CHECKED(uc_hook_del(uc, hook));
|
|
}
|