1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
|
bpb4crash
=========
(BreakPoint Before Crash)
This little script allows a buggy program to be stopped just before it
crashes, at the start of the function that crashes (or optionally, in
any other function in a call stack).
This is useful in the following circumstances:
* When you have a crashing program, and
* it crashes on a large input, that makes reverse-execution too slow, and
* the crashing function is called multiple times (so you can just simply
set a plain breakpoint on it).
NOTE: It only works for programs that are both deterministic and
non-interactive!
Dependencies
------------
* gdb
* mkfifo
* POSIX shell (e.g. Dash, Bash, ...)
How it works
------------
It first runs the program under gdb, using the GDB/MI interface, to analyze
how many times the failing function runs in total (before the crash happens).
Then starts gdb normally, but with a pre-set breakpoint with an ignore count
such that it stops just before the crash.
From there you can inspect the function arguments, step through the function,
etc.
Usage
-----
By default, it stops at start of the function where the crash or assertion
failure happens:
$ ./bpb4crash.sh ./test_crash
You can also override which function in should stop in:
$ TRACKED_FUNC=crash_loop ./bpb4crash.sh ./test_crash
If you want to stop not a the last call before the crash, but at an earlier
call, then you can overrides this too:
$ STEPS_BEFORE=2 ./bpb4crash.sh ./test_crash
Example use case
----------------
The test_crash.c file has an assertion failure, but it can happen
in several cases:
void check_number()
{
if (num == -90) goto fail;
if (num == 0) goto fail;
if (num == 200) goto fail;
return;
fail:
num = -1;
assert(0);
}
When running it, we can't know which number triggered the assertion failure:
$ ./test_crash
test_crash: test_crash.c:35: check_number: Assertion `0' failed.
Aborted
And gdb isn't very helpful either:
$ gdb ./test_crash
Reading symbols from ./test_crash...
(gdb) run
Starting program: /home/privmisc/code/beforecrash/test_crash
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".
test_crash: test_crash.c:35: check_number: Assertion `0' failed.
Program received signal SIGABRT, Aborted.
__pthread_kill_implementation (threadid=<optimized out>, signo=signo@entry=6,
no_tid=no_tid@entry=0) at ./nptl/pthread_kill.c:44
44 ./nptl/pthread_kill.c: Filen eller katalogen finns inte.
(gdb) f 6
#6 0x0000555555555197 in check_number () at test_crash.c:35
35 assert(0);
(gdb) p num
$1 = -1
That doesn't tell anything at all :(
But we can use the bpb4crash to get a breakpoint at the last call to
this function, just before the assertion failure happened.
$ ./bpb4crash.sh ./test_crash
First run (analysis)...
test_crash: test_crash.c:35: check_number: Assertion `0' failed.
#6 0x0000555555555197 in check_number () at test_crash.c:35
#7 0x00005555555551ba in crash_loop (i=123) at test_crash.c:42
#8 0x00005555555551ed in intermediate_func (i=123) at test_crash.c:49
#9 0x0000555555555209 in main (argc=1, argv=0x7fffffffe298) at test_crash.c:55
Second run (to breakpoint)...
test_crash: test_crash.c:35: check_number: Assertion `0' failed.
Reading symbols from ./test_crash...
Breakpoint 1 at 0x113d: file test_crash.c, line 29.
Will ignore next 122 crossings of breakpoint 1.
Starting program: /home/privmisc/code/beforecrash/test_crash
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".
Breakpoint 1, check_number () at test_crash.c:29
29 if (num == -90) goto fail;
From here we can see the state at the beginning of the function call:
(gdb) p num
$1 = 0
|