From 25bbdbee73968d6007c5d139474ca155ae445feb Mon Sep 17 00:00:00 2001 From: hackenbergstefan Date: Fri, 1 Mar 2024 06:50:12 +0100 Subject: [PATCH] Implement write-back cache and refactor write-through cache (#9) * Implement write-back cache and refactor write-through cache * Implement review --- Cargo.toml | 2 +- src/main.rs | 19 +++- src/memory_extension.rs | 225 +++++++++++++++++++++++++++++++++------- tests/libtests.rs | 66 +++++++++++- 4 files changed, 265 insertions(+), 47 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 7b912af..aa1d776 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,7 +4,7 @@ [package] name = "rainbow-rs" -version = "0.5.0" +version = "0.6.0" edition = "2021" [dependencies] diff --git a/src/main.rs b/src/main.rs index 5e34f2a..daa0264 100644 --- a/src/main.rs +++ b/src/main.rs @@ -21,7 +21,10 @@ use rainbow_rs::{ leakage::{ ElmoPowerLeakage, HammingDistanceLeakage, HammingWeightLeakage, PessimisticHammingLeakage, }, - memory_extension::{BusNoCache, CacheLru, NoBusNoCache, MAX_BUS_SIZE, MAX_CACHE_LINES}, + memory_extension::{ + BusNoCache, CacheLruWriteBack, CacheLruWriteThrough, NoBusNoCache, MAX_BUS_SIZE, + MAX_CACHE_LINES, + }, new_simpleserialsocket_stm32f4, ThumbTraceEmulatorTrait, }; @@ -37,7 +40,8 @@ enum LeakageModel { enum MemoryExtension { NoBusNoCache, BusNoCache, - CacheLru, + CacheLruWriteThrough, + CacheLruWriteBack, } #[derive(Parser, Debug)] @@ -222,9 +226,16 @@ fn main() -> Result<()> { MemoryExtension::BusNoCache => { Box::new(BusNoCache::new(args.memory_buswidth)) } - MemoryExtension::CacheLru => { - Box::new(CacheLru::new(args.memory_buswidth, args.memory_cache_lines)) + MemoryExtension::CacheLruWriteThrough => { + Box::new(CacheLruWriteThrough::new( + args.memory_buswidth, + args.memory_cache_lines, + )) } + MemoryExtension::CacheLruWriteBack => Box::new(CacheLruWriteBack::new( + args.memory_buswidth, + args.memory_cache_lines, + )), }, client.clone(), )?; diff --git a/src/memory_extension.rs b/src/memory_extension.rs index 52ba3b9..39b6c41 100644 --- a/src/memory_extension.rs +++ b/src/memory_extension.rs @@ -113,14 +113,14 @@ impl MemoryExtension for BusNoCache { } } -pub struct CacheLru { +pub struct CacheLruWriteThrough { bus_size: usize, bus: [u8; MAX_BUS_SIZE], cache_lines: usize, cache: ArrayVec<(u64, [u8; MAX_BUS_SIZE]), MAX_CACHE_LINES>, } -impl CacheLru { +impl CacheLruWriteThrough { pub fn new(bus_size: usize, cache_lines: usize) -> Self { Self { bus_size, @@ -129,15 +129,41 @@ impl CacheLru { cache: ArrayVec::new(), } } + + /// Update bus. Leaks: Bus + fn update_and_leak_bus(&mut self, scadata: &mut ScaData, newvalue: [u8; MAX_BUS_SIZE]) { + scadata.bus_updates.push((self.bus, newvalue)); + self.bus = newvalue; + } + + /// Update cache line with new content. + /// Leaks: Cache (may), Bus[write] (may), Memory (may) + fn update_and_leak_cache_bus_memory( + &mut self, + scadata: &mut ScaData, + index: usize, + newcontent: [u8; MAX_BUS_SIZE], + ) { + let line = &self.cache[index]; + if line.1 == newcontent { + return; + } + let (address, oldcontent) = self.cache.pop_at(index).unwrap(); + + scadata.cache_updates.push((oldcontent, newcontent)); + scadata.bus_updates.push((oldcontent, newcontent)); + scadata.memory_updates.push((oldcontent, newcontent)); + self.cache.push((address, newcontent)); + } } -/// Least Recently Used (LRU) cache implementation. +/// Least Recently Used (LRU) cache implementation with write-through (https://stackoverflow.com/a/27161893). /// On a memory update the cache behaves as follows: /// 1. If the address is in the cache, the cache line is updated (and moved to end) -/// and the bus is updated if necessary. +/// and the bus and memory are updated if necessary. /// 2. If the address is not in the cache, the last cache line is removed if necessary, -/// the new cache line is added and the bus is updated if necessary. -impl MemoryExtension for CacheLru { +/// the new cache line is added and the bus and memory are updated if necessary. +impl MemoryExtension for CacheLruWriteThrough { #[inline] fn bus_size(&self) -> usize { self.bus_size @@ -165,43 +191,168 @@ impl MemoryExtension for CacheLru { self.cache[index].1, memory_before ); - - // Update cache line and bus if necessary - if memory_before != memory_after { - scadata - .cache_updates - .push((self.cache[index].1, memory_after)); - self.cache.remove(index); - self.cache.push((address, memory_after)); - scadata.bus_updates.push((self.bus, memory_after)); - self.bus = memory_after; - } + self.update_and_leak_cache_bus_memory(scadata, index, memory_after); } else { - // Remove last cache line if necessary - if self.cache.len() == self.cache_lines { - scadata.cache_updates.push((self.cache[0].1, memory_before)); - self.cache.remove(0); - } else { - scadata - .cache_updates - .push(([0; MAX_BUS_SIZE], memory_before)); - } + // 1. Read new memory. Leaks: Bus[read] + self.update_and_leak_bus(scadata, memory_before); + + // 2. Update cache. Leaks: Cache + let oldline_content = { + if self.cache.len() == self.cache_lines { + // 2a. Remove last cache line + let (_, oldline_content) = self.cache.pop_at(0).unwrap(); + oldline_content + } else { + // 2b. Add new line + [0; MAX_BUS_SIZE] + } + }; + scadata.cache_updates.push((oldline_content, memory_before)); self.cache.push((address, memory_before)); - // Update cache line and bus if necessary - if memory_before != memory_after { - let last = self.cache.last_mut().unwrap(); - scadata.cache_updates.push((last.1, memory_after)); - last.1 = memory_after; - scadata.bus_updates.push((self.bus, memory_after)); - self.bus = memory_after; - } + // 3. Update cache line and bus if necessary. Leaks: Cache (may), Bus (may), Memory (may) + self.update_and_leak_cache_bus_memory(scadata, self.cache.len() - 1, memory_after); } trace!("Cache: {:x?}", self.cache); + } +} - // Update memory - if memory_before != memory_after { - scadata.memory_updates.push((memory_before, memory_after)); +#[derive(Debug)] +struct CacheLruWriteBackCacheLine { + address: u64, + content: [u8; MAX_BUS_SIZE], + memory: [u8; MAX_BUS_SIZE], + dirty: bool, +} + +pub struct CacheLruWriteBack { + bus_size: usize, + bus: [u8; MAX_BUS_SIZE], + cache_lines: usize, + cache: ArrayVec, +} + +impl CacheLruWriteBack { + pub fn new(bus_size: usize, cache_lines: usize) -> Self { + Self { + bus_size, + bus: [0; MAX_BUS_SIZE], + cache_lines, + cache: ArrayVec::new(), + } + } + + /// Update bus. Leaks: Bus + fn update_and_leak_bus(&mut self, scadata: &mut ScaData, newvalue: [u8; MAX_BUS_SIZE]) { + scadata.bus_updates.push((self.bus, newvalue)); + self.bus = newvalue; + } + + /// Update cache line with new content. + /// Leaks: Cache (may) + fn update_and_leak_cache( + &mut self, + scadata: &mut ScaData, + index: usize, + newcontent: [u8; MAX_BUS_SIZE], + ) { + let line = &self.cache[index]; + if line.content == newcontent { + return; + } + + scadata.cache_updates.push((line.content, newcontent)); + let newline = CacheLruWriteBackCacheLine { + address: line.address, + content: newcontent, + memory: line.memory, + dirty: true, + }; + self.cache.remove(index); + self.cache.push(newline); + } + + /// Execute write-back: + /// 1. Pop lru cache line + /// 2. If line is dirty, leak bus and memory + fn write_back_and_leak(&mut self, scadata: &mut ScaData) -> CacheLruWriteBackCacheLine { + let oldline = self.cache.pop_at(0).unwrap(); + if oldline.dirty { + self.update_and_leak_bus(scadata, oldline.content); + scadata + .memory_updates + .push((oldline.memory, oldline.content)); + } + oldline + } +} + +/// Least Recently Used (LRU) cache implementation with write-through (https://stackoverflow.com/a/27161893). +/// The memory is modelled as read-modify-write independently of actual content changes. +/// On an update the cache behaves as follows: +/// 1. If the address is in the cache, the cache line is updated (and moved to end). +/// 2. If the address is not in the cache, the last cache line is removed if necessary, +/// the new cache line is added and updated if necessary. +/// 3. If the line pops out of the cache and it is written back to bus and memory if necessary. +impl MemoryExtension for CacheLruWriteBack { + #[inline] + fn bus_size(&self) -> usize { + self.bus_size + } + + fn reset(&mut self) { + self.cache.clear(); + self.bus = [0; MAX_BUS_SIZE]; + } + + fn update( + &mut self, + scadata: &mut ScaData, + _memtype: MemType, + address: u64, + memory_before: [u8; MAX_BUS_SIZE], + memory_after: [u8; MAX_BUS_SIZE], + ) { + assert!((address as usize % self.bus_size) == 0); + // Check if address is in cache + if let Some(index) = self.cache.iter().position(|line| line.address == address) { + let line = &self.cache[index]; + assert!( + line.content == memory_before, + "Cache line {index} at {address:08x} is not up to date: {:x?} != {:x?}", + line.content, + memory_before + ); + + // Update cache line. Leaks: cache (may) + self.update_and_leak_cache(scadata, index, memory_after); + } else { + let oldline_content = { + // 1a. Remove lru cache line. Leaks: cache, bus[write] (may), memory (may) + if self.cache.len() == self.cache_lines { + let oldline = self.write_back_and_leak(scadata); + oldline.content + // 1b. Leaks: cache + } else { + [0; MAX_BUS_SIZE] + } + }; + + // 2. "Read" from memory. Leaks: bus[read] and add new cache line + self.update_and_leak_bus(scadata, memory_before); + + // 3. Add new cache line. Leaks: Cache + scadata.cache_updates.push((oldline_content, memory_before)); + self.cache.push(CacheLruWriteBackCacheLine { + address, + content: memory_before, + memory: memory_before, + dirty: false, + }); + + // 4. Update cache line if necessary. Leaks: Cache (may) + self.update_and_leak_cache(scadata, self.cache.len() - 1, memory_after); } + trace!("Cache: {:x?}", self.cache); } } diff --git a/tests/libtests.rs b/tests/libtests.rs index 8e7c3eb..514e5b3 100644 --- a/tests/libtests.rs +++ b/tests/libtests.rs @@ -13,7 +13,9 @@ use rainbow_rs::{ HammingDistanceLeakage, HammingWeightLeakage, Leakage, LeakageModel, PessimisticHammingLeakage, }, - memory_extension::{BusNoCache, CacheLru, MemoryExtension, NoBusNoCache}, + memory_extension::{ + BusNoCache, CacheLruWriteBack, CacheLruWriteThrough, MemoryExtension, NoBusNoCache, + }, ScaData, ThumbTraceEmulator, ThumbTraceEmulatorTrait, }; use rstest::rstest; @@ -266,14 +268,14 @@ fn test_leakage_memory_bus(#[case] leakage: Box, #[case] expec } #[rstest] -#[case(Box::new(HammingWeightLeakage::new()), vec![1.0, 2.0, 4.0, 4.0, 8.0, 12.0, 1.0])] -#[case(Box::new(HammingDistanceLeakage::new()), vec![1.0, 1.0, 4.0, 4.0, 8.0, 12.0, 3.0])] -#[case(Box::new(PessimisticHammingLeakage::new()), vec![4.0, 10.0, 12.0, 28.0, 32.0, 16.0])] +#[case(Box::new(HammingWeightLeakage::new()), vec![1.0, 2.0, 4.0, 4.0, 8.0, 8.0, 1.0])] +#[case(Box::new(HammingDistanceLeakage::new()), vec![1.0, 1.0, 4.0, 4.0, 8.0, 8.0, 3.0])] +#[case(Box::new(PessimisticHammingLeakage::new()), vec![4.0, 10.0, 12.0, 28.0, 28.0, 16.0])] fn test_leakage_cache(#[case] leakage: Box, #[case] expected: Vec) { assert_eq!( &generate_leakage( leakage, - Box::new(CacheLru::new(4, 2)), + Box::new(CacheLruWriteThrough::new(4, 2)), Segment( 0x1000_0000, vec![ @@ -294,6 +296,60 @@ fn test_leakage_cache(#[case] leakage: Box, #[case] expected: ); } +#[rstest] +#[case(Box::new(CacheLruWriteThrough::new(4, 2)), vec![ + 2.0, // movs: Operand, Register + 4.0, // movt: Operand, Register, previous + 0.0, // nop + 0.0, // ldrb: r1 == 0 + 4.0, // adds: r1 == 1, Operand, Result + 5.0, // strb: Cache write-through Register before, Register after, Cache, Bus, Memory + 0.0, // nop + 0.0, // ldrb: r2 == 0 + 0.0, // nop + 1.0, // ldrb: r3 == 0: Cache update + ])] +#[case(Box::new(CacheLruWriteBack::new(4, 2)), vec![ + 2.0, // movs: Operand, Register + 4.0, // movt: Operand, Register, previous + 0.0, // nop + 0.0, // ldrb: r1 == 0 + 4.0, // adds: r1 == 1, Operand, Result + 3.0, // strb: Register before, Register after, Cache + 0.0, // nop + 0.0, // ldrb: r2 == 0 + 0.0, // nop + 4.0, // ldrb: r3 == 0: Cache write-back: Cache, 2x Bus, Memory + ])] +fn test_leakage_lrucaches(#[case] cache: Box, #[case] expected: Vec) { + assert_eq!( + &generate_leakage( + Box::new(PessimisticHammingLeakage::new()), + cache, + Segment( + 0x1000_0000, + vec![ + 0x00, 0xBF, // nop for hook + 0x00, 0xBF, // nop + 0x5f, 0xf4, 0x80, 0x70, // movs r0, #0x0100 + 0xc1, 0xf2, 0x00, 0x00, // movt r0, #0x1000 + 0x00, 0xBF, // nop + 0x01, 0x78, // ldrb r1, [r0] + 0x49, 0x1c, // adds r1, r1, #1 + 0x01, 0x70, // strb r1, [r0] + 0x00, 0xBF, // nop + 0x02, 0x79, // ldrb r2, [r0, #4] + 0x00, 0xBF, // nop + 0x03, 0x7A, // ldrb r3, [r0, #8] + 0x00, 0xBF, // nop + 0x00, 0xBF, // nop for hook + ], + ) + ), + &expected + ); +} + #[test] fn test_victim_communication() { let (channel_host, channel_emu) = create_inter_thread_channels();