Skip to content

Commit 91e427e

Browse files
committed
Merge branch 'develop'
2 parents 710b13f + 1bad74f commit 91e427e

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

57 files changed

+211
-78
lines changed

coverage/README.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,19 +16,19 @@ Example usage:
1616

1717
## Intel Pin
1818

19-
Using a [custom pintool](coverage/pin) contributed by [Agustin Gianni](https://twitter.com/agustingianni), the Intel Pin DBI can also be used to collect coverage data.
19+
Using a [custom pintool](pin/README.md) contributed by [Agustin Gianni](https://twitter.com/agustingianni), the Intel Pin DBI can also be used to collect coverage data.
2020

2121
Example usage:
2222

2323
```
2424
pin.exe -t CodeCoverage64.dll -- boombox.exe
2525
```
2626

27-
For convenience, binaries for the Windows pintool can be found on the [releases](https://github.com/gaasedelen/lighthouse/releases) page. macOS and Linux users need to compile the pintool themselves following the [instructions](coverage/pin#compilation) included with the pintool for their respective platforms.
27+
For convenience, binaries for the Windows pintool can be found on the [releases](https://github.com/gaasedelen/lighthouse/releases) page. macOS and Linux users need to compile the pintool themselves following the [instructions](pin/README.md#compilation) included with the pintool for their respective platforms.
2828

2929
## Frida (Experimental)
3030

31-
Lighthouse offers limited support for Frida based code coverage via a custom [instrumentation script](coverage/frida) contributed by [yrp](https://twitter.com/yrp604).
31+
Lighthouse offers limited support for Frida based code coverage via a custom [instrumentation script](frida/README.md) contributed by [yrp](https://twitter.com/yrp604).
3232

3333
Example usage:
3434

coverage/pin/CodeCoverage.cpp

Lines changed: 28 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -46,12 +46,9 @@ static std::string base_name(const std::string& path)
4646
}
4747

4848
// Per thread data structure. This is mainly done to avoid locking.
49+
// - Per-thread map of executed basic blocks, and their size.
4950
struct ThreadData {
50-
// Unique list of hit basic blocks.
51-
pintool::unordered_set<ADDRINT> m_block_hit;
52-
53-
// Map basic a block address to its size.
54-
pintool::unordered_map<ADDRINT, uint16_t> m_block_size;
51+
pintool::unordered_map<ADDRINT, uint16_t> m_blocks;
5552
};
5653

5754
class ToolContext {
@@ -166,24 +163,37 @@ static VOID PIN_FAST_ANALYSIS_CALL OnBasicBlockHit(THREADID tid, ADDRINT addr, U
166163
{
167164
auto& context = *reinterpret_cast<ToolContext*>(v);
168165
ThreadData* data = context.GetThreadLocalData(tid);
169-
data->m_block_hit.insert(addr);
170-
data->m_block_size[addr] = size;
166+
data->m_blocks[addr] = size;
167+
PIN_RemoveInstrumentationInRange(addr, addr);
171168
}
172169

173170
// Trace hit event handler.
174171
static VOID OnTrace(TRACE trace, VOID* v)
175172
{
176173
auto& context = *reinterpret_cast<ToolContext*>(v);
177-
BBL bbl = TRACE_BblHead(trace);
178-
ADDRINT addr = BBL_Address(bbl);
179174

180175
// Check if the address is inside a white-listed image.
181-
if (!context.m_tracing_enabled || !context.m_images->isInterestingAddress(addr))
176+
if (!context.m_tracing_enabled || !context.m_images->isInterestingAddress(TRACE_Address(trace)))
182177
return;
183178

184-
// For each basic block in the trace.
185-
for (; BBL_Valid(bbl); bbl = BBL_Next(bbl)) {
186-
addr = BBL_Address(bbl);
179+
auto tid = PIN_ThreadId();
180+
ThreadData* data = context.GetThreadLocalData(tid);
181+
182+
// This trace is getting JIT'd, which implies the head must get executed.
183+
auto bbl = TRACE_BblHead(trace);
184+
auto addr = BBL_Address(bbl);
185+
data->m_blocks[addr] = (uint16_t)BBL_Size(bbl);
186+
187+
// For each basic block in the trace...
188+
for (bbl = BBL_Next(bbl); BBL_Valid(bbl); bbl = BBL_Next(bbl))
189+
{
190+
191+
// Ignore blocks that have already been marked as executed in the past...
192+
ADDRINT addr = BBL_Address(bbl);
193+
if (data->m_blocks.find(addr) != data->m_blocks.end())
194+
continue;
195+
196+
// Instrument blocks that have not yet been executed (at least... by this thread).
187197
BBL_InsertCall(bbl, IPOINT_ANYWHERE, (AFUNPTR)OnBasicBlockHit,
188198
IARG_FAST_ANALYSIS_CALL,
189199
IARG_THREAD_ID,
@@ -192,6 +202,7 @@ static VOID OnTrace(TRACE trace, VOID* v)
192202
IARG_PTR, v,
193203
IARG_END);
194204
}
205+
195206
}
196207

197208
// Program finish event handler.
@@ -219,7 +230,7 @@ static VOID OnFini(INT32 code, VOID* v)
219230
// Count the global number of basic blocks.
220231
size_t number_of_bbs = 0;
221232
for (const auto& data : context.m_terminated_threads) {
222-
number_of_bbs += data->m_block_hit.size();
233+
number_of_bbs += data->m_blocks.size();
223234
}
224235

225236
context.m_trace->write_string("BB Table: %u bbs\n", number_of_bbs);
@@ -233,7 +244,8 @@ static VOID OnFini(INT32 code, VOID* v)
233244
drcov_bb tmp;
234245

235246
for (const auto& data : context.m_terminated_threads) {
236-
for (const auto& address : data->m_block_hit) {
247+
for (const auto& block : data->m_blocks) {
248+
auto address = block.first;
237249
auto it = std::find_if(context.m_loaded_images.begin(), context.m_loaded_images.end(), [&address](const LoadedImage& image) {
238250
return address >= image.low_ && address < image.high_;
239251
});
@@ -243,7 +255,7 @@ static VOID OnFini(INT32 code, VOID* v)
243255

244256
tmp.id = (uint16_t)std::distance(context.m_loaded_images.begin(), it);
245257
tmp.start = (uint32_t)(address - it->low_);
246-
tmp.size = data->m_block_size[address];
258+
tmp.size = data->m_blocks[address];
247259

248260
context.m_trace->write_binary(&tmp, sizeof(tmp));
249261
}
Lines changed: 77 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@ def __init__(self, metadata, palette):
6767
# the coverage file parser
6868
self.reader = CoverageReader()
6969
self._target_whitelist = []
70+
self.suppressed_errors = set()
7071

7172
# the name of the active coverage
7273
self.coverage_name = NEW_COMPOSITION
@@ -380,6 +381,9 @@ def load_coverage_batch(self, filepaths, batch_name, progress_callback=logger.de
380381
errors = collections.defaultdict(list)
381382
aggregate_addresses = set()
382383

384+
# unsupress NO_COVERAGE_ERROR per-load, instead of per-session
385+
self.suppressed_errors.discard(CoverageMissingError)
386+
383387
start = time.time()
384388
#----------------------------------------------------------------------
385389

@@ -439,6 +443,9 @@ def load_coverage_files(self, filepaths, progress_callback=logger.debug):
439443
errors = collections.defaultdict(list)
440444
all_coverage = []
441445

446+
# unsupress NO_COVERAGE_ERROR per-load, instead of per-session
447+
self.suppressed_errors.discard(CoverageMissingError)
448+
442449
start = time.time()
443450
#----------------------------------------------------------------------
444451

@@ -541,16 +548,39 @@ def _extract_coverage_data(self, coverage_file):
541548
if not module_name and coverage_file.modules:
542549

543550
#
544-
# if the user closes the dialog without selecting a name, there's
545-
# nothing we can do for them ...
551+
# earlier in this load, the user opted to ignore future attempts
552+
# to alias or select coverage data. this is useful when trying to
553+
# load a batch of coverage files, where some coverage files
554+
# contain data, but none relevant to this database.
555+
#
556+
557+
if CoverageMissingError in self.suppressed_errors:
558+
return []
559+
560+
#
561+
# show the module selection dialog to the user, and wait for them
562+
# to select something, or close the dialog
546563
#
547564

548565
dialog = ModuleSelector(database_target, coverage_file.modules, coverage_file.filepath)
549-
if not dialog.exec_():
550-
return [] # no coverage data extracted ...
566+
result = dialog.exec_()
567+
568+
# check if the user opted to ignore future warnings for missing coverage
569+
if dialog.ignore_missing:
570+
self.suppressed_errors.add(CoverageMissingError)
571+
572+
#
573+
# if the user closed the dialog without selecting a name, there's
574+
# nothing we can do for them. return an empty set of coverage data
575+
#
576+
577+
if not result:
578+
return []
551579

552580
# the user selected a module name! use that to extract coverage
553581
module_name = dialog.selected_name
582+
583+
# the user opted to save the selected name as an 'alias'
554584
if dialog.remember_alias:
555585
self._target_whitelist.append(module_name)
556586

@@ -713,37 +743,73 @@ def _find_fuzzy_name(self, coverage_file, target_name):
713743
"""
714744
target_name = target_name.lower()
715745

746+
#
747+
# 0. Pre-process module names, strip filepath if present
748+
#
749+
750+
clean_module_names = {}
751+
for module_name_raw in coverage_file.modules:
752+
753+
# trim 'path' from a 'module name' entry... if present (uncommon)
754+
module_name = os.path.basename(module_name_raw)
755+
756+
#
757+
# if this triggers, it's probably because the coverage file is
758+
# using full filepaths for 'module names', and that there was
759+
# two unique filepaths with the same module name, eg:
760+
#
761+
# - C:\foo.dll
762+
# - C:\bar\foo.dll
763+
#
764+
# this should be super rare, but we'll just revert to using the
765+
# full / unprocessed paths and bail...
766+
#
767+
768+
if module_name in clean_module_names:
769+
clean_module_names = {name: name for name in coverage_file.modules}
770+
break
771+
772+
clean_module_names[module_name] = module_name_raw
773+
716774
#
717775
# 1. exact, case-insensitive filename matching
718776
#
719777

720-
for module_name in coverage_file.modules:
778+
for module_name in clean_module_names:
779+
if target_name == module_name.lower():
780+
return clean_module_names[module_name]
781+
782+
#
783+
# 2. exact, case-insensitive filename matching
784+
#
785+
786+
for module_name in clean_module_names:
721787
if target_name == module_name.lower():
722-
return module_name
788+
return clean_module_names[module_name]
723789

724790
#
725791
# 2. cleave the extension from the target module name (the source)
726792
# and try again to see if matches anything in the coverage file
727793
#
728794

729795
target_name, extension = os.path.splitext(target_name)
730-
for module_name in coverage_file.modules:
796+
for module_name in clean_module_names:
731797
if target_name == module_name.lower():
732-
return module_name
798+
return clean_module_names[module_name]
733799

734800
# too risky to do fuzzy matching on short names...
735801
if len(target_name) < 6:
736802
return None
737803

738804
#
739805
# 3. try to match *{target_name}*{extension} in module_name, assuming
740-
# target_name is more than 6 characters and there is no othe ambiguity
806+
# target_name is more than 6 characters and there is no other ambiguity
741807
#
742808

743809
possible_names = []
744-
for module_name in coverage_file.modules:
810+
for module_name in clean_module_names:
745811
if target_name in module_name.lower() and extension in module_name.lower():
746-
possible_names.append(module_name)
812+
possible_names.append(clean_module_names[module_name])
747813

748814
# there were no matches on the wildcarding, so we're done
749815
if not possible_names:
Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -112,7 +112,7 @@ def __init__(self, coverage):
112112
# UI Warnings
113113
#------------------------------------------------------------------------------
114114

115-
def warn_errors(errors):
115+
def warn_errors(errors, ignore=[]):
116116
"""
117117
Warn the user of any encountered errors with a messagebox.
118118
"""
@@ -131,6 +131,10 @@ def warn_errors(errors):
131131
for error in error_list:
132132
lmsg(" - %s" % error.filepath)
133133

134+
# suppress popups for certain errors, if the user has specified such
135+
if error_type in ignore:
136+
continue
137+
134138
#
135139
# popup a more verbose error messagebox for the user to read regarding
136140
# this class of error they encountered

0 commit comments

Comments
 (0)