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 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 return None;
269 }
270 LookupAddress::FileOffset(_) => {
271 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
426struct 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 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 fn matches_chrome_gitiles_workaround(srcsrv_stream: &srcsrv::SrcSrvStream<'a>) -> bool {
486 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 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
577fn 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 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}