Files
Mu/src/debug/sandbox.c
2019-04-11 09:51:26 -07:00

1224 lines
45 KiB
C

#include <stdint.h>
#include <stdbool.h>
#if defined(EMU_DEBUG) && defined(EMU_SANDBOX)
//this wrappers 68k code and allows calling it for tests, this should be useful for determining if hardware accesses are correct
//Note: when running a test the emulator runs at native speed and no clocks are emulated
#include <stdlib.h>
#include <string.h>
#include <stdarg.h>
#include <time.h>
#include "../m68k/m68k.h"
#include "../m68k/m68kcpu.h"
#include "../emulator.h"
#include "../ads7846.h"
#include "../hardwareRegisters.h"
#include "../portability.h"
#include "../specs/emuFeatureRegisterSpec.h"
#include "../armv5.h"
#include "sandbox.h"
#include "trapNames.h"
#define SANDBOX_MAX_UNLOGGED_JUMP_SIZE 0xFFFFFFFF
#define SANDBOX_MAX_WATCH_REGIONS 1000
#define SANDBOX_SECONDS_TO_FRAMES(x) ((x) * EMU_FPS)
typedef struct{
void* hostPointer;
uint32_t emuPointer;
uint64_t bytes;//may be used for strings or structs in the future
}return_pointer_t;
typedef struct{
uint32_t sp;
uint32_t pc;
uint16_t sr;
uint32_t a0;
uint32_t d0;
}local_cpu_state_t;
typedef struct{
uint32_t address;
uint32_t size;
uint8_t type;
}mem_region_t;
static bool sandboxActive;//used to "log out" of the emulator once a test has finished
static bool sandboxControlHandoff;//used for functions that depend on timing, hands full control to the m68k
static uint8_t sandboxCurrentCpuArch;
static local_cpu_state_t sandboxOldFunctionCpuState;
static uint64_t sandboxFramesRan;
static mem_region_t sandboxWatchRegions[SANDBOX_MAX_WATCH_REGIONS];//code locations in 68k address space to be sandboxed
static uint16_t sandboxWatchRegionsActive;//number of used sandboxWatchRegions entrys
static uint32_t sandboxCallGuestFunction(bool fallthrough, uint32_t address, uint16_t trap, const char* prototype, ...);
#include "sandboxTrapNumToName.c.h"
static uint32_t getCurrentCpuPc(void){
//returns the current program counter for the current CPU and architecture
return sandboxCurrentCpuArch == SANDBOX_CPU_ARCH_M68K ? m68k_get_reg(NULL, M68K_REG_PC) : armv5GetRegister(15) & 0xFFFFFFFE;
}
static uint32_t getCurrentCpuPreviousPc(void){
//returns the program counter of the current running opcode for the current CPU and architecture
return sandboxCurrentCpuArch == SANDBOX_CPU_ARCH_M68K ? m68k_get_reg(NULL, M68K_REG_PPC) : armv5GetRegister(15) & 0xFFFFFFFE;
}
uint32_t getRandomRange(uint32_t start, uint32_t end){
static bool seeded = false;
if(!seeded){
srand(time(NULL));
seeded = true;
}
return (uint32_t)rand() % (end + 1 - start) + start;
}
static char* takeStackDump(uint32_t bytes){
char* textBytes = malloc(bytes * 2);
uint32_t textBytesOffset = 0;
uint32_t stackAddress = m68k_get_reg(NULL, M68K_REG_SP);
uint32_t count;
textBytes[0] = '\0';
for(count = 0; count < bytes; count++){
sprintf(textBytes + textBytesOffset, "%02X", m68k_read_memory_8(stackAddress + count));
textBytesOffset = strlen(textBytes);
}
return textBytes;
}
static void patchOsRom(uint32_t address, char* patch){
uint32_t offset;
uint32_t patchBytes = strlen(patch) / 2;//1 char per nibble
uint32_t swapBegin = address & 0xFFFFFFFE;
uint32_t swapSize = patchBytes / sizeof(uint16_t) + 1;
char conv[5] = "0xXX";
swap16BufferIfLittle(&palmRom[swapBegin], swapSize);
for(offset = 0; offset < patchBytes; offset++){
conv[2] = patch[offset * 2];
conv[3] = patch[offset * 2 + 1];
palmRom[address + offset] = strtol(conv, NULL, 0);
}
swap16BufferIfLittle(&palmRom[swapBegin], swapSize);
}
static const char* getLowMemGlobalName(uint32_t address){
switch(address){
case 0x00000100:
return "MemTotalCards";
case 0x00000102:
return "MemCard0Start";
case 0x0000010A:
return "MemDebugFlags";
case 0x00000122:
return "TrapTablePtr";
case 0x00000164:
return "ScrStatePtr";
case 0x0000016C:
return "PenStatePtr";
case 0x00000170:
return "EvtStatePtr";
case 0x00000174:
return "SndStatePtr";
case 0x00000178:
return "TimStatePtr";
case 0x0000017C:
return "AlmStatePtr";
case 0x00000180:
return "FtrStatePtr";
case 0x00000184:
return "GrfStatePtr";
default:
return "UNKNOWN";
}
}
static bool ignoreTrap(uint16_t trap){
switch(trap){
case HwrDisableDataWrites:
case HwrEnableDataWrites:
case DmGetDatabase://this could be important later but is spammy on boot
case MemStoreInfo://this could be important later but is spammy on boot
case HwrDelay:
return true;
}
return false;
}
static void printTrapInfo(uint16_t trap){
debugLog("name:%s, API:0x%04X, location:0x%08X\n", lookupTrap(trap), trap, m68k_read_memory_32(0x000008CC + (trap & 0x0FFF) * 4));
}
bool validExecutionAddress(uint32_t address){
if(chips[CHIP_A0_ROM].inBootMode || address >= chips[CHIP_A0_ROM].start && address < chips[CHIP_A0_ROM].start + chips[CHIP_A0_ROM].lineSize)
return true;
if(address >= chips[CHIP_DX_RAM].start && address < chips[CHIP_DX_RAM].start + chips[CHIP_DX_RAM].lineSize)
return true;
if(sandboxActive && address >= 0xFFFFFE00)//used to run custom code when in sandbox mode
return true;
return false;
}
void log68kJumps(void){
uint32_t opcodeStartPc = m68k_get_reg(NULL, M68K_REG_PPC);
uint32_t opcodeEndPc = m68k_get_reg(NULL, M68K_REG_PC);
uint32_t difference = llabs((int64_t)opcodeStartPc - (int64_t)opcodeEndPc);
//if invalid always log, otherwise only log if big jump
if(!validExecutionAddress(opcodeEndPc))
debugLog("m68k jumped 0x%08X bytes to invalid address, from 0x%08X to 0x%08X\n", difference, opcodeStartPc, opcodeEndPc);
else if(difference > SANDBOX_MAX_UNLOGGED_JUMP_SIZE)
debugLog("m68k jumped 0x%08X bytes, from 0x%08X to 0x%08X\n", difference, opcodeStartPc, opcodeEndPc);
}
static void logApiCalls(void){
uint32_t programCounter = m68k_get_reg(NULL, M68K_REG_PPC);
uint16_t instruction = m68k_get_reg(NULL, M68K_REG_IR);
if(instruction == 0x4E4F/*Trap F/API call opcode*/){
uint16_t trap = m68k_read_memory_16(programCounter + 2);
if(!ignoreTrap(trap))
debugLog("Trap F API:%s, API number:0x%04X, PC:0x%08X\n", lookupTrap(trap), trap, programCounter);
}
}
static bool isAlphanumeric(char chr){
if((chr >= 'a' && chr <= 'z') || (chr >= 'A' && chr <= 'Z') || (chr >= '0' && chr <= '9'))
return true;
return false;
}
static bool readable6CharsBack(uint32_t address){
uint8_t count;
for(count = 0; count < 6; count++)
if(!isAlphanumeric(m68k_read_memory_8(address - count)))
return false;
return true;
}
static uint32_t find68kString(const char* str, uint32_t rangeStart, uint32_t rangeEnd){
uint32_t strLength = strlen(str) + 1;//include null terminator
uint32_t scanAddress;
for(scanAddress = rangeStart; scanAddress <= rangeEnd - (strLength - 1); scanAddress++){
//since only the first char is range checked remove the rest from the range to prevent reading off the end
//check every byte against the start character
if(m68k_read_memory_8(scanAddress) == str[0]){
bool wrongString = false;
uint32_t strIndex;
//character match found, check for string
for(strIndex = 1; strIndex < strLength; strIndex++){
if(m68k_read_memory_8(scanAddress + strIndex) != str[strIndex]){
wrongString = true;
break;
}
}
if(wrongString == false)
return scanAddress;
}
}
return rangeEnd;
}
static uint32_t skip68kString(uint32_t address){
while(m68k_read_memory_8(address) != '\0')
address++;
address++;//skip null terminator too
return address;
}
//THIS FUNCTION DOES NOT WORK IF A WORD ALIGNED 0x0000 IS FOUND IN THE FUNCTION BEING SEARCHED FOR, THIS IS A BUG
static uint32_t scanForPrivateFunctionAddress(const char* name){
//function name format [0x**(unknown), string(with null terminator), 0x00, 0x00(if last 0x00 was on an even address, protects opcode alignemnt)]
//this is not 100% accurate, it scans memory for a function address based on a string
//if a duplicate set of stings is found but not encasing a function a fatal error will occur on execution
uint32_t rangeEnd = chips[CHIP_A0_ROM].start + chips[CHIP_A0_ROM].lineSize - 1;
uint32_t address = find68kString(name, chips[CHIP_A0_ROM].start, rangeEnd);
while(address < rangeEnd){
uint32_t signatureBegining = address - 3;//last opcode of function being looked for if the string is correct
//skip string to test the null terminators
address = skip68kString(address);
//after a function string there are 2 null terminators(the one all strings have and 1 extra) or 3(2 extra) if the previous one was on an even address
if(m68k_read_memory_8(address) == '\0' && (address & 0x00000001 || m68k_read_memory_8(address + 1) == '\0')){
//valid string match found, get prior functions signature(a function string has a minimum of 6 printable chars with 2 null terminators)
uint32_t priorFunctionSignature = signatureBegining;//last opcode of the function thats being looked for
bool extraNull = false;
while(m68k_read_memory_16(priorFunctionSignature) != '\0\0')
priorFunctionSignature -= 2;
//remove extra null terminator if present
if(m68k_read_memory_8(priorFunctionSignature - 1) == '\0'){
priorFunctionSignature--;
extraNull = true;
}
//check that there are 6 valid alphanumeric characters before the nulls, this indicates that its a valid function signature
if(readable6CharsBack(priorFunctionSignature - 1)){
//valid, get first opcode after string and return its address
return priorFunctionSignature + (extraNull ? 3 : 2);
}
}
//string matched but structure was invalid, get next string
address = find68kString(name, address, rangeEnd);
}
return 0x00000000;
}
//call anywhere in a function to get its name, used to determine the location of a crash, must free the pointer after reading the string
static char* getFunctionName68k(uint32_t address){
char* data;
uint32_t offset;
address &= 0xFFFFFFFE;
for(offset = 0; offset < 0x10000; offset += 2){
if(m68k_read_memory_16(address + offset) == 0x4E75/*RTS*/){
offset += 3;
break;
}
}
if(offset < 0x10000){
uint16_t size = 0;
uint16_t offset2;
for(offset2 = 0; offset2 < 0x100; offset2++)
if(isAlphanumeric(m68k_read_memory_8(address + offset + offset2)))
size++;
data = malloc(size + 1);
for(offset2 = 0; offset2 < size; offset2++)
data[offset2] = m68k_read_memory_8(address + offset + offset2);
data[offset2] = '\0';
}
else{
data = NULL;
}
return data;
}
static char* getFunctionNameArmv5(uint32_t address){
return NULL;
}
static char* getFunctionNameThumb(uint32_t address){
return NULL;
}
static uint32_t makePalmString(const char* str){
uint32_t strLength = strlen(str) + 1;
uint32_t strData = sandboxCallGuestFunction(false, 0x00000000, MemPtrNew, "p(l)", strLength);
if(strData){
uint32_t count;
for(count = 0; count < strLength; count++)
m68k_write_memory_8(strData + count, str[count]);
}
return strData;
}
static char* makeNativeString(uint32_t address){
if(address){
int16_t strLength = sandboxCallGuestFunction(false, 0x00000000, StrLen, "w(p)", address) + 1;
char* nativeStr = malloc(strLength);
int16_t count;
for(count = 0; count < strLength; count++)
nativeStr[count] = m68k_read_memory_8(address + count);
return nativeStr;
}
return NULL;
}
static void freePalmString(uint32_t address){
sandboxCallGuestFunction(false, 0x00000000, MemChunkFree, "w(p)", address);
}
static bool installResourceToDevice(buffer_t resourceBuffer){
/*
#define memNewChunkFlagNonMovable 0x0200
#define memNewChunkFlagAllowLarge 0x1000 // this is not in the sdk *g*
void *MemPtrNewL ( UInt32 size ) {
SysAppInfoPtr appInfoP;
UInt16 ownerID;
ownerID =
((SysAppInfoPtr)SysGetAppInfo(&appInfoP, &appInfoP))->memOwnerID;
return MemChunkNew ( 0, size, ownerID |
memNewChunkFlagNonMovable |
memNewChunkFlagAllowLarge );
}
*/
uint32_t palmSideResourceData = sandboxCallGuestFunction(false, 0x00000000, MemChunkNew, "p(wlw)", 1/*heapID, storage RAM*/, resourceBuffer.size, 0x1200/*attr, seems to work without memOwnerID*/);
bool storageRamReadOnly = chips[CHIP_DX_RAM].readOnlyForProtectedMemory;
uint16_t error;
uint32_t count;
//buffer not allocated
if(!palmSideResourceData)
return false;
chips[CHIP_DX_RAM].readOnlyForProtectedMemory = false;//need to unprotect storage RAM
for(count = 0; count < resourceBuffer.size; count++)
m68k_write_memory_8(palmSideResourceData + count, resourceBuffer.data[count]);
chips[CHIP_DX_RAM].readOnlyForProtectedMemory = storageRamReadOnly;//restore old protection state
error = sandboxCallGuestFunction(false, 0x00000000, DmCreateDatabaseFromImage, "w(p)", palmSideResourceData);//Err DmCreateDatabaseFromImage(MemPtr bufferP);//this looks best
sandboxCallGuestFunction(false, 0x00000000, MemChunkFree, "w(p)", palmSideResourceData);
//didnt install
if(error != 0)
return false;
return true;
}
static void checkMemoryAlignment(uint16_t heap){
uint32_t memPtrs[100];
uint8_t index;
uint16_t error;
memset(memPtrs, 0x00, sizeof(memPtrs));
//get randomly sized memory regions
for(index = 0; index < 100; index++){
memPtrs[index] = sandboxCallGuestFunction(false, 0x00000000, MemChunkNew, "p(wlw)", heap, getRandomRange(1, 100), 0x0200/*memNewChunkFlagNonMovable*/);
if(!memPtrs[index]){
debugLog("Memory test: Failed to allocate memory\n");
goto failed;
}
}
//check alignment
for(index = 0; index < 100; index++){
if(memPtrs[index] & 0x00000003){
debugLog("Memory test: Memory allocations are not 32 bit aligned:0x%08X\n", memPtrs[index]);
goto failed;
}
}
//resize memory
for(index = 0; index < 100; index++){
error = sandboxCallGuestFunction(false, 0x00000000, MemPtrResize, "w(pl)", memPtrs[index], getRandomRange(1, 100));
if(error != 0x0000/*errNone*/){
debugLog("Memory test: Failed to resize memory:0x%04X\n", error);
goto failed;
}
}
//check alignment again
for(index = 0; index < 100; index++){
if(memPtrs[index] & 0x00000003){
debugLog("Memory test: Memory misaligned by resize:0x%08X\n", memPtrs[index]);
goto failed;
}
}
//shuffle memory
error = sandboxCallGuestFunction(false, 0x00000000, MemHeapScramble, "w(w)", heap);
if(error != 0x0000/*errNone*/){
debugLog("Memory test: Unable to scramble heap:0x%04X\n", error);
goto failed;
}
//check alignment again
for(index = 0; index < 100; index++){
if(memPtrs[index] & 0x00000003){
debugLog("Memory test: Memory misaligned by defragment:0x%08X\n", memPtrs[index]);
goto failed;
}
}
debugLog("Memory test: Memory aligned correctly\n");
failed:
//free pointers
for(index = 0; index < 100; index++)
if(memPtrs[index])
sandboxCallGuestFunction(false, 0x00000000, MemChunkFree, "w(p)", memPtrs[index]);
}
static uint32_t sandboxGetStackFrameSize(const char* prototype){
const char* params = prototype + 2;
uint32_t size = 0;
while(*params != ')'){
switch(*params){
case 'v':
case 'V':
//do nothing
break;
case 'b':
//bytes are 16 bits long on the stack due to memory alignment restrictions
case 'w':
size += 2;
break;
case 'l':
case 'p':
case 'B':
case 'W':
case 'L':
case 'P':
size += 4;
break;
}
params++;
}
return size;
}
static void sandboxBackupCpuState(void){
sandboxOldFunctionCpuState.sp = m68k_get_reg(NULL, M68K_REG_SP);
sandboxOldFunctionCpuState.pc = m68k_get_reg(NULL, M68K_REG_PC);
sandboxOldFunctionCpuState.sr = m68k_get_reg(NULL, M68K_REG_SR);
sandboxOldFunctionCpuState.a0 = m68k_get_reg(NULL, M68K_REG_A0);
sandboxOldFunctionCpuState.d0 = m68k_get_reg(NULL, M68K_REG_D0);
}
static void sandboxRestoreCpuState(void){
m68k_set_reg(M68K_REG_SP, sandboxOldFunctionCpuState.sp);
m68k_set_reg(M68K_REG_PC, sandboxOldFunctionCpuState.pc);
m68k_set_reg(M68K_REG_SR, sandboxOldFunctionCpuState.sr & 0xF0FF | m68k_get_reg(NULL, M68K_REG_SR) & 0x0700);//dont restore intMask
m68k_set_reg(M68K_REG_A0, sandboxOldFunctionCpuState.a0);
m68k_set_reg(M68K_REG_D0, sandboxOldFunctionCpuState.d0);
}
static uint32_t sandboxCallGuestFunction(bool fallthrough, uint32_t address, uint16_t trap, const char* prototype, ...){
//prototype is a Java style function signature describing values passed and returned "v(wllp)"
//is return void and pass a uint16_t(word), 2 uint32_t(long) and 1 pointer
//valid types are b(yte), w(ord), l(ong), p(ointer) and v(oid), a capital letter means its a return pointer
//EvtGetPen v(WWB) returns nothing but writes back to the calling function with 3 pointers,
//these are allocated in the bootloader area and interpreted to host pointers on return
va_list args;
const char* params = prototype + 2;
uint32_t stackFrameStart = m68k_get_reg(NULL, M68K_REG_SP);
uint32_t newStackFrameSize = sandboxGetStackFrameSize(prototype);
uint32_t stackWriteAddr = stackFrameStart - newStackFrameSize;
uint32_t oldStopped = m68ki_cpu.stopped;
uint32_t functionReturn = 0x00000000;
return_pointer_t functionReturnPointers[10];
uint8_t functionReturnPointerIndex = 0;
uint32_t callWriteOut = 0xFFFFFFE0;
uint32_t callStart;
uint8_t count;
sandboxBackupCpuState();
va_start(args, prototype);
while(*params != ')'){
switch(*params){
case 'v':
case 'V':
//do nothing, this is wrong for "V"
break;
case 'b':
//bytes are 16 bits long on the stack due to memory alignment restrictions
//bytes are written to the top byte of there word
m68k_write_memory_8(stackWriteAddr, va_arg(args, uint32_t));
stackWriteAddr += 2;
break;
case 'w':
m68k_write_memory_16(stackWriteAddr, va_arg(args, uint32_t));
stackWriteAddr += 2;
break;
case 'l':
case 'p':
m68k_write_memory_32(stackWriteAddr, va_arg(args, uint32_t));
stackWriteAddr += 4;
break;
//return pointer values
case 'B':
functionReturnPointers[functionReturnPointerIndex].hostPointer = va_arg(args, void*);
functionReturnPointers[functionReturnPointerIndex].emuPointer = callWriteOut;
functionReturnPointers[functionReturnPointerIndex].bytes = 1;
m68k_write_memory_32(stackWriteAddr, functionReturnPointers[functionReturnPointerIndex].emuPointer);
stackWriteAddr += 4;
callWriteOut += 4;
functionReturnPointerIndex++;
break;
case 'W':
functionReturnPointers[functionReturnPointerIndex].hostPointer = va_arg(args, void*);
functionReturnPointers[functionReturnPointerIndex].emuPointer = callWriteOut;
functionReturnPointers[functionReturnPointerIndex].bytes = 2;
m68k_write_memory_32(stackWriteAddr, functionReturnPointers[functionReturnPointerIndex].emuPointer);
stackWriteAddr += 4;
callWriteOut += 4;
functionReturnPointerIndex++;
break;
case 'L':
case 'P':
functionReturnPointers[functionReturnPointerIndex].hostPointer = va_arg(args, void*);
functionReturnPointers[functionReturnPointerIndex].emuPointer = callWriteOut;
functionReturnPointers[functionReturnPointerIndex].bytes = 4;
m68k_write_memory_32(stackWriteAddr, functionReturnPointers[functionReturnPointerIndex].emuPointer);
stackWriteAddr += 4;
callWriteOut += 4;
functionReturnPointerIndex++;
break;
}
params++;
}
//write to the bootloader memory, its not important when debugging
callStart = callWriteOut;
if(address){
//direct jump handler, used for private APIs
m68k_write_memory_16(callWriteOut, 0x4EB9);//jump to subroutine opcode
callWriteOut += 2;
m68k_write_memory_32(callWriteOut, address);
callWriteOut += 4;
}
else{
//OS function handler
m68k_write_memory_16(callWriteOut, 0x4E4F);//trap f opcode
callWriteOut += 2;
m68k_write_memory_16(callWriteOut, trap);
callWriteOut += 2;
}
//end execution with CMD_EXECUTION_DONE
m68k_write_memory_16(callWriteOut, 0x23FC);//move.l data imm to address at imm2 opcode
callWriteOut += 2;
m68k_write_memory_32(callWriteOut, CMD_DEBUG_EXEC_END);
callWriteOut += 4;
m68k_write_memory_32(callWriteOut, EMU_REG_ADDR(EMU_CMD));
callWriteOut += 4;
sandboxActive = true;
if(fallthrough)
sandboxControlHandoff = true;
else
m68ki_cpu.stopped = 0;
m68k_set_reg(M68K_REG_SP, stackFrameStart - newStackFrameSize);
m68k_set_reg(M68K_REG_PC, callStart);
if(!fallthrough){
while(sandboxActive)
m68k_execute(1);//m68k_execute() always runs requested cycles + extra cycles of the final opcode, this executes 1 opcode
if(prototype[0] == 'p')
functionReturn = m68k_get_reg(NULL, M68K_REG_A0);
else if(prototype[0] == 'b' || prototype[0] == 'w' || prototype[0] == 'l')
functionReturn = m68k_get_reg(NULL, M68K_REG_D0);
m68ki_cpu.stopped = oldStopped;
sandboxRestoreCpuState();
//remap all argument pointers
for(count = 0; count < functionReturnPointerIndex; count++){
switch(functionReturnPointers[count].bytes){
case 1:
*(uint8_t*)functionReturnPointers[count].hostPointer = m68k_read_memory_8(functionReturnPointers[count].emuPointer);
break;
case 2:
*(uint16_t*)functionReturnPointers[count].hostPointer = m68k_read_memory_16(functionReturnPointers[count].emuPointer);
break;
case 4:
*(uint32_t*)functionReturnPointers[count].hostPointer = m68k_read_memory_32(functionReturnPointers[count].emuPointer);
break;
}
}
}
va_end(args);
return functionReturn;
}
void sandboxInit(void){
//nothing to init yet
}
void sandboxReset(void){
sandboxActive = false;
sandboxControlHandoff = false;
sandboxFramesRan = 0;
memset(sandboxWatchRegions, 0x00, sizeof(sandboxWatchRegions));
sandboxWatchRegionsActive = 0;
//patch OS here if needed
sandboxCommand(SANDBOX_CMD_PATCH_OS, NULL);
//log all register accesses
//sandboxCommand(SANDBOX_CMD_REGISTER_WATCH_ENABLE, NULL);
//monitor for strange jumps
sandboxSetWatchRegion(0x00000000, 0xFFFFFFFE, SANDBOX_WATCH_CODE);
}
uint32_t sandboxStateSize(void){
uint32_t size = 0;
size += sizeof(uint8_t) * 3;//sandboxActive / sandboxControlHandoff / sandboxCurrentCpuArch
size += sizeof(uint32_t) * 4;//sandboxOldFunctionCpuState.(sp/pc/a0/d0)
size += sizeof(uint16_t);//sandboxOldFunctionCpuState.sr
size += sizeof(uint64_t);//sandboxFramesRan
size += sizeof(uint32_t) * 2 * SANDBOX_MAX_WATCH_REGIONS;//sandboxWatchRegions.(address/size)
size += sizeof(uint8_t) * SANDBOX_MAX_WATCH_REGIONS;//sandboxWatchRegions.type
size += sizeof(uint16_t);//sandboxWatchRegionsActive
return size;
}
void sandboxSaveState(uint8_t* data){
uint32_t offset = 0;
uint16_t index;
writeStateValue8(data + offset, sandboxActive);
offset += sizeof(uint8_t);
writeStateValue8(data + offset, sandboxControlHandoff);
offset += sizeof(uint8_t);
writeStateValue8(data + offset, sandboxCurrentCpuArch);//currently cant be ARMv5 during a frame boundry but that may change
offset += sizeof(uint8_t);
writeStateValue32(data + offset, sandboxOldFunctionCpuState.sp);
offset += sizeof(uint32_t);
writeStateValue32(data + offset, sandboxOldFunctionCpuState.pc);
offset += sizeof(uint32_t);
writeStateValue16(data + offset, sandboxOldFunctionCpuState.sr);
offset += sizeof(uint16_t);
writeStateValue32(data + offset, sandboxOldFunctionCpuState.a0);
offset += sizeof(uint32_t);
writeStateValue32(data + offset, sandboxOldFunctionCpuState.d0);
offset += sizeof(uint32_t);
writeStateValue64(data + offset, sandboxFramesRan);
offset += sizeof(uint64_t);
for(index = 0; index < SANDBOX_MAX_WATCH_REGIONS; index++){
writeStateValue32(data + offset, sandboxWatchRegions[index].address);
offset += sizeof(uint32_t);
writeStateValue32(data + offset, sandboxWatchRegions[index].size);
offset += sizeof(uint32_t);
writeStateValue8(data + offset, sandboxWatchRegions[index].type);
offset += sizeof(uint8_t);
}
writeStateValue16(data + offset, sandboxWatchRegionsActive);
offset += sizeof(uint16_t);
}
void sandboxLoadState(uint8_t* data){
uint32_t offset = 0;
uint16_t index;
sandboxActive = readStateValue8(data + offset);
offset += sizeof(uint8_t);
sandboxControlHandoff = readStateValue8(data + offset);
offset += sizeof(uint8_t);
sandboxCurrentCpuArch = readStateValue8(data + offset);//currently cant be ARMv5 during a frame boundry but that may change
offset += sizeof(uint8_t);
sandboxOldFunctionCpuState.sp = readStateValue32(data + offset);
offset += sizeof(uint32_t);
sandboxOldFunctionCpuState.pc = readStateValue32(data + offset);
offset += sizeof(uint32_t);
sandboxOldFunctionCpuState.sr = readStateValue16(data + offset);
offset += sizeof(uint16_t);
sandboxOldFunctionCpuState.a0 = readStateValue32(data + offset);
offset += sizeof(uint32_t);
sandboxOldFunctionCpuState.d0 = readStateValue32(data + offset);
offset += sizeof(uint32_t);
sandboxFramesRan = readStateValue64(data + offset);
offset += sizeof(uint64_t);
for(index = 0; index < SANDBOX_MAX_WATCH_REGIONS; index++){
sandboxWatchRegions[index].address = readStateValue32(data + offset);
offset += sizeof(uint32_t);
sandboxWatchRegions[index].size = readStateValue32(data + offset);
offset += sizeof(uint32_t);
sandboxWatchRegions[index].type = readStateValue8(data + offset);
offset += sizeof(uint8_t);
}
sandboxWatchRegionsActive = readStateValue16(data + offset);
offset += sizeof(uint16_t);
}
uint32_t sandboxCommand(uint32_t command, void* data){
uint32_t result = EMU_ERROR_NONE;
//tests cant run properly(they hang forever) unless the debug return hook is enabled, it also completly destroys accuracy to execute hacked in asm buffers
if(!(palmEmuFeatures.info & FEATURE_DEBUG))
return EMU_ERROR_NOT_IMPLEMENTED;
debugLog("Sandbox: Command %d started\n", command);
switch(command){
case SANDBOX_CMD_PATCH_OS:{
//double dynamic heap size, verified working
//HwrCalcDynamicRAMSize_10005CC6:
//HwrCalcDynamicRAMSize_10083B0A:
//patchOsRom(0x5CC6, "203C000800004E75");//move.l 0x80000, d0; rts
//patchOsRom(0x83B0A, "203C000800004E75");//move.l 0x80000, d0; rts
//patchOsRom(0x5CC6, "203C001000004E75");//move.l 0x100000, d0; rts
//patchOsRom(0x83B0A, "203C001000004E75");//move.l 0x100000, d0; rts
//patch PrvChunkNew to only allocate in 4 byte intervals
//PrvChunkNew_10020CBC:
//TODO
//set RAM to 32MB
//patchOsRom(0x2C5E, "203C020000004E75");//move.l 0x2000000, d0; rts
//patchOsRom(0x8442E, "203C020000004E75");//move.l 0x2000000, d0; rts
//set RAM to 128MB
//PrvGetRAMSize_10002C5E, small ROM
//PrvGetRAMSize_1008442E, big ROM
//patchOsRom(0x2C5E, "203C080000004E75");//move.l 0x8000000, d0; rts
//patchOsRom(0x8442E, "203C080000004E75");//move.l 0x8000000, d0; rts
//ROM:100219D0 move.l #unk_FFFFFF,d0
//patchOsRom(0x219D0, "203C01FFFFFF4E75");//move.l 0x1FFFFFF, d0; rts
//bus error at 0x1001DEDA when 128MB is present
//0x55 memory filler, 32 bit, at PC:0x1001FFDC, and of course, its MemSet, need a stack trace now
//PrvInitHeapPtr_10021908 sets up the 0x55 stuff in RAM
/*
ROM:100219C2 move.l d0,6(a4) ; Move Data from Source to Destination
ROM:100219C6 tst.b arg_A(a6) ; Test an Operand
ROM:100219CA beq.s loc_100219EA ; Branch if Equal
ROM:100219CC move.b #$55,-(sp) ; 'U' ; Move Data from Source to Destination
ROM:100219D0 move.l #unk_FFFFFF,d0 ; Move Data from Source to Destination
ROM:100219D6 and.l (a3),d0 ; AND Logical
ROM:100219D8 subq.l #8,d0 ; Subtract Quick
ROM:100219DA move.l d0,-(sp) ; Move Data from Source to Destination
ROM:100219DC movea.l a3,a0 ; Move Address
ROM:100219DE pea 8(a0) ; Push Effective Address
ROM:100219E2 trap #$F ; Trap sysTrapMemSet
ROM:100219E2 dc.w $A027
ROM:100219E6 lea $A(sp),sp ; Load Effective Address
*/
//D0 is 0x3E1CC(254412) at PC:0x10021988
// Add the heap, as long as it's not the dynamic heap. During
// bootup, the memory initialization sequence goes like:
//
// if hard reset required:
// MemCardFormat
// lay out the card
// MemStoreInit
// for each heap
// MemHeapInit
// MemInit
// for each card:
// MemInitHeapTable
// for each dynamic heap:
// MemHeapInit
// for each RAM heap:
// Unlock all chunks
// Compact
//
// Which means that if there's no hard reset, MemHeapInit
// has not been called on the dynamic heap at the time
// MemInitHeapTable is called. And since the dynamic heap
// is currently in a corrupted state (because the boot stack
// and initial LCD buffer have been whapped over it), we
// can't perform the heap walk we'd normally do when adding
// a heap object.
//need to investigate what these vars are
/*
ROM:100148EE move.l #$3BE,(dword_15C).w ; Move Data from Source to Destination
ROM:100148F6 move.l #$422,(dword_112).w ; Move Data from Source to Destination
ROM:100148FE move.l #$890,(dword_11A).w ; Move Data from Source to Destination
ROM:10014906 move.l #$8CC,(TrapTablePointer).w ; Move Data from Source to Destination
ROM:1001490E move.w #$45A,(word_13E).w ; Move Data from Source to Destination
ROM:10014914 move.w #$1000,(word_28E).w ; Move Data from Source to Destination
*/
//may be able to use these to set the border colors like in OS 5
/*
RAM:00001758 dc.l UIColorInit_10074672
RAM:0000175C dc.l UIColorGetTableEntryIndex_100746F6
RAM:00001760 dc.l UIColorGetTableEntryRGB_1007472A
RAM:00001760 ; DATA XREF: ROM:101D66A1↓o
RAM:00001764 dc.l UIColorSetTableEntry_10074762
RAM:00001768 off_1768: dc.l UIColorPushTable_100747B0
*/
//another road block surfaces
/*
When a Palm Powered handheld is presented with multiple dynamic heaps,
the first heap (heap 0) on card 0 is the active dynamic heap.
All other potential dynamic heaps are ignored. For example, it
is possible that a future Palm Powered handheld supporting multiple
cards might be presented with two cards, each having its own dynamic heap;
if so, only the dynamic heap residing on card 0 would be active—the system
would not treat any heaps on other cards as dynamic heaps, nor would heap
IDs be assigned to these heaps. Subsequent storage heaps would be assigned
IDs in sequential order, as always beginning with RAM heaps, followed by ROM heaps.
*/
//this looks like the code that ensures heap alignment sizes are correct, if I force everything to a multiple of 4 that may fix ARM behavior
/*
ROM:10020D04 moveq #1,d0 ; Move Quick
ROM:10020D06 and.l size(a6),d0 ; Check if size is a multiple of 2
ROM:10020D0A addq.w #2,sp ; Add Quick
ROM:10020D0C beq.s loc_10020D16 ; Skip adding extra byte if already aligned
ROM:10020D0E moveq #9,d0 ; Size is not 16 bit aligned, align and add 8 bytes
ROM:10020D10 add.l size(a6),d0 ; Add requested size
ROM:10020D14 bra.s loc_10020D1C ; Branch Always
ROM:10020D16 ; ---------------------------------------------------------------------------
ROM:10020D16
ROM:10020D16 loc_10020D16: ; CODE XREF: PrvChunkNew_10020CBC+50↑j
ROM:10020D16 move.l size(a6),d0 ; Set requested size
ROM:10020D1A addq.l #8,d0 ; Add 8 bytes(currently dont know what there for)
ROM:10020D1C
*/
//align memory chunks to 4 instead of 2, 24 / 0x18 bytes
//patchOsRom(0x20D04, "202E000AC0BC0000000367000006528060F25080544F4E71");
patchOsRom(0x20D04, "202E000AC0BCFFFFFFFC50805080544F4E714E714E714E71");//adds an extra 4 bytes regardless
//patchOsRom(0x20D04, "202E000AC0BCFFFFFFFCB0AE000A6700000458805080544F");//adds an extra 4 bytes if & 0x00000003 is true
//this seems to be successfuly aligning things, its not :(
//align by 2, patch test
//patchOsRom(0x20D04, "202E000AC0BC0000000167000006528060F25080544F4E71");
//patchOsRom(0x20D04, "202E000AC0BCFFFFFFFE50805080544F4E714E714E714E71");
}
break;
case SANDBOX_CMD_DEBUG_INSTALL_APP:{
buffer_t* app = (buffer_t*)data;
bool success = installResourceToDevice(*app);
if(!success)
result = EMU_ERROR_OUT_OF_MEMORY;
}
break;
case SANDBOX_CMD_REGISTER_WATCH_ENABLE:{
sandboxSetWatchRegion(0xFFFFF000, 0xFFF, SANDBOX_WATCH_DATA);//0x1000 will cause an overflow to 0x00000000 making it never trigger
}
break;
case SANDBOX_CMD_TEST_MEMORY_ALIGNMENT:{
checkMemoryAlignment(0);//RAM heap
checkMemoryAlignment(1);//storage heap
}
break;
default:
break;
}
debugLog("Sandbox: Command %d finished\n", command);
return result;
}
void sandboxOnFrameRun(void){
//run at the end of every frame
sandboxFramesRan++;
if(sandboxFramesRan == SANDBOX_SECONDS_TO_FRAMES(10)){
sandboxCommand(SANDBOX_CMD_TEST_MEMORY_ALIGNMENT, NULL);
}
}
void sandboxOnOpcodeRun(void){
if(sandboxRunning()){
#if defined(EMU_SANDBOX_LOG_JUMPS)
log68kJumps();
#endif
#if defined(EMU_SANDBOX_LOG_APIS)
logApiCalls();
#endif
}
switch(m68k_get_reg(NULL, M68K_REG_PC)){//switched this from PPC to PC
//case 0x10083652://USB issue location //address based on PPC
//case 0x100846C0://HwrDelay, before mysterious jump //address based on PPC
//case 0x100846CC://HwrDelay, after mysterious jump //address based on PPC
//case 0x100AD514://HwrSpiSdioInterrupts, SD card interrupt handler
case 0x10021988://just before "andi.l #unk_FFFFFF, d0"
//to add a emulator breakpoint add a new line above here|^^^
{
uint32_t m68kRegisters[M68K_REG_CPU_TYPE];
uint8_t count;
for(count = 0; count < M68K_REG_CPU_TYPE; count++)
m68kRegisters[count] = m68k_get_reg(NULL, count);
/*
register order, read m68kRegisters with debugger
M68K_REG_D0,
M68K_REG_D1,
M68K_REG_D2,
M68K_REG_D3,
M68K_REG_D4,
M68K_REG_D5,
M68K_REG_D6,
M68K_REG_D7,
M68K_REG_A0,
M68K_REG_A1,
M68K_REG_A2,
M68K_REG_A3,
M68K_REG_A4,
M68K_REG_A5,
M68K_REG_A6,
M68K_REG_A7,
M68K_REG_PC,
M68K_REG_SR,
M68K_REG_SP,
M68K_REG_USP,
M68K_REG_ISP,
M68K_REG_MSP,
M68K_REG_SFC,
M68K_REG_DFC,
M68K_REG_VBR,
M68K_REG_CACR,
M68K_REG_CAAR,
M68K_REG_PREF_ADDR,
M68K_REG_PREF_DATA,
M68K_REG_PPC,
M68K_REG_IR
*/
//set host breakpoint here|vvv
if(true){
bool breakpoint = true;
}
//set host breakpoint here|^^^
}
break;
}
}
void sandboxOnMemoryAccess(uint32_t address, uint8_t size, bool write, uint32_t value){
static bool isRecursive = false;
//this function can read or write Palm memory, which calls this function, so dont run this function when calling from this function to prevent an infinte loop
if(!isRecursive){
isRecursive = true;
{//actual code goes below:vvv
bool sandboxedMemory = false;
uint16_t memRegion;
for(memRegion = 0; memRegion < sandboxWatchRegionsActive; memRegion++){
if(sandboxWatchRegions[memRegion].type == SANDBOX_WATCH_DATA && address >= sandboxWatchRegions[memRegion].address && address < sandboxWatchRegions[memRegion].address + sandboxWatchRegions[memRegion].size){
sandboxedMemory = true;
break;
}
}
if(sandboxedMemory || sandboxRunning()){
uint32_t pc = getCurrentCpuPreviousPc();
char* function = sandboxCurrentCpuArch == SANDBOX_CPU_ARCH_M68K ? getFunctionName68k(pc) : sandboxCurrentCpuArch == SANDBOX_CPU_ARCH_ARMV5 ? getFunctionNameArmv5(pc) : getFunctionNameThumb(pc);
bool functionValid = !!function;
if(!functionValid)
function = "NAME NOT FOUND";
if(address >= chips[CHIP_DX_RAM].start && address < chips[CHIP_DX_RAM].start + 0x10000){
//low mem globals
address &= 0xFFFF;
if(write)
debugLog("Writing low mem global: name:%s/global:0x%08X, size:%d, value:0x%08X, function:%s/PC:0x%08X\n", getLowMemGlobalName(address), address, size, value, function, pc);
else
debugLog("Reading low mem global: name:%s/global:0x%08X, size:%d, function:%s/PC:0x%08X\n", getLowMemGlobalName(address), address, size, function, pc);
}
else if(address >= 0xFFFFF000){
//hardware registers
if(address >= 0xFFFFFE00){
//bootloader area
if(write){
if(address >= 0xFFFFFFC0)
debugLog("Writing bootloader area(valid): address:0x%08X, size:%d, function:%s/PC:0x%08X\n", address, size, function, pc);
else
debugLog("Writing bootloader area(invalid): address:0x%08X, size:%d, function:%s/PC:0x%08X\n", address, size, function, pc);
}
else{
debugLog("Reading bootloader area: address:0x%08X, size:%d, function:%s/PC:0x%08X\n", address, size, function, pc);
}
}
else{
//normal regs
const char* registerName = "UNKNOWN";
//TODO: get register name list
if(write)
debugLog("Writing hardware register: name:%s/address:0x%08X, size:%d, value:0x%08X, function:%s/PC:0x%08X\n", registerName, address, size, value, function, pc);
else
debugLog("Reading hardware register: name:%s/address:0x%08X, size:%d, function:%s/PC:0x%08X\n", registerName, address, size, function, pc);
}
}
else if(address >= chips[CHIP_B0_SED].start && address < chips[CHIP_B0_SED].start + chips[CHIP_B0_SED].lineSize){
//SED1376
if(address & SED1376_MR_BIT){
//SED1376 data
if(write)
debugLog("Writing SED1376 buffer: address:0x%08X, size:%d, value:0x%08X, function:%s/PC:0x%08X\n", address, size, value, function, pc);
else
debugLog("Reading SED1376 buffer: address:0x%08X, size:%d, function:%s/PC:0x%08X\n", address, size, function, pc);
}
else{
//SED1376 register, these are 8 bit only
if(write)
debugLog("Writing SED1376 register: address:0x%08X, value:0x%02X, function:%s/PC:0x%08X\n", address, value & 0xFF, function, pc);
else
debugLog("Reading SED1376 register: address:0x%08X, function:%s/PC:0x%08X\n", address, function, pc);
}
}
else{
//everywhere else
if(write)
debugLog("Writing: address:0x%08X, size:%d, value:0x%08X, function:%s/PC:0x%08X\n", address, size, value, function, pc);
else
debugLog("Reading: address:0x%08X, size:%d, function:%s/PC:0x%08X\n", address, size, function, pc);
}
if(functionValid)
free(function);
}
}//actual code goes above:^^^
isRecursive = false;
}
}
bool sandboxRunning(void){
uint32_t pc = getCurrentCpuPc();
uint16_t memRegion;
//this is used to capture full logs when running from specific locations
for(memRegion = 0; memRegion < sandboxWatchRegionsActive; memRegion++){
if(sandboxWatchRegions[memRegion].type == SANDBOX_WATCH_CODE && pc >= sandboxWatchRegions[memRegion].address && pc < sandboxWatchRegions[memRegion].address + sandboxWatchRegions[memRegion].size)
return true;
}
return sandboxActive;
}
void sandboxReturn(void){
sandboxActive = false;
if(sandboxControlHandoff){
//control was just handed back to the host
sandboxControlHandoff = false;
sandboxRestoreCpuState();
debugLog("Sandbox: Control returned to host\n");
}
}
uint16_t sandboxSetWatchRegion(uint32_t address, uint32_t size, uint8_t type){
uint16_t index;
//invalid type
if(type == SANDBOX_WATCH_NONE || type >= SANDBOX_WATCH_TOTAL_TYPES)
return 0xFFFF;
//cant watch 0 sized memory area
if(size < 1)
return 0xFFFF;
//try to use old watch region if available
for(index = 0; index < sandboxWatchRegionsActive; index++){
if(sandboxWatchRegions[index].type == SANDBOX_WATCH_NONE){
//found reusable region
sandboxWatchRegions[index].address = address;
sandboxWatchRegions[index].size = size;
sandboxWatchRegions[index].type = type;
//return watch region reference, this doesnt change until the region is deleted
return index;
}
}
//check if new region is available
if(sandboxWatchRegionsActive < SANDBOX_MAX_WATCH_REGIONS){
//use new region
sandboxWatchRegions[sandboxWatchRegionsActive].address = address;
sandboxWatchRegions[sandboxWatchRegionsActive].size = size;
sandboxWatchRegions[sandboxWatchRegionsActive].type = type;
sandboxWatchRegionsActive++;
//return watch region reference, this doesnt change until the region is deleted
return sandboxWatchRegionsActive - 1;
}
//no regions left, error
return 0xFFFF;
}
void sandboxClearWatchRegion(uint16_t index){
if(index < sandboxWatchRegionsActive){
if(index == sandboxWatchRegionsActive - 1)
sandboxWatchRegionsActive--;//remove from end
else
sandboxWatchRegions[index].type = SANDBOX_WATCH_NONE;//mark as empty
}
}
void sandboxSetCpuArch(uint8_t arch){
sandboxCurrentCpuArch = arch;
}
#else
void sandboxInit(void){}
void sandboxReset(void){}
uint32_t sandboxStateSize(void){return 0;}
void sandboxSaveState(uint8_t* data){}
void sandboxLoadState(uint8_t* data){}
uint32_t sandboxCommand(uint32_t command, void* data){return 0;}
void sandboxOnFrameRun(void){}
void sandboxOnOpcodeRun(void){}
void sandboxOnMemoryAccess(uint32_t address, uint8_t size, bool write, uint32_t value){}
bool sandboxRunning(void){return false;}
void sandboxReturn(void){}
uint16_t sandboxSetWatchRegion(uint32_t address, uint32_t size, uint8_t type){return 0;}
void sandboxClearWatchRegion(uint16_t index){}
void sandboxSetCpuArch(uint8_t arch){}
#endif