Skip to content

Commit 7339ca2

Browse files
committedJul 2, 2019
[GWP-ASan] Add generic unwinders and structure backtrace output.
Summary: Adds two flavours of generic unwinder and all the supporting cruft. If the supporting allocator is okay with bringing in sanitizer_common, they can use the fast frame-pointer based unwinder from sanitizer_common. Otherwise, we also provide the backtrace() libc-based unwinder as well. Of course, the allocator can always specify its own unwinder and unwinder-symbolizer. The slightly changed output format is exemplified in the first comment on this patch. It now better incorporates backtrace information, and displays allocation details on the second line. Reviewers: eugenis, vlad.tsyrklevich Reviewed By: eugenis, vlad.tsyrklevich Subscribers: srhines, kubamracek, mgorny, cryptoad, #sanitizers, llvm-commits, morehouse Tags: #sanitizers, #llvm Differential Revision: https://reviews.llvm.org/D63841 llvm-svn: 364941
1 parent cb1a5a7 commit 7339ca2

24 files changed

+434
-137
lines changed
 

‎compiler-rt/lib/gwp_asan/CMakeLists.txt

+24-17
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,10 @@ set(GWP_ASAN_HEADERS
2323
# parts of the C++ standard library.
2424
set(GWP_ASAN_CFLAGS -fno-rtti -fno-exceptions -nostdinc++ -pthread)
2525
append_list_if(COMPILER_RT_HAS_FPIC_FLAG -fPIC GWP_ASAN_CFLAGS)
26+
append_list_if(COMPILER_RT_HAS_OMIT_FRAME_POINTER_FLAG -fno-omit-frame-pointer
27+
GWP_ASAN_CFLAGS)
28+
append_list_if(COMPILER_RT_HAS_OMIT_FRAME_POINTER_FLAG
29+
-mno-omit-leaf-frame-pointer GWP_ASAN_CFLAGS)
2630

2731
# Remove -stdlib= which is unused when passing -nostdinc++.
2832
string(REGEX REPLACE "-stdlib=[a-zA-Z+]*" "" CMAKE_CXX_FLAGS ${CMAKE_CXX_FLAGS})
@@ -40,6 +44,12 @@ set(GWP_ASAN_OPTIONS_PARSER_HEADERS
4044
options.h
4145
options.inc
4246
)
47+
set(GWP_ASAN_BACKTRACE_HEADERS
48+
optional/backtrace.h
49+
options.h
50+
options.inc
51+
)
52+
4353
set(GWP_ASAN_OPTIONS_PARSER_CFLAGS
4454
${GWP_ASAN_CFLAGS}
4555
${SANITIZER_COMMON_CFLAGS})
@@ -68,29 +78,26 @@ if (COMPILER_RT_HAS_GWP_ASAN)
6878

6979
# Note: If you choose to add this as an object library, ensure you also
7080
# include the sanitizer_common flag parsing object lib (generally
71-
# 'RTSanitizerCommonNoTermination').
81+
# 'RTSanitizerCommonNoTermination'). Also, you'll need to either implement
82+
# your own backtrace support (see optional/backtrace.h), or choose between one
83+
# of the pre-implemented backtrace support options (see below).
7284
add_compiler_rt_object_libraries(RTGwpAsanOptionsParser
7385
ARCHS ${GWP_ASAN_SUPPORTED_ARCH}
7486
SOURCES ${GWP_ASAN_OPTIONS_PARSER_SOURCES}
7587
ADDITIONAL_HEADERS ${GWP_ASAN_OPTIONS_PARSER_HEADERS}
7688
CFLAGS ${GWP_ASAN_OPTIONS_PARSER_CFLAGS})
7789

78-
# Ensure that the build for the options parser succeeds, as
79-
# 'RTGwpAsanOptionsParser' may not be built if it's not needed. This library
80-
# has only a very small amount of logic, all of the testable components are
81-
# exercised in the sanitizer_common test suite.
82-
foreach(arch ${GWP_ASAN_SUPPORTED_ARCH})
83-
add_compiler_rt_runtime(
84-
clang_rt.gwp_asan_options_parser
85-
STATIC
86-
ARCHS ${arch}
87-
SOURCES ${GWP_ASAN_OPTIONS_PARSER_SOURCES}
88-
ADDITIONAL_HEADERS ${GWP_ASAN_OPTIONS_PARSER_HEADERS}
89-
CFLAGS ${GWP_ASAN_OPTIONS_PARSER_CFLAGS}
90-
OBJECT_LIBS ${GWP_ASAN_OPTIONS_PARSER_OBJECT_LIBS}
91-
PARENT_TARGET gwp_asan
92-
)
93-
endforeach()
90+
# As above, build the pre-implemented optional backtrace support libraries.
91+
add_compiler_rt_object_libraries(RTGwpAsanBacktraceLibc
92+
ARCHS ${GWP_ASAN_SUPPORTED_ARCH}
93+
SOURCES optional/backtrace_linux_libc.cpp
94+
ADDITIONAL_HEADERS ${GWP_ASAN_BACKTRACE_HEADERS}
95+
CFLAGS ${GWP_ASAN_CFLAGS})
96+
add_compiler_rt_object_libraries(RTGwpAsanBacktraceSanitizerCommon
97+
ARCHS ${GWP_ASAN_SUPPORTED_ARCH}
98+
SOURCES optional/backtrace_sanitizer_common.cpp
99+
ADDITIONAL_HEADERS ${GWP_ASAN_BACKTRACE_HEADERS}
100+
CFLAGS ${GWP_ASAN_CFLAGS} ${SANITIZER_COMMON_CFLAGS})
94101
endif()
95102

96103
if(COMPILER_RT_INCLUDE_TESTS)

‎compiler-rt/lib/gwp_asan/guarded_pool_allocator.cpp

+128-82
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@
1111
#include "gwp_asan/options.h"
1212

1313
#include <assert.h>
14+
#include <inttypes.h>
15+
#include <stdio.h>
1416
#include <stdlib.h>
1517
#include <string.h>
1618
#include <time.h>
@@ -26,32 +28,59 @@ namespace {
2628
// referenced by users outside this translation unit, in order to avoid
2729
// init-order-fiasco.
2830
GuardedPoolAllocator *SingletonPtr = nullptr;
31+
32+
class ScopedBoolean {
33+
public:
34+
ScopedBoolean(bool &B) : Bool(B) { Bool = true; }
35+
~ScopedBoolean() { Bool = false; }
36+
37+
private:
38+
bool &Bool;
39+
};
40+
41+
void defaultPrintStackTrace(uintptr_t *Trace, options::Printf_t Printf) {
42+
if (Trace[0] == 0)
43+
Printf(" <unknown (does your allocator support backtracing?)>\n");
44+
45+
for (size_t i = 0; Trace[i] != 0; ++i) {
46+
Printf(" #%zu 0x%zx in <unknown>\n", i, Trace[i]);
47+
}
48+
Printf("\n");
49+
}
2950
} // anonymous namespace
3051

3152
// Gets the singleton implementation of this class. Thread-compatible until
3253
// init() is called, thread-safe afterwards.
3354
GuardedPoolAllocator *getSingleton() { return SingletonPtr; }
3455

3556
void GuardedPoolAllocator::AllocationMetadata::RecordAllocation(
36-
uintptr_t AllocAddr, size_t AllocSize) {
57+
uintptr_t AllocAddr, size_t AllocSize, options::Backtrace_t Backtrace) {
3758
Addr = AllocAddr;
3859
Size = AllocSize;
3960
IsDeallocated = false;
4061

41-
// TODO(hctim): Implement stack trace collection.
4262
// TODO(hctim): Ask the caller to provide the thread ID, so we don't waste
4363
// other thread's time getting the thread ID under lock.
4464
AllocationTrace.ThreadID = getThreadID();
4565
DeallocationTrace.ThreadID = kInvalidThreadID;
46-
AllocationTrace.Trace[0] = 0;
66+
if (Backtrace)
67+
Backtrace(AllocationTrace.Trace, kMaximumStackFrames);
68+
else
69+
AllocationTrace.Trace[0] = 0;
4770
DeallocationTrace.Trace[0] = 0;
4871
}
4972

50-
void GuardedPoolAllocator::AllocationMetadata::RecordDeallocation() {
73+
void GuardedPoolAllocator::AllocationMetadata::RecordDeallocation(
74+
options::Backtrace_t Backtrace) {
5175
IsDeallocated = true;
52-
// TODO(hctim): Implement stack trace collection. Ensure that the unwinder is
53-
// not called if we have our recursive flag called, otherwise non-reentrant
54-
// unwinders may deadlock.
76+
// Ensure that the unwinder is not called if the recursive flag is set,
77+
// otherwise non-reentrant unwinders may deadlock.
78+
if (Backtrace && !ThreadLocals.RecursiveGuard) {
79+
ScopedBoolean B(ThreadLocals.RecursiveGuard);
80+
Backtrace(DeallocationTrace.Trace, kMaximumStackFrames);
81+
} else {
82+
DeallocationTrace.Trace[0] = 0;
83+
}
5584
DeallocationTrace.ThreadID = getThreadID();
5685
}
5786

@@ -93,6 +122,11 @@ void GuardedPoolAllocator::init(const options::Options &Opts) {
93122

94123
PerfectlyRightAlign = Opts.PerfectlyRightAlign;
95124
Printf = Opts.Printf;
125+
Backtrace = Opts.Backtrace;
126+
if (Opts.PrintBacktrace)
127+
PrintBacktrace = Opts.PrintBacktrace;
128+
else
129+
PrintBacktrace = defaultPrintStackTrace;
96130

97131
size_t PoolBytesRequired =
98132
PageSize * (1 + MaxSimultaneousAllocations) +
@@ -126,17 +160,6 @@ void GuardedPoolAllocator::init(const options::Options &Opts) {
126160
installSignalHandlers();
127161
}
128162

129-
namespace {
130-
class ScopedBoolean {
131-
public:
132-
ScopedBoolean(bool &B) : Bool(B) { Bool = true; }
133-
~ScopedBoolean() { Bool = false; }
134-
135-
private:
136-
bool &Bool;
137-
};
138-
} // anonymous namespace
139-
140163
void *GuardedPoolAllocator::allocate(size_t Size) {
141164
// GuardedPagePoolEnd == 0 when GWP-ASan is disabled. If we are disabled, fall
142165
// back to the supporting allocator.
@@ -169,7 +192,7 @@ void *GuardedPoolAllocator::allocate(size_t Size) {
169192
// unmapped.
170193
markReadWrite(reinterpret_cast<void *>(getPageAddr(Ptr)), Size);
171194

172-
Meta->RecordAllocation(Ptr, Size);
195+
Meta->RecordAllocation(Ptr, Size, Backtrace);
173196

174197
return reinterpret_cast<void *>(Ptr);
175198
}
@@ -196,7 +219,7 @@ void GuardedPoolAllocator::deallocate(void *Ptr) {
196219
// Ensure that the deallocation is recorded before marking the page as
197220
// inaccessible. Otherwise, a racy use-after-free will have inconsistent
198221
// metadata.
199-
Meta->RecordDeallocation();
222+
Meta->RecordDeallocation(Backtrace);
200223
}
201224

202225
markInaccessible(reinterpret_cast<void *>(SlotStart),
@@ -328,85 +351,109 @@ Error GuardedPoolAllocator::diagnoseUnknownError(uintptr_t AccessPtr,
328351

329352
// If we have reached here, the error is still unknown. There is no metadata
330353
// available.
354+
*Meta = nullptr;
331355
return Error::UNKNOWN;
332356
}
333357

334-
// Prints the provided error and metadata information. Returns true if there is
335-
// additional context that can be provided, false otherwise (i.e. returns false
336-
// if Error == {UNKNOWN, INVALID_FREE without metadata}).
337-
bool printErrorType(Error E, uintptr_t AccessPtr, AllocationMetadata *Meta,
338-
options::Printf_t Printf) {
358+
namespace {
359+
// Prints the provided error and metadata information.
360+
void printErrorType(Error E, uintptr_t AccessPtr, AllocationMetadata *Meta,
361+
options::Printf_t Printf, uint64_t ThreadID) {
362+
// Print using intermediate strings. Platforms like Android don't like when
363+
// you print multiple times to the same line, as there may be a newline
364+
// appended to a log file automatically per Printf() call.
365+
const char *ErrorString;
339366
switch (E) {
340367
case Error::UNKNOWN:
341-
Printf("GWP-ASan couldn't automatically determine the source of the "
342-
"memory error when accessing 0x%zx. It was likely caused by a wild "
343-
"memory access into the GWP-ASan pool.\n",
344-
AccessPtr);
345-
return false;
368+
ErrorString = "GWP-ASan couldn't automatically determine the source of "
369+
"the memory error. It was likely caused by a wild memory "
370+
"access into the GWP-ASan pool. The error occured";
371+
break;
346372
case Error::USE_AFTER_FREE:
347-
Printf("Use after free occurred when accessing memory at: 0x%zx\n",
348-
AccessPtr);
373+
ErrorString = "Use after free";
349374
break;
350375
case Error::DOUBLE_FREE:
351-
Printf("Double free occurred when trying to free memory at: 0x%zx\n",
352-
AccessPtr);
376+
ErrorString = "Double free";
353377
break;
354378
case Error::INVALID_FREE:
355-
Printf(
356-
"Invalid (wild) free occurred when trying to free memory at: 0x%zx\n",
357-
AccessPtr);
358-
// It's possible for an invalid free to fall onto a slot that has never been
359-
// allocated. If this is the case, there is no valid metadata.
360-
if (Meta == nullptr)
361-
return false;
379+
ErrorString = "Invalid (wild) free";
362380
break;
363381
case Error::BUFFER_OVERFLOW:
364-
Printf("Buffer overflow occurred when accessing memory at: 0x%zx\n",
365-
AccessPtr);
382+
ErrorString = "Buffer overflow";
366383
break;
367384
case Error::BUFFER_UNDERFLOW:
368-
Printf("Buffer underflow occurred when accessing memory at: 0x%zx\n",
369-
AccessPtr);
385+
ErrorString = "Buffer underflow";
370386
break;
371387
}
372388

373-
Printf("0x%zx is ", AccessPtr);
374-
if (AccessPtr < Meta->Addr)
375-
Printf("located %zu bytes to the left of a %zu-byte allocation located at "
376-
"0x%zx\n",
377-
Meta->Addr - AccessPtr, Meta->Size, Meta->Addr);
378-
else if (AccessPtr > Meta->Addr)
379-
Printf("located %zu bytes to the right of a %zu-byte allocation located at "
380-
"0x%zx\n",
381-
AccessPtr - Meta->Addr, Meta->Size, Meta->Addr);
389+
constexpr size_t kDescriptionBufferLen = 128;
390+
char DescriptionBuffer[kDescriptionBufferLen];
391+
if (Meta) {
392+
if (E == Error::USE_AFTER_FREE) {
393+
snprintf(DescriptionBuffer, kDescriptionBufferLen,
394+
"(%zu byte%s into a %zu-byte allocation at 0x%zx)",
395+
AccessPtr - Meta->Addr, (AccessPtr - Meta->Addr == 1) ? "" : "s",
396+
Meta->Size, Meta->Addr);
397+
} else if (AccessPtr < Meta->Addr) {
398+
snprintf(DescriptionBuffer, kDescriptionBufferLen,
399+
"(%zu byte%s to the left of a %zu-byte allocation at 0x%zx)",
400+
Meta->Addr - AccessPtr, (Meta->Addr - AccessPtr == 1) ? "" : "s",
401+
Meta->Size, Meta->Addr);
402+
} else if (AccessPtr > Meta->Addr) {
403+
snprintf(DescriptionBuffer, kDescriptionBufferLen,
404+
"(%zu byte%s to the right of a %zu-byte allocation at 0x%zx)",
405+
AccessPtr - Meta->Addr, (AccessPtr - Meta->Addr == 1) ? "" : "s",
406+
Meta->Size, Meta->Addr);
407+
} else {
408+
snprintf(DescriptionBuffer, kDescriptionBufferLen,
409+
"(a %zu-byte allocation)", Meta->Size);
410+
}
411+
}
412+
413+
// Possible number of digits of a 64-bit number: ceil(log10(2^64)) == 20. Add
414+
// a null terminator, and round to the nearest 8-byte boundary.
415+
constexpr size_t kThreadBufferLen = 24;
416+
char ThreadBuffer[kThreadBufferLen];
417+
if (ThreadID == GuardedPoolAllocator::kInvalidThreadID)
418+
snprintf(ThreadBuffer, kThreadBufferLen, "<unknown>");
382419
else
383-
Printf("a %zu-byte allocation\n", Meta->Size);
384-
return true;
420+
snprintf(ThreadBuffer, kThreadBufferLen, "%" PRIu64, ThreadID);
421+
422+
Printf("%s at 0x%zx %s by thread %s here:\n", ErrorString, AccessPtr,
423+
DescriptionBuffer, ThreadBuffer);
385424
}
386425

387-
void printThreadInformation(Error E, uintptr_t AccessPtr,
388-
AllocationMetadata *Meta,
389-
options::Printf_t Printf) {
390-
Printf("0x%zx was allocated by thread ", AccessPtr);
391-
if (Meta->AllocationTrace.ThreadID == UINT64_MAX)
392-
Printf("UNKNOWN.\n");
393-
else
394-
Printf("%zu.\n", Meta->AllocationTrace.ThreadID);
426+
void printAllocDeallocTraces(uintptr_t AccessPtr, AllocationMetadata *Meta,
427+
options::Printf_t Printf,
428+
options::PrintBacktrace_t PrintBacktrace) {
429+
assert(Meta != nullptr && "Metadata is non-null for printAllocDeallocTraces");
395430

396-
if (E == Error::USE_AFTER_FREE || E == Error::DOUBLE_FREE) {
397-
Printf("0x%zx was freed by thread ", AccessPtr);
398-
if (Meta->AllocationTrace.ThreadID == UINT64_MAX)
399-
Printf("UNKNOWN.\n");
431+
if (Meta->IsDeallocated) {
432+
if (Meta->DeallocationTrace.ThreadID ==
433+
GuardedPoolAllocator::kInvalidThreadID)
434+
Printf("0x%zx was deallocated by thread <unknown> here:\n", AccessPtr);
400435
else
401-
Printf("%zu.\n", Meta->AllocationTrace.ThreadID);
436+
Printf("0x%zx was deallocated by thread %zu here:\n", AccessPtr,
437+
Meta->DeallocationTrace.ThreadID);
438+
439+
PrintBacktrace(Meta->DeallocationTrace.Trace, Printf);
402440
}
441+
442+
if (Meta->AllocationTrace.ThreadID == GuardedPoolAllocator::kInvalidThreadID)
443+
Printf("0x%zx was allocated by thread <unknown> here:\n", Meta->Addr);
444+
else
445+
Printf("0x%zx was allocated by thread %zu here:\n", Meta->Addr,
446+
Meta->AllocationTrace.ThreadID);
447+
448+
PrintBacktrace(Meta->AllocationTrace.Trace, Printf);
403449
}
404450

405451
struct ScopedEndOfReportDecorator {
406452
ScopedEndOfReportDecorator(options::Printf_t Printf) : Printf(Printf) {}
407453
~ScopedEndOfReportDecorator() { Printf("*** End GWP-ASan report ***\n"); }
408454
options::Printf_t Printf;
409455
};
456+
} // anonymous namespace
410457

411458
void GuardedPoolAllocator::reportErrorInternal(uintptr_t AccessPtr, Error E) {
412459
if (!pointerIsMine(reinterpret_cast<void *>(AccessPtr))) {
@@ -434,22 +481,21 @@ void GuardedPoolAllocator::reportErrorInternal(uintptr_t AccessPtr, Error E) {
434481
Meta = nullptr;
435482
}
436483

437-
// Print the error information, and if there is no valid metadata, stop here.
438-
if (!printErrorType(E, AccessPtr, Meta, Printf)) {
439-
return;
440-
}
484+
// Print the error information.
485+
uint64_t ThreadID = getThreadID();
486+
printErrorType(E, AccessPtr, Meta, Printf, ThreadID);
487+
if (Backtrace) {
488+
static constexpr unsigned kMaximumStackFramesForCrashTrace = 128;
489+
uintptr_t Trace[kMaximumStackFramesForCrashTrace];
490+
Backtrace(Trace, kMaximumStackFramesForCrashTrace);
441491

442-
// Ensure that we have a valid metadata pointer from this point forward.
443-
if (Meta == nullptr) {
444-
Printf("GWP-ASan internal unreachable error. Metadata is not null.\n");
445-
return;
492+
PrintBacktrace(Trace, Printf);
493+
} else {
494+
Printf(" <unknown (does your allocator support backtracing?)>\n\n");
446495
}
447496

448-
printThreadInformation(E, AccessPtr, Meta, Printf);
449-
// TODO(hctim): Implement stack unwinding here. Ask the caller to provide us
450-
// with the base pointer, and we unwind the stack to give a stack trace for
451-
// the access.
452-
// TODO(hctim): Implement dumping here of allocation/deallocation traces.
497+
if (Meta)
498+
printAllocDeallocTraces(AccessPtr, Meta, Printf, PrintBacktrace);
453499
}
454500

455501
TLS_INITIAL_EXEC

‎compiler-rt/lib/gwp_asan/guarded_pool_allocator.h

+8-8
Original file line numberDiff line numberDiff line change
@@ -41,16 +41,14 @@ class GuardedPoolAllocator {
4141
struct AllocationMetadata {
4242
// Maximum number of stack trace frames to collect for allocations + frees.
4343
// TODO(hctim): Implement stack frame compression, a-la Chromium.
44-
// Currently the maximum stack frames is one, as we don't collect traces.
45-
static constexpr size_t kMaximumStackFrames = 1;
44+
static constexpr size_t kMaximumStackFrames = 64;
4645

47-
// Records the given allocation metadata into this struct. In the future,
48-
// this will collect the allocation trace as well.
49-
void RecordAllocation(uintptr_t Addr, size_t Size);
46+
// Records the given allocation metadata into this struct.
47+
void RecordAllocation(uintptr_t Addr, size_t Size,
48+
options::Backtrace_t Backtrace);
5049

51-
// Record that this allocation is now deallocated. In future, this will
52-
// collect the deallocation trace as well.
53-
void RecordDeallocation();
50+
// Record that this allocation is now deallocated.
51+
void RecordDeallocation(options::Backtrace_t Backtrace);
5452

5553
struct CallSiteInfo {
5654
// The backtrace to the allocation/deallocation. If the first value is
@@ -234,6 +232,8 @@ class GuardedPoolAllocator {
234232
// general) use printf() from the cstdlib as it may malloc(), causing infinite
235233
// recursion.
236234
options::Printf_t Printf = nullptr;
235+
options::Backtrace_t Backtrace = nullptr;
236+
options::PrintBacktrace_t PrintBacktrace = nullptr;
237237

238238
// The adjusted sample rate for allocation sampling. Default *must* be
239239
// nonzero, as dynamic initialisation may call malloc (e.g. from libstdc++)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
//===-- backtrace.h ---------------------------------------------*- C++ -*-===//
2+
//
3+
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4+
// See https://llvm.org/LICENSE.txt for license information.
5+
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6+
//
7+
//===----------------------------------------------------------------------===//
8+
9+
#ifndef GWP_ASAN_OPTIONAL_BACKTRACE_H_
10+
#define GWP_ASAN_OPTIONAL_BACKTRACE_H_
11+
12+
#include "gwp_asan/options.h"
13+
14+
namespace gwp_asan {
15+
namespace options {
16+
// Functions to get the platform-specific and implementation-specific backtrace
17+
// and backtrace printing functions.
18+
Backtrace_t getBacktraceFunction();
19+
PrintBacktrace_t getPrintBacktraceFunction();
20+
} // namespace options
21+
} // namespace gwp_asan
22+
23+
#endif // GWP_ASAN_OPTIONAL_BACKTRACE_H_
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
//===-- backtrace_linux_libc.cpp --------------------------------*- C++ -*-===//
2+
//
3+
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4+
// See https://llvm.org/LICENSE.txt for license information.
5+
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6+
//
7+
//===----------------------------------------------------------------------===//
8+
9+
#include <assert.h>
10+
#include <execinfo.h>
11+
#include <stddef.h>
12+
#include <stdint.h>
13+
#include <stdlib.h>
14+
#include <string.h>
15+
16+
#include "gwp_asan/optional/backtrace.h"
17+
#include "gwp_asan/options.h"
18+
19+
namespace {
20+
void Backtrace(uintptr_t *TraceBuffer, size_t Size) {
21+
// Grab (what seems to be) one more trace than we need. TraceBuffer needs to
22+
// be null-terminated, but we wish to remove the frame of this function call.
23+
static_assert(sizeof(uintptr_t) == sizeof(void *), "uintptr_t is not void*");
24+
int NumTraces =
25+
backtrace(reinterpret_cast<void **>(TraceBuffer), Size);
26+
27+
// Now shift the entire trace one place to the left and null-terminate.
28+
memmove(TraceBuffer, TraceBuffer + 1, NumTraces * sizeof(void *));
29+
TraceBuffer[NumTraces - 1] = 0;
30+
}
31+
32+
static void PrintBacktrace(uintptr_t *Trace,
33+
gwp_asan::options::Printf_t Printf) {
34+
size_t NumTraces = 0;
35+
for (; Trace[NumTraces] != 0; ++NumTraces) {
36+
}
37+
38+
if (NumTraces == 0) {
39+
Printf(" <not found (does your allocator support backtracing?)>\n\n");
40+
return;
41+
}
42+
43+
char **BacktraceSymbols =
44+
backtrace_symbols(reinterpret_cast<void **>(Trace), NumTraces);
45+
46+
for (size_t i = 0; i < NumTraces; ++i) {
47+
if (!BacktraceSymbols)
48+
Printf(" #%zu %p\n", i, Trace[i]);
49+
else
50+
Printf(" #%zu %s\n", i, BacktraceSymbols[i]);
51+
}
52+
53+
Printf("\n");
54+
if (BacktraceSymbols)
55+
free(BacktraceSymbols);
56+
}
57+
} // anonymous namespace
58+
59+
namespace gwp_asan {
60+
namespace options {
61+
Backtrace_t getBacktraceFunction() { return Backtrace; }
62+
PrintBacktrace_t getPrintBacktraceFunction() { return PrintBacktrace; }
63+
} // namespace options
64+
} // namespace gwp_asan
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
//===-- backtrace_sanitizer_common.cpp --------------------------*- C++ -*-===//
2+
//
3+
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4+
// See https://llvm.org/LICENSE.txt for license information.
5+
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6+
//
7+
//===----------------------------------------------------------------------===//
8+
9+
#include <assert.h>
10+
#include <stddef.h>
11+
#include <stdint.h>
12+
#include <string.h>
13+
14+
#include "gwp_asan/optional/backtrace.h"
15+
#include "gwp_asan/options.h"
16+
#include "sanitizer_common/sanitizer_stacktrace.h"
17+
18+
void __sanitizer::BufferedStackTrace::UnwindImpl(uptr pc, uptr bp,
19+
void *context,
20+
bool request_fast,
21+
u32 max_depth) {
22+
if (!StackTrace::WillUseFastUnwind(request_fast)) {
23+
return Unwind(max_depth, pc, bp, context, 0, 0, request_fast);
24+
}
25+
Unwind(max_depth, pc, 0, context, 0, 0, false);
26+
}
27+
28+
namespace {
29+
void Backtrace(uintptr_t *TraceBuffer, size_t Size) {
30+
__sanitizer::BufferedStackTrace Trace;
31+
Trace.Reset();
32+
if (Size > __sanitizer::kStackTraceMax)
33+
Size = __sanitizer::kStackTraceMax;
34+
35+
Trace.Unwind((__sanitizer::uptr)__builtin_return_address(0),
36+
(__sanitizer::uptr)__builtin_frame_address(0),
37+
/* ucontext */ nullptr,
38+
/* fast unwind */ true, Size - 1);
39+
40+
memcpy(TraceBuffer, Trace.trace, Trace.size * sizeof(uintptr_t));
41+
TraceBuffer[Trace.size] = 0;
42+
}
43+
44+
static void PrintBacktrace(uintptr_t *Trace,
45+
gwp_asan::options::Printf_t Printf) {
46+
__sanitizer::StackTrace StackTrace;
47+
StackTrace.trace = reinterpret_cast<__sanitizer::uptr *>(Trace);
48+
49+
for (StackTrace.size = 0; StackTrace.size < __sanitizer::kStackTraceMax;
50+
++StackTrace.size) {
51+
if (Trace[StackTrace.size] == 0)
52+
break;
53+
}
54+
55+
if (StackTrace.size == 0) {
56+
Printf(" <unknown (does your allocator support backtracing?)>\n\n");
57+
return;
58+
}
59+
60+
StackTrace.Print();
61+
}
62+
} // anonymous namespace
63+
64+
namespace gwp_asan {
65+
namespace options {
66+
Backtrace_t getBacktraceFunction() { return Backtrace; }
67+
PrintBacktrace_t getPrintBacktraceFunction() { return PrintBacktrace; }
68+
} // namespace options
69+
} // namespace gwp_asan

‎compiler-rt/lib/gwp_asan/optional/options_parser.cpp

+3-1
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,8 @@ Options *getOptionsInternal() {
4747
} // anonymous namespace
4848

4949
void initOptions() {
50+
__sanitizer::SetCommonFlagsDefaults();
51+
5052
Options *o = getOptionsInternal();
5153
o->setDefaults();
5254

@@ -85,7 +87,7 @@ void initOptions() {
8587
o->Printf = __sanitizer::Printf;
8688
}
8789

88-
const Options &getOptions() { return *getOptionsInternal(); }
90+
Options &getOptions() { return *getOptionsInternal(); }
8991

9092
} // namespace options
9193
} // namespace gwp_asan

‎compiler-rt/lib/gwp_asan/optional/options_parser.h

+4-5
Original file line numberDiff line numberDiff line change
@@ -9,18 +9,17 @@
99
#ifndef GWP_ASAN_OPTIONAL_OPTIONS_PARSER_H_
1010
#define GWP_ASAN_OPTIONAL_OPTIONS_PARSER_H_
1111

12+
#include "gwp_asan/optional/backtrace.h"
1213
#include "gwp_asan/options.h"
1314
#include "sanitizer_common/sanitizer_common.h"
1415

1516
namespace gwp_asan {
1617
namespace options {
17-
1818
// Parse the options from the GWP_ASAN_FLAGS environment variable.
1919
void initOptions();
20-
// Returns a pointer to the initialised options. Call initOptions() prior to
21-
// calling this function.
22-
const Options &getOptions();
23-
20+
// Returns the initialised options. Call initOptions() prior to calling this
21+
// function.
22+
Options &getOptions();
2423
} // namespace options
2524
} // namespace gwp_asan
2625

‎compiler-rt/lib/gwp_asan/options.h

+18
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,9 @@
99
#ifndef GWP_ASAN_OPTIONS_H_
1010
#define GWP_ASAN_OPTIONS_H_
1111

12+
#include <stddef.h>
13+
#include <stdint.h>
14+
1215
namespace gwp_asan {
1316
namespace options {
1417
// The function pointer type for printf(). Follows the standard format from the
@@ -17,8 +20,21 @@ namespace options {
1720
// printf() signature, and pass the wrapper instead.
1821
typedef void (*Printf_t)(const char *Format, ...);
1922

23+
// The function pointer type for backtrace information. Required to be
24+
// implemented by the supporting allocator. The callee should elide itself and
25+
// all frames below itself from TraceBuffer, i.e. the caller's frame should be
26+
// in TraceBuffer[0], and subsequent frames 1..n into TraceBuffer[1..n], where a
27+
// maximum of `MaximumDepth - 1` frames are stored. TraceBuffer should be
28+
// nullptr-terminated (i.e. if there are 5 frames; TraceBuffer[5] == nullptr).
29+
// If the allocator cannot supply backtrace information, it should set
30+
// TraceBuffer[0] == nullptr.
31+
typedef void (*Backtrace_t)(uintptr_t *TraceBuffer, size_t Size);
32+
typedef void (*PrintBacktrace_t)(uintptr_t *TraceBuffer, Printf_t Print);
33+
2034
struct Options {
2135
Printf_t Printf = nullptr;
36+
Backtrace_t Backtrace = nullptr;
37+
PrintBacktrace_t PrintBacktrace = nullptr;
2238

2339
// Read the options from the included definitions file.
2440
#define GWP_ASAN_OPTION(Type, Name, DefaultValue, Description) \
@@ -33,6 +49,8 @@ struct Options {
3349
#undef GWP_ASAN_OPTION
3450

3551
Printf = nullptr;
52+
Backtrace = nullptr;
53+
PrintBacktrace = nullptr;
3654
}
3755
};
3856
} // namespace options

‎compiler-rt/lib/gwp_asan/tests/CMakeLists.txt

+7-3
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,8 @@ set(GWP_ASAN_UNITTEST_CFLAGS
44
${COMPILER_RT_UNITTEST_CFLAGS}
55
${COMPILER_RT_GTEST_CFLAGS}
66
-I${COMPILER_RT_SOURCE_DIR}/lib/
7-
-O2)
7+
-O2
8+
-g)
89

910
file(GLOB GWP_ASAN_HEADERS ../*.h)
1011
file(GLOB GWP_ASAN_UNITTESTS *.cpp)
@@ -15,7 +16,7 @@ set(GWP_ASAN_UNIT_TEST_HEADERS
1516
add_custom_target(GwpAsanUnitTests)
1617
set_target_properties(GwpAsanUnitTests PROPERTIES FOLDER "Compiler-RT Tests")
1718

18-
set(GWP_ASAN_UNITTEST_LINK_FLAGS ${COMPILER_RT_UNITTEST_LINK_FLAGS})
19+
set(GWP_ASAN_UNITTEST_LINK_FLAGS ${COMPILER_RT_UNITTEST_LINK_FLAGS} -ldl)
1920
list(APPEND GWP_ASAN_UNITTEST_LINK_FLAGS --driver-mode=g++)
2021
if(NOT WIN32)
2122
list(APPEND GWP_ASAN_UNITTEST_LINK_FLAGS -pthread)
@@ -30,8 +31,11 @@ if(COMPILER_RT_DEFAULT_TARGET_ARCH IN_LIST GWP_ASAN_SUPPORTED_ARCH)
3031
# RTSanitizerCommonNoTermination(NoLibc) required for __sanitizer::Printf.
3132
set(GWP_ASAN_TEST_RUNTIME_OBJECTS
3233
$<TARGET_OBJECTS:RTGwpAsan.${arch}>
34+
$<TARGET_OBJECTS:RTGwpAsanBacktraceSanitizerCommon.${arch}>
35+
$<TARGET_OBJECTS:RTGwpAsanOptionsParser.${arch}>
3336
$<TARGET_OBJECTS:RTSanitizerCommon.${arch}>
34-
$<TARGET_OBJECTS:RTSanitizerCommonNoLibc.${arch}>)
37+
$<TARGET_OBJECTS:RTSanitizerCommonLibc.${arch}>
38+
$<TARGET_OBJECTS:RTSanitizerCommonSymbolizer.${arch}>)
3539

3640
add_library(${GWP_ASAN_TEST_RUNTIME} STATIC
3741
${GWP_ASAN_TEST_RUNTIME_OBJECTS})
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
//===-- backtrace.cc --------------------------------------------*- C++ -*-===//
2+
//
3+
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4+
// See https://llvm.org/LICENSE.txt for license information.
5+
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6+
//
7+
//===----------------------------------------------------------------------===//
8+
9+
#include <string>
10+
11+
#include "gwp_asan/tests/harness.h"
12+
13+
TEST_F(BacktraceGuardedPoolAllocator, DoubleFree) {
14+
void *Ptr = GPA.allocate(1);
15+
GPA.deallocate(Ptr);
16+
17+
std::string DeathRegex = "Double free.*";
18+
DeathRegex.append("backtrace\\.cpp:25.*");
19+
20+
DeathRegex.append("was deallocated.*");
21+
DeathRegex.append("backtrace\\.cpp:15.*");
22+
23+
DeathRegex.append("was allocated.*");
24+
DeathRegex.append("backtrace\\.cpp:14.*");
25+
ASSERT_DEATH(GPA.deallocate(Ptr), DeathRegex);
26+
}
27+
28+
TEST_F(BacktraceGuardedPoolAllocator, UseAfterFree) {
29+
char *Ptr = static_cast<char *>(GPA.allocate(1));
30+
GPA.deallocate(Ptr);
31+
32+
std::string DeathRegex = "Use after free.*";
33+
DeathRegex.append("backtrace\\.cpp:40.*");
34+
35+
DeathRegex.append("was deallocated.*");
36+
DeathRegex.append("backtrace\\.cpp:30.*");
37+
38+
DeathRegex.append("was allocated.*");
39+
DeathRegex.append("backtrace\\.cpp:29.*");
40+
ASSERT_DEATH({ *Ptr = 7; }, DeathRegex);
41+
}

‎compiler-rt/lib/gwp_asan/tests/harness.h

+23
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@
1717
#include "sanitizer_common/sanitizer_common.h"
1818

1919
#include "gwp_asan/guarded_pool_allocator.h"
20+
#include "gwp_asan/optional/backtrace.h"
21+
#include "gwp_asan/optional/options_parser.h"
2022
#include "gwp_asan/options.h"
2123

2224
class DefaultGuardedPoolAllocator : public ::testing::Test {
@@ -57,4 +59,25 @@ class CustomGuardedPoolAllocator : public ::testing::Test {
5759
MaxSimultaneousAllocations;
5860
};
5961

62+
class BacktraceGuardedPoolAllocator : public ::testing::Test {
63+
public:
64+
BacktraceGuardedPoolAllocator() {
65+
// Call initOptions to initialise the internal sanitizer_common flags. These
66+
// flags are referenced by the sanitizer_common unwinder, and if left
67+
// uninitialised, they'll unintentionally crash the program.
68+
gwp_asan::options::initOptions();
69+
70+
gwp_asan::options::Options Opts;
71+
Opts.setDefaults();
72+
73+
Opts.Printf = __sanitizer::Printf;
74+
Opts.Backtrace = gwp_asan::options::getBacktraceFunction();
75+
Opts.PrintBacktrace = gwp_asan::options::getPrintBacktraceFunction();
76+
GPA.init(Opts);
77+
}
78+
79+
protected:
80+
gwp_asan::GuardedPoolAllocator GPA;
81+
};
82+
6083
#endif // GWP_ASAN_TESTS_HARNESS_H_

‎compiler-rt/test/gwp_asan/double_delete.cpp

+1-1
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
// RUN: not %run %t 2>&1 | FileCheck %s
44

55
// CHECK: GWP-ASan detected a memory error
6-
// CHECK: Double free occurred when trying to free memory at:
6+
// CHECK: Double free at 0x{{[a-f0-9]+}} (a 1-byte allocation)
77

88
#include <cstdlib>
99

‎compiler-rt/test/gwp_asan/double_deletea.cpp

+1-1
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
// RUN: not %run %t 2>&1 | FileCheck %s
44

55
// CHECK: GWP-ASan detected a memory error
6-
// CHECK: Double free occurred when trying to free memory at:
6+
// CHECK: Double free at 0x{{[a-f0-9]+}} (a 50-byte allocation)
77

88
#include <cstdlib>
99

‎compiler-rt/test/gwp_asan/double_free.cpp

+3-3
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,13 @@
22
// RUN: %clangxx_gwp_asan %s -o %t
33
// RUN: not %run %t 2>&1 | FileCheck %s
44

5-
// CHECK: GWP-ASan detected a memory error
6-
// CHECK: Double free occurred when trying to free memory at:
7-
85
#include <cstdlib>
96

107
int main() {
8+
// CHECK: GWP-ASan detected a memory error
9+
// CHECK: Double free at 0x{{[a-f0-9]+}} (a 10-byte allocation)
1110
void *Ptr = malloc(10);
11+
1212
free(Ptr);
1313
free(Ptr);
1414
return 0;

‎compiler-rt/test/gwp_asan/heap_buffer_overflow.cpp

+2-2
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,8 @@
33
// RUN: %expect_crash %run %t 2>&1 | FileCheck %s
44

55
// CHECK: GWP-ASan detected a memory error
6-
// CHECK: Buffer overflow occurred when accessing memory at:
7-
// CHECK: is located {{[0-9]+}} bytes to the right
6+
// CHECK: Buffer overflow at 0x{{[a-f0-9]+}} ({{[1-9][0-9]*}} bytes to the right
7+
// CHECK-SAME: of a {{[1-9][0-9]*}}-byte allocation
88

99
#include <cstdlib>
1010

‎compiler-rt/test/gwp_asan/heap_buffer_underflow.cpp

+2-2
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,8 @@
33
// RUN: %expect_crash %run %t 2>&1 | FileCheck %s
44

55
// CHECK: GWP-ASan detected a memory error
6-
// CHECK: Buffer underflow occurred when accessing memory at:
7-
// CHECK: is located 1 bytes to the left
6+
// CHECK: Buffer underflow at 0x{{[a-f0-9]+}} (1 byte to the left
7+
// CHECK-SAME: of a {{[1-9][0-9]*}}-byte allocation
88

99
#include <cstdlib>
1010

‎compiler-rt/test/gwp_asan/invalid_free_left.cpp

+2-2
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,8 @@
33
// RUN: not %run %t 2>&1 | FileCheck %s
44

55
// CHECK: GWP-ASan detected a memory error
6-
// CHECK: Invalid (wild) free occurred when trying to free memory at:
7-
// CHECK: is located 1 bytes to the left of
6+
// CHECK: Invalid (wild) free at 0x{{[a-f0-9]+}} (1 byte to the left of a
7+
// CHECK-SAME: 1-byte allocation
88

99
#include <cstdlib>
1010

‎compiler-rt/test/gwp_asan/invalid_free_right.cpp

+2-2
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,8 @@
33
// RUN: not %run %t 2>&1 | FileCheck %s
44

55
// CHECK: GWP-ASan detected a memory error
6-
// CHECK: Invalid (wild) free occurred when trying to free memory at:
7-
// CHECK: is located 1 bytes to the right
6+
// CHECK: Invalid (wild) free at 0x{{[a-f0-9]+}} (1 byte to the right of a
7+
// CHECK-SAME: 1-byte allocation
88

99
#include <cstdlib>
1010

‎compiler-rt/test/gwp_asan/lit.cfg.py

+2-1
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,8 @@
2020

2121
cxx_flags = (c_flags + config.cxx_mode_flags + ["-std=c++11"])
2222

23-
gwp_asan_flags = ["-fsanitize=scudo"]
23+
gwp_asan_flags = ["-fsanitize=scudo", "-g", "-fno-omit-frame-pointer",
24+
"-mno-omit-leaf-frame-pointer"]
2425

2526
def build_invocation(compile_flags):
2627
return " " + " ".join([config.clang] + compile_flags) + " "

‎compiler-rt/test/gwp_asan/realloc.cpp

+4-4
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,8 @@ int main() {
2323
free(Ptr + 1);
2424

2525
// CHECK-MALLOC: GWP-ASan detected a memory error
26-
// CHECK-MALLOC: Invalid (wild) free occurred when trying to free memory at:
27-
// CHECK-MALLOC: is located 1 bytes to the right of a 1-byte allocation
26+
// CHECK-MALLOC: Invalid (wild) free at 0x{{[a-f0-9]+}} (1 byte to the right
27+
// CHECK-MALLOC-SAME: of a 1-byte allocation
2828
#elif defined(TEST_FREE)
2929
char *Ptr = (char *) malloc(1);
3030
// realloc(ptr, 0) is equivalent to free(ptr) and must return nullptr. Note
@@ -36,8 +36,8 @@ int main() {
3636
*Ptr = 0;
3737

3838
// CHECK-FREE: GWP-ASan detected a memory error
39-
// CHECK-FREE: Use after free occurred when accessing memory at:
40-
// CHECK-FREE: is a 1-byte allocation
39+
// CHECK-FREE: Use after free at 0x{{[a-f0-9]+}} (0 bytes into a 1-byte
40+
// CHECK-FREE-SAME: allocation
4141
#endif
4242

4343
return 0;

‎compiler-rt/test/gwp_asan/use_after_delete.cpp

+1-1
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
// RUN: %expect_crash %run %t 2>&1 | FileCheck %s
44

55
// CHECK: GWP-ASan detected a memory error
6-
// CHECK: Use after free occurred when accessing memory at:
6+
// CHECK: Use after free at 0x{{[a-f0-9]+}} (0 bytes into a 1-byte allocation
77

88
#include <cstdlib>
99

‎compiler-rt/test/gwp_asan/use_after_deletea.cpp

+1-1
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
// RUN: %expect_crash %run %t 2>&1 | FileCheck %s
44

55
// CHECK: GWP-ASan detected a memory error
6-
// CHECK: Use after free occurred when accessing memory at:
6+
// CHECK: Use after free at 0x{{[a-f0-9]+}} (0 bytes into a 10-byte allocation
77

88
#include <cstdlib>
99

‎compiler-rt/test/gwp_asan/use_after_free.cpp

+1-1
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
// RUN: %expect_crash %run %t 2>&1 | FileCheck %s
44

55
// CHECK: GWP-ASan detected a memory error
6-
// CHECK: Use after free occurred when accessing memory at:
6+
// CHECK: Use after free at 0x{{[a-f0-9]+}} (0 bytes into a 10-byte allocation
77

88
#include <cstdlib>
99

0 commit comments

Comments
 (0)
Please sign in to comment.