Skip to content

Commit

Permalink
Use the first stack in a sanitizer log
Browse files Browse the repository at this point in the history
  • Loading branch information
tysmith committed Oct 26, 2023
1 parent 9cf25f7 commit dac0d4d
Show file tree
Hide file tree
Showing 2 changed files with 84 additions and 36 deletions.
53 changes: 25 additions & 28 deletions grizzly/common/stack_hasher.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
# These entries pad out the stack and make bucketing more difficult
IGNORED_FRAMES = (
"core::panicking::",
"mozglue_static::panic_hook::",
"mozglue_static::panic_hook",
"rust_begin_unwind",
"RustMozCrash",
"std::panicking::",
Expand Down Expand Up @@ -342,14 +342,21 @@ def from_file(self, file_name): # pragma: no cover

@classmethod
def from_text(cls, input_text, major_depth=MAJOR_DEPTH, parse_mode=None):
"""
parse a stack trace from text.
input_txt is the data to parse the trace from.
"""Parse a stack trace from text. This is intended to parse the output
from a single result. Some debuggers such as ASan and TSan can include
multiple stacks per result.
Args:
input_text: Data to parse.
major_depth: Number of frames use to calculate the major hash.
parse_mode: Format to use. If None the format is detected automatically.
Returns:
Stack
"""

frames = []
prev_line = None
for line in reversed(input_text.split("\n")):
for line in input_text.split("\n"):
line = line.rstrip()
if not line:
# skip empty lines
Expand All @@ -368,29 +375,19 @@ def from_text(cls, input_text, major_depth=MAJOR_DEPTH, parse_mode=None):
LOG.debug("parser mode: %s", parse_mode.name)
assert frame.mode == parse_mode

if frame.stack_line is not None:
stack_line = int(frame.stack_line)
# check if we've found a different stack in the data
if prev_line is not None and prev_line <= stack_line:
if frame.stack_line is not None and frames:
num = int(frame.stack_line)
# check for new stack
if num == 0:
# select stack to use
if parse_mode in (Mode.SANITIZER, Mode.TSAN):
break
frames.clear()
# check for out of order or missing frames
elif frames[-1].stack_line and num - 1 != int(frames[-1].stack_line):
LOG.debug("scrambled logs?")
break
frames.insert(0, frame)
if stack_line < 1:
break
prev_line = stack_line
else:
frames.insert(0, frame)

# sanity check
if frames and prev_line is not None:
# assuming the first frame is 0
if int(frames[0].stack_line) != 0:
LOG.warning("First stack frame is %s not 0", frames[0].stack_line)
if int(frames[-1].stack_line) != len(frames) - 1:
LOG.warning(
"Missing frames? Last frame is %s, expected %d (frames-1)",
frames[-1].stack_line,
len(frames) - 1,
)
frames.append(frame)

return cls(frames=frames, major_depth=major_depth)

Expand Down
67 changes: 59 additions & 8 deletions grizzly/common/test_stack_hasher.py
Original file line number Diff line number Diff line change
Expand Up @@ -125,13 +125,20 @@ def test_stack_07():


def test_stack_08():
"""test creating a Stack by calling from_text() with text containing 2 stacks"""
"""test creating a Stack by calling from_text() with text multiple stacks"""
input_txt = (
""
" #0 0x0bad0bad in bad::frame0(nsA*, nsB*) /aa/a.cpp:12:1\n"
" #1 0x0bad0bad in bad::frame1(mz::d::EE*, int) /aa/a.cpp:12:1\n"
"=================================================================\n"
"==5540==ERROR: AddressSanitizer: ...\n"
" #0 0x1badf00d in good::frame0(nsA*, nsB*) /aa/a.cpp:12:1\n"
" #1 0xdeadbeef in good::frame1(mz::d::EE*, int) /aa/a.cpp:12:1\n"
" #1 0xdeadbeef in good::frame1(mz::d::EE*, int) /aa/a.cpp:12:1\n\n"
"0x12876 is located 1024 bytes after 4096-byte region [0x12876,0x12876)\n"
"freed by thread T5 here:\n"
" #0 0x0bad0bad in bad::frame0(nsA*, nsB*) /aa/a.cpp:12:1\n"
" #1 0x0bad0bad in bad::frame1(mz::d::EE*, int) /aa/a.cpp:12:1\n\n"
"0x12876 is located 1024 bytes after 4096-byte region [0x12876,0x12876)\n"
"previously allocated by thread T0 here:\n"
" #0 0x0bad0bad in bad::frame0(nsA*, nsB*) /aa/a.cpp:12:1\n"
" #1 0x0bad0bad in bad::frame1(mz::d::EE*, int) /aa/a.cpp:12:1\n\n"
)
stack = Stack.from_text(input_txt)
assert len(stack.frames) == 2
Expand Down Expand Up @@ -308,8 +315,8 @@ def test_stack_16():
" #1 0x0001232 in stack_1b() test/a.cpp:23\n"
" #2 0x0001233 in stack_1c() test/a.cpp:34\n"
)
assert len(stack.frames) == 2
assert stack.frames[0].function == "stack_1b"
assert len(stack.frames) == 1
assert stack.frames[0].function == "stack_1a"
assert stack.frames[0].mode == Mode.SANITIZER


Expand All @@ -336,12 +343,31 @@ def test_stack_17():
)
stack02 = Stack.from_text(st_02, major_depth=3)
assert len(stack02.frames) == 6

assert stack01.minor != stack02.minor
assert stack01.major != stack02.major
assert stack01.frames[0].mode == Mode.SANITIZER


def test_stack_18():
"""test creating a Stack from two minidump stackwalk traces"""
stack = Stack.from_text(
"CPU|x86|GenuineIntel family 6 model 85 stepping 4|8\n"
"Crash|EXCEPTION_BREAKPOINT|0x67a30091|44\n"
"42|0|xul.dll|foo_a()|s3:g-g-sources:e/a.cpp:|14302|0x3f1\n"
"42|1|xul.dll|foo_b()|s3:g-g-sources:e/a.cpp:|14302|0x3f1\n"
"42|2|xul.dll|foo_c()|s3:g-g-sources:e/a.cpp:|14302|0x3f1\n"
"43|0|xul.dll|bar_a()|s3:g-g-sources:e/a.cpp:|14302|0x3f1\n"
"43|1|xul.dll|bar_b()|s3:g-g-sources:e/a.cpp:|14302|0x3f1\n"
"43|2|xul.dll|bar_c()|s3:g-g-sources:e/a.cpp:|14302|0x3f1\n"
"44|0|xul.dll|good_a()|s3:g-g-sources:e/a.cpp:|14302|0x3f1\n"
"44|1|xul.dll|good_a()|s3:g-g-sources:e/a.cpp:|14302|0x3f1\n"
"44|2|xul.dll|good_c()|s3:g-g-sources:e/a.cpp:|14302|0x3f1\n"
)
assert len(stack.frames) == 3
assert stack.frames[0].function == "good_a()"
assert stack.frames[0].mode == Mode.MINIDUMP


def test_stackframe_01():
"""test creating an empty StackFrame"""
stack = StackFrame()
Expand Down Expand Up @@ -474,6 +500,19 @@ def test_sanitizer_stackframe_09():
assert frame.mode == Mode.SANITIZER


def test_sanitizer_stackframe_10():
"""test creating a StackFrame from a line with build id"""
frame = StackFrame.from_line(
" #0 0x7f76d25b7fc0 (/usr/lib/x86_64-linux-gnu/dri/swrast_dri.so+0x704fc0) "
"(BuildId: d04a40e4062a8d444ff6f23d4fe768215b2e32c7)"
)
assert frame.stack_line == "0"
assert frame.function is None
assert frame.location == "swrast_dri.so"
assert frame.offset == "0x704fc0"
assert frame.mode == Mode.SANITIZER


def test_gdb_stackframe_01():
"""test creating a StackFrame from a GDB line with symbols"""
frame = StackFrame.from_line(
Expand Down Expand Up @@ -560,6 +599,18 @@ def test_minidump_stackframe_03():
assert frame.mode == Mode.MINIDUMP


def test_minidump_stackframe_04():
"""test creating a StackFrame from a Minidump line with s3 repo info"""
frame = StackFrame.from_line(
"42|0|xul.dll|foo_a() const|s3:g-g-sources:e/a.cpp:|14302|0x3f1"
)
assert frame.stack_line == "0"
assert frame.function == "foo_a() const"
assert frame.location == "a.cpp"
assert frame.offset == "14302"
assert frame.mode == Mode.MINIDUMP


def test_tsan_stackframe_01():
"""test creating a StackFrame from a symbolized TSan line"""
frame = StackFrame.from_line(" #0 main race.c:10 (exe+0xa3b4)")
Expand Down

0 comments on commit dac0d4d

Please sign in to comment.