1use std::collections::BTreeMap;
7use std::error::Error;
8
9use chrono::{TimeZone, Utc};
10use minidump::{
11 MinidumpAnnotation, MinidumpCrashpadInfo, MinidumpModuleList, Module, StabilityReport,
12};
13use relay_dynamic_config::Feature;
14use relay_event_schema::protocol::{
15 ClientSdkInfo, Context, Contexts, Event, Exception, JsonLenientString, Level, Mechanism,
16 StabilityReportContext, Values,
17};
18use relay_protocol::{Annotated, Value, get_value};
19
20use crate::envelope::{Item, ItemType};
21use crate::services::projects::project::ProjectInfo;
22
23type Minidump<'a> = minidump::Minidump<'a, &'a [u8]>;
24
25#[derive(Debug)]
34struct NativePlaceholder {
35 exception_type: &'static str,
37 exception_value: &'static str,
39 mechanism_type: &'static str,
41}
42
43fn write_native_placeholder(
49 event: &mut Event,
50 placeholder: NativePlaceholder,
51 project_info: &ProjectInfo,
52) {
53 let platform = event.platform.value_mut();
55 *platform = Some("native".to_owned());
56
57 event.level.get_or_insert_with(|| Level::Fatal);
62
63 let exceptions = event
67 .exceptions
68 .value_mut()
69 .get_or_insert_with(Values::default)
70 .values
71 .value_mut()
72 .get_or_insert_with(Vec::new);
73
74 let allow_multiple_exceptions = project_info.has_feature(Feature::MinidumpMultiException);
75 if let Some(exc) = exceptions.first() {
76 relay_log::info!(
77 additional_exceptions = exceptions.len(),
78 native_exception_mechanism = placeholder.exception_type,
79 additional_exception_mechanism = ?get_value!(exc.mechanism.ty),
80 sentry_project = ?event.project,
81 event_id = ?event.id,
82 has_feature = allow_multiple_exceptions,
83 platform = ?event.platform,
84 "Native event has additional exceptions",
85 )
86 }
87
88 if !allow_multiple_exceptions {
89 exceptions.clear(); }
91
92 exceptions.insert(
95 0,
96 Annotated::new(Exception {
97 ty: Annotated::new(placeholder.exception_type.to_owned()),
98 value: Annotated::new(JsonLenientString(placeholder.exception_value.to_owned())),
99 mechanism: Annotated::new(Mechanism {
100 ty: Annotated::from(placeholder.mechanism_type.to_owned()),
101 handled: Annotated::from(false),
102 synthetic: Annotated::from(true),
103 ..Mechanism::default()
104 }),
105 ..Exception::default()
106 }),
107 );
108}
109
110fn write_crashpad_annotations(
120 event: &mut Event,
121 minidump: &Minidump<'_>,
122) -> Result<(), minidump::Error> {
123 let module_list = minidump.get_stream::<MinidumpModuleList>()?;
124 let crashpad_info = match minidump.get_stream::<MinidumpCrashpadInfo>() {
125 Err(minidump::Error::StreamNotFound) => return Ok(()),
126 result => result?,
127 };
128
129 let contexts = event.contexts.get_or_insert_with(Contexts::new);
130
131 if !crashpad_info.simple_annotations.is_empty() {
132 let crashpad_context = crashpad_info
135 .simple_annotations
136 .into_iter()
137 .map(|(key, value)| (key, Annotated::new(Value::from(value))))
138 .collect();
139
140 contexts.insert("crashpad".to_owned(), Context::Other(crashpad_context));
141 }
142
143 match minidump.get_stream::<StabilityReport>() {
144 Ok(stability_report) => {
145 contexts.add(StabilityReportContext::from(stability_report));
146 }
147 Err(minidump::Error::StreamNotFound) => {
148 }
150 Err(err) => {
151 relay_log::debug!(
152 error = &err as &dyn Error,
153 "failed to parse stability report"
154 );
155 }
156 };
157
158 if crashpad_info.module_list.is_empty() {
159 return Ok(());
160 }
161
162 let modules = module_list.iter().collect::<Vec<_>>();
163
164 for module_info in crashpad_info.module_list {
165 let module = match modules.get(module_info.module_index) {
169 Some(module) => module,
170 None => {
171 relay_log::debug!(
172 module_index = module_info.module_index,
173 "Skipping invalid minidump module index",
174 );
175 continue;
176 }
177 };
178
179 let code_file = module.code_file();
183 let (_, module_name) = symbolic_common::split_path(&code_file);
184
185 let mut module_context = BTreeMap::new();
186 module_context.insert(
187 "type".to_owned(),
188 Annotated::new(Value::String("crashpad".to_owned())),
189 );
190
191 for (key, value) in module_info.simple_annotations {
192 module_context.insert(key, Annotated::new(Value::String(value)));
193 }
194
195 for (key, annotation) in module_info.annotation_objects {
196 if let MinidumpAnnotation::String(value) = annotation {
197 module_context.insert(key, Annotated::new(Value::String(value)));
198 }
199 }
200
201 if !module_info.list_annotations.is_empty() {
202 let annotation_list = module_info
205 .list_annotations
206 .into_iter()
207 .map(|s| Annotated::new(Value::String(s)))
208 .collect();
209
210 module_context.insert(
211 "annotations".to_owned(),
212 Annotated::new(Value::Array(annotation_list)),
213 );
214 }
215
216 contexts.insert(module_name.to_owned(), Context::Other(module_context));
217 }
218
219 Ok(())
220}
221
222pub fn process_minidump(event: &mut Event, item: &Item, project_info: &ProjectInfo) {
227 debug_assert_eq!(item.ty(), &ItemType::Attachment);
228 let placeholder = NativePlaceholder {
229 exception_type: "Minidump",
230 exception_value: "Invalid Minidump",
231 mechanism_type: "minidump",
232 };
233 write_native_placeholder(event, placeholder, project_info);
234
235 if item.is_attachment_ref() {
236 event.client_sdk.get_or_insert_with(|| ClientSdkInfo {
239 name: "minidump.upload".to_owned().into(),
240 version: "0.0.0".to_owned().into(),
241 ..Default::default()
242 });
243 return;
244 }
245
246 let data = item.payload();
247 let minidump = match Minidump::read(&data) {
248 Ok(minidump) => minidump,
249 Err(err) => {
250 relay_log::debug!(error = &err as &dyn Error, "failed to parse minidump");
251 return;
252 }
253 };
254
255 let client_sdk_name = if minidump.get_stream::<MinidumpCrashpadInfo>().is_ok() {
256 "minidump.crashpad"
257 } else if minidump
258 .get_stream::<minidump::MinidumpBreakpadInfo>()
259 .is_ok()
260 {
261 "minidump.breakpad"
262 } else {
263 "minidump.unknown"
264 };
265
266 event.client_sdk.get_or_insert_with(|| ClientSdkInfo {
268 name: Annotated::new(client_sdk_name.to_owned()),
269 version: "0.0.0".to_owned().into(),
270 ..ClientSdkInfo::default()
271 });
272
273 let timestamp = Utc
276 .timestamp_opt(minidump.header.time_date_stamp.into(), 0)
277 .latest();
278
279 if let Some(timestamp) = timestamp {
280 event.timestamp.set_value(Some(timestamp.into()));
281 }
282
283 if let Err(err) = write_crashpad_annotations(event, &minidump) {
286 relay_log::debug!(
288 error = &err as &dyn Error,
289 "failed to parse minidump module list"
290 );
291 }
292}
293
294pub fn process_apple_crash_report(event: &mut Event, project_info: &ProjectInfo) {
297 let placeholder = NativePlaceholder {
298 exception_type: "AppleCrashReport",
299 exception_value: "Invalid Apple Crash Report",
300 mechanism_type: "applecrashreport",
301 };
302 write_native_placeholder(event, placeholder, project_info);
303}