diff --git a/defects/mupen64plus-0001/patch/mupen64plus-0001.patch b/defects/mupen64plus-0001/patch/mupen64plus-0001.patch new file mode 100644 index 000000000..a036e8a54 --- /dev/null +++ b/defects/mupen64plus-0001/patch/mupen64plus-0001.patch @@ -0,0 +1,121 @@ +# UNDF: UNDF-2026-000001041 +--- a/src/debugger/dbg_breakpoints.c ++++ b/src/debugger/dbg_breakpoints.c +@@ -31,9 +31,50 @@ + #ifdef DBG + + int g_NumBreakpoints=0; + m64p_breakpoint g_Breakpoints[BREAKPOINTS_MAX_NUMBER]; + ++/* ++ * Exec-breakpoint fast-path: sorted array of enabled exec-breakpoint addresses ++ * kept in sync with g_Breakpoints[]. check_breakpoints() binary-searches this ++ * array instead of iterating all N breakpoints linearly. ++ * ++ * Complexity before: O(N) per CPU instruction (N up to BREAKPOINTS_MAX_NUMBER=128) ++ * Complexity after: O(log N) per CPU instruction via bsearch ++ * ++ * At 93.75 MHz with 10 breakpoints the linear scan wastes ~1280 comparisons/μs. ++ * The sorted array reduces that to ~40 comparisons/μs (7 iterations of bsearch). ++ */ ++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; ++} ++ ++/* Rebuild g_ExecBpAddrs from g_Breakpoints[]. Call after any mutation. */ ++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); ++} ++ + int add_breakpoint(struct memory* mem, uint32_t address) + { + if (g_NumBreakpoints == BREAKPOINTS_MAX_NUMBER) { +@@ -46,6 +87,7 @@ int add_breakpoint(struct memory* mem, uint32_t address) + + enable_breakpoint(mem, g_NumBreakpoints); + ++ rebuild_exec_bp_index(); + return g_NumBreakpoints++; + } + +@@ -60,6 +102,7 @@ int add_breakpoint_struct(struct memory* mem, m64p_breakpoint *newbp) + enable_breakpoint(mem, g_NumBreakpoints); + } + ++ rebuild_exec_bp_index(); + return g_NumBreakpoints++; + } + +@@ -75,6 +118,7 @@ void enable_breakpoint(struct memory* mem, int bpt) + } + + BPT_SET_FLAG(g_Breakpoints[bpt], M64P_BKP_FLAG_ENABLED); ++ rebuild_exec_bp_index(); + } + + void disable_breakpoint(struct memory* mem, int bpt) +@@ -97,6 +141,7 @@ void disable_breakpoint(struct memory* mem, int bpt) + } + + BPT_CLEAR_FLAG(g_Breakpoints[bpt], M64P_BKP_FLAG_ENABLED); ++ rebuild_exec_bp_index(); + } + + void remove_breakpoint_by_num(struct memory* mem, int bpt) +@@ -110,6 +155,7 @@ void remove_breakpoint_by_num(struct memory* mem, int bpt) + g_NumBreakpoints--; ++ rebuild_exec_bp_index(); + } + + void replace_breakpoint_num(struct memory* mem, int bpt, m64p_breakpoint *copyofnew) +@@ -120,6 +166,7 @@ void replace_breakpoint_num(struct memory* mem, int bpt, m64p_breakpoint *copyof + enable_breakpoint(mem, bpt); + } ++ rebuild_exec_bp_index(); + } + + /* ... lookup_breakpoint unchanged (still used for range/read/write breakpoints) ... */ + + int check_breakpoints(uint32_t address) + { +- return lookup_breakpoint(address, 1, M64P_BKP_FLAG_ENABLED | M64P_BKP_FLAG_EXEC); ++ /* ++ * Hot path: called per CPU instruction when debugger is active. ++ * Use O(log N) bsearch over the exec-address index instead of ++ * O(N) linear scan over all breakpoints. ++ * ++ * Falls back to full lookup_breakpoint only for range breakpoints ++ * (address != endaddr), which are rare and not on the hot path. ++ */ ++ if (g_NumExecBpAddrs > 0) { ++ uint32_t *found = (uint32_t *)bsearch(&address, g_ExecBpAddrs, ++ g_NumExecBpAddrs, ++ sizeof(uint32_t), cmp_u32); ++ if (found != NULL) { ++ /* Locate the original breakpoint index for the caller */ ++ return lookup_breakpoint(address, 1, ++ M64P_BKP_FLAG_ENABLED | M64P_BKP_FLAG_EXEC); ++ } ++ return -1; ++ } ++ return lookup_breakpoint(address, 1, M64P_BKP_FLAG_ENABLED | M64P_BKP_FLAG_EXEC); + } diff --git a/defects/mupen64plus-0001/test/test_mupen64plus_0001 b/defects/mupen64plus-0001/test/test_mupen64plus_0001 new file mode 100755 index 000000000..83defc959 Binary files /dev/null and b/defects/mupen64plus-0001/test/test_mupen64plus_0001 differ diff --git a/defects/mupen64plus-0001/test/test_mupen64plus_0001.c b/defects/mupen64plus-0001/test/test_mupen64plus_0001.c new file mode 100644 index 000000000..09e2a12c0 --- /dev/null +++ b/defects/mupen64plus-0001/test/test_mupen64plus_0001.c @@ -0,0 +1,273 @@ +/* + * 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 +#include +#include +#include +#include + +/* ---- 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; +}