From 0031b1296d3d1713d8f1a8e19d73aa38077680ce Mon Sep 17 00:00:00 2001 From: "russell@unturf.com" Date: Tue, 31 Mar 2026 17:38:14 -0400 Subject: [PATCH] mupen64plus: 1 CWE-407 defect, MOAD 0002-0005 CLEAN --- .../patch/mupen64plus-0001.patch | 121 ++++++++ .../test/test_mupen64plus_0001 | Bin 0 -> 16688 bytes .../test/test_mupen64plus_0001.c | 273 ++++++++++++++++++ 3 files changed, 394 insertions(+) create mode 100644 defects/mupen64plus-0001/patch/mupen64plus-0001.patch create mode 100755 defects/mupen64plus-0001/test/test_mupen64plus_0001 create mode 100644 defects/mupen64plus-0001/test/test_mupen64plus_0001.c 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 0000000000000000000000000000000000000000..83defc959f9bc484d81c082a5b891fd7f15591e5 GIT binary patch literal 16688 zcmeHOdvsjId7sq-3uCl0*w_YQ-++|(f!A7FGLk_-D@*2zBio8C2h#%AtKF5g^=fz7 zy$iCTMvkoDtx#~GCp9Pi1DbOhCy*RBC+)#dV%g?lNFX*T!4Q(85}+$=h%n#>4{v|p z%zUe>jgc+tjkEz~vI03dPq1ab>d6@gTx;{{`BiN*^-$!<1f#-Ud#STN-p5+%Ed(#9!D!c>$g zp6rS#tNDD|G*#&_WqGbVL&-%R?U7D}VCuA^*z8%X?Do`+rQJF%M|HVuM=;s#)pmQe z9n%r25E4_4C&h%W8XeDkT1iGoO!MtJwOu}KRAz!Hl|gC!J9hBDQ(mjK+k3V0lTRZG z7EHOkufvYw@?TBd&Fge|6ZJ!g5vKd-V`ahcX!+Ag6@#`yh^)33kr)BV&oBnM6 z_eh88O*W)MiTvp*N}lpGWJpKFV~50f7#$0}+$Dht{!#5O8ArAom@y7c<&3Alegd58 zG@gF>1o-2?4P5z&rl0ZrJO$jyR|emR>#A{Z5l`7kVOdGrAJ}3AI=5II{%{09JQ0rC z9fW}KCzGLs%_4MtfDC|i`NL7j1KobBBOLWd!rMaPj$|xh3sR!tuw2~O(%i7pTIyNm zsmkR?b4xwdIdoZ-u$tReTftBwv^ku#Ly5N4D6X$8S(LjSm`AoQ52@bDU=!xSrCb$%q3-#dGPb z1Lu2zg!?p3KKR}w{0Rq6_Z_D}2d<}fDLUf7)qSTdjyiB|Gy9sPlM$GVz+?m_BQP0( z$p}nF;QwL-K620f)a*T3V)hrmzC;MKZ_qBt9yNQPDLE{wo2}jkI9qWK{=4VZ3)1f- z$?#j*Y_?;!#1HT;uNo*uY{|31fOE28k;;hu+wc=DI6xGaw^ z&Eqe~+Mgc7`))P|>Snox@O{I#!PmB7JsNBV&Hkc_`KXAQZY;0y4racI`efe6 zbK(JjJ()}KG}n7J>+b7BZ6)*+H8Zn?@IjZm?+)PmsijZSKXagZq*Ms+-~r;ErWR-7 z@Q9L&@hqMxC+`nP?`81b2QLTkklvGd3|RVb-=KTfcV$6@O@Y*xeulslz}{zF-oeK7 zzop-h&Qa9sC@M1@E`7Ccy8DKpgK`d{g_(ajpUob?3aKZv4vyhOw&5Yv2VRum2eL$o z|AoREYk%+VyIXmA5w2SXBIRY7JK(A%eWGO`SYER_{Q*k24<)n=)XlF%LF*yyaL0-l zO-JGV#cd$&1&!rpgngATgtd|oBeMq&%&6^E95pSJ!@=I?^&w?L+A&E_KiAk$!p)7okPXwQt&0 z{nuqeD8iRMoOwyo!Nv=ZZ~blh)5i4cE$P!TXx>2+7Mb6Ek%YD{Ge`T{p?!V%xQx{$=vXST_nD&AwI@^mx}TQw>G#kZXQt&ko|dOQ znY&O5!Zk#_vb~IU zv9qqDX5dKwv^W~P6q2U+GGSPvgp{Xd2LF9IN=b>eZ9 zu<;^5vq@(c>JFfafGGr63K+oZHFNq?Y8VZr&*=`ubhGojV75dh@U)El213^Y#b6sgg66835J92>-^I#Y&xFc;i<}8B9zj5!98o$y zhYnn}QyJI78|r+F#QT)^aV0Jx@oXjj35kEG!~;q^43ar_PFg=i;!{c-RpP73`VA$v zN&KM_uT|nt$oeUWGyi~pqrX4i_nXtlZa33!nZ0k1w6-;Q2fZ(#s{aV3?9A`Y7vkd% zPi{UNsCxm3IZ%9vNbOmBCT7ELQ64{~7iNd_e1*}{gTkrF4@>>(2Z<~=Z>C4ggCE>r z9z0uUx}GzSowsMe!3OQ1Bs-+)#rE|3*i#pRSfmzhFnjBkQr1krV^1^t>z0Gb{NOBF z6hvL0E3O0Yx(&9%X+L}ygt|WDuYXp5=@WpmwJ#>7cn52bxgQ*4{r>6|nC<&cZ?7~5 zW-eKXetqG@Met|#9-3DBp*^d=p``bq>&(%;g{hOZD^i)-ZAGcyr(aHw9QK|*ajs*x z8JKx)i4e~h|MIKUe7JgL`zA!|?FAHz8SA{K&4FpVPfX9nvv+WsS$o93w!gLH%qzWb z6{gL1&#d;MYYfS+z%b})t&`8&nv-VhWK7KN$n#VG}{;HnZGl}`N=M&S0 zE!gD7ndyRF;<)VC!w8OUZ~D-Q1CAEq_vjXn{5?Vd;|KZ2$UHbyke+!1Bxq(0Mp17| zK}lf;exD157hraf&uQ|xG54gX;x_qzG6Itkn2f+=1pa?Sz=hRCTPSH8-W5iuD{gN! zB6u-nBtmv75ltG4y}~e7`I=h*M??MubL~kmflja$!6g;JsltX2t1A@`MVD8_BdMfS zSy}1z1VkLK){MG3gXBrJR=$kRu+50}ry(V;s|XqzM|LXSxJAiRdr}qbsFl$bPUf7m zD~+}K)~^>~ylS%|el!dYqKYNC@ik(#g+*FP|KZLqdWmTM)t}v3}=;lbsXitRvTjDXihLp`#<3;wxvhj3e zdvb$H>Q${U*3@|`9iEcc7=c(K5enGR5SrH!OBnu0#PA1$3Gks@kO-)&?5?P+>874l zp+;O~ps5hu>#69LXzZ@&#Hz9$)hi5&o!<_}q9{Lw79~Q#RDgJ0V*@p<2(Hk>$ha$( zNZM4YjQKT=er6;Y55YyeuEOK(=$@)#w>;M&@ZS}?GlUir4{Vn@gEU`vGJ5q|*nSvwzEdVD7X z{-}^|kaOKeFF$)|@hh-JgO4$KL_MxnT$^ykaZ$p8KkLSY1f)mo7Co*j3a^VmgjI~_ zKAFuv0(uV4WXjv%A1={5YFE3)&4j4fqqF2I$65ve_!oU7#lD zSx{PHZ#nG(xgXa? zv}uk5dj$C)u&+6=pCZ2-*d7N)@wpQ1zS@DkiF^&PxC0wO{%?S(uP)U4^CGl&2-vk+ zuLX2Au*cEHh1eVtt4hlrC}=L7{q4erQe&W~p>+QJ#lF(YZ(ZOkt?8Z8SlZ(%ytZg+ zX$>%6>3rxkl+JD_Eo&$(S%u~HWSWe?WCSK7Fd2c#2uwy`G6G{Fz~9;NcXj%)On%!( ziu4>P6FndyVY^?Zn#&dCXC?lA?+Pt1(NwDm{*I5H=_s-Mlk?da@%!-pff8QD$TU=> zC_VmAItkzQCN6)j=2;J~E@b-rg2U4?fx^qq+V&{zfEQrFBy>mexL&4lL*u*-XaepMmy1m% zGDv#3ifb<^res)bov_+zCVD@jQH) z#(BQc>Y^Dq#m%T!2w0gmNc^(g{GnlSkM_g!7vsC6{>#Eef=|L;rv8RDJ_)*Pkd zwfCQ-{w$Gy-~0@?5SI!*XTV;LN&VTm`G@g;2R`0+=SApegOwcgK8d3f>m4An2JrFx zcqhPD0xuJDL|LAnn@GQ4y5M=8OKF|J&z1@BKJ8!R`F{|&!A>3YQQ*@H<_Mk_SoCAU zsb$)T?+bb@T!wMdsqZ`1rw;)xfuu~2x36fyA+2xJv!sUS*j+UKrEWHig6x8$>HP^5rqeJ zFa*rlQzBL{8M8Y5(IA~5X43N8u3C zIhAn_pNu^XlXD>JN&O;(C%LuD_P2xD2}L_Oht2L#A}&1GOAmQAM^hdemN7G#M~#7_n~7mwJ&Bdh5_ z*0VVVmNLmuKzQs>H&_~n(2mJo^MpF}$mBXnC06)3XEdH{&T;#`gfPt zsoIUKE9Z&+9WJN6q3xJ%A|*%SpL3?)hs5@b|9 z+w*fZ(}=^Cviarz2AI=+cDXWO+DSr3;_<`KJILTGO?eLz#`_z5zBgZ=d8YIQpxa^3 z`yY=iCU2wTRdJ&{+tWIX#x946*Bg9Zo6lv_{YQ!IIF7U)q$aXG?|B(T0Muey$)jzr@B&d zQXV@>qW;%whdhsd2{7&1aCLb8rQ3t+t}NAmqgv5rc|tCib@65Txc2L7l*Z+G26?Rb E4>tBZkpKVy literal 0 HcmV?d00001 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; +}