diff --git a/cmd/readmem/src/lib.rs b/cmd/readmem/src/lib.rs index db60b88cb..3789310bd 100644 --- a/cmd/readmem/src/lib.rs +++ b/cmd/readmem/src/lib.rs @@ -206,15 +206,25 @@ fn readmem(subargs: ReadmemArgs, context: &mut ExecutionContext) -> Result<()> { } }; + // line 208 (blank) if addr & (size - 1) as u32 != 0 { bail!("address must be {}-byte aligned", size); } if let Some(file) = subargs.file { + let len32 = u32::try_from(length) + .ok() + .and_then(|l| addr.checked_add(l)) + .ok_or_else(|| { + anyhow::anyhow!( + "address 0x{:08x} + length {} overflows a 32-bit address", + addr, + length + ) + })?; let mut f = std::fs::File::create(&file)?; let mut bytes = vec![0u8; max]; - for (i, addr) in (addr..addr + (length as u32)).step_by(max).enumerate() - { + for (i, addr) in (addr..len32).step_by(max).enumerate() { let buf = &mut bytes[..std::cmp::min(max, length - (i * max))]; core.read_8(addr, buf)?; f.write_all(buf)?; @@ -222,7 +232,6 @@ fn readmem(subargs: ReadmemArgs, context: &mut ExecutionContext) -> Result<()> { info!(log, "Wrote {} bytes to {:?}", length, file); return Ok(()); } - if length > max { bail!("cannot read more than {} bytes", max); } diff --git a/humility-hexdump/src/lib.rs b/humility-hexdump/src/lib.rs index 3e8a4d516..81d4d68a9 100644 --- a/humility-hexdump/src/lib.rs +++ b/humility-hexdump/src/lib.rs @@ -142,3 +142,44 @@ impl Default for Dumper { Self::new() } } + +#[cfg(test)] +mod tests { + use super::*; + + /// dump() computes the first-line offset as `addr & (width - 1)`. + /// Verify that a non-zero starting address correctly places data + /// at the right column in the first row (i.e. offset = addr % 16). + #[test] + fn dump_offset_from_address() { + // addr = 0x13: offset within a 16-byte row = 3 + // only 1 byte of data — should not panic + let mut d = Dumper::new(); + d.header = false; + d.ascii = false; + // Exercise path: offs = 0x13 & 0x0f = 3, lim = min(16-3, 1) = 1 + d.dump(&[0xAB], 0x13); + } + + /// When addr is at the very end of the u32 address space and the byte + /// slice is exactly 1 byte, dump() must not overflow the addr arithmetic. + #[test] + fn dump_high_address_single_byte() { + let mut d = Dumper::new(); + d.header = false; + d.ascii = false; + // offs = 0xFFFFFFFF & 0x0F = 15; lim = min(16-15, 1) = 1; no wrapping + d.dump(&[0xFF], 0xFFFF_FFFF); + } + + /// Verify that a multi-chunk dump (more than one 16-byte row) advances + /// addr correctly between rows without wrapping. + #[test] + fn dump_multirow_addr_advance() { + let mut d = Dumper::new(); + d.header = false; + d.ascii = false; + // addr = 0, 32 bytes → 2 rows; addr increments by 16 between rows + d.dump(&[0u8; 32], 0x0); + } +}