SPU Analyser: Detect invalid relative branches

This commit is contained in:
Eladash 2024-03-07 15:16:22 +02:00 committed by Elad.Ash
parent 8e15de5c83
commit d699db2d19
3 changed files with 56 additions and 7 deletions

View file

@ -1024,7 +1024,7 @@ void spu_cache::initialize(bool build_existing_cache)
}
}
if (new_entry != umax && !spu_thread::is_exec_code(new_entry, { reinterpret_cast<const u8*>(ls.data()), SPU_LS_SIZE }))
if (new_entry != umax && !spu_thread::is_exec_code(new_entry, { reinterpret_cast<const u8*>(ls.data()), SPU_LS_SIZE }, 0, true))
{
new_entry = umax;
}
@ -1033,7 +1033,7 @@ void spu_cache::initialize(bool build_existing_cache)
{
new_entry = start_new;
while (new_entry < next_func && (ls[start_new / 4] < 0x3fffc || !spu_thread::is_exec_code(new_entry, { reinterpret_cast<const u8*>(ls.data()), SPU_LS_SIZE })))
while (new_entry < next_func && (ls[start_new / 4] < 0x3fffc || !spu_thread::is_exec_code(new_entry, { reinterpret_cast<const u8*>(ls.data()), SPU_LS_SIZE }, 0, true)))
{
new_entry += 4;
}
@ -2281,13 +2281,13 @@ std::vector<u32> spu_thread::discover_functions(u32 base_addr, std::span<const u
calls.erase(std::remove_if(calls.begin(), calls.end(), [&](u32 caller)
{
// Check the validity of both the callee code and the following caller code
return !is_exec_code(caller, ls, base_addr) || !is_exec_code(caller + 4, ls, base_addr);
return !is_exec_code(caller, ls, base_addr, true) || !is_exec_code(caller + 4, ls, base_addr, true);
}), calls.end());
branches.erase(std::remove_if(branches.begin(), branches.end(), [&](u32 caller)
{
// Check the validity of the callee code
return !is_exec_code(caller, ls, base_addr);
return !is_exec_code(caller, ls, base_addr, true);
}), branches.end());
std::vector<u32> addrs;

View file

@ -4119,9 +4119,11 @@ bool spu_thread::check_mfc_interrupts(u32 next_pc)
return false;
}
bool spu_thread::is_exec_code(u32 addr, std::span<const u8> ls_ptr, u32 base_addr)
bool spu_thread::is_exec_code(u32 addr, std::span<const u8> ls_ptr, u32 base_addr, bool avoid_dead_code)
{
for (u32 i = 0; i < 30; i++)
bool had_conditional = false;
for (u32 i = 0; i < 40; i++)
{
if (addr & ~0x3FFFC)
{
@ -4179,6 +4181,51 @@ bool spu_thread::is_exec_code(u32 addr, std::span<const u8> ls_ptr, u32 base_add
break;
}
}
else
{
switch (type)
{
case spu_itype::BR:
case spu_itype::BRNZ:
case spu_itype::BRZ:
case spu_itype::BRHNZ:
case spu_itype::BRHZ:
case spu_itype::BRSL:
{
const s32 rel = bf_t<s32, 0, 18>::extract(static_cast<s32>(u32{op.i16} << 2));
if (rel == 0 && !had_conditional && avoid_dead_code)
{
// Infinite loop 100%, detect that as invalid code
return false;
}
// Detect "invalid" relative branches
// Branch offsets that, although are the only way to get X code address using relative address
// Rely on overflow/underflow of SPU memory bounds
// Thus they would behave differently if SPU LS memory size was to increase (evolving the CELL architecture was the original plan)
// Making them highly unlikely to be valid code
if (rel < 0)
{
if (addr < 0u - rel)
{
return false;
}
}
else if (SPU_LS_SIZE - addr <= rel + 0u)
{
return false;
}
break;
}
default:
{
break;
}
}
}
for (usz res_i = 1; res_i < results.size(); res_i++)
{
@ -4203,6 +4250,8 @@ bool spu_thread::is_exec_code(u32 addr, std::span<const u8> ls_ptr, u32 base_add
{
return false;
}
had_conditional = true;
}
addr = spu_branch_target(results[0]);

View file

@ -835,7 +835,7 @@ public:
void set_events(u32 bits);
void set_interrupt_status(bool enable);
bool check_mfc_interrupts(u32 next_pc);
static bool is_exec_code(u32 addr, std::span<const u8> ls_ptr, u32 base_addr = 0); // Only a hint, do not rely on it other than debugging purposes
static bool is_exec_code(u32 addr, std::span<const u8> ls_ptr, u32 base_addr = 0, bool avoid_dead_code = false); // Only a hint, do not rely on it other than debugging purposes
static std::vector<u32> discover_functions(u32 base_addr, std::span<const u8> ls, bool is_known_addr, u32 /*entry*/);
u32 get_ch_count(u32 ch);
s64 get_ch_value(u32 ch);