[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:
Sergey Matveev 2013-12-17 11:11:23 +00:00
parent 5069790b24
commit 7237879926
4 changed files with 145 additions and 3 deletions

View File

@ -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

View File

@ -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)

View File

@ -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

View File

@ -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;