Skip to content

Commit 69639a0

Browse files
authored
fix(crashpad): preserve UUIDs from minimal PDB70 records (#2003)
* test(crashpad): verify module CodeView UUID Parse module CodeView records from uploaded minidumps and assert the example module carries a non-zero UUID. * test(crashpad): check shared library CodeView record Target the SDK shared library and accept both PDB70 and ELF build-ID record signatures. * fix review findings * Bump crashpad * Update CHANGELOG.md * fix lib_name for mingw * Bump crashpad
1 parent c8ff836 commit 69639a0

6 files changed

Lines changed: 86 additions & 15 deletions

File tree

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
- Improve log and metric delivery when telemetry is captured faster than envelopes can be serialized by offloading serialization to an internal thread pool. ([#1946](https://github.com/getsentry/sentry-native/pull/1946))
2222
- Wine: fix OS version detection and cross-compiling Windows builds from Linux. ([#2001](https://github.com/getsentry/sentry-native/pull/2001))
2323
- Destroy condition variables as approriate when no longer needed. ([#2004](https://github.com/getsentry/sentry-native/pull/2004))
24+
- Crashpad/Windows: preserve module CodeView UUIDs for minimal PDB70 records with empty PDB filenames. ([#2003](https://github.com/getsentry/sentry-native/pull/2003))
2425

2526
## 0.16.3
2627

external/crashpad

tests/__init__.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,15 @@
1414
sourcedir = os.path.dirname(os.path.dirname(os.path.realpath(__file__)))
1515

1616

17+
def lib_name(name):
18+
if sys.platform == "win32":
19+
prefix = "lib" if os.environ.get("TEST_MINGW") else ""
20+
return prefix + name + ".dll"
21+
elif sys.platform == "darwin":
22+
return "lib" + name + ".dylib"
23+
return "lib" + name + ".so"
24+
25+
1726
def adb(*args, **kwargs):
1827
return subprocess.run(
1928
["{}/platform-tools/adb".format(os.environ["ANDROID_HOME"]), *args], **kwargs

tests/assertions.py

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -516,6 +516,7 @@ class CrashpadAttachments:
516516
view_hierarchy: dict
517517
cmake_cache: int
518518
bytes_bin: bytes = None
519+
minidump: bytes = None
519520

520521

521522
def _unpack_breadcrumbs(payload):
@@ -531,6 +532,7 @@ def _load_crashpad_attachments(msg):
531532
view_hierarchy = {}
532533
cmake_cache = -1
533534
bytes_bin = None
535+
minidump = None
534536
for part in msg.walk():
535537
if part.get_filename() is not None:
536538
assert part.get("Content-Type") is None
@@ -549,8 +551,20 @@ def _load_crashpad_attachments(msg):
549551
case "bytes.bin":
550552
bytes_bin = part.get_payload(decode=True)
551553

554+
if (
555+
part.get_param("name", header="content-disposition")
556+
== "upload_file_minidump"
557+
):
558+
minidump = part.get_payload(decode=True)
559+
552560
return CrashpadAttachments(
553-
event, breadcrumb1, breadcrumb2, view_hierarchy, cmake_cache, bytes_bin
561+
event,
562+
breadcrumb1,
563+
breadcrumb2,
564+
view_hierarchy,
565+
cmake_cache,
566+
bytes_bin,
567+
minidump,
554568
)
555569

556570

@@ -594,11 +608,8 @@ def assert_crashpad_upload(req, expect_attachment=False, expect_view_hierarchy=F
594608
assert attachments.bytes_bin == None
595609
if expect_view_hierarchy:
596610
assert_attachment_content_view_hierarchy(attachments.view_hierarchy)
597-
assert any(
598-
b'name="upload_file_minidump"' in part.as_bytes()
599-
and b"\n\nMDMP" in part.as_bytes()
600-
for part in msg.walk()
601-
)
611+
assert attachments.minidump is not None, "minidump attachment missing"
612+
assert attachments.minidump.startswith(b"MDMP"), "invalid minidump signature"
602613
return attachments
603614

604615

tests/cmake.py

Lines changed: 1 addition & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88

99
import pytest
1010

11-
from . import adb, exe_name
11+
from . import adb, exe_name, lib_name
1212
from .conditions import has_sccache
1313
from .build_config import (
1414
get_android_config,
@@ -73,13 +73,6 @@ def destroy(self):
7373

7474
if "llvm-cov" in os.environ.get("RUN_ANALYZER", ""):
7575

76-
def lib_name(name):
77-
if sys.platform == "win32":
78-
return name + ".dll"
79-
elif sys.platform == "darwin":
80-
return "lib" + name + ".dylib"
81-
return "lib" + name + ".so"
82-
8376
for i, (d, _) in enumerate(self.runs.values()):
8477
# first merge the raw profiling runs
8578
files = [f for f in os.listdir(d) if f.endswith(".profraw")]

tests/test_integration_crashpad.py

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import os
2+
import struct
23
import subprocess
34
import sys
45
import time
@@ -17,6 +18,7 @@
1718
is_logs_envelope,
1819
is_feedback_envelope,
1920
is_replay_envelope,
21+
lib_name,
2022
REPLAY_ID,
2123
)
2224
from .conditions import has_crashpad, has_oom
@@ -49,6 +51,28 @@
4951
flushes_state = sys.platform != "darwin"
5052

5153

54+
def _minidump_stream(minidump, stream_type):
55+
stream_count, directory_rva = struct.unpack_from("<II", minidump, 8)
56+
for stream in range(stream_count):
57+
stream_offset = directory_rva + stream * 12
58+
current_type, size, rva = struct.unpack_from("<III", minidump, stream_offset)
59+
if current_type == stream_type:
60+
return minidump[rva : rva + size]
61+
raise AssertionError(f"stream {stream_type} not found in minidump")
62+
63+
64+
def _minidump_modules(minidump):
65+
modules = _minidump_stream(minidump, 4)
66+
module_count = struct.unpack_from("<I", modules)[0]
67+
for module in range(module_count):
68+
offset = 4 + module * 108
69+
name_rva = struct.unpack_from("<I", modules, offset + 20)[0]
70+
name_size = struct.unpack_from("<I", minidump, name_rva)[0]
71+
name = minidump[name_rva + 4 : name_rva + 4 + name_size].decode("utf-16-le")
72+
record_size, record_rva = struct.unpack_from("<II", modules, offset + 76)
73+
yield name, minidump[record_rva : record_rva + record_size]
74+
75+
5276
def test_crashpad_capture(cmake, httpserver):
5377
tmp_path = cmake(["sentry_example"], {"SENTRY_BACKEND": "crashpad"})
5478

@@ -64,6 +88,39 @@ def test_crashpad_capture(cmake, httpserver):
6488
assert len(httpserver.log) == 2
6589

6690

91+
def test_crashpad_codeview(cmake, httpserver):
92+
tmp_path = cmake(["sentry_example"], {"SENTRY_BACKEND": "crashpad"})
93+
94+
httpserver.expect_oneshot_request("/api/123456/minidump/").respond_with_data("OK")
95+
96+
with httpserver.wait(timeout=10) as waiting:
97+
run(
98+
tmp_path,
99+
"sentry_example",
100+
["log", "crashpad-wait-for-upload", "crash"],
101+
expect_failure=True,
102+
env=dict(os.environ, SENTRY_DSN=make_dsn(httpserver)),
103+
)
104+
105+
assert waiting.result
106+
assert len(httpserver.log) == 1
107+
attachments = assert_crashpad_upload(httpserver.log[0][0])
108+
codeviews = {
109+
name.replace("\\", "/").rsplit("/", 1)[-1]: codeview
110+
for name, codeview in _minidump_modules(attachments.minidump)
111+
}
112+
codeview = codeviews[lib_name("sentry")]
113+
signature = codeview[:4]
114+
if sys.platform == "linux":
115+
assert signature == b"LEpB"
116+
identifier = codeview[4:]
117+
else:
118+
assert signature == b"RSDS"
119+
identifier = codeview[4:20]
120+
121+
assert any(identifier)
122+
123+
67124
def _setup_crashpad_proxy_test(cmake, httpserver, proxy):
68125
if proxy:
69126
proxy_process, port = start_proxy(proxy)

0 commit comments

Comments
 (0)