1use std::collections::BTreeSet;
7use std::sync::atomic::{AtomicU64, Ordering};
8
9#[derive(Debug, Clone)]
11pub enum FixupStatus {
12 Absent,
14 Applied { pairs: usize },
17 Skipped { reason: String },
22}
23
24pub struct Pe {
26 data: Vec<u8>,
27 pub image_base: u64,
28 sections: Vec<Section>,
29 fixup_posts: Vec<(u64, u64)>,
42 fixup_rel32: Vec<(u64, u64)>,
48 fixup_status: FixupStatus,
50 id: u64,
58}
59
60fn next_pe_id() -> u64 {
61 static NEXT_ID: AtomicU64 = AtomicU64::new(1);
62 NEXT_ID.fetch_add(1, Ordering::Relaxed)
63}
64
65#[derive(Clone, Debug)]
66pub struct Section {
67 pub name: String,
68 pub virtual_address: u32, pub virtual_size: u32,
70 pub raw_offset: u32,
71 pub raw_size: u32,
72 pub characteristics: u32,
76}
77
78pub mod section_flags {
81 pub const CODE: u32 = 0x0000_0020;
83 pub const DISCARDABLE: u32 = 0x0200_0000;
85 pub const EXECUTE: u32 = 0x2000_0000;
87 pub const READ: u32 = 0x4000_0000;
89 pub const WRITE: u32 = 0x8000_0000;
91}
92
93impl Section {
94 pub fn is_executable(&self) -> bool {
97 self.characteristics & (section_flags::EXECUTE | section_flags::CODE) != 0
98 }
99}
100
101fn u16(d: &[u8], o: usize) -> u16 {
102 u16::from_le_bytes([d[o], d[o + 1]])
103}
104fn u32(d: &[u8], o: usize) -> u32 {
105 u32::from_le_bytes([d[o], d[o + 1], d[o + 2], d[o + 3]])
106}
107
108impl Pe {
109 pub fn load(path: &str) -> Result<Pe, String> {
110 let data = std::fs::read(path).map_err(|e| format!("read {path}: {e}"))?;
111 Pe::parse(data)
112 }
113
114 pub fn parse(data: Vec<u8>) -> Result<Pe, String> {
115 let sz = data.len();
116 if sz < 0x40 || &data[0..2] != b"MZ" {
117 return Err(format!(
118 "not an MZ image: first 2 bytes are {:?} (expected b\"MZ\"), file size = {sz} bytes",
119 &data[..data.len().min(2)]
120 ));
121 }
122 let e_lfanew = u32(&data, 0x3c) as usize;
123 if data.len() >= e_lfanew + 2 && &data[e_lfanew..e_lfanew + 2] == b"LE" {
124 return parse_le(data, e_lfanew);
125 }
126 if data.len() < e_lfanew + 24 || &data[e_lfanew..e_lfanew + 4] != b"PE\0\0" {
127 let there = data
132 .get(e_lfanew..e_lfanew + 4)
133 .map(|s| format!("{:02x?}", s))
134 .unwrap_or_else(|| "<past EOF>".into());
135 return Err(format!(
136 "bad PE signature: e_lfanew = {e_lfanew:#x}, bytes there = [{there}] (expected b\"PE\\0\\0\"); file size = {sz} bytes"
137 ));
138 }
139 let coff = e_lfanew + 4;
140 let machine = u16(&data, coff);
141 if machine != 0x014c {
142 return Err(format!(
143 "unsupported machine {machine:#06x} (expected i386 = 0x014c); this crate only decodes 32-bit x86"
144 ));
145 }
146 let num_sections = u16(&data, coff + 2) as usize;
147 let opt_size = u16(&data, coff + 16) as usize;
148 let opt = coff + 20;
149 let magic = u16(&data, opt);
150 if magic != 0x10b {
151 return Err(format!(
152 "unsupported optional-header magic {magic:#06x} (expected PE32 = 0x10b); PE32+ (64-bit) and ROM images are not handled"
153 ));
154 }
155 let image_base = u32(&data, opt + 28) as u64;
156
157 let sec_table = opt + opt_size;
158 let mut sections = Vec::with_capacity(num_sections);
159 for i in 0..num_sections {
160 let s = sec_table + i * 40;
161 if s + 40 > data.len() {
162 return Err(format!(
163 "section table runs past EOF: section #{i} would start at file offset {s:#x} \
164 (e_lfanew={e_lfanew:#x} + opt_size={opt_size:#x} + {i}*40), \
165 but file size is only {sz} bytes — corrupt header or wrong format"
166 ));
167 }
168 let raw_name = &data[s..s + 8];
169 let end = raw_name.iter().position(|&b| b == 0).unwrap_or(8);
170 sections.push(Section {
171 name: String::from_utf8_lossy(&raw_name[..end]).into_owned(),
172 virtual_size: u32(&data, s + 8),
173 virtual_address: u32(&data, s + 12),
174 raw_size: u32(&data, s + 16),
175 raw_offset: u32(&data, s + 20),
176 characteristics: u32(&data, s + 36),
177 });
178 }
179 Ok(Pe {
180 data,
181 image_base,
182 sections,
183 fixup_posts: Vec::new(),
184 fixup_rel32: Vec::new(),
185 fixup_status: FixupStatus::Absent,
186 id: next_pe_id(),
187 })
188}
189
190 pub fn id(&self) -> u64 {
192 self.id
193 }
194
195 pub fn fixup_post_images(&self, pres: &BTreeSet<u64>) -> BTreeSet<u64> {
201 let mut out = BTreeSet::new();
202 if self.fixup_posts.is_empty() || pres.is_empty() {
203 return out;
204 }
205 for &(pre, post) in &self.fixup_posts {
206 if pres.contains(&pre) {
207 out.insert(post);
208 }
209 }
210 out
211 }
212
213 pub fn fixup_post_image(&self, pre: u64) -> Option<u64> {
222 self.fixup_posts
223 .iter()
224 .find_map(|&(p, post)| (p == pre).then_some(post))
225 }
226
227 pub fn fixup_count(&self) -> usize {
230 self.fixup_posts.len()
231 }
232
233 pub fn fixup_pairs(&self) -> &[(u64, u64)] {
237 &self.fixup_posts
238 }
239
240 pub fn fixup_rel32_pairs(&self) -> &[(u64, u64)] {
242 &self.fixup_rel32
243 }
244
245 pub fn fixup_rel32_target(&self, loc: u64) -> Option<u64> {
248 self.fixup_rel32
249 .iter()
250 .find_map(|&(l, t)| (l == loc).then_some(t))
251 }
252
253 pub fn fixup_status(&self) -> &FixupStatus {
255 &self.fixup_status
256 }
257
258 pub fn section_of(&self, va: u64) -> Option<&Section> {
260 let rva = va.checked_sub(self.image_base)? as u32;
261 self.sections.iter().find(|s| {
262 let span = s.virtual_size.max(s.raw_size);
265 rva >= s.virtual_address && rva < s.virtual_address + span
266 })
267 }
268
269 pub fn read(&self, va: u64, len: usize) -> Option<&[u8]> {
273 let sec = self.section_of(va)?;
274 let rva = (va - self.image_base) as u32;
275 let off_in_sec = rva - sec.virtual_address;
276 if off_in_sec >= sec.raw_size {
277 return None; }
279 let file_off = (sec.raw_offset + off_in_sec) as usize;
280 let avail = (sec.raw_size - off_in_sec) as usize;
281 let take = len.min(avail).min(self.data.len().saturating_sub(file_off));
282 Some(&self.data[file_off..file_off + take])
283 }
284
285 pub fn sections(&self) -> &[Section] {
287 &self.sections
288 }
289
290 pub fn is_executable_va(&self, va: u64) -> bool {
291 self.section_of(va).is_some()
303 }
304
305 pub fn is_executable_section_va(&self, va: u64) -> bool {
312 self.section_of(va)
313 .map(|s| s.is_executable())
314 .unwrap_or(false)
315 }
316
317 pub fn describe_sections(&self, va: u64) -> String {
322 let mut s = String::new();
323 let mut best: Option<(&Section, u64)> = None;
324 for sec in &self.sections {
325 let start = self.image_base + sec.virtual_address as u64;
326 let span = sec.virtual_size.max(sec.raw_size) as u64;
327 let end = start + span;
328 s.push_str(&format!(
329 " {name:<8} [0x{start:08x}..0x{end:08x}) ({span} bytes){flag}\n",
330 name = sec.name,
331 start = start,
332 end = end,
333 span = span,
334 flag = if sec.is_executable() { " code" } else { "" },
335 ));
336 let d = if va < start {
337 start - va
338 } else { va.saturating_sub(end) };
339 if best.map(|(_, bd)| d < bd).unwrap_or(true) {
340 best = Some((sec, d));
341 }
342 }
343 if let Some((sec, d)) = best {
344 s.push_str(&format!(
345 " closest section to {va:#010x}: {name} (distance {d:#x} bytes) — \
346 did you mean 0x{start:08x}?",
347 name = sec.name,
348 start = self.image_base + sec.virtual_address as u64,
349 ));
350 }
351 s
352 }
353
354 pub fn image_range(&self) -> (u64, u64) {
356 let end = self
357 .sections
358 .iter()
359 .map(|s| s.virtual_address as u64 + s.virtual_size.max(s.raw_size) as u64)
360 .max()
361 .unwrap_or(0);
362 (self.image_base, self.image_base + end)
363 }
364
365 pub fn referenced_addrs(&self) -> std::collections::HashSet<u32> {
378 let (lo, hi) = self.image_range();
379 let mut out: std::collections::HashSet<u32> = std::collections::HashSet::new();
380 for sec in &self.sections {
381 let start = sec.raw_offset as usize;
382 let end = (start + sec.raw_size as usize).min(self.data.len());
383 if start >= end {
384 continue;
385 }
386 let blob = &self.data[start..end];
387 let sec_va = self.image_base + sec.virtual_address as u64;
388 if sec.name == ".text" || !sec.is_executable() {
395 for i in 0..blob.len().saturating_sub(4) {
396 if blob[i] == 0xEB && i + 2 <= blob.len() {
403 let rel = blob[i + 1] as i8 as i64;
404 let t = (sec_va + i as u64 + 2).wrapping_add(rel as u64);
405 if (lo..hi).contains(&t) {
406 out.insert(t as u32);
407 }
408 }
409 let (op, rel_at) = match blob[i] {
410 0xE8 | 0xE9 => (blob[i], i + 1), 0x0F if i + 6 <= blob.len() && (0x80..=0x8F).contains(&blob[i + 1]) => {
412 (blob[i + 1], i + 2) }
414 _ => continue,
415 };
416 let _ = op;
417 if rel_at + 4 > blob.len() {
418 continue;
419 }
420 let rel = i32::from_le_bytes([
421 blob[rel_at],
422 blob[rel_at + 1],
423 blob[rel_at + 2],
424 blob[rel_at + 3],
425 ]) as i64;
426 let t = (sec_va + (rel_at as u64 + 4)).wrapping_add(rel as u64);
427 if (lo..hi).contains(&t) {
428 out.insert(t as u32);
429 }
430 }
431 }
432 for w in blob.windows(4) {
434 let v = u32::from_le_bytes([w[0], w[1], w[2], w[3]]) as u64;
435 if (lo..hi).contains(&v) {
436 out.insert(v as u32);
437 }
438 }
439 }
440 if !self.fixup_posts.is_empty() {
451 let pres: BTreeSet<u64> = out.iter().map(|&v| v as u64).collect();
452 for post in self.fixup_post_images(&pres) {
453 if (lo..hi).contains(&post) {
454 out.insert(post as u32);
455 }
456 }
457 }
458 for &(_, t) in &self.fixup_rel32 {
462 if (lo..hi).contains(&t) {
463 out.insert(t as u32);
464 }
465 }
466 out
467 }
468}
469
470fn parse_le(data: Vec<u8>, le_off: usize) -> Result<Pe, String> {
491 let sz = data.len();
492 let need = le_off + 0xAC;
493 if sz < need + 4 {
494 return Err(format!(
495 "truncated LE header: need {need:#x} bytes for header, file size = {sz} bytes"
496 ));
497 }
498 let at32 = |o: usize| -> Result<u32, String> {
499 if o + 4 > sz {
500 return Err(format!("LE header read past EOF at {o:#x}"));
501 }
502 Ok(u32::from_le_bytes([data[o], data[o + 1], data[o + 2], data[o + 3]]))
503 };
504 let at16 = |o: usize| -> Result<u16, String> {
505 if o + 2 > sz {
506 return Err(format!("LE header read past EOF at {o:#x}"));
507 }
508 Ok(u16::from_le_bytes([data[o], data[o + 1]]))
509 };
510 let cpu = at16(le_off + 8)?;
511 if cpu != 2 {
512 return Err(format!(
513 "unsupported LE cpu {cpu:#x} (expected 386 = 0x2); only 32-bit x86 LE is handled"
514 ));
515 }
516 let numpages = at32(le_off + 0x14)? as usize;
517 let pagesize = at32(le_off + 0x28)? as usize;
518 let lastpagesize = at32(le_off + 0x2C)? as usize;
519 let objtab_rel = at32(le_off + 0x40)? as usize;
520 let objcnt = at32(le_off + 0x44)? as usize;
521 let pagemap_rel = at32(le_off + 0x48)? as usize;
522 let itermap = at32(le_off + 0x4C)?;
523 let datapage_abs = at32(le_off + 0x80)? as usize;
524 if !(512..=16384).contains(&pagesize) {
525 return Err(format!("implausible LE pagesize {pagesize}"));
526 }
527 if numpages == 0 || numpages > 100_000 {
528 return Err(format!("implausible LE page count {numpages}"));
529 }
530 if objcnt == 0 || objcnt > 100 {
531 return Err(format!("implausible LE object count {objcnt}"));
532 }
533 if itermap != 0 {
534 return Err(format!(
535 "unsupported LE iterated-data map at {itermap:#x}: iterated pages not yet supported"
536 ));
537 }
538 let objtab = le_off.checked_add(objtab_rel).ok_or("LE objtab overflow")?;
539 let pagemap = le_off.checked_add(pagemap_rel).ok_or("LE pagemap overflow")?;
540 if objtab + objcnt * 24 > sz {
541 return Err(format!(
542 "LE object table runs past EOF: objtab={objtab:#x} + {objcnt}*24, file size {sz:#x}"
543 ));
544 }
545 if pagemap + numpages * 4 > sz {
546 return Err(format!(
547 "LE page map runs past EOF: pagemap={pagemap:#x} + {numpages}*4, file size {sz:#x}"
548 ));
549 }
550 if datapage_abs >= sz {
551 return Err(format!(
552 "LE data pages offset {datapage_abs:#x} past EOF (file size {sz:#x})"
553 ));
554 }
555 let mut phys_of_logical: Vec<(u32, u8)> = Vec::with_capacity(numpages);
559 for i in 0..numpages {
560 let o = pagemap + i * 4;
561 let b0 = data[o] as u32;
562 let b1 = data[o + 1] as u32;
563 let b2 = data[o + 2] as u32;
564 let flags = data[o + 3];
565 let page = (b0 << 16) | (b1 << 8) | b2;
566 if flags != 0 {
567 return Err(format!(
568 "unsupported LE page {i} flags={flags:#x} (page={page}): only flag-0 legal pages are supported; iterated/zero pages TODO"
569 ));
570 }
571 if page == 0 || page as usize > numpages {
572 return Err(format!(
573 "LE page map entry {i} has out-of-range physical page {page} (numpages={numpages})"
574 ));
575 }
576 phys_of_logical.push((page, flags));
577 }
578 let mut new_data: Vec<u8> = Vec::new();
579 let mut sections: Vec<Section> = Vec::with_capacity(objcnt);
580 let mut objs: Vec<(u32, usize, usize, usize)> = Vec::with_capacity(objcnt);
583 let mut exec_seen = 0usize;
584 let mut data_seen = 0usize;
585 for oi in 0..objcnt {
586 let o = objtab + oi * 24;
587 let vsize = u32::from_le_bytes([data[o], data[o + 1], data[o + 2], data[o + 3]]) as usize;
588 let base =
589 u32::from_le_bytes([data[o + 4], data[o + 5], data[o + 6], data[o + 7]]) as u32;
590 let flags =
591 u32::from_le_bytes([data[o + 8], data[o + 9], data[o + 10], data[o + 11]]);
592 let page_idx =
593 u32::from_le_bytes([data[o + 12], data[o + 13], data[o + 14], data[o + 15]])
594 as usize;
595 let page_cnt =
596 u32::from_le_bytes([data[o + 16], data[o + 17], data[o + 18], data[o + 19]])
597 as usize;
598 if page_idx == 0 || page_idx - 1 + page_cnt > numpages {
599 return Err(format!(
600 "LE object {oi} page range [{page_idx}..{}) out of 1..={numpages}",
601 page_idx + page_cnt
602 ));
603 }
604 objs.push((base, vsize, page_idx, page_cnt));
605 let start = new_data.len();
606 for j in 0..page_cnt {
607 let (phys, _) = phys_of_logical[page_idx - 1 + j];
608 let is_last_phys = phys as usize == numpages;
609 let plen = if is_last_phys { lastpagesize } else { pagesize };
610 let foff = datapage_abs + (phys as usize - 1) * pagesize;
611 if foff + plen > sz {
612 return Err(format!(
613 "LE object {oi} page {j} (phys {phys}) file range [{foff:#x}..{:#x}) past EOF {sz:#x}",
614 foff + plen
615 ));
616 }
617 new_data.extend_from_slice(&data[foff..foff + plen]);
618 }
619 let file_len = new_data.len() - start;
620 let raw_size = vsize.min(file_len);
621 new_data.truncate(start + raw_size);
624 let is_exec = flags & 0x4 != 0;
625 let (name, characteristics) = if is_exec {
626 let n = if exec_seen == 0 {
627 ".text".to_string()
628 } else {
629 format!(".text{exec_seen}")
630 };
631 exec_seen += 1;
632 (
633 n,
634 section_flags::CODE | section_flags::EXECUTE | section_flags::READ,
635 )
636 } else {
637 let n = if data_seen == 0 {
638 ".data".to_string()
639 } else {
640 format!(".data{data_seen}")
641 };
642 data_seen += 1;
643 (
644 n,
645 section_flags::READ | section_flags::WRITE,
646 )
647 };
648 sections.push(Section {
649 name,
650 virtual_address: base,
651 virtual_size: vsize as u32,
652 raw_offset: start as u32,
653 raw_size: raw_size as u32,
654 characteristics,
655 });
656 }
657 let (fixup_posts, fixup_rel32, fixup_status) = match collect_fixups(
658 &data, le_off, &objs, numpages, pagesize, §ions, &new_data,
659 ) {
660 Ok((pairs, rel32)) => {
661 let n = pairs.len();
662 (pairs, rel32, FixupStatus::Applied { pairs: n })
663 }
664 Err(e) if e.starts_with(NOVEL_FIXUP_PREFIX) => {
665 (Vec::new(), Vec::new(), FixupStatus::Skipped { reason: e })
666 }
667 Err(e) => return Err(e),
668 };
669 Ok(Pe {
670 data: new_data,
671 image_base: 0,
672 sections,
673 fixup_posts,
674 fixup_rel32,
675 fixup_status,
676 id: next_pe_id(),
677 })
678}
679
680const NOVEL_FIXUP_PREFIX: &str = "NOVEL-FIXUP-RECORD: ";
688
689#[allow(clippy::too_many_arguments)]
702fn collect_fixups(
703 data: &[u8],
704 le_off: usize,
705 objs: &[(u32, usize, usize, usize)],
706 numpages: usize,
707 pagesize: usize,
708 sections: &[Section],
709 image: &[u8],
710) -> Result<(Vec<(u64, u64)>, Vec<(u64, u64)>), String> {
711 let pre_of = |va: u64| -> Option<u32> {
712 for s in sections {
713 let lo = s.virtual_address as u64;
714 let hi = lo + s.virtual_size as u64;
715 if (lo..hi).contains(&va) {
716 let off = s.raw_offset as u64 + (va - lo);
717 if off + 4 <= s.raw_offset as u64 + s.raw_size as u64 {
718 let o = off as usize;
719 return Some(u32::from_le_bytes([
720 image[o],
721 image[o + 1],
722 image[o + 2],
723 image[o + 3],
724 ]));
725 }
726 return Some(0);
728 }
729 }
730 None
731 };
732 let at32 = |o: usize| -> Result<u32, String> {
733 if o + 4 > data.len() {
734 return Err(format!("LE fixup header read past EOF at {o:#x}"));
735 }
736 Ok(u32::from_le_bytes([data[o], data[o + 1], data[o + 2], data[o + 3]]))
737 };
738 let fpt_field = le_off
739 .checked_add(0x68)
740 .ok_or("LE fixup page-table field overflow")?;
741 let frt_field = le_off
742 .checked_add(0x6C)
743 .ok_or("LE fixup record-table field overflow")?;
744 let fpt = le_off
745 .checked_add(at32(fpt_field)? as usize)
746 .ok_or("LE fixup page-table offset overflow")?;
747 let frt = le_off
748 .checked_add(at32(frt_field)? as usize)
749 .ok_or("LE fixup record-table offset overflow")?;
750 if fpt == le_off && frt == le_off {
751 return Ok((Vec::new(), Vec::new()));
753 }
754 let tab_len = (numpages + 1)
755 .checked_mul(4)
756 .ok_or("LE fixup page-table size overflow")?;
757 if fpt.checked_add(tab_len) != Some(frt) {
760 return Err(format!(
761 "LE fixup tables not adjacent: pagetab={fpt:#x} + {tab_len:#x} != rectab={frt:#x}"
762 ));
763 }
764 if frt > data.len() {
765 return Err(format!(
766 "LE fixup record table {frt:#x} past EOF (file size {:#x})",
767 data.len()
768 ));
769 }
770 let mut bounds: Vec<(usize, usize)> = Vec::with_capacity(numpages);
771 let mut prev = 0usize;
772 let mut first = true;
773 for i in 0..=numpages {
774 let e = at32(fpt + i * 4)? as usize;
775 if !first && e < prev {
776 return Err(format!(
777 "LE fixup page table not monotonic at page {i}: {e:#x} < {prev:#x}"
778 ));
779 }
780 first = false;
781 prev = e;
782 if i < numpages {
783 bounds.push((e, 0));
784 } else if frt.checked_add(e).map_or(true, |end| end > data.len()) {
785 return Err(format!(
786 "LE fixup record table end {e:#x} past EOF (file size {:#x})",
787 data.len()
788 ));
789 }
790 if i > 0 {
791 bounds[i - 1].1 = e;
792 }
793 }
794 let mut out: Vec<(u64, u64)> = Vec::new();
795 let mut out_rel32: Vec<(u64, u64)> = Vec::new();
796 for (pi, (start, end)) in bounds.iter().enumerate() {
797 let lp = pi + 1; let (obase, _, opage, _) = objs
799 .iter()
800 .find(|(_, _, p, c)| lp >= *p && lp < p + c)
801 .ok_or(format!("LE fixup page {lp} belongs to no object"))?;
802 let lin_base = (*obase as usize)
803 .checked_add((lp - opage) * pagesize)
804 .ok_or(format!("LE fixup page {lp} linear base overflow"))?;
805 let mut p = frt
806 .checked_add(*start)
807 .ok_or("LE fixup record offset overflow")?;
808 let pend = frt
809 .checked_add(*end)
810 .ok_or("LE fixup record end overflow")?;
811 while p < pend {
812 let rec_off = p - frt;
813 let st = *data.get(p).ok_or(format!(
814 "LE fixup record {rec_off:#x} (page {lp}) truncated: missing srctype"
815 ))?;
816 let fl = *data.get(p + 1).ok_or(format!(
817 "LE fixup record {rec_off:#x} (page {lp}) truncated: missing tgtflags"
818 ))?;
819 p += 2;
820 let mut srcs: Vec<u16> = Vec::new();
821 if st & 0x20 != 0 {
822 let cnt = *data.get(p).ok_or(format!(
823 "LE fixup record {rec_off:#x} (page {lp}) truncated: missing source count"
824 ))?;
825 p += 1;
826 for _ in 0..cnt {
827 if p + 2 > pend || p + 2 > data.len() {
828 return Err(format!(
829 "LE fixup record {rec_off:#x} (page {lp}) source runs past page end"
830 ));
831 }
832 srcs.push(u16::from_le_bytes([data[p], data[p + 1]]));
833 p += 2;
834 }
835 } else {
836 if p + 2 > pend || p + 2 > data.len() {
837 return Err(format!(
838 "LE fixup record {rec_off:#x} (page {lp}) source runs past page end"
839 ));
840 }
841 srcs.push(u16::from_le_bytes([data[p], data[p + 1]]));
842 p += 2;
843 }
844 let low4 = st & 0x0F;
845 let is_sel = (st == 0x02 || st == 0x22) && fl == 0x00;
846 let is_off = (low4 == 7 || low4 == 8)
847 && (st & 0xD0) == 0
848 && (fl == 0x00 || fl == 0x10);
849 if !is_sel && !is_off {
850 return Err(format!(
855 "{NOVEL_FIXUP_PREFIX}record {rec_off:#x} (page {lp}): srctype=0x{st:02X} tgtflags=0x{fl:02X}"
856 ));
857 }
858 if p + 1 > pend || p + 1 > data.len() {
859 return Err(format!(
860 "LE fixup record {rec_off:#x} (page {lp}) truncated: missing target object"
861 ));
862 }
863 let obj = data[p] as usize;
864 p += 1;
865 if obj < 1 || obj > objs.len() {
866 return Err(format!(
867 "LE fixup record {rec_off:#x} (page {lp}) has bad target object {obj}"
868 ));
869 }
870 let wide = fl & 0x10 != 0;
871 let toff: u32 = if is_sel {
872 0
873 } else if wide {
874 if p + 4 > pend || p + 4 > data.len() {
875 return Err(format!(
876 "LE fixup record {rec_off:#x} (page {lp}) truncated: missing wide target offset"
877 ));
878 }
879 let v = u32::from_le_bytes([data[p], data[p + 1], data[p + 2], data[p + 3]]);
880 p += 4;
881 v
882 } else {
883 if p + 2 > pend || p + 2 > data.len() {
884 return Err(format!(
885 "LE fixup record {rec_off:#x} (page {lp}) truncated: missing narrow target offset"
886 ));
887 }
888 let v = u16::from_le_bytes([data[p], data[p + 1]]) as u32;
889 p += 2;
890 v
891 };
892 let target = objs[obj - 1].0.wrapping_add(toff);
893 for s in srcs {
894 let loc = if (s as usize) < pagesize {
898 lin_base.checked_add(s as usize)
899 } else if s >= 0xFFFD {
900 lin_base
901 .checked_add(s as usize)
902 .and_then(|l| l.checked_sub(0x1_0000))
903 } else {
904 None
905 };
906 let loc = loc.ok_or(format!(
907 "LE fixup record {rec_off:#x} (page {lp}) bad source offset {s:#x} (pagesize {pagesize:#x})"
908 ))?;
909 let pre = pre_of(loc as u64).ok_or(format!(
910 "LE fixup record {rec_off:#x} (page {lp}) location {loc:#x} outside image"
911 ))?;
912 if is_sel {
913 continue; }
915 if low4 == 7 {
916 out.push((pre as u64, target as u64));
917 } else if low4 == 8 {
918 out_rel32.push((loc as u64, target as u64));
921 }
922 }
923 }
924 if p != pend {
925 return Err(format!(
926 "LE fixup page {lp} consumed to {:#x}, expected end {:#x}",
927 p - frt,
928 pend - frt
929 ));
930 }
931 }
932 Ok((out, out_rel32))
933}
934
935#[cfg(test)]
936mod tests {
937 use super::*;
938
939 fn build_fixup_le() -> Vec<u8> {
945 let le_off = 0x40usize;
946 let pagesize = 0x1000usize;
947 let mut data = vec![0u8; 0x1400];
948 data[0..2].copy_from_slice(b"MZ");
949 data[0x3c..0x40].copy_from_slice(&(le_off as u32).to_le_bytes());
950 data[le_off..le_off + 2].copy_from_slice(b"LE");
951 data[le_off + 8..le_off + 10].copy_from_slice(&2u16.to_le_bytes()); data[le_off + 0x14..le_off + 0x18].copy_from_slice(&1u32.to_le_bytes()); data[le_off + 0x28..le_off + 0x2C]
954 .copy_from_slice(&(pagesize as u32).to_le_bytes()); data[le_off + 0x2C..le_off + 0x30]
956 .copy_from_slice(&(pagesize as u32).to_le_bytes()); data[le_off + 0x40..le_off + 0x44].copy_from_slice(&0x1C0u32.to_le_bytes()); data[le_off + 0x44..le_off + 0x48].copy_from_slice(&1u32.to_le_bytes()); data[le_off + 0x48..le_off + 0x4C].copy_from_slice(&0x1D8u32.to_le_bytes()); data[le_off + 0x4C..le_off + 0x50].copy_from_slice(&0u32.to_le_bytes()); data[le_off + 0x68..le_off + 0x6C].copy_from_slice(&0x2C0u32.to_le_bytes()); data[le_off + 0x6C..le_off + 0x70].copy_from_slice(&0x2C8u32.to_le_bytes()); data[le_off + 0x80..le_off + 0x84].copy_from_slice(&0x400u32.to_le_bytes()); let o = 0x200usize;
966 data[o..o + 4].copy_from_slice(&0x1000u32.to_le_bytes());
967 data[o + 4..o + 8].copy_from_slice(&0x10000u32.to_le_bytes());
968 data[o + 8..o + 12].copy_from_slice(&0x4u32.to_le_bytes());
969 data[o + 12..o + 16].copy_from_slice(&1u32.to_le_bytes());
970 data[o + 16..o + 20].copy_from_slice(&1u32.to_le_bytes());
971 data[0x218..0x21C].copy_from_slice(&[0x00, 0x00, 0x01, 0x00]);
973 data[0x300..0x304].copy_from_slice(&0u32.to_le_bytes());
975 data[0x304..0x308].copy_from_slice(&26u32.to_le_bytes());
976 data[0x308..0x30F].copy_from_slice(&[0x07, 0x00, 0x10, 0x00, 0x01, 0x00, 0x05]);
979 data[0x30F..0x316].copy_from_slice(&[0x08, 0x00, 0x21, 0x00, 0x01, 0x00, 0x01]);
982 data[0x316..0x322].copy_from_slice(&[
986 0x27, 0x10, 0x02, 0x30, 0x00, 0x34, 0x00, 0x01, 0x00, 0x06, 0x00, 0x00,
987 ]);
988 let pg = 0x400usize;
990 data[pg..pg + 6].copy_from_slice(&[0xA1, 0x00, 0x04, 0x01, 0x00, 0xC3]);
991 data[pg + 0x10..pg + 0x14].copy_from_slice(&0x10400u32.to_le_bytes());
992 data[pg + 0x20..pg + 0x25].copy_from_slice(&[0xE8, 0x00, 0x00, 0x00, 0x00]);
993 data[pg + 0x30..pg + 0x34].copy_from_slice(&0x10410u32.to_le_bytes());
994 data[pg + 0x34..pg + 0x38].copy_from_slice(&0x10420u32.to_le_bytes());
995 data
996 }
997
998 #[test]
999 fn le_fixup_07_pairs_absorbed() {
1000 let pe = Pe::parse(build_fixup_le()).expect("parse synthetic LE");
1001 assert_eq!(pe.fixup_count(), 3, "one narrow 07 + two listed 07 dwords");
1002 let got = pe.fixup_post_images(&[0x10400].into_iter().collect());
1003 assert_eq!(got, [0x10500].into_iter().collect());
1004 let got = pe.fixup_post_images(&[0x10410, 0x10420].into_iter().collect());
1005 assert_eq!(got, [0x10600].into_iter().collect());
1006 let got = pe.fixup_post_images(&[0x99999].into_iter().collect());
1008 assert!(got.is_empty());
1009 }
1010
1011 #[test]
1012 fn le_fixup_post_global_passes_spec() {
1013 use crate::{analyze, Spec};
1014 let pe = Pe::parse(build_fixup_le()).expect("parse synthetic LE");
1015 let cg = analyze(&pe, 0x10000).expect("analyze synthetic fn");
1016 assert!(cg.globals.contains(&0x10400), "{cg:?}");
1017 Spec::new(0x10000).globals(&[0x10400]).check(&pe).expect("pre global");
1019 Spec::new(0x10000).globals(&[0x10500]).check(&pe).expect("post global");
1021 assert!(Spec::new(0x10000).globals(&[0x10999]).check(&pe).is_err());
1023 }
1024
1025 #[test]
1026 fn le_fixup_post_image_single_lookup() {
1027 let pe = Pe::parse(build_fixup_le()).expect("parse synthetic LE");
1028 assert_eq!(pe.fixup_post_image(0x10400), Some(0x10500));
1029 assert_eq!(pe.fixup_post_image(0x10410), Some(0x10600));
1030 assert_eq!(pe.fixup_post_image(0x10420), Some(0x10600));
1031 assert_eq!(pe.fixup_post_image(0x99999), None);
1033 }
1034
1035 #[test]
1036 fn le_referenced_addrs_include_fixup_post_images() {
1037 let pe = Pe::parse(build_fixup_le()).expect("parse synthetic LE");
1038 let refs = pe.referenced_addrs();
1039 assert!(refs.contains(&0x10400), "pre operand ref missing: {refs:x?}");
1042 assert!(refs.contains(&0x10410), "pre stored dword ref missing");
1043 assert!(refs.contains(&0x10420), "pre stored dword ref missing");
1044 assert!(refs.contains(&0x10500), "post image of 0x10400 missing");
1048 assert!(
1049 refs.contains(&0x10600),
1050 "post image of 0x10410/0x10420 missing"
1051 );
1052 assert!(
1054 !refs.contains(&0x10999),
1055 "garbage address must stay unreferenced"
1056 );
1057 }
1058
1059 #[test]
1060 fn le_fixup_unknown_record_skips_loudly() {
1061 use super::FixupStatus;
1062 let mut data = build_fixup_le();
1063 data[0x308] = 0x09; let pe = Pe::parse(data).expect("novel fixup kind must skip, not fail");
1067 assert_eq!(pe.fixup_count(), 0);
1068 match pe.fixup_status() {
1069 FixupStatus::Skipped { reason } => {
1070 assert!(reason.contains("srctype=0x09"), "{reason}")
1071 }
1072 other => panic!("expected Skipped, got {other:?}"),
1073 }
1074 }
1075
1076 #[test]
1077 fn le_fixup_tables_must_be_adjacent() {
1078 let mut data = build_fixup_le();
1079 data[0x40 + 0x68..0x40 + 0x6C].copy_from_slice(&0x2C4u32.to_le_bytes());
1080 match Pe::parse(data) {
1081 Ok(_) => panic!("moved page table must fail"),
1082 Err(err) => assert!(err.contains("not adjacent"), "{err}"),
1083 }
1084 }
1085}