[lsan] Introduce __lsan_(un)register_root_region().
Add an interface for telling LSan that a region of memory is to be treated as a source of live pointers. Useful for code which stores pointers in mapped memory. llvm-svn: 197489
This commit is contained in:
parent
5069790b24
commit
7237879926
|
@ -23,13 +23,30 @@ extern "C" {
|
|||
// be treated as non-leaks. Disable/enable pairs may be nested.
|
||||
void __lsan_disable();
|
||||
void __lsan_enable();
|
||||
|
||||
// The heap object into which p points will be treated as a non-leak.
|
||||
void __lsan_ignore_object(const void *p);
|
||||
|
||||
// Memory regions registered through this interface will be treated as sources
|
||||
// of live pointers during leak checking. Useful if you store pointers in
|
||||
// mapped memory.
|
||||
// Points of note:
|
||||
// - __lsan_unregister_root_region() must be called with the same pointer and
|
||||
// size that have earlier been passed to __lsan_register_root_region()
|
||||
// - LSan will skip any inaccessible memory when scanning a root region. E.g.,
|
||||
// if you map memory within a larger region that you have mprotect'ed, you can
|
||||
// register the entire large region.
|
||||
// - the implementation is not optimized for performance. This interface is
|
||||
// intended to be used for a small number of relatively static regions.
|
||||
void __lsan_register_root_region(const void *p, size_t size);
|
||||
void __lsan_unregister_root_region(const void *p, size_t size);
|
||||
|
||||
// The user may optionally provide this function to disallow leak checking
|
||||
// for the program it is linked into (if the return value is non-zero). This
|
||||
// function must be defined as returning a constant value; any behavior beyond
|
||||
// that is unsupported.
|
||||
int __lsan_is_turned_off();
|
||||
|
||||
// Calling this function makes LSan enter the leak checking phase immediately.
|
||||
// Use this if normal end-of-process leak checking happens too late (e.g. if
|
||||
// you have intentional memory leaks in your shutdown code). Calling this
|
||||
|
|
|
@ -0,0 +1,32 @@
|
|||
// Test for __lsan_(un)register_root_region().
|
||||
// RUN: LSAN_BASE="use_stacks=0:use_registers=0"
|
||||
// RUN: %clangxx_lsan %s -o %t
|
||||
// RUN: LSAN_OPTIONS=$LSAN_BASE %t
|
||||
// RUN: LSAN_OPTIONS=$LSAN_BASE not %t foo 2>&1 | FileCheck %s
|
||||
// RUN: LSAN_OPTIONS=$LSAN_BASE:use_root_regions=0 not %t 2>&1 | FileCheck %s
|
||||
|
||||
#include <assert.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <sys/mman.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <sanitizer/lsan_interface.h>
|
||||
|
||||
int main(int argc, char *argv[]) {
|
||||
size_t size = getpagesize() * 2;
|
||||
void *p =
|
||||
mmap(0, size, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
|
||||
assert(p);
|
||||
// Make half of the memory inaccessible. LSan must not crash trying to read it.
|
||||
assert(0 == mprotect((char *)p + size / 2, size / 2, PROT_NONE));
|
||||
|
||||
__lsan_register_root_region(p, size);
|
||||
*((void **)p) = malloc(1337);
|
||||
fprintf(stderr, "Test alloc: %p.\n", p);
|
||||
if (argc > 1)
|
||||
__lsan_unregister_root_region(p, size);
|
||||
return 0;
|
||||
}
|
||||
// CHECK: Test alloc: [[ADDR:.*]].
|
||||
// CHECK: SUMMARY: {{(Leak|Address)}}Sanitizer: 1337 byte(s) leaked in 1 allocation(s)
|
|
@ -17,6 +17,7 @@
|
|||
#include "sanitizer_common/sanitizer_common.h"
|
||||
#include "sanitizer_common/sanitizer_flags.h"
|
||||
#include "sanitizer_common/sanitizer_placement_new.h"
|
||||
#include "sanitizer_common/sanitizer_procmaps.h"
|
||||
#include "sanitizer_common/sanitizer_stackdepot.h"
|
||||
#include "sanitizer_common/sanitizer_stacktrace.h"
|
||||
#include "sanitizer_common/sanitizer_stoptheworld.h"
|
||||
|
@ -26,7 +27,8 @@
|
|||
#if CAN_SANITIZE_LEAKS
|
||||
namespace __lsan {
|
||||
|
||||
// This mutex is used to prevent races between DoLeakCheck and IgnoreObject.
|
||||
// This mutex is used to prevent races between DoLeakCheck and IgnoreObject, and
|
||||
// also to protect the global list of root regions.
|
||||
BlockingMutex global_mutex(LINKER_INITIALIZED);
|
||||
|
||||
THREADLOCAL int disable_counter;
|
||||
|
@ -46,6 +48,7 @@ static void InitializeFlags() {
|
|||
f->use_globals = true;
|
||||
f->use_stacks = true;
|
||||
f->use_tls = true;
|
||||
f->use_root_regions = true;
|
||||
f->use_unaligned = false;
|
||||
f->use_poisoned = false;
|
||||
f->verbosity = 0;
|
||||
|
@ -58,6 +61,7 @@ static void InitializeFlags() {
|
|||
ParseFlag(options, &f->use_globals, "use_globals");
|
||||
ParseFlag(options, &f->use_stacks, "use_stacks");
|
||||
ParseFlag(options, &f->use_tls, "use_tls");
|
||||
ParseFlag(options, &f->use_root_regions, "use_root_regions");
|
||||
ParseFlag(options, &f->use_unaligned, "use_unaligned");
|
||||
ParseFlag(options, &f->use_poisoned, "use_poisoned");
|
||||
ParseFlag(options, &f->report_objects, "report_objects");
|
||||
|
@ -77,8 +81,8 @@ SuppressionContext *suppression_ctx;
|
|||
|
||||
void InitializeSuppressions() {
|
||||
CHECK(!suppression_ctx);
|
||||
ALIGNED(64) static char placeholder_[sizeof(SuppressionContext)];
|
||||
suppression_ctx = new(placeholder_) SuppressionContext;
|
||||
ALIGNED(64) static char placeholder[sizeof(SuppressionContext)];
|
||||
suppression_ctx = new(placeholder) SuppressionContext;
|
||||
char *suppressions_from_file;
|
||||
uptr buffer_size;
|
||||
if (ReadFileToBuffer(flags()->suppressions, &suppressions_from_file,
|
||||
|
@ -93,8 +97,22 @@ void InitializeSuppressions() {
|
|||
suppression_ctx->Parse(__lsan_default_suppressions());
|
||||
}
|
||||
|
||||
struct RootRegion {
|
||||
const void *begin;
|
||||
uptr size;
|
||||
};
|
||||
|
||||
InternalMmapVector<RootRegion> *root_regions;
|
||||
|
||||
void InitializeRootRegions() {
|
||||
CHECK(!root_regions);
|
||||
ALIGNED(64) static char placeholder[sizeof(InternalMmapVector<RootRegion>)];
|
||||
root_regions = new(placeholder) InternalMmapVector<RootRegion>(1);
|
||||
}
|
||||
|
||||
void InitCommonLsan() {
|
||||
InitializeFlags();
|
||||
InitializeRootRegions();
|
||||
if (common_flags()->detect_leaks) {
|
||||
// Initialization which can fail or print warnings should only be done if
|
||||
// LSan is actually enabled.
|
||||
|
@ -245,6 +263,38 @@ static void ProcessThreads(SuspendedThreadsList const &suspended_threads,
|
|||
}
|
||||
}
|
||||
|
||||
static void ProcessRootRegion(Frontier *frontier, uptr root_begin,
|
||||
uptr root_end) {
|
||||
MemoryMappingLayout proc_maps(/*cache_enabled*/true);
|
||||
uptr begin, end, prot;
|
||||
while (proc_maps.Next(&begin, &end,
|
||||
/*offset*/ 0, /*filename*/ 0, /*filename_size*/ 0,
|
||||
&prot)) {
|
||||
uptr intersection_begin = Max(root_begin, begin);
|
||||
uptr intersection_end = Min(end, root_end);
|
||||
if (intersection_begin >= intersection_end) continue;
|
||||
bool is_readable = prot & MemoryMappingLayout::kProtectionRead;
|
||||
if (flags()->log_pointers)
|
||||
Report("Root region %p-%p intersects with mapped region %p-%p (%s)\n",
|
||||
root_begin, root_end, begin, end,
|
||||
is_readable ? "readable" : "unreadable");
|
||||
if (is_readable)
|
||||
ScanRangeForPointers(intersection_begin, intersection_end, frontier,
|
||||
"ROOT", kReachable);
|
||||
}
|
||||
}
|
||||
|
||||
// Scans root regions for heap pointers.
|
||||
static void ProcessRootRegions(Frontier *frontier) {
|
||||
if (!flags()->use_root_regions) return;
|
||||
CHECK(root_regions);
|
||||
for (uptr i = 0; i < root_regions->size(); i++) {
|
||||
RootRegion region = (*root_regions)[i];
|
||||
uptr begin_addr = reinterpret_cast<uptr>(region.begin);
|
||||
ProcessRootRegion(frontier, begin_addr, begin_addr + region.size);
|
||||
}
|
||||
}
|
||||
|
||||
static void FloodFillTag(Frontier *frontier, ChunkTag tag) {
|
||||
while (frontier->size()) {
|
||||
uptr next_chunk = frontier->back();
|
||||
|
@ -284,6 +334,7 @@ static void ClassifyAllChunks(SuspendedThreadsList const &suspended_threads) {
|
|||
if (flags()->use_globals)
|
||||
ProcessGlobalRegions(&frontier);
|
||||
ProcessThreads(suspended_threads, &frontier);
|
||||
ProcessRootRegions(&frontier);
|
||||
FloodFillTag(&frontier, kReachable);
|
||||
// The check here is relatively expensive, so we do this in a separate flood
|
||||
// fill. That way we can skip the check for chunks that are reachable
|
||||
|
@ -570,6 +621,46 @@ void __lsan_ignore_object(const void *p) {
|
|||
#endif // CAN_SANITIZE_LEAKS
|
||||
}
|
||||
|
||||
SANITIZER_INTERFACE_ATTRIBUTE
|
||||
void __lsan_register_root_region(const void *begin, uptr size) {
|
||||
#if CAN_SANITIZE_LEAKS
|
||||
BlockingMutexLock l(&global_mutex);
|
||||
CHECK(root_regions);
|
||||
RootRegion region = {begin, size};
|
||||
root_regions->push_back(region);
|
||||
if (flags()->verbosity)
|
||||
Report("Registered root region at %p of size %llu\n", begin, size);
|
||||
#endif // CAN_SANITIZE_LEAKS
|
||||
}
|
||||
|
||||
SANITIZER_INTERFACE_ATTRIBUTE
|
||||
void __lsan_unregister_root_region(const void *begin, uptr size) {
|
||||
#if CAN_SANITIZE_LEAKS
|
||||
BlockingMutexLock l(&global_mutex);
|
||||
CHECK(root_regions);
|
||||
bool removed = false;
|
||||
for (uptr i = 0; i < root_regions->size(); i++) {
|
||||
RootRegion region = (*root_regions)[i];
|
||||
if (region.begin == begin && region.size == size) {
|
||||
removed = true;
|
||||
uptr last_index = root_regions->size() - 1;
|
||||
(*root_regions)[i] = (*root_regions)[last_index];
|
||||
root_regions->pop_back();
|
||||
if (flags()->verbosity)
|
||||
Report("Unregistered root region at %p of size %llu\n", begin, size);
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!removed) {
|
||||
Report(
|
||||
"__lsan_unregister_root_region(): region at %p of size %llu has not "
|
||||
"been registered.\n",
|
||||
begin, size);
|
||||
Die();
|
||||
}
|
||||
#endif // CAN_SANITIZE_LEAKS
|
||||
}
|
||||
|
||||
SANITIZER_INTERFACE_ATTRIBUTE
|
||||
void __lsan_disable() {
|
||||
#if CAN_SANITIZE_LEAKS
|
||||
|
|
|
@ -63,6 +63,8 @@ struct Flags {
|
|||
bool use_registers;
|
||||
// TLS and thread-specific storage.
|
||||
bool use_tls;
|
||||
// Regions added via __lsan_register_root_region().
|
||||
bool use_root_regions;
|
||||
|
||||
// Consider unaligned pointers valid.
|
||||
bool use_unaligned;
|
||||
|
|
Loading…
Reference in New Issue