Skip to content

Commit d275552

Browse files
committed
fix: handle minidumps missing crashing thread data
1 parent cacd3c4 commit d275552

File tree

4 files changed

+93
-72
lines changed

4 files changed

+93
-72
lines changed

src/ffpuppet/core.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -533,11 +533,17 @@ def close(self, force_close: bool = False) -> None:
533533
MDSW_URL,
534534
)
535535
elif dmp_files:
536+
# use SAVE_DMP to include dmp files in report
536537
if getenv("SAVE_DMP") == "1":
537538
# save minidump directory contents (.dmp and .extra files)
538539
dmps = self._logs.add_path("minidumps")
539540
for md_file in (self.profile.path / "minidumps").glob("*"):
540541
copy(md_file, dmps)
542+
# use DEBUG_DMP to copy dmp files to a known location
543+
debug_dmp = Path(getenv("DEBUG_DMP", ""))
544+
if debug_dmp.is_dir():
545+
for md_file in (self.profile.path / "minidumps").glob("*"):
546+
copy(md_file, debug_dmp)
541547
# check for local build symbols
542548
if (self._bin_path.parent / "crashreporter-symbols").is_dir():
543549
sym_path = self._bin_path.parent / "crashreporter-symbols"

src/ffpuppet/minidump_parser.py

Lines changed: 14 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -75,14 +75,19 @@ def _fmt_output(data: dict[str, Any], out_fp: IO[bytes], limit: int = 150) -> No
7575
"""
7676
assert limit > 0
7777
# generate register information lines
78-
frames = data["crashing_thread"]["frames"]
79-
reg_lines: list[str] = []
80-
for reg, value in frames[0]["registers"].items():
81-
# display three registers per line
82-
sep = "\t" if (len(reg_lines) + 1) % 3 else "\n"
83-
reg_lines.append(f"{reg:>3} = {value}{sep}")
84-
out_fp.write("".join(reg_lines).rstrip().encode())
85-
out_fp.write(b"\n")
78+
try:
79+
frames = data["crashing_thread"]["frames"]
80+
except KeyError:
81+
LOG.warning("No frames available for 'crashing thread'")
82+
frames = []
83+
if frames:
84+
reg_lines: list[str] = []
85+
for reg, value in frames[0]["registers"].items():
86+
# display three registers per line
87+
sep = "\t" if (len(reg_lines) + 1) % 3 else "\n"
88+
reg_lines.append(f"{reg:>3} = {value}{sep}")
89+
out_fp.write("".join(reg_lines).rstrip().encode())
90+
out_fp.write(b"\n")
8691

8792
# generate OS information line
8893
line = "|".join(
@@ -104,7 +109,7 @@ def _fmt_output(data: dict[str, Any], out_fp: IO[bytes], limit: int = 150) -> No
104109
out_fp.write(b"\n")
105110

106111
# generate Crash information line
107-
crashing_thread = str(data["crash_info"]["crashing_thread"])
112+
crashing_thread = str(data["crash_info"].get("crashing_thread", "?"))
108113
line = "|".join(
109114
(
110115
"Crash",

src/ffpuppet/test_ffpuppet.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -526,7 +526,9 @@ def _fake_create_log(src, filename, _timeout: int = 90):
526526
(dst / filename).write_text(src.read_text())
527527
return dst / filename
528528

529-
mocker.patch("ffpuppet.core.getenv", return_value="1")
529+
debug_dmps = tmp_path / "debug_dmps"
530+
debug_dmps.mkdir()
531+
mocker.patch("ffpuppet.core.getenv", side_effect=("1", str(debug_dmps), "1"))
530532
mocker.patch.object(MinidumpParser, "mdsw_available", return_value=mdsw_available)
531533
mocker.patch.object(MinidumpParser, "create_log", side_effect=_fake_create_log)
532534
profile = tmp_path / "profile"
@@ -555,6 +557,9 @@ def _fake_create_log(src, filename, _timeout: int = 90):
555557
assert len(tuple((logs / "minidumps").glob("*"))) == 3
556558
else:
557559
assert not any(logs.glob("log_minidump_*"))
560+
if mdsw_available:
561+
# files should still be available after ffp cleanup
562+
assert len(tuple(debug_dmps.iterdir())) == 3
558563

559564

560565
def test_ffpuppet_22(tmp_path):

src/ffpuppet/test_minidump_parser.py

Lines changed: 67 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
# You can obtain one at http://mozilla.org/MPL/2.0/.
44
"""ffpuppet minidump parser tests"""
55

6+
from copy import deepcopy
67
from json import dumps
78
from pathlib import Path
89
from subprocess import CompletedProcess
@@ -12,26 +13,11 @@
1213

1314
from .minidump_parser import MinidumpParser
1415

15-
MD_UNSYMBOLIZED_AMD64_WIN = {
16+
MD_BASE_AMD64_WIN = {
1617
"crash_info": {
1718
"address": "0x00007ffe4e09af8d",
18-
"crashing_thread": 0,
1919
"type": "EXCEPTION_BREAKPOINT",
2020
},
21-
"crashing_thread": {
22-
"frame_count": 49,
23-
"frames": [
24-
{
25-
"file": None,
26-
"frame": 0,
27-
"function": None,
28-
"function_offset": None,
29-
"line": None,
30-
"module": "xul.dll",
31-
"registers": {"r10": "0x0"},
32-
},
33-
],
34-
},
3521
"system_info": {
3622
"cpu_arch": "amd64",
3723
"cpu_count": 8,
@@ -41,6 +27,23 @@
4127
},
4228
}
4329

30+
MD_UNSYMBOLIZED_AMD64_WIN = deepcopy(MD_BASE_AMD64_WIN)
31+
MD_UNSYMBOLIZED_AMD64_WIN["crash_info"]["crashing_thread"] = 0
32+
MD_UNSYMBOLIZED_AMD64_WIN["crashing_thread"] = {
33+
"frame_count": 49,
34+
"frames": [
35+
{
36+
"file": None,
37+
"frame": 0,
38+
"function": None,
39+
"function_offset": None,
40+
"line": None,
41+
"module": "xul.dll",
42+
"registers": {"r10": "0x0"},
43+
},
44+
],
45+
}
46+
4447
MD_UNSYMBOLIZED_ARM64_MAC = {
4548
"crash_info": {
4649
"address": "0x0000000000000000",
@@ -165,54 +168,43 @@ def test_minidump_parser_03(tmp_path, data, reg, operating_system, cpu, crash, f
165168

166169
def test_minidump_parser_04(tmp_path):
167170
"""test MinidumpParser._fmt_output() - symbolized"""
168-
data = {
169-
"crash_info": {
170-
"address": "0x00007ffe4e09af8d",
171-
"crashing_thread": 0,
172-
"type": "EXCEPTION_BREAKPOINT",
173-
},
174-
"crashing_thread": {
175-
"frames": [
176-
{
177-
"file": "file0.cpp",
178-
"frame": 0,
179-
"function": "function00()",
180-
"function_offset": "0x00000000000001ed",
181-
"line": 47,
182-
"module": "xul.dll",
183-
"registers": {
184-
"r10": "0x12345678",
185-
"r11": "0x0badf00d",
186-
"r12": "0x00000000",
187-
"r13": "0x000000dceebfc2e8",
188-
},
189-
},
190-
{
191-
"file": "file1.cpp",
192-
"frame": 1,
193-
"function": "function01()",
194-
"function_offset": "0x00000000000001bb",
195-
"line": 210,
196-
"module": "xul.dll",
197-
},
198-
{
199-
"file": "file2.cpp",
200-
"frame": 2,
201-
"function": "function02()",
202-
"function_offset": "0x0000000000000123",
203-
"line": 123,
204-
"module": "xul.dll",
171+
data = deepcopy(MD_BASE_AMD64_WIN)
172+
data["crash_info"]["crashing_thread"] = 0
173+
data["crashing_thread"] = {
174+
"frames": [
175+
{
176+
"file": "file0.cpp",
177+
"frame": 0,
178+
"function": "function00()",
179+
"function_offset": "0x00000000000001ed",
180+
"line": 47,
181+
"module": "xul.dll",
182+
"registers": {
183+
"r10": "0x12345678",
184+
"r11": "0x0badf00d",
185+
"r12": "0x00000000",
186+
"r13": "0x000000dceebfc2e8",
205187
},
206-
],
207-
},
208-
"system_info": {
209-
"cpu_arch": "amd64",
210-
"cpu_count": 8,
211-
"cpu_info": "family 6 model 70 stepping 1",
212-
"os": "Windows NT",
213-
"os_ver": "10.0.19044",
214-
},
188+
},
189+
{
190+
"file": "file1.cpp",
191+
"frame": 1,
192+
"function": "function01()",
193+
"function_offset": "0x00000000000001bb",
194+
"line": 210,
195+
"module": "xul.dll",
196+
},
197+
{
198+
"file": "file2.cpp",
199+
"frame": 2,
200+
"function": "function02()",
201+
"function_offset": "0x0000000000000123",
202+
"line": 123,
203+
"module": "xul.dll",
204+
},
205+
],
215206
}
207+
216208
with (tmp_path / "out.txt").open("w+b") as ofp:
217209
# pylint: disable=protected-access
218210
MinidumpParser._fmt_output(data, ofp, limit=2)
@@ -289,3 +281,16 @@ def test_minidump_parser_06(tmp_path):
289281
# add .extra file to prioritize .dmp file
290282
(tmp_path / "b.extra").write_text('{"MozCrashReason":"foo"}')
291283
assert MinidumpParser.dmp_files(tmp_path)[0] == (tmp_path / "b.dmp")
284+
285+
286+
def test_minidump_parser_missing_crashing_thread(tmp_path):
287+
"""test MinidumpParser._fmt_output() - missing crashing thread"""
288+
with (tmp_path / "out.txt").open("w+b") as ofp:
289+
# pylint: disable=protected-access
290+
MinidumpParser._fmt_output(MD_BASE_AMD64_WIN, ofp)
291+
ofp.seek(0)
292+
formatted = ofp.read().rstrip().decode().split("\n")
293+
assert len(formatted) == 3
294+
assert formatted[0] == "OS|Windows NT|10.0.19044"
295+
assert formatted[1] == "CPU|amd64|family 6 model 70 stepping 1|8"
296+
assert formatted[2] == "Crash|EXCEPTION_BREAKPOINT|0x00007ffe4e09af8d|?"

0 commit comments

Comments
 (0)