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
212 lines
8.2 KiB
Java
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");
|
|
}
|
|
}
|