@@ -1,3 +1,5 @@ |
| 1 | +use std::thread; |
| 2 | + |
| 1 | 3 | use crate::layout::Layout; |
| 2 | 4 | use crate::section::is_executable; |
| 3 | 5 | use crate::LinkOptions; |
@@ -52,6 +54,10 @@ impl CodeSignaturePlan { |
| 52 | 54 | } |
| 53 | 55 | |
| 54 | 56 | pub fn build(&self, signed_prefix: &[u8]) -> Vec<u8> { |
| 57 | + self.build_with_jobs(signed_prefix, 1) |
| 58 | + } |
| 59 | + |
| 60 | + pub fn build_with_jobs(&self, signed_prefix: &[u8], parallel_jobs: usize) -> Vec<u8> { |
| 55 | 61 | debug_assert_eq!(signed_prefix.len(), self.code_limit as usize); |
| 56 | 62 | |
| 57 | 63 | let code_slots = code_slots(self.code_limit as usize); |
@@ -92,8 +98,8 @@ impl CodeSignaturePlan { |
| 92 | 98 | |
| 93 | 99 | out.extend_from_slice(self.identifier.as_bytes()); |
| 94 | 100 | out.push(0); |
| 95 | | - for page in signed_prefix.chunks(PAGE_SIZE) { |
| 96 | | - out.extend_from_slice(&sha256(page)); |
| 101 | + for hash in page_hashes(signed_prefix, parallel_jobs) { |
| 102 | + out.extend_from_slice(&hash); |
| 97 | 103 | } |
| 98 | 104 | out.resize(padded_len, 0); |
| 99 | 105 | out |
@@ -149,6 +155,33 @@ fn code_slots(code_limit: usize) -> usize { |
| 149 | 155 | } |
| 150 | 156 | } |
| 151 | 157 | |
| 158 | +fn page_hashes(data: &[u8], parallel_jobs: usize) -> Vec<[u8; 32]> { |
| 159 | + let page_count = code_slots(data.len()); |
| 160 | + if page_count == 0 { |
| 161 | + return Vec::new(); |
| 162 | + } |
| 163 | + let parallel_jobs = parallel_jobs.max(1).min(page_count); |
| 164 | + if parallel_jobs == 1 || page_count < 2 { |
| 165 | + return data.chunks(PAGE_SIZE).map(sha256).collect(); |
| 166 | + } |
| 167 | + |
| 168 | + let chunk_pages = page_count.div_ceil(parallel_jobs); |
| 169 | + let chunk_bytes = PAGE_SIZE * chunk_pages; |
| 170 | + thread::scope(|scope| { |
| 171 | + let mut handles = Vec::new(); |
| 172 | + for chunk in data.chunks(chunk_bytes) { |
| 173 | + handles |
| 174 | + .push(scope.spawn(move || chunk.chunks(PAGE_SIZE).map(sha256).collect::<Vec<_>>())); |
| 175 | + } |
| 176 | + |
| 177 | + let mut hashes = Vec::with_capacity(page_count); |
| 178 | + for handle in handles { |
| 179 | + hashes.extend(handle.join().expect("code-signature hash worker panicked")); |
| 180 | + } |
| 181 | + hashes |
| 182 | + }) |
| 183 | +} |
| 184 | + |
| 152 | 185 | fn push_be_u32(out: &mut Vec<u8>, value: u32) { |
| 153 | 186 | out.extend_from_slice(&value.to_be_bytes()); |
| 154 | 187 | } |
@@ -336,4 +369,42 @@ mod tests { |
| 336 | 369 | assert_eq!(read_be_u32(&blob, 52), 16_512); |
| 337 | 370 | assert_eq!(&blob[108..114], b"apple\0"); |
| 338 | 371 | } |
| 372 | + |
| 373 | + #[test] |
| 374 | + fn parallel_page_hashes_preserve_serial_order() { |
| 375 | + let mut bytes = Vec::with_capacity(PAGE_SIZE * 9 + 123); |
| 376 | + for index in 0..PAGE_SIZE * 9 + 123 { |
| 377 | + bytes.push((index.wrapping_mul(37).wrapping_add(19) & 0xff) as u8); |
| 378 | + } |
| 379 | + |
| 380 | + let serial = page_hashes(&bytes, 1); |
| 381 | + let parallel = page_hashes(&bytes, 4); |
| 382 | + assert_eq!(parallel, serial); |
| 383 | + assert_eq!(parallel.len(), 10); |
| 384 | + } |
| 385 | + |
| 386 | + #[test] |
| 387 | + fn parallel_code_signature_matches_single_worker() { |
| 388 | + let opts = LinkOptions { |
| 389 | + output: Some("parallel".into()), |
| 390 | + ..LinkOptions::default() |
| 391 | + }; |
| 392 | + let code_limit = PAGE_SIZE * 11 + 777; |
| 393 | + let plan = CodeSignaturePlan::new( |
| 394 | + &Layout::empty(crate::OutputKind::Executable, 0), |
| 395 | + &opts, |
| 396 | + code_limit as u64, |
| 397 | + true, |
| 398 | + ) |
| 399 | + .unwrap(); |
| 400 | + let mut signed_prefix = Vec::with_capacity(code_limit); |
| 401 | + for index in 0..code_limit { |
| 402 | + signed_prefix.push((index.wrapping_mul(13).wrapping_add(index / 7) & 0xff) as u8); |
| 403 | + } |
| 404 | + |
| 405 | + assert_eq!( |
| 406 | + plan.build_with_jobs(&signed_prefix, 8), |
| 407 | + plan.build_with_jobs(&signed_prefix, 1) |
| 408 | + ); |
| 409 | + } |
| 339 | 410 | } |