pdb/source.rs
1// Copyright 2017 pdb Developers
2//
3// Licensed under the Apache License, Version 2.0, <LICENSE-APACHE or
4// http://apache.org/licenses/LICENSE-2.0> or the MIT license <LICENSE-MIT or
5// http://opensource.org/licenses/MIT>, at your option. This file may not be
6// copied, modified, or distributed except according to those terms.
7
8use std::fmt;
9use std::io;
10
11/// Represents an offset + size of the source file.
12///
13/// The multi-stream file implementation (used by `pdb::PDB`) determines which byte ranges it needs
14/// to satisfy its requests, and it describes those requests as a `&[SourceSlice]`.
15#[derive(Debug, Clone, Copy, Eq, PartialEq)]
16pub struct SourceSlice {
17 /// Offset into the source file.
18 pub offset: u64,
19 /// Size of the slice.
20 pub size: usize,
21}
22
23/// The `pdb` crate accesses PDB files via the `pdb::Source` trait.
24///
25/// This library is written with zero-copy in mind. `Source`s provide [`SourceView`]s which need not
26/// outlive their parent, supporting implementations of e.g. memory mapped files.
27///
28/// PDB files are "multi-stream files" (MSF) under the hood. MSFs have various layers of
29/// indirection, but ultimately the MSF code asks a `Source` to view a series of
30/// [`{ offset, size }` records](SourceSlice), which the `Source` provides as a
31/// contiguous `&[u8]`.
32///
33/// # Default
34///
35/// There is a default `Source` implementation for `std::io::Read` + `std::io::Seek` +
36/// `std::fmt::Debug`, allowing a `std::fs::File` to be treated as `pdb::Source`. This
37/// implementation provides views by allocating a buffer, seeking, and reading the contents into
38/// that buffer.
39///
40/// # Alignment
41///
42/// The requested offsets will always be aligned to the MSF's page size, which is always a power of
43/// two and is usually (but not always) 4096 bytes. The requested sizes will also be multiples of
44/// the page size, except for the size of the final `SourceSlice`, which may be smaller.
45///
46/// PDB files are specified as always being a multiple of the page size, so `Source` implementations
47/// are free to e.g. map whole pages and return a sub-slice of the requested length.
48///
49pub trait Source<'s>: fmt::Debug {
50 /// Provides a contiguous view of the source file composed of the requested position(s).
51 ///
52 /// Note that the SourceView's as_slice() method cannot fail, so `view()` is the time to raise
53 /// IO errors.
54 fn view(&mut self, slices: &[SourceSlice]) -> Result<Box<dyn SourceView<'s>>, io::Error>;
55}
56
57/// An owned, droppable, read-only view of the source file which can be referenced as a byte slice.
58pub trait SourceView<'s>: fmt::Debug {
59 /// Returns a view to the raw data.
60 fn as_slice(&self) -> &[u8];
61}
62
63#[derive(Clone)]
64struct ReadView {
65 bytes: Vec<u8>,
66}
67
68impl fmt::Debug for ReadView {
69 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
70 write!(f, "ReadView({} bytes)", self.bytes.len())
71 }
72}
73
74impl SourceView<'_> for ReadView {
75 fn as_slice(&self) -> &[u8] {
76 self.bytes.as_slice()
77 }
78}
79
80impl<'s, T> Source<'s> for T
81where
82 T: io::Read + io::Seek + fmt::Debug + 's,
83{
84 fn view(&mut self, slices: &[SourceSlice]) -> Result<Box<dyn SourceView<'s>>, io::Error> {
85 let len = slices.iter().fold(0, |acc, s| acc + s.size);
86
87 let mut v = ReadView {
88 bytes: Vec::with_capacity(len),
89 };
90 v.bytes.resize(len, 0);
91
92 {
93 let bytes = v.bytes.as_mut_slice();
94 let mut output_offset: usize = 0;
95 for slice in slices {
96 self.seek(io::SeekFrom::Start(slice.offset))?;
97 self.read_exact(&mut bytes[output_offset..(output_offset + slice.size)])?;
98 output_offset += slice.size;
99 }
100 }
101
102 Ok(Box::new(v))
103 }
104}
105
106#[cfg(test)]
107mod tests {
108 mod read_view {
109 use crate::source::*;
110 use std::io::Cursor;
111 use std::io::ErrorKind;
112
113 #[test]
114 fn test_basic_reading() {
115 let mut data = vec![0; 4096];
116 data[42] = 42;
117
118 let mut source: Box<dyn Source<'_>> = Box::new(Cursor::new(data.as_slice()));
119
120 let source_slices = vec![SourceSlice {
121 offset: 40,
122 size: 4,
123 }];
124 let view = source
125 .view(source_slices.as_slice())
126 .expect("viewing must succeed");
127 assert_eq!(&[0u8, 0, 42, 0], view.as_slice());
128 }
129
130 #[test]
131 fn test_discontinuous_reading() {
132 let mut data = vec![0; 4096];
133 data[42] = 42;
134 data[88] = 88;
135
136 let mut source: Box<dyn Source<'_>> = Box::new(Cursor::new(data.as_slice()));
137
138 let source_slices = vec![
139 SourceSlice {
140 offset: 88,
141 size: 1,
142 },
143 SourceSlice {
144 offset: 40,
145 size: 4,
146 },
147 ];
148 let view = source
149 .view(source_slices.as_slice())
150 .expect("viewing must succeed");
151 assert_eq!(&[88u8, 0, 0, 42, 0], view.as_slice());
152 }
153
154 #[test]
155 fn test_duplicate_reading() {
156 let mut data = vec![0; 4096];
157 data[42] = 42;
158 data[88] = 88;
159
160 let mut source: Box<dyn Source<'_>> = Box::new(Cursor::new(data.as_slice()));
161
162 let source_slices = vec![
163 SourceSlice {
164 offset: 88,
165 size: 1,
166 },
167 SourceSlice {
168 offset: 40,
169 size: 4,
170 },
171 SourceSlice {
172 offset: 88,
173 size: 1,
174 },
175 ];
176 let view = source
177 .view(source_slices.as_slice())
178 .expect("viewing must succeed");
179 assert_eq!(&[88u8, 0, 0, 42, 0, 88], view.as_slice());
180 }
181
182 #[test]
183 fn test_eof_reading() {
184 let data = vec![0; 4096];
185
186 let mut source: Box<dyn Source<'_>> = Box::new(Cursor::new(data.as_slice()));
187
188 // one byte is readable, but we asked for two
189 let source_slices = vec![SourceSlice {
190 offset: 4095,
191 size: 2,
192 }];
193 let r = source.view(source_slices.as_slice());
194 match r {
195 Ok(_) => panic!("should have failed"),
196 Err(e) => {
197 assert_eq!(ErrorKind::UnexpectedEof, e.kind());
198 }
199 }
200 }
201 }
202}