Skip to content

Commit c6dbd22

Browse files
committed
add --coverage-at-exit
1 parent b6e5676 commit c6dbd22

File tree

3 files changed

+66
-19
lines changed

3 files changed

+66
-19
lines changed

ChangeLog

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,12 @@ afl-cov-0.6 (05//2016):
66
faster than code coverage can be calculated for each test case. The
77
trade off in this mode is that code coverage stats are not tracked per
88
AFL test case, but rather across all new test cases essentially as a
9-
unified set.
9+
unified set. In --live mode, --cover-corpus causes coverage to be
10+
calculated once per sleep cycle after all test cases have been executed.
11+
In --coverage-at-exit mode, coverage calculation will be reserved to
12+
just before afl-cov exits.
13+
- Add a prerequisite test to make sure the targeted binary is compiled
14+
with code coverage support ('-fprofile-arcs -ftest-coverage').
1015
- Use the tempfile module for temporary files (suggested by Markus
1116
Teufelberger in issue #19).
1217

afl-cov

Lines changed: 44 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -104,12 +104,11 @@ def process_afl_test_cases(cargs):
104104
run_once = False
105105
tot_files = 0
106106
fuzz_dir = ''
107+
curr_file = ''
107108

108109
afl_files = []
109110
cov_paths = {}
110111

111-
curr_file = ''
112-
113112
### main coverage tracking dictionary
114113
cov = {}
115114
cov['zero'] = {}
@@ -121,21 +120,25 @@ def process_afl_test_cases(cargs):
121120
rv = False
122121
break
123122

124-
dir_ctr = 0
123+
dir_ctr = 0
124+
last_dir = False
125125

126126
do_coverage = True
127127
if cargs.cover_corpus:
128128
do_coverage = False
129129

130130
for fuzz_dir in cov_paths['dirs']:
131131

132+
do_break = False
133+
last_file = False
132134
num_files = 0
133135
new_files = []
134136
tmp_files = import_test_cases(fuzz_dir + '/queue')
135137
dir_ctr += 1
138+
f_ctr = 0
136139

137-
if cargs.cover_corpus and dir_ctr == len(cov_paths):
138-
do_coverage = True
140+
if dir_ctr == len(cov_paths['dirs']):
141+
last_dir = True
139142

140143
for f in tmp_files:
141144
if f not in afl_files:
@@ -149,6 +152,15 @@ def process_afl_test_cases(cargs):
149152

150153
for f in new_files:
151154

155+
f_ctr += 1
156+
if f_ctr == len(new_files):
157+
last_file = True
158+
159+
if cargs.cover_corpus and last_dir and last_file:
160+
### in --cover-corpus mode, only run lcov after all AFL
161+
### test cases have been processed
162+
do_coverage = True
163+
152164
out_lines = []
153165
curr_cycle = get_cycle_num(num_files, cargs)
154166

@@ -169,16 +181,25 @@ def process_afl_test_cases(cargs):
169181
cov_paths['log_file'], cargs, WANT_OUTPUT)[1]
170182
run_once = True
171183

172-
if do_coverage:
184+
if cargs.afl_queue_id_limit \
185+
and num_files >= cargs.afl_queue_id_limit - 1:
186+
logr("[+] queue/ id limit of %d reached..." \
187+
% cargs.afl_queue_id_limit,
188+
cov_paths['log_file'], cargs)
189+
do_break = True
190+
if cargs.cover_corpus and last_dir:
191+
do_coverage = True
192+
193+
if do_coverage and not cargs.coverage_at_exit:
173194
### generate the code coverage stats for this test case
174-
gen_coverage(cov_paths, cargs)
195+
lcov_gen_coverage(cov_paths, cargs)
175196

176197
### diff to the previous code coverage, look for new
177198
### lines/functions, and write out results
178199
coverage_diff(curr_cycle, fuzz_dir, cov_paths, f,
179200
cov, cargs)
180201

181-
if not cargs.disable_lcov_web and cargs.lcov_web_all:
202+
if cargs.lcov_web_all:
182203
gen_web_cov_report(fuzz_dir, cov_paths, cargs)
183204

184205
### log the output of the very first coverage command to
@@ -196,11 +217,7 @@ def process_afl_test_cases(cargs):
196217
num_files += 1
197218
tot_files += 1
198219

199-
if cargs.afl_queue_id_limit \
200-
and num_files > cargs.afl_queue_id_limit:
201-
logr("[+] queue/ id limit of %d reached..." \
202-
% cargs.afl_queue_id_limit,
203-
cov_paths['log_file'], cargs)
220+
if do_break:
204221
break
205222

206223
if cargs.live:
@@ -223,12 +240,21 @@ def process_afl_test_cases(cargs):
223240
% (tot_files, len(afl_files)),
224241
cov_paths['log_file'], cargs)
225242

243+
if cargs.coverage_at_exit:
244+
### generate the code coverage stats for this test case
245+
lcov_gen_coverage(cov_paths, cargs)
246+
247+
### diff to the previous code coverage, look for new
248+
### lines/functions, and write out results
249+
coverage_diff(curr_cycle, fuzz_dir, cov_paths,
250+
cov_paths['id_file'], cov, cargs)
251+
226252
### write out the final zero coverage and positive coverage reports
227253
write_zero_cov(cov['zero'], cov_paths, cargs)
228254
write_pos_cov(cov['pos'], cov_paths, cargs)
229255

230256
if not cargs.disable_lcov_web:
231-
gen_coverage(cov_paths, cargs)
257+
lcov_gen_coverage(cov_paths, cargs)
232258
gen_web_cov_report(fuzz_dir, cov_paths, cargs)
233259

234260
else:
@@ -496,7 +522,7 @@ def get_cycle_num(id_num, cargs):
496522

497523
return cycle_num
498524

499-
def gen_coverage(cov_paths, cargs):
525+
def lcov_gen_coverage(cov_paths, cargs):
500526

501527
out_lines = []
502528

@@ -1029,7 +1055,9 @@ def parse_cmdline():
10291055
default=False)
10301056
p.add_argument("--cover-corpus", action='store_true',
10311057
help="Measure coverage after running all available tests instead of individually per queue file",
1032-
# TODO: Document that queue limit should/will be ignored!
1058+
default=False)
1059+
p.add_argument("--coverage-at-exit", action='store_true',
1060+
help="Only calculate coverage just before afl-cov exit.",
10331061
default=False)
10341062
p.add_argument("--sleep", type=int,
10351063
help="In --live mode, # of seconds to sleep between checking for new queue files",

test/test-afl-cov.py

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -206,7 +206,13 @@ def test_queue_limit_5(self):
206206
out_str = ''.join(self.do_cmd("%s --afl-queue-id-limit 5 --overwrite" \
207207
% (self.single_generator)))
208208
self.assertTrue('Final lcov web report' in out_str
209-
and "New 'line' coverage: 1571" in out_str)
209+
and "New 'line' coverage: 1585" in out_str)
210+
211+
def test_queue_limit_5_cover_corpus(self):
212+
out_str = ''.join(self.do_cmd("%s --afl-queue-id-limit 5 --overwrite --cover-corpus" \
213+
% (self.single_generator)))
214+
self.assertTrue('Final lcov web report' in out_str
215+
and "New 'line' coverage: 1585" in out_str)
210216

211217
def test_overwrite_dir(self):
212218
### generate coverage, and then try to regenerate without --overwrite
@@ -221,7 +227,15 @@ def test_queue_limit_5_parallel(self):
221227
out_str = ''.join(self.do_cmd("%s --afl-queue-id-limit 5 --overwrite" \
222228
% (self.parallel_generator)))
223229
self.assertTrue('Final lcov web report' in out_str
224-
and "New 'line' coverage: 1571" in out_str
230+
and "New 'line' coverage: 977" in out_str
231+
and "Imported 145 new test cases" in out_str
232+
and "Imported 212 new test cases" in out_str)
233+
234+
def test_queue_limit_5_parallel_cover_corpus(self):
235+
out_str = ''.join(self.do_cmd("%s --afl-queue-id-limit 5 --overwrite --cover-corpus" \
236+
% (self.parallel_generator)))
237+
self.assertTrue('Final lcov web report' in out_str
238+
and "New 'line' coverage: 977" in out_str
225239
and "Imported 145 new test cases" in out_str
226240
and "Imported 212 new test cases" in out_str)
227241

0 commit comments

Comments
 (0)