java-topology/defects/kronos-0001/test/KronosBreakpointLookupTest.java
russell@unturf.com f30b6bdb52 kronos: 2 CWE-407/CWE-312 defects; mesen-s: all 5 MOADs CLEAN
kronos-0001 (CWE-407, MEDIUM): SH2HandleBreakpoints() in sh2core.h
  linearly scans codebreakpoint[] on every SH2 instruction fetch in
  debug interpreter. MAX_BREAKPOINTS=10, O(10) per fetch at 28.6 MHz
  emulated = 286M extra comparisons/s. Fix: sorted_bp_addrs[] +
  binary search, O(log N), 2.54x fewer comparisons measured.

kronos-0002 (CWE-312, LOW): netlink.c:553 logs password response
  verbatim via NETLINK_LOG when compiled with -DNETLINK_DEBUG.
  Fix: replace %s format with literal [REDACTED].

kronos MOAD-0002/0003/0005: CLEAN
mesen-s: all 5 MOADs CLEAN (CheatManager unordered_map O(1),
  BreakpointManager guarded by _hasBreakpoint fast-path,
  password hashed before network use, no TLS credential leakage)

8/8 tests PASS
2026-03-31 19:56:06 -04:00

212 lines
8.2 KiB
Java

import java.util.Arrays;
/**
* KronosBreakpointLookupTest — kronos-0001 (CWE-407)
*
* Defect: SH2HandleBreakpoints() in
* yabause/src/sys/sh2/include/sh2core.h:565
* scans codebreakpoint[0..numcodebreakpoints-1] linearly on EVERY instruction
* fetch in the Kronos/Yabause SH2 debug interpreter.
*
* The Saturn SH2 runs at ~28.6 MHz. At 1 breakpoint lookup per instruction
* and MAX_BREAKPOINTS=10, that is up to 286 M extra comparisons per emulated
* second in debug sessions.
*
* Fix: maintain sorted_bp_addrs[] (insertion-sort on add/del, max 10
* elements) and replace the linear scan with binary search — O(log N),
* worst-case 4 comparisons at N=10 instead of 10.
*
* This test simulates both strategies and confirms:
* 1. Correctness: both return the same hit/miss result.
* 2. Performance: binary search does at most log2(N+1) comparisons.
* 3. Op-count ratio: binary is at least 2x fewer comparisons overall.
*/
public class KronosBreakpointLookupTest {
static final int MAX_BREAKPOINTS = 10;
// -----------------------------------------------------------------------
// Minimal model of the breakpoint table
// -----------------------------------------------------------------------
static class BreakpointTable {
final long[] addrs = new long[MAX_BREAKPOINTS];
int count = 0;
void add(long addr) {
if (count < MAX_BREAKPOINTS) addrs[count++] = addr;
}
/**
* Defective: linear scan O(N) per instruction.
* Returns [hit(0/1), comparison_count].
*/
int[] linearLookup(long pc) {
int cmp = 0;
for (int i = 0; i < count; i++) {
cmp++;
if (addrs[i] == pc) return new int[]{1, cmp};
}
return new int[]{0, cmp};
}
/**
* Patched: binary search O(log N).
* Mirrors SH2RebuildSortedBreakpoints (insertion-sort) + bsearch in
* SH2HandleBreakpoints.
* Returns [hit(0/1), comparison_count].
*/
int[] binaryLookup(long pc) {
// Rebuild sorted copy (as SH2RebuildSortedBreakpoints does on add/del)
long[] sorted = Arrays.copyOf(addrs, count);
Arrays.sort(sorted);
int cmp = 0, lo = 0, hi = count - 1;
while (lo <= hi) {
cmp++;
int mid = (lo + hi) >>> 1;
if (sorted[mid] == pc) return new int[]{1, cmp};
else if (pc < sorted[mid]) hi = mid - 1;
else lo = mid + 1;
}
return new int[]{0, cmp};
}
}
// -----------------------------------------------------------------------
// Helpers
// -----------------------------------------------------------------------
static void check(boolean cond, String msg) {
if (!cond) throw new AssertionError("FAIL: " + msg);
}
// -----------------------------------------------------------------------
// Test cases
// -----------------------------------------------------------------------
static void testMissEmptyTable() {
BreakpointTable t = new BreakpointTable();
int[] lin = t.linearLookup(0x06000000L);
int[] bin = t.binaryLookup(0x06000000L);
check(lin[0] == 0, "linear: empty table = miss");
check(bin[0] == 0, "binary: empty table = miss");
check(lin[1] == 0, "linear: zero comparisons on empty table");
check(bin[1] == 0, "binary: zero comparisons on empty table");
System.out.println("PASS testMissEmptyTable");
}
static void testHitSingleBreakpoint() {
BreakpointTable t = new BreakpointTable();
t.add(0x06001234L);
int[] lin = t.linearLookup(0x06001234L);
int[] bin = t.binaryLookup(0x06001234L);
check(lin[0] == 1, "linear: single BP hit");
check(bin[0] == 1, "binary: single BP hit");
System.out.println("PASS testHitSingleBreakpoint");
}
static void testMissSingleBreakpoint() {
BreakpointTable t = new BreakpointTable();
t.add(0x06001234L);
int[] lin = t.linearLookup(0x06009999L);
int[] bin = t.binaryLookup(0x06009999L);
check(lin[0] == 0, "linear: single BP miss");
check(bin[0] == 0, "binary: single BP miss");
System.out.println("PASS testMissSingleBreakpoint");
}
static void testHitLastElementFullTable() {
BreakpointTable t = new BreakpointTable();
long[] bps = new long[MAX_BREAKPOINTS];
for (int i = 0; i < MAX_BREAKPOINTS; i++) {
bps[i] = 0x06000100L + i * 0x100L;
t.add(bps[i]);
}
// Hit the last element: worst case for linear (scans all 10)
long hitPC = bps[MAX_BREAKPOINTS - 1];
int[] lin = t.linearLookup(hitPC);
int[] bin = t.binaryLookup(hitPC);
check(lin[0] == 1, "linear: last element hit");
check(bin[0] == 1, "binary: last element hit");
check(lin[1] == MAX_BREAKPOINTS,
"linear: scans all " + MAX_BREAKPOINTS + " on last element (was " + lin[1] + ")");
check(bin[1] <= 4,
"binary: at most 4 comparisons at N=10 (was " + bin[1] + ")");
System.out.println("PASS testHitLastElementFullTable: linear=" + lin[1] +
" binary=" + bin[1]);
}
static void testMissFullTable() {
BreakpointTable t = new BreakpointTable();
for (int i = 0; i < MAX_BREAKPOINTS; i++) {
t.add(0x06000100L + i * 0x100L);
}
long missPC = 0x07000000L;
int[] lin = t.linearLookup(missPC);
int[] bin = t.binaryLookup(missPC);
check(lin[0] == 0, "linear: full table miss");
check(bin[0] == 0, "binary: full table miss");
check(lin[1] == MAX_BREAKPOINTS,
"linear: exhausts all slots on miss");
check(bin[1] <= 4,
"binary: at most ceil(log2(11))=4 comparisons at N=10 (was " + bin[1] + ")");
System.out.println("PASS testMissFullTable: linear=" + lin[1] +
" binary=" + bin[1]);
}
static void testAllAddressesHit() {
BreakpointTable t = new BreakpointTable();
long[] bps = new long[MAX_BREAKPOINTS];
for (int i = 0; i < MAX_BREAKPOINTS; i++) {
bps[i] = 0x06000100L + i * 0x100L;
t.add(bps[i]);
}
for (long bp : bps) {
check(t.linearLookup(bp)[0] == 1, "linear hit: 0x" + Long.toHexString(bp));
check(t.binaryLookup(bp)[0] == 1, "binary hit: 0x" + Long.toHexString(bp));
}
System.out.println("PASS testAllAddressesHit: all 10 breakpoints hit by both strategies");
}
static void testOpCountRatioOverManyInstructions() {
BreakpointTable t = new BreakpointTable();
for (int i = 0; i < MAX_BREAKPOINTS; i++) {
t.add(0x06000000L + i * 0x1000L);
}
long linTotal = 0, binTotal = 0;
int N = 1_000_000;
for (int i = 0; i < N; i++) {
// Mostly misses (random-ish PC distribution), occasional hits
long pc = 0x06000000L + (long)(i % 0x20000) * 2;
linTotal += t.linearLookup(pc)[1];
binTotal += t.binaryLookup(pc)[1];
}
double ratio = (double) linTotal / binTotal;
System.out.printf("BENCH kronos-0001: linear=%d binary=%d ratio=%.2fx%n",
linTotal, binTotal, ratio);
check(ratio >= 2.0,
"expected op-count ratio >= 2x, got " + String.format("%.2f", ratio) + "x");
System.out.println("PASS testOpCountRatioOverManyInstructions: " +
String.format("%.2f", ratio) + "x fewer comparisons");
}
// -----------------------------------------------------------------------
// Entry point
// -----------------------------------------------------------------------
public static void main(String[] args) {
testMissEmptyTable();
testHitSingleBreakpoint();
testMissSingleBreakpoint();
testHitLastElementFullTable();
testMissFullTable();
testAllAddressesHit();
testOpCountRatioOverManyInstructions();
System.out.println("ALL TESTS PASSED");
}
}