273 lines
9.1 KiB
C
273 lines
9.1 KiB
C
/*
|
|
* test_mupen64plus_0001.c
|
|
*
|
|
* Unit test for mupen64plus-0001: lookup_breakpoint() O(N) linear scan per
|
|
* CPU instruction replaced with O(log N) bsearch over a sorted exec-address
|
|
* index.
|
|
*
|
|
* Reproduces the defect and validates the fix without requiring the full
|
|
* mupen64plus build environment.
|
|
*/
|
|
|
|
#include <stdio.h>
|
|
#include <stdint.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <assert.h>
|
|
|
|
/* ---- minimal reproduction of breakpoint structures ---- */
|
|
|
|
#define BREAKPOINTS_MAX_NUMBER 128
|
|
|
|
#define M64P_BKP_FLAG_ENABLED 0x01
|
|
#define M64P_BKP_FLAG_EXEC 0x02
|
|
#define M64P_BKP_FLAG_READ 0x04
|
|
#define M64P_BKP_FLAG_WRITE 0x08
|
|
#define M64P_BKP_FLAG_LOG 0x10
|
|
|
|
#define BPT_CHECK_FLAG(b,f) ((b).flags & (f))
|
|
#define BPT_SET_FLAG(b,f) ((b).flags |= (f))
|
|
#define BPT_CLEAR_FLAG(b,f) ((b).flags &= ~(f))
|
|
|
|
typedef struct {
|
|
uint32_t address;
|
|
uint32_t endaddr;
|
|
uint32_t flags;
|
|
} m64p_breakpoint;
|
|
|
|
static int g_NumBreakpoints = 0;
|
|
static m64p_breakpoint g_Breakpoints[BREAKPOINTS_MAX_NUMBER];
|
|
|
|
/* ---- O(N) original implementation ---- */
|
|
|
|
static int lookup_breakpoint_linear(uint32_t address, uint32_t size, uint32_t flags)
|
|
{
|
|
int i;
|
|
uint64_t endaddr = (uint64_t)address + (uint64_t)size - 1;
|
|
|
|
for (i = 0; i < g_NumBreakpoints; i++) {
|
|
if ((g_Breakpoints[i].flags & flags) == flags) {
|
|
if (g_Breakpoints[i].endaddr < g_Breakpoints[i].address) {
|
|
if ((endaddr >= g_Breakpoints[i].address) ||
|
|
(address <= g_Breakpoints[i].endaddr))
|
|
return i;
|
|
} else {
|
|
if ((endaddr >= g_Breakpoints[i].address) &&
|
|
(address <= g_Breakpoints[i].endaddr))
|
|
return i;
|
|
}
|
|
}
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
static int check_breakpoints_linear(uint32_t address)
|
|
{
|
|
return lookup_breakpoint_linear(address, 1,
|
|
M64P_BKP_FLAG_ENABLED | M64P_BKP_FLAG_EXEC);
|
|
}
|
|
|
|
/* ---- O(log N) patched implementation ---- */
|
|
|
|
static uint32_t g_ExecBpAddrs[BREAKPOINTS_MAX_NUMBER];
|
|
static int g_NumExecBpAddrs = 0;
|
|
|
|
static int cmp_u32(const void *a, const void *b)
|
|
{
|
|
uint32_t ua = *(const uint32_t *)a;
|
|
uint32_t ub = *(const uint32_t *)b;
|
|
if (ua < ub) return -1;
|
|
if (ua > ub) return 1;
|
|
return 0;
|
|
}
|
|
|
|
static void rebuild_exec_bp_index(void)
|
|
{
|
|
int i;
|
|
g_NumExecBpAddrs = 0;
|
|
for (i = 0; i < g_NumBreakpoints; i++) {
|
|
if (BPT_CHECK_FLAG(g_Breakpoints[i], M64P_BKP_FLAG_ENABLED) &&
|
|
BPT_CHECK_FLAG(g_Breakpoints[i], M64P_BKP_FLAG_EXEC) &&
|
|
g_Breakpoints[i].address == g_Breakpoints[i].endaddr) {
|
|
g_ExecBpAddrs[g_NumExecBpAddrs++] = g_Breakpoints[i].address;
|
|
}
|
|
}
|
|
if (g_NumExecBpAddrs > 1)
|
|
qsort(g_ExecBpAddrs, g_NumExecBpAddrs, sizeof(uint32_t), cmp_u32);
|
|
}
|
|
|
|
static int check_breakpoints_bsearch(uint32_t address)
|
|
{
|
|
if (g_NumExecBpAddrs > 0) {
|
|
uint32_t *found = (uint32_t *)bsearch(&address, g_ExecBpAddrs,
|
|
g_NumExecBpAddrs,
|
|
sizeof(uint32_t), cmp_u32);
|
|
if (found != NULL)
|
|
return lookup_breakpoint_linear(address, 1,
|
|
M64P_BKP_FLAG_ENABLED | M64P_BKP_FLAG_EXEC);
|
|
return -1;
|
|
}
|
|
return lookup_breakpoint_linear(address, 1,
|
|
M64P_BKP_FLAG_ENABLED | M64P_BKP_FLAG_EXEC);
|
|
}
|
|
|
|
/* ---- helpers ---- */
|
|
|
|
static void reset_breakpoints(void)
|
|
{
|
|
g_NumBreakpoints = 0;
|
|
memset(g_Breakpoints, 0, sizeof(g_Breakpoints));
|
|
rebuild_exec_bp_index();
|
|
}
|
|
|
|
static int add_exec_bp(uint32_t address)
|
|
{
|
|
int idx = g_NumBreakpoints++;
|
|
g_Breakpoints[idx].address = address;
|
|
g_Breakpoints[idx].endaddr = address;
|
|
BPT_SET_FLAG(g_Breakpoints[idx], M64P_BKP_FLAG_ENABLED | M64P_BKP_FLAG_EXEC);
|
|
rebuild_exec_bp_index();
|
|
return idx;
|
|
}
|
|
|
|
/* Count iterations of linear scan for a given address */
|
|
static int linear_iterations(uint32_t address)
|
|
{
|
|
int i, count = 0;
|
|
uint32_t flags = M64P_BKP_FLAG_ENABLED | M64P_BKP_FLAG_EXEC;
|
|
for (i = 0; i < g_NumBreakpoints; i++) {
|
|
count++;
|
|
if ((g_Breakpoints[i].flags & flags) == flags &&
|
|
g_Breakpoints[i].address == address &&
|
|
g_Breakpoints[i].endaddr == address)
|
|
return count;
|
|
}
|
|
return count; /* not found */
|
|
}
|
|
|
|
int main(void)
|
|
{
|
|
int i, lin, bsrch;
|
|
int tests = 0, pass = 0;
|
|
|
|
/* Test 1: empty breakpoint list -- both return -1 */
|
|
printf("Test 1: empty list returns -1\n");
|
|
reset_breakpoints();
|
|
tests++; if (check_breakpoints_linear(0x80000000) == -1) pass++;
|
|
else printf(" FAIL linear\n");
|
|
tests++; if (check_breakpoints_bsearch(0x80000000) == -1) pass++;
|
|
else printf(" FAIL bsearch\n");
|
|
printf(" %d/%d\n", pass, tests);
|
|
assert(pass == tests);
|
|
|
|
/* Test 2: single breakpoint hit */
|
|
printf("Test 2: single breakpoint hit\n");
|
|
reset_breakpoints();
|
|
add_exec_bp(0x80001000);
|
|
tests++; lin = check_breakpoints_linear(0x80001000); if (lin >= 0) pass++; else printf(" FAIL linear hit\n");
|
|
tests++; bsrch = check_breakpoints_bsearch(0x80001000); if (bsrch >= 0) pass++; else printf(" FAIL bsearch hit\n");
|
|
printf(" linear=%d bsearch=%d\n", lin, bsrch);
|
|
assert(lin == bsrch);
|
|
|
|
/* Test 3: single breakpoint miss */
|
|
printf("Test 3: single breakpoint miss\n");
|
|
tests++; lin = check_breakpoints_linear(0x80002000); if (lin == -1) pass++; else printf(" FAIL linear miss\n");
|
|
tests++; bsrch = check_breakpoints_bsearch(0x80002000); if (bsrch == -1) pass++; else printf(" FAIL bsearch miss\n");
|
|
assert(lin == bsrch);
|
|
printf(" PASS\n");
|
|
|
|
/* Test 4: N breakpoints -- correctness for all addresses */
|
|
printf("Test 4: N=10 breakpoints, correctness for all addresses\n");
|
|
{
|
|
uint32_t addrs[10] = {
|
|
0x80000100, 0x80001000, 0x80002000, 0x80003000, 0x80004000,
|
|
0x80005000, 0x80006000, 0x80007000, 0x80008000, 0x80009000
|
|
};
|
|
reset_breakpoints();
|
|
for (i = 0; i < 10; i++)
|
|
add_exec_bp(addrs[i]);
|
|
|
|
/* check each known address */
|
|
for (i = 0; i < 10; i++) {
|
|
lin = check_breakpoints_linear(addrs[i]);
|
|
bsrch = check_breakpoints_bsearch(addrs[i]);
|
|
tests++;
|
|
if (lin >= 0 && bsrch >= 0) pass++;
|
|
else printf(" FAIL addr=0x%08x lin=%d bsrch=%d\n", addrs[i], lin, bsrch);
|
|
}
|
|
|
|
/* check unknown addresses */
|
|
uint32_t unknown[] = { 0x00000000, 0x80000200, 0x80000FFF, 0xFFFFFFFF };
|
|
for (i = 0; i < 4; i++) {
|
|
lin = check_breakpoints_linear(unknown[i]);
|
|
bsrch = check_breakpoints_bsearch(unknown[i]);
|
|
tests++;
|
|
if (lin == -1 && bsrch == -1) pass++;
|
|
else printf(" FAIL unknown=0x%08x lin=%d bsrch=%d\n", unknown[i], lin, bsrch);
|
|
}
|
|
printf(" %d/%d\n", pass, tests);
|
|
}
|
|
|
|
/* Test 5: O(log N) vs O(N) iteration reduction */
|
|
printf("Test 5: iteration count reduction\n");
|
|
{
|
|
int n = 128;
|
|
reset_breakpoints();
|
|
for (i = 0; i < n; i++)
|
|
add_exec_bp(0x80000000 + (uint32_t)(i * 4));
|
|
|
|
/* worst-case linear: last breakpoint or miss */
|
|
int iter_last = linear_iterations(0x80000000 + (uint32_t)((n-1) * 4));
|
|
int iter_miss = linear_iterations(0xDEADBEEF);
|
|
int bsearch_iters = 0;
|
|
/* bsearch over 128 entries: ceil(log2(128)) = 7 */
|
|
{
|
|
int lo = 0, hi = g_NumExecBpAddrs - 1;
|
|
uint32_t target = 0x80000000 + (uint32_t)((n-1) * 4);
|
|
while (lo <= hi) {
|
|
bsearch_iters++;
|
|
int mid = lo + (hi - lo) / 2;
|
|
if (g_ExecBpAddrs[mid] == target) break;
|
|
else if (g_ExecBpAddrs[mid] < target) lo = mid + 1;
|
|
else hi = mid - 1;
|
|
}
|
|
}
|
|
|
|
printf(" N=%d: linear worst=%d iters, bsearch=%d iters, speedup=%.1fx\n",
|
|
n, iter_last, bsearch_iters,
|
|
(double)iter_last / (double)bsearch_iters);
|
|
|
|
assert(iter_last == n);
|
|
assert(iter_miss >= n);
|
|
assert(bsearch_iters <= 8); /* log2(128) = 7, allow 1 slack */
|
|
|
|
tests++; pass++;
|
|
}
|
|
|
|
/* Test 6: disable a breakpoint removes it from index */
|
|
printf("Test 6: disable removes from exec index\n");
|
|
{
|
|
reset_breakpoints();
|
|
add_exec_bp(0x80001000);
|
|
add_exec_bp(0x80002000);
|
|
|
|
/* Disable first bp */
|
|
BPT_CLEAR_FLAG(g_Breakpoints[0], M64P_BKP_FLAG_ENABLED);
|
|
rebuild_exec_bp_index();
|
|
|
|
tests++;
|
|
int r = check_breakpoints_bsearch(0x80001000);
|
|
if (r == -1) { pass++; printf(" PASS: disabled bp not found\n"); }
|
|
else printf(" FAIL: disabled bp still found idx=%d\n", r);
|
|
|
|
tests++;
|
|
r = check_breakpoints_bsearch(0x80002000);
|
|
if (r >= 0) { pass++; printf(" PASS: enabled bp still found idx=%d\n", r); }
|
|
else printf(" FAIL: enabled bp not found\n");
|
|
}
|
|
|
|
printf("\n%d/%d tests passed\n", pass, tests);
|
|
if (pass != tests) { printf("FAIL\n"); return 1; }
|
|
printf("PASS: O(log N) exec-breakpoint bsearch replaces O(N) linear scan\n");
|
|
return 0;
|
|
}
|