samply_symbols/
windows.rs

1use std::borrow::Cow;
2use std::collections::HashMap;
3use std::ops::Deref;
4use std::sync::{Arc, Mutex};
5
6use debugid::DebugId;
7use nom::bytes::complete::{tag, take_until1};
8use nom::combinator::eof;
9use nom::sequence::terminated;
10use object::{File, FileKind};
11use pdb::PDB;
12use pdb_addr2line::pdb;
13use yoke::Yoke;
14use yoke_derive::Yokeable;
15
16use crate::debugid_util::debug_id_for_object;
17use crate::dwarf::Addr2lineContextData;
18use crate::error::{Context, Error};
19use crate::mapped_path::MappedPath;
20use crate::path_mapper::{ExtraPathMapper, PathMapper};
21use crate::shared::{
22    FileAndPathHelper, FileContents, FileContentsWrapper, FileLocation, FrameDebugInfo,
23    FramesLookupResult, LookupAddress, SourceFilePath, SymbolInfo,
24};
25use crate::symbol_map::{GetInnerSymbolMap, SymbolMap, SymbolMapTrait};
26use crate::symbol_map_object::{
27    ObjectSymbolMap, ObjectSymbolMapInnerWrapper, ObjectSymbolMapOuter,
28};
29use crate::{demangle, SyncAddressInfo};
30
31pub async fn load_symbol_map_for_pdb_corresponding_to_binary<H: FileAndPathHelper>(
32    file_kind: FileKind,
33    file_contents: &FileContentsWrapper<H::F>,
34    file_location: H::FL,
35    helper: &H,
36) -> Result<SymbolMap<H>, Error> {
37    use object::Object;
38    let pe =
39        object::File::parse(file_contents).map_err(|e| Error::ObjectParseError(file_kind, e))?;
40
41    let info = match pe.pdb_info() {
42        Ok(Some(info)) => info,
43        _ => return Err(Error::NoDebugInfoInPeBinary(file_location.to_string())),
44    };
45    let binary_debug_id = debug_id_for_object(&pe).expect("we checked pdb_info above");
46
47    let pdb_path_str = std::str::from_utf8(info.path())
48        .map_err(|_| Error::PdbPathNotUtf8(file_location.to_string()))?;
49    let pdb_location = file_location
50        .location_for_pdb_from_binary(pdb_path_str)
51        .ok_or(Error::FileLocationRefusedPdbLocation)?;
52    let pdb_file = helper
53        .load_file(pdb_location)
54        .await
55        .map_err(|e| Error::HelperErrorDuringOpenFile(pdb_path_str.to_string(), e))?;
56    let symbol_map = get_symbol_map_for_pdb(FileContentsWrapper::new(pdb_file), file_location)?;
57    if symbol_map.debug_id() != binary_debug_id {
58        return Err(Error::UnmatchedDebugId(
59            binary_debug_id,
60            symbol_map.debug_id(),
61        ));
62    }
63    Ok(symbol_map)
64}
65
66pub fn get_symbol_map_for_pe<H: FileAndPathHelper>(
67    file_contents: FileContentsWrapper<H::F>,
68    file_kind: FileKind,
69    file_location: H::FL,
70    helper: Arc<H>,
71) -> Result<SymbolMap<H>, Error> {
72    let owner = PeSymbolMapDataAndObject::new(file_contents, file_kind)?;
73    let symbol_map = ObjectSymbolMap::new(owner)?;
74    Ok(SymbolMap::new_with_external_file_support(
75        file_location,
76        Box::new(symbol_map),
77        helper,
78    ))
79}
80
81#[derive(Yokeable)]
82struct PeObject<'data, T: FileContents> {
83    file_data: &'data FileContentsWrapper<T>,
84    object: File<'data, &'data FileContentsWrapper<T>>,
85    addr2line_context: Addr2lineContextData,
86}
87
88impl<'data, T: FileContents> PeObject<'data, T> {
89    pub fn new(
90        file_data: &'data FileContentsWrapper<T>,
91        object: File<'data, &'data FileContentsWrapper<T>>,
92    ) -> Self {
93        Self {
94            file_data,
95            object,
96            addr2line_context: Addr2lineContextData::new(),
97        }
98    }
99}
100
101struct PeSymbolMapDataAndObject<T: FileContents + 'static>(
102    Yoke<PeObject<'static, T>, Box<FileContentsWrapper<T>>>,
103);
104impl<T: FileContents + 'static> PeSymbolMapDataAndObject<T> {
105    pub fn new(file_data: FileContentsWrapper<T>, file_kind: FileKind) -> Result<Self, Error> {
106        let data_and_object = Yoke::try_attach_to_cart(
107            Box::new(file_data),
108            move |file_data| -> Result<PeObject<'_, T>, Error> {
109                let object =
110                    File::parse(file_data).map_err(|e| Error::ObjectParseError(file_kind, e))?;
111                Ok(PeObject::new(file_data, object))
112            },
113        )?;
114        Ok(Self(data_and_object))
115    }
116}
117
118impl<T: FileContents + 'static> ObjectSymbolMapOuter<T> for PeSymbolMapDataAndObject<T> {
119    fn make_symbol_map_inner(&self) -> Result<ObjectSymbolMapInnerWrapper<T>, Error> {
120        let PeObject {
121            file_data,
122            object,
123            addr2line_context,
124        } = &self.0.get();
125        let debug_id = debug_id_for_object(object)
126            .ok_or(Error::InvalidInputError("debug ID cannot be read"))?;
127        let (function_starts, function_ends) = compute_function_addresses_pe(object);
128        let symbol_map = ObjectSymbolMapInnerWrapper::new(
129            object,
130            addr2line_context
131                .make_context(*file_data, object, None, None)
132                .ok(),
133            None,
134            debug_id,
135            function_starts.as_deref(),
136            function_ends.as_deref(),
137            &(),
138        );
139
140        Ok(symbol_map)
141    }
142}
143
144fn compute_function_addresses_pe<'data, O: object::Object<'data>>(
145    object_file: &O,
146) -> (Option<Vec<u32>>, Option<Vec<u32>>) {
147    // Get function start and end addresses from the function list in .pdata.
148    use object::ObjectSection;
149    if let Some(pdata) = object_file
150        .section_by_name_bytes(b".pdata")
151        .and_then(|s| s.data().ok())
152    {
153        let (s, e) = function_start_and_end_addresses(pdata);
154        (Some(s), Some(e))
155    } else {
156        (None, None)
157    }
158}
159
160pub fn is_pdb_file<F: FileContents>(file: &FileContentsWrapper<F>) -> bool {
161    PDB::open(file).is_ok()
162}
163
164struct PdbObject<'data, FC: FileContents + 'static> {
165    context_data: pdb_addr2line::ContextPdbData<'data, 'data, &'data FileContentsWrapper<FC>>,
166    debug_id: DebugId,
167    srcsrv_stream: Option<Box<dyn Deref<Target = [u8]> + Send + 'data>>,
168}
169
170trait PdbObjectTrait {
171    fn make_pdb_symbol_map(&self) -> Result<PdbSymbolMapInner<'_>, Error>;
172}
173
174#[derive(Yokeable)]
175pub struct PdbObjectWrapper<'data>(Box<dyn PdbObjectTrait + Send + 'data>);
176
177impl<FC: FileContents + 'static> PdbObjectTrait for PdbObject<'_, FC> {
178    fn make_pdb_symbol_map(&self) -> Result<PdbSymbolMapInner<'_>, Error> {
179        let context = self.make_context()?;
180
181        let path_mapper = match &self.srcsrv_stream {
182            Some(srcsrv_stream) => Some(SrcSrvPathMapper::new(srcsrv::SrcSrvStream::parse(
183                srcsrv_stream.deref(),
184            )?)),
185            None => None,
186        };
187        let path_mapper = PathMapper::new_with_maybe_extra_mapper(path_mapper);
188
189        let symbol_map = PdbSymbolMapInner {
190            context,
191            debug_id: self.debug_id,
192            path_mapper: Mutex::new(path_mapper),
193        };
194        Ok(symbol_map)
195    }
196}
197
198impl<FC: FileContents + 'static> PdbObject<'_, FC> {
199    fn make_context<'object>(
200        &'object self,
201    ) -> Result<Box<dyn PdbAddr2lineContextTrait + Send + 'object>, Error> {
202        let context = self.context_data.make_context().context("make_context()")?;
203        Ok(Box::new(context))
204    }
205}
206
207trait PdbAddr2lineContextTrait {
208    fn find_frames(
209        &self,
210        probe: u32,
211    ) -> Result<Option<pdb_addr2line::FunctionFrames>, pdb_addr2line::Error>;
212    fn function_count(&self) -> usize;
213    fn functions(&self) -> Box<dyn Iterator<Item = pdb_addr2line::Function> + '_>;
214}
215
216impl PdbAddr2lineContextTrait for pdb_addr2line::Context<'_, '_> {
217    fn find_frames(
218        &self,
219        probe: u32,
220    ) -> Result<Option<pdb_addr2line::FunctionFrames>, pdb_addr2line::Error> {
221        self.find_frames(probe)
222    }
223
224    fn function_count(&self) -> usize {
225        self.function_count()
226    }
227
228    fn functions(&self) -> Box<dyn Iterator<Item = pdb_addr2line::Function> + '_> {
229        Box::new(self.functions())
230    }
231}
232
233#[derive(Yokeable)]
234pub struct PdbSymbolMapInnerWrapper<'data>(Box<dyn SymbolMapTrait + Send + 'data>);
235
236struct PdbSymbolMapInner<'object> {
237    context: Box<dyn PdbAddr2lineContextTrait + Send + 'object>,
238    debug_id: DebugId,
239    path_mapper: Mutex<PathMapper<SrcSrvPathMapper<'object>>>,
240}
241
242impl SymbolMapTrait for PdbSymbolMapInner<'_> {
243    fn debug_id(&self) -> DebugId {
244        self.debug_id
245    }
246
247    fn symbol_count(&self) -> usize {
248        self.context.function_count()
249    }
250
251    fn iter_symbols(&self) -> Box<dyn Iterator<Item = (u32, Cow<'_, str>)> + '_> {
252        let iter = self.context.functions().map(|f| {
253            let start_rva = f.start_rva;
254            (
255                start_rva,
256                Cow::Owned(f.name.unwrap_or_else(|| format!("fun_{start_rva:x}"))),
257            )
258        });
259        Box::new(iter)
260    }
261
262    fn lookup_sync(&self, address: LookupAddress) -> Option<SyncAddressInfo> {
263        let rva = match address {
264            LookupAddress::Relative(rva) => rva,
265            LookupAddress::Svma(_) => {
266                // TODO: Convert svma into rva by subtracting the image base address.
267                // Does the PDB know about the image base address?
268                return None;
269            }
270            LookupAddress::FileOffset(_) => {
271                // TODO
272                // Does the PDB know at which file offsets the sections are stored in the binary?
273                return None;
274            }
275        };
276        let function_frames = self.context.find_frames(rva).ok()??;
277        let symbol_address = function_frames.start_rva;
278        let symbol_name = match &function_frames.frames.last().unwrap().function {
279            Some(name) => demangle::demangle_any(name),
280            None => "unknown".to_string(),
281        };
282        let function_size = function_frames
283            .end_rva
284            .map(|end_rva| end_rva - function_frames.start_rva);
285
286        let symbol = SymbolInfo {
287            address: symbol_address,
288            size: function_size,
289            name: symbol_name,
290        };
291        let frames = if has_debug_info(&function_frames) {
292            let mut path_mapper = self.path_mapper.lock().unwrap();
293            let mut map_path = |path: Cow<str>| {
294                let mapped_path = path_mapper.map_path(&path);
295                SourceFilePath::new(path.into_owned(), mapped_path)
296            };
297            let frames: Vec<_> = function_frames
298                .frames
299                .into_iter()
300                .map(|frame| FrameDebugInfo {
301                    function: frame.function,
302                    file_path: frame.file.map(&mut map_path),
303                    line_number: frame.line,
304                })
305                .collect();
306            Some(FramesLookupResult::Available(frames))
307        } else {
308            None
309        };
310
311        Some(SyncAddressInfo { symbol, frames })
312    }
313}
314
315fn box_stream<'data, T>(stream: T) -> Box<dyn Deref<Target = [u8]> + Send + 'data>
316where
317    T: Deref<Target = [u8]> + Send + 'data,
318{
319    Box::new(stream)
320}
321
322struct PdbFileData<T: FileContents + 'static>(FileContentsWrapper<T>);
323
324pub struct PdbObjectWithFileData<T: FileContents + 'static>(
325    Yoke<PdbObjectWrapper<'static>, Box<PdbFileData<T>>>,
326);
327
328impl<T: FileContents + 'static> PdbObjectWithFileData<T> {
329    fn new(file_data: PdbFileData<T>) -> Result<Self, Error> {
330        let data_and_object = Yoke::try_attach_to_cart(Box::new(file_data), |file_data| {
331            let mut pdb = PDB::open(&file_data.0)?;
332            let info = pdb.pdb_information().context("pdb_information")?;
333            let dbi = pdb.debug_information()?;
334            let age = dbi.age().unwrap_or(info.age);
335            let debug_id = DebugId::from_parts(info.guid, age);
336
337            let srcsrv_stream = match pdb.named_stream(b"srcsrv") {
338                Ok(stream) => Some(box_stream(stream)),
339                Err(pdb::Error::StreamNameNotFound | pdb::Error::StreamNotFound(_)) => None,
340                Err(e) => return Err(Error::PdbError("pdb.named_stream(srcsrv)", e)),
341            };
342
343            let context_data = pdb_addr2line::ContextPdbData::try_from_pdb(pdb)
344                .context("ContextConstructionData::try_from_pdb")?;
345
346            let pdb_object = PdbObject {
347                context_data,
348                debug_id,
349                srcsrv_stream,
350            };
351
352            Ok(PdbObjectWrapper(Box::new(pdb_object)))
353        })?;
354        Ok(PdbObjectWithFileData(data_and_object))
355    }
356}
357
358pub struct PdbSymbolMap<T: FileContents + 'static>(
359    Mutex<Yoke<PdbSymbolMapInnerWrapper<'static>, Box<PdbObjectWithFileData<T>>>>,
360);
361
362impl<T: FileContents> PdbSymbolMap<T> {
363    pub fn new(outer: PdbObjectWithFileData<T>) -> Result<Self, Error> {
364        let outer_and_inner = Yoke::try_attach_to_cart(
365            Box::new(outer),
366            |outer| -> Result<PdbSymbolMapInnerWrapper<'_>, Error> {
367                let maker = outer.0.get().0.as_ref();
368                let symbol_map = maker.make_pdb_symbol_map()?;
369                Ok(PdbSymbolMapInnerWrapper(Box::new(symbol_map)))
370            },
371        )?;
372        Ok(PdbSymbolMap(Mutex::new(outer_and_inner)))
373    }
374
375    fn with_inner<F, R>(&self, f: F) -> R
376    where
377        F: FnOnce(&dyn SymbolMapTrait) -> R,
378    {
379        f(&*self.0.lock().unwrap().get().0)
380    }
381}
382
383impl<T: FileContents> GetInnerSymbolMap for PdbSymbolMap<T> {
384    fn get_inner_symbol_map<'a>(&'a self) -> &'a (dyn SymbolMapTrait + 'a) {
385        self
386    }
387}
388
389impl<T: FileContents> SymbolMapTrait for PdbSymbolMap<T> {
390    fn debug_id(&self) -> debugid::DebugId {
391        self.with_inner(|inner| inner.debug_id())
392    }
393
394    fn symbol_count(&self) -> usize {
395        self.with_inner(|inner| inner.symbol_count())
396    }
397
398    fn iter_symbols(&self) -> Box<dyn Iterator<Item = (u32, Cow<'_, str>)> + '_> {
399        let vec = self.with_inner(|inner| {
400            let vec: Vec<_> = inner
401                .iter_symbols()
402                .map(|(addr, s)| (addr, s.to_string()))
403                .collect();
404            vec
405        });
406        Box::new(vec.into_iter().map(|(addr, s)| (addr, Cow::Owned(s))))
407    }
408
409    fn lookup_sync(&self, address: LookupAddress) -> Option<SyncAddressInfo> {
410        self.with_inner(|inner| inner.lookup_sync(address))
411    }
412}
413
414pub fn get_symbol_map_for_pdb<H: FileAndPathHelper>(
415    file_contents: FileContentsWrapper<H::F>,
416    debug_file_location: H::FL,
417) -> Result<SymbolMap<H>, Error> {
418    let file_data_and_object = PdbObjectWithFileData::new(PdbFileData(file_contents))?;
419    let symbol_map = PdbSymbolMap::new(file_data_and_object)?;
420    Ok(SymbolMap::new_plain(
421        debug_file_location,
422        Box::new(symbol_map),
423    ))
424}
425
426/// Map raw file paths to special "permalink" paths, using the srcsrv stream.
427/// This allows finding source code for applications that were not compiled on this
428/// machine, for example when using PDBs that were downloaded from a symbol server.
429/// The special paths produced here have the following formats:
430///   - "hg:<repo>:<path>:<rev>"
431///   - "git:<repo>:<path>:<rev>"
432///   - "s3:<bucket>:<digest_and_path>:"
433struct SrcSrvPathMapper<'a> {
434    srcsrv_stream: srcsrv::SrcSrvStream<'a>,
435    cache: HashMap<String, Option<MappedPath>>,
436    command_is_file_download_with_url_in_var4_and_uncompress_function_in_var5: bool,
437}
438
439impl ExtraPathMapper for SrcSrvPathMapper<'_> {
440    fn map_path(&mut self, path: &str) -> Option<MappedPath> {
441        if let Some(value) = self.cache.get(path) {
442            return value.clone();
443        }
444
445        let value = match self
446            .srcsrv_stream
447            .source_and_raw_var_values_for_path(path, "C:\\Dummy")
448        {
449            Ok(Some((srcsrv::SourceRetrievalMethod::Download { url }, _map))) => {
450                MappedPath::from_url(&url)
451            }
452            Ok(Some((srcsrv::SourceRetrievalMethod::ExecuteCommand { .. }, map))) => {
453                // We're not going to execute a command here.
454                // Instead, we have special handling for a few known cases (well, only one case for now).
455                self.gitiles_to_mapped_path(&map)
456            }
457            _ => None,
458        };
459        self.cache.insert(path.to_string(), value.clone());
460        value
461    }
462}
463
464impl<'a> SrcSrvPathMapper<'a> {
465    pub fn new(srcsrv_stream: srcsrv::SrcSrvStream<'a>) -> Self {
466        let command_is_file_download_with_url_in_var4_and_uncompress_function_in_var5 =
467            Self::matches_chrome_gitiles_workaround(&srcsrv_stream);
468
469        SrcSrvPathMapper {
470            srcsrv_stream,
471            cache: HashMap::new(),
472            command_is_file_download_with_url_in_var4_and_uncompress_function_in_var5,
473        }
474    }
475
476    /// Chrome PDBs contain a workaround for the fact that googlesource.com (which runs
477    /// a "gitiles" instance) does not have URLs to request raw source files.
478    /// Instead, there is only a URL to request base64-encoded files. But srcsrv doesn't
479    /// have built-in support for base64-encoded files. So, as a workaround, the Chrome
480    /// PDBs contain a command which uses python to manually download and decode the
481    /// base64-encoded files.
482    ///
483    /// We do not want to execute any commands here. Instead, we try to detect this
484    /// workaround and get the raw URL back out.
485    fn matches_chrome_gitiles_workaround(srcsrv_stream: &srcsrv::SrcSrvStream<'a>) -> bool {
486        // old (python 2):
487        // SRC_EXTRACT_TARGET_DIR=%targ%\%fnbksl%(%var2%)\%var3%
488        // SRC_EXTRACT_TARGET=%SRC_EXTRACT_TARGET_DIR%\%fnfile%(%var1%)
489        // SRC_EXTRACT_CMD=cmd /c "mkdir "%SRC_EXTRACT_TARGET_DIR%" & python -c "import urllib2, base64;url = \"%var4%\";u = urllib2.urlopen(url);open(r\"%SRC_EXTRACT_TARGET%\", \"wb\").write(%var5%(u.read()))"
490        // c:\b\s\w\ir\cache\builder\src\third_party\pdfium\core\fdrm\fx_crypt.cpp*core/fdrm/fx_crypt.cpp*dab1161c861cc239e48a17e1a5d729aa12785a53*https://pdfium.googlesource.com/pdfium.git/+/dab1161c861cc239e48a17e1a5d729aa12785a53/core/fdrm/fx_crypt.cpp?format=TEXT*base64.b64decode
491        //
492        // new (python 3):
493        // SRC_EXTRACT_TARGET_DIR=%targ%\%fnbksl%(%var2%)\%var3%
494        // SRC_EXTRACT_TARGET=%SRC_EXTRACT_TARGET_DIR%\%fnfile%(%var1%)
495        // SRC_EXTRACT_CMD=cmd /c "mkdir "%SRC_EXTRACT_TARGET_DIR%" & python3 -c "import urllib.request, base64;url = \"%var4%\";u = urllib.request.urlopen(url);open(r\"%SRC_EXTRACT_TARGET%\", \"wb\").write(%var5%(u.read()))"
496        // c:\b\s\w\ir\cache\builder\src\third_party\pdfium\core\fdrm\fx_crypt.cpp*core/fdrm/fx_crypt.cpp*dab1161c861cc239e48a17e1a5d729aa12785a53*https://pdfium.googlesource.com/pdfium.git/+/dab1161c861cc239e48a17e1a5d729aa12785a53/core/fdrm/fx_crypt.cpp?format=TEXT*base64.b64decode
497        let cmd = srcsrv_stream.get_raw_var("SRC_EXTRACT_CMD");
498        srcsrv_stream.get_raw_var("SRCSRVCMD") == Some("%SRC_EXTRACT_CMD%")
499            && (cmd
500                == Some(
501                    r#"cmd /c "mkdir "%SRC_EXTRACT_TARGET_DIR%" & python3 -c "import urllib.request, base64;url = \"%var4%\";u = urllib.request.urlopen(url);open(r\"%SRC_EXTRACT_TARGET%\", \"wb\").write(%var5%(u.read()))""#,
502                )
503                || cmd
504                    == Some(
505                        r#"SRC_EXTRACT_CMD=cmd /c "mkdir "%SRC_EXTRACT_TARGET_DIR%" & python3 -c "import urllib.request, base64;url = \"%var4%\";u = urllib.request.urlopen(url);open(r\"%SRC_EXTRACT_TARGET%\", \"wb\").write(%var5%(u.read()))""#,
506                    ))
507    }
508
509    /// Gitiles is the git source hosting service used by Google projects like android, chromium
510    /// and pdfium. The chromium instance is at https://chromium.googlesource.com/chromium/src.git.
511    ///
512    /// There is *no* way to get raw source files over HTTP from this service.
513    /// See https://github.com/google/gitiles/issues/7.
514    ///
515    /// Instead, you can get base64-encoded files, using the ?format=TEXT modifier.
516    ///
517    /// Due to this limitation, the Chrome PDBs contain a workaround which uses python to do the
518    /// base64 decoding. We detect this workaround and try to obtain the original paths.
519    fn gitiles_to_mapped_path(&self, map: &HashMap<String, String>) -> Option<MappedPath> {
520        if !self.command_is_file_download_with_url_in_var4_and_uncompress_function_in_var5 {
521            return None;
522        }
523        if map.get("var5").map(String::as_str) != Some("base64.b64decode") {
524            return None;
525        }
526
527        let url = map.get("var4")?;
528        parse_gitiles_url(url).ok()
529    }
530}
531
532fn has_debug_info(func: &pdb_addr2line::FunctionFrames) -> bool {
533    if func.frames.len() > 1 {
534        true
535    } else if func.frames.is_empty() {
536        false
537    } else {
538        func.frames[0].file.is_some() || func.frames[0].line.is_some()
539    }
540}
541
542#[derive(Clone)]
543struct ReadView {
544    bytes: Vec<u8>,
545}
546
547impl std::fmt::Debug for ReadView {
548    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
549        write!(f, "ReadView({} bytes)", self.bytes.len())
550    }
551}
552
553impl pdb::SourceView<'_> for ReadView {
554    fn as_slice(&self) -> &[u8] {
555        self.bytes.as_slice()
556    }
557}
558
559impl<'s, F: FileContents> pdb::Source<'s> for &'s FileContentsWrapper<F> {
560    fn view(
561        &mut self,
562        slices: &[pdb::SourceSlice],
563    ) -> std::result::Result<Box<dyn pdb::SourceView<'s> + Send + Sync>, std::io::Error> {
564        let len = slices.iter().fold(0, |acc, s| acc + s.size);
565
566        let mut bytes = Vec::with_capacity(len);
567
568        for slice in slices {
569            self.read_bytes_into(&mut bytes, slice.offset, slice.size)
570                .map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, e))?;
571        }
572
573        Ok(Box::new(ReadView { bytes }))
574    }
575}
576
577/// Get the function start addresses (in rva form) from the .pdata section.
578/// This section has the addresses for functions with unwind info. That means
579/// it only covers a subset of functions; it does not include entries for
580/// leaf functions which don't allocate any stack space.
581fn function_start_and_end_addresses(pdata: &[u8]) -> (Vec<u32>, Vec<u32>) {
582    let mut start_addresses = Vec::new();
583    let mut end_addresses = Vec::new();
584    for entry in pdata.chunks_exact(3 * std::mem::size_of::<u32>()) {
585        let start_address = u32::from_le_bytes([entry[0], entry[1], entry[2], entry[3]]);
586        let end_address = u32::from_le_bytes([entry[4], entry[5], entry[6], entry[7]]);
587        start_addresses.push(start_address);
588        end_addresses.push(end_address);
589    }
590    (start_addresses, end_addresses)
591}
592
593fn parse_gitiles_url(input: &str) -> Result<MappedPath, nom::Err<nom::error::Error<&str>>> {
594    // https://pdfium.googlesource.com/pdfium.git/+/dab1161c861cc239e48a17e1a5d729aa12785a53/core/fdrm/fx_crypt.cpp?format=TEXT
595    // -> "git:pdfium.googlesource.com/pdfium:core/fdrm/fx_crypt.cpp:dab1161c861cc239e48a17e1a5d729aa12785a53"
596    // https://chromium.googlesource.com/chromium/src.git/+/c15858db55ed54c230743eaa9678117f21d5517e/third_party/blink/renderer/core/svg/svg_point.cc?format=TEXT
597    // -> "git:chromium.googlesource.com/chromium/src:third_party/blink/renderer/core/svg/svg_point.cc:c15858db55ed54c230743eaa9678117f21d5517e"
598    let (input, _) = tag("https://")(input)?;
599    let (input, repo) = terminated(take_until1(".git/+/"), tag(".git/+/"))(input)?;
600    let (input, rev) = terminated(take_until1("/"), tag("/"))(input)?;
601    let (_, path) = terminated(
602        take_until1("?format=TEXT"),
603        terminated(tag("?format=TEXT"), eof),
604    )(input)?;
605    Ok(MappedPath::Git {
606        repo: repo.to_owned(),
607        path: path.to_owned(),
608        rev: rev.to_owned(),
609    })
610}
611
612#[cfg(test)]
613mod test {
614    use super::*;
615
616    #[test]
617    fn test_parse_gitiles_url() {
618        assert_eq!(
619            parse_gitiles_url("https://pdfium.googlesource.com/pdfium.git/+/dab1161c861cc239e48a17e1a5d729aa12785a53/core/fdrm/fx_crypt.cpp?format=TEXT"),
620            Ok(MappedPath::Git{
621                repo: "pdfium.googlesource.com/pdfium".into(),
622                rev: "dab1161c861cc239e48a17e1a5d729aa12785a53".into(),
623                path: "core/fdrm/fx_crypt.cpp".into(),
624            })
625        );
626
627        assert_eq!(
628            parse_gitiles_url("https://chromium.googlesource.com/chromium/src.git/+/c15858db55ed54c230743eaa9678117f21d5517e/third_party/blink/renderer/core/svg/svg_point.cc?format=TEXT"),
629            Ok(MappedPath::Git{
630                repo: "chromium.googlesource.com/chromium/src".into(),
631                rev: "c15858db55ed54c230743eaa9678117f21d5517e".into(),
632                path: "third_party/blink/renderer/core/svg/svg_point.cc".into(),
633            })
634        );
635
636        assert_eq!(
637            parse_gitiles_url("https://pdfium.googlesource.com/pdfium.git/+/dab1161c861cc239e48a17e1a5d729aa12785a53/core/fdrm/fx_crypt.cpp?format=TEXTotherstuff"),
638            Err(nom::Err::Error(nom::error::Error::new("otherstuff", nom::error::ErrorKind::Eof)))
639        );
640    }
641}