fix(bios): return cleanly for empty LZ77 streams
This commit is contained in:
parent
423d0ba120
commit
7772a9d8eb
8 changed files with 156 additions and 11 deletions
|
|
@ -77,6 +77,27 @@ fn raw_original_program_runs_with_a_bounded_report_without_changing_the_file() {
|
|||
assert_eq!(fs::read(path).unwrap(), original);
|
||||
}
|
||||
|
||||
fn empty_lz_setup() -> Vec<u32> {
|
||||
// Original ARM caller, an all-zero header at EOF, and a RAM sentinel.
|
||||
// Both LZ variants must return without reading tokens or writing the destination.
|
||||
vec![
|
||||
0xe59f001c, 0xe59f101c, 0xe3a0205a, 0xe5812000, 0xef110000, 0xef120000, 0xe5913000,
|
||||
0xe3a0402a, 0xeafffffe, 0x0800002c, 0x02001000, 0,
|
||||
]
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn empty_lz_calls_return_through_original_bios_without_changing_destination() {
|
||||
let fixture = Fixture::new();
|
||||
let path = fixture.rom(&empty_lz_setup());
|
||||
let original = fs::read(&path).unwrap();
|
||||
let output = run(&path, "1000");
|
||||
assert!(output.status.success(), "{}", stderr(&output));
|
||||
assert!(stdout(&output).contains("r3=0x0000005a"));
|
||||
assert!(stdout(&output).contains("r4=0x0000002a"));
|
||||
assert_eq!(fs::read(path).unwrap(), original);
|
||||
}
|
||||
|
||||
fn wave_setup() -> Vec<u32> {
|
||||
// Original ARM halfword stores and a status load; no external audio or assets.
|
||||
let mut code = Vec::new();
|
||||
|
|
@ -641,6 +662,12 @@ fn native_rom_window_presents_file_bytes_and_reports_stop_cpu_and_video_errors()
|
|||
);
|
||||
assert!(stdout(&output).contains("Captured ROM frames: 3"));
|
||||
assert!(stdout(&output).contains("Result: window frame limit reached"));
|
||||
let lz_path = fixture.rom(&empty_lz_setup());
|
||||
let lz_output = window(&lz_path, false, None);
|
||||
assert!(lz_output.status.success(), "{}", stderr(&lz_output));
|
||||
assert!(stdout(&lz_output).contains("Captured ROM frames: 3"));
|
||||
assert!(stdout(&lz_output).contains("r3=0x0000005a"));
|
||||
assert!(stdout(&lz_output).contains("r4=0x0000002a"));
|
||||
let wave_path = fixture.rom(&wave_setup());
|
||||
let wave_output = window(&wave_path, false, None);
|
||||
assert!(wave_output.status.success(), "{}", stderr(&wave_output));
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
//! Original LZ77 decoder emitted as ARM instructions.
|
||||
//! Invalid streams trap after any earlier successful writes. VRAM output requires
|
||||
//! an even size and halfword alignment; distance-one references are rejected.
|
||||
//! an even size and halfword alignment for nonempty output; distance-one references
|
||||
//! are rejected. A zero-length header returns without reading tokens or checking its type.
|
||||
|
||||
use super::ArmImage;
|
||||
|
||||
|
|
@ -18,9 +19,11 @@ pub(super) fn emit(a: &mut ArmImage) {
|
|||
a.branch(3, "invalid_argument");
|
||||
a.emit(0xe490_2004); // LDR r2,[r0],#4 (header)
|
||||
a.emit(0xe202_b0ff); // AND r11,r2,#0xff
|
||||
a.emit(0xe35b_0010); // CMP r11,#0x10 (LZ77 type, reserved bits zero)
|
||||
a.branch(1, "invalid_argument");
|
||||
a.emit(0xe1a0_2422); // MOV r2,r2,LSR #8 (24-bit output byte count)
|
||||
a.emit(0xe352_0000); // CMP r2,#0
|
||||
a.branch(0, "lz_done"); // Empty streams need neither a type tag nor a usable destination.
|
||||
a.emit(0xe35b_0010); // CMP r11,#0x10 (nonempty LZ77 type, reserved bits zero)
|
||||
a.branch(1, "invalid_argument");
|
||||
a.emit(0xe35c_0000); // CMP r12,#0
|
||||
a.branch(0, "lz_validate_end");
|
||||
a.emit(0xe181_b002); // ORR r11,r1,r2 (both destination and size must be even)
|
||||
|
|
@ -29,8 +32,6 @@ pub(super) fn emit(a: &mut ArmImage) {
|
|||
a.label("lz_validate_end");
|
||||
a.emit(0xe091_b002); // ADDS r11,r1,r2
|
||||
a.branch(2, "invalid_argument"); // Reject output address wrap.
|
||||
a.emit(0xe352_0000); // CMP r2,#0
|
||||
a.branch(0, "lz_done");
|
||||
a.emit(0xe1a0_8001); // MOV r8,r1 (start of produced output)
|
||||
a.emit(0xe3a0_9000); // MOV r9,#0 (pending halfword)
|
||||
a.emit(0xe3a0_a000); // MOV r10,#0 (bit position 0 or 8)
|
||||
|
|
|
|||
|
|
@ -305,16 +305,39 @@ fn decompression_restores_user_mode_masks_and_all_caller_registers() {
|
|||
}
|
||||
|
||||
#[test]
|
||||
fn zero_output_length_does_not_read_flags_or_touch_destination() {
|
||||
fn empty_headers_ignore_type_without_reading_payload_or_accessing_destination() {
|
||||
// Original synthetic headers, including the all-zero empty-stream case.
|
||||
// The unmapped, odd destination also proves no output alignment/access is needed.
|
||||
for service in [0x11, 0x12] {
|
||||
let (mut machine, return_pc) = prepare(service, false, 0x0e00_0000, &[0x10, 0, 0, 0]);
|
||||
complete(&mut machine, return_pc); // Destination and next source byte are unmapped.
|
||||
for thumb in [false, true] {
|
||||
for low_byte in 0..=255 {
|
||||
let (mut machine, return_pc) =
|
||||
prepare(service, thumb, 0x0e00_0001, &[low_byte, 0, 0, 0]);
|
||||
let before = machine.cpu().clone();
|
||||
complete(&mut machine, return_pc); // Header ends at the last supplied ROM byte.
|
||||
assert_eq!(&machine.cpu().registers()[..15], &before.registers()[..15]);
|
||||
assert_eq!(machine.cpu().cpsr(), before.cpsr());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn empty_headers_preserve_ram_video_and_stack_sentinels() {
|
||||
for service in [0x11, 0x12] {
|
||||
for thumb in [false, true] {
|
||||
for destination in [DEST, VRAM_START + 4] {
|
||||
for low_byte in [0, 0x10, 0xff] {
|
||||
verify(service, thumb, destination, &[low_byte, 0, 0, 0], &[]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn bad_headers_alignment_and_wrapping_destinations_are_rejected() {
|
||||
for header in [0_u32, 0x0200, 0x0211, 0x0220, 0x02ff] {
|
||||
for header in [0x0100_u32, 0x0200, 0x0211, 0x0220, 0x02ff] {
|
||||
let (mut machine, _) = prepare(0x11, false, DEST, &header.to_le_bytes());
|
||||
assert_invalid(fail(&mut machine));
|
||||
assert_eq!(machine.memory().read32(DEST).unwrap(), 0);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue