mirror of
https://gcc.gnu.org/git/gcc.git
synced 2024-11-27 05:44:15 +08:00
libsanitizer mege from upstream r171973
From-SVN: r195083
This commit is contained in:
parent
e1f674e4c2
commit
e9772e16b3
@ -1,3 +1,7 @@
|
||||
2013-01-10 Kostya Serebryany <kcc@google.com>
|
||||
|
||||
* g++.dg/asan/asan_test.cc: Sync from upstream.
|
||||
|
||||
2013-01-10 Jakub Jelinek <jakub@redhat.com>
|
||||
|
||||
PR tree-optimization/55921
|
||||
|
@ -17,6 +17,15 @@
|
||||
#include <stdint.h>
|
||||
#include <setjmp.h>
|
||||
#include <assert.h>
|
||||
#include <algorithm>
|
||||
|
||||
#ifdef __linux__
|
||||
# include <sys/prctl.h>
|
||||
# include <sys/types.h>
|
||||
# include <sys/stat.h>
|
||||
# include <fcntl.h>
|
||||
#include <unistd.h>
|
||||
#endif
|
||||
|
||||
#if defined(__i386__) || defined(__x86_64__)
|
||||
#include <emmintrin.h>
|
||||
@ -242,7 +251,7 @@ void OOBTest() {
|
||||
for (int i = 0; i < (int)(size - sizeof(T) + 1); i++)
|
||||
oob_test<T>(size, i);
|
||||
|
||||
for (int i = size - sizeof(T) + 1; i <= (int)(size + 3 * sizeof(T)); i++) {
|
||||
for (int i = size - sizeof(T) + 1; i <= (int)(size + 2 * sizeof(T)); i++) {
|
||||
const char *str =
|
||||
"is located.*%d byte.*to the right";
|
||||
int off = i >= size ? (i - size) : 0;
|
||||
@ -298,6 +307,18 @@ TEST(AddressSanitizer, OOBRightTest) {
|
||||
}
|
||||
}
|
||||
|
||||
#if ASAN_ALLOCATOR_VERSION == 2 // Broken with the asan_allocator1
|
||||
TEST(AddressSanitizer, LargeOOBRightTest) {
|
||||
size_t large_power_of_two = 1 << 19;
|
||||
for (size_t i = 16; i <= 256; i *= 2) {
|
||||
size_t size = large_power_of_two - i;
|
||||
char *p = Ident(new char[size]);
|
||||
EXPECT_DEATH(p[size] = 0, "is located 0 bytes to the right");
|
||||
delete [] p;
|
||||
}
|
||||
}
|
||||
#endif // ASAN_ALLOCATOR_VERSION == 2
|
||||
|
||||
TEST(AddressSanitizer, UAF_char) {
|
||||
const char *uaf_string = "AddressSanitizer:.*heap-use-after-free";
|
||||
EXPECT_DEATH(uaf_test<U1>(1, 0), uaf_string);
|
||||
@ -456,6 +477,24 @@ TEST(AddressSanitizer, HugeMallocTest) {
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifndef __APPLE__
|
||||
void MemalignRun(size_t align, size_t size, int idx) {
|
||||
char *p = (char *)memalign(align, size);
|
||||
Ident(p)[idx] = 0;
|
||||
free(p);
|
||||
}
|
||||
|
||||
TEST(AddressSanitizer, memalign) {
|
||||
for (int align = 16; align <= (1 << 23); align *= 2) {
|
||||
size_t size = align * 5;
|
||||
EXPECT_DEATH(MemalignRun(align, size, -1),
|
||||
"is located 1 bytes to the left");
|
||||
EXPECT_DEATH(MemalignRun(align, size, size + 1),
|
||||
"is located 1 bytes to the right");
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
TEST(AddressSanitizer, ThreadedMallocStressTest) {
|
||||
const int kNumThreads = 4;
|
||||
const int kNumIterations = (ASAN_LOW_MEMORY) ? 10000 : 100000;
|
||||
@ -784,14 +823,39 @@ TEST(AddressSanitizer, Store128Test) {
|
||||
}
|
||||
#endif
|
||||
|
||||
static string RightOOBErrorMessage(int oob_distance) {
|
||||
static string RightOOBErrorMessage(int oob_distance, bool is_write) {
|
||||
assert(oob_distance >= 0);
|
||||
char expected_str[100];
|
||||
sprintf(expected_str, "located %d bytes to the right", oob_distance);
|
||||
sprintf(expected_str, ASAN_PCRE_DOTALL "%s.*located %d bytes to the right",
|
||||
is_write ? "WRITE" : "READ", oob_distance);
|
||||
return string(expected_str);
|
||||
}
|
||||
|
||||
static string LeftOOBErrorMessage(int oob_distance) {
|
||||
static string RightOOBWriteMessage(int oob_distance) {
|
||||
return RightOOBErrorMessage(oob_distance, /*is_write*/true);
|
||||
}
|
||||
|
||||
static string RightOOBReadMessage(int oob_distance) {
|
||||
return RightOOBErrorMessage(oob_distance, /*is_write*/false);
|
||||
}
|
||||
|
||||
static string LeftOOBErrorMessage(int oob_distance, bool is_write) {
|
||||
assert(oob_distance > 0);
|
||||
char expected_str[100];
|
||||
sprintf(expected_str, ASAN_PCRE_DOTALL "%s.*located %d bytes to the left",
|
||||
is_write ? "WRITE" : "READ", oob_distance);
|
||||
return string(expected_str);
|
||||
}
|
||||
|
||||
static string LeftOOBWriteMessage(int oob_distance) {
|
||||
return LeftOOBErrorMessage(oob_distance, /*is_write*/true);
|
||||
}
|
||||
|
||||
static string LeftOOBReadMessage(int oob_distance) {
|
||||
return LeftOOBErrorMessage(oob_distance, /*is_write*/false);
|
||||
}
|
||||
|
||||
static string LeftOOBAccessMessage(int oob_distance) {
|
||||
assert(oob_distance > 0);
|
||||
char expected_str[100];
|
||||
sprintf(expected_str, "located %d bytes to the left", oob_distance);
|
||||
@ -805,44 +869,48 @@ void MemSetOOBTestTemplate(size_t length) {
|
||||
T *array = Ident((T*)malloc(size));
|
||||
int element = Ident(42);
|
||||
int zero = Ident(0);
|
||||
void *(*MEMSET)(void *s, int c, size_t n) = Ident(memset);
|
||||
// memset interval inside array
|
||||
memset(array, element, size);
|
||||
memset(array, element, size - 1);
|
||||
memset(array + length - 1, element, sizeof(T));
|
||||
memset(array, element, 1);
|
||||
MEMSET(array, element, size);
|
||||
MEMSET(array, element, size - 1);
|
||||
MEMSET(array + length - 1, element, sizeof(T));
|
||||
MEMSET(array, element, 1);
|
||||
|
||||
// memset 0 bytes
|
||||
memset(array - 10, element, zero);
|
||||
memset(array - 1, element, zero);
|
||||
memset(array, element, zero);
|
||||
memset(array + length, 0, zero);
|
||||
memset(array + length + 1, 0, zero);
|
||||
MEMSET(array - 10, element, zero);
|
||||
MEMSET(array - 1, element, zero);
|
||||
MEMSET(array, element, zero);
|
||||
MEMSET(array + length, 0, zero);
|
||||
MEMSET(array + length + 1, 0, zero);
|
||||
|
||||
// try to memset bytes to the right of array
|
||||
EXPECT_DEATH(memset(array, 0, size + 1),
|
||||
RightOOBErrorMessage(0));
|
||||
EXPECT_DEATH(memset((char*)(array + length) - 1, element, 6),
|
||||
RightOOBErrorMessage(4));
|
||||
EXPECT_DEATH(memset(array + 1, element, size + sizeof(T)),
|
||||
RightOOBErrorMessage(2 * sizeof(T) - 1));
|
||||
EXPECT_DEATH(MEMSET(array, 0, size + 1),
|
||||
RightOOBWriteMessage(0));
|
||||
EXPECT_DEATH(MEMSET((char*)(array + length) - 1, element, 6),
|
||||
RightOOBWriteMessage(0));
|
||||
EXPECT_DEATH(MEMSET(array + 1, element, size + sizeof(T)),
|
||||
RightOOBWriteMessage(0));
|
||||
// whole interval is to the right
|
||||
EXPECT_DEATH(memset(array + length + 1, 0, 10),
|
||||
RightOOBErrorMessage(sizeof(T)));
|
||||
EXPECT_DEATH(MEMSET(array + length + 1, 0, 10),
|
||||
RightOOBWriteMessage(sizeof(T)));
|
||||
|
||||
// try to memset bytes to the left of array
|
||||
EXPECT_DEATH(memset((char*)array - 1, element, size),
|
||||
LeftOOBErrorMessage(1));
|
||||
EXPECT_DEATH(memset((char*)array - 5, 0, 6),
|
||||
LeftOOBErrorMessage(5));
|
||||
EXPECT_DEATH(memset(array - 5, element, size + 5 * sizeof(T)),
|
||||
LeftOOBErrorMessage(5 * sizeof(T)));
|
||||
EXPECT_DEATH(MEMSET((char*)array - 1, element, size),
|
||||
LeftOOBWriteMessage(1));
|
||||
EXPECT_DEATH(MEMSET((char*)array - 5, 0, 6),
|
||||
LeftOOBWriteMessage(5));
|
||||
if (length >= 100) {
|
||||
// Large OOB, we find it only if the redzone is large enough.
|
||||
EXPECT_DEATH(memset(array - 5, element, size + 5 * sizeof(T)),
|
||||
LeftOOBWriteMessage(5 * sizeof(T)));
|
||||
}
|
||||
// whole interval is to the left
|
||||
EXPECT_DEATH(memset(array - 2, 0, sizeof(T)),
|
||||
LeftOOBErrorMessage(2 * sizeof(T)));
|
||||
EXPECT_DEATH(MEMSET(array - 2, 0, sizeof(T)),
|
||||
LeftOOBWriteMessage(2 * sizeof(T)));
|
||||
|
||||
// try to memset bytes both to the left & to the right
|
||||
EXPECT_DEATH(memset((char*)array - 2, element, size + 4),
|
||||
LeftOOBErrorMessage(2));
|
||||
EXPECT_DEATH(MEMSET((char*)array - 2, element, size + 4),
|
||||
LeftOOBWriteMessage(2));
|
||||
|
||||
free(array);
|
||||
}
|
||||
@ -854,6 +922,51 @@ TEST(AddressSanitizer, MemSetOOBTest) {
|
||||
// We can test arrays of structres/classes here, but what for?
|
||||
}
|
||||
|
||||
// Try to allocate two arrays of 'size' bytes that are near each other.
|
||||
// Strictly speaking we are not guaranteed to find such two pointers,
|
||||
// but given the structure of asan's allocator we will.
|
||||
static bool AllocateTwoAdjacentArrays(char **x1, char **x2, size_t size) {
|
||||
vector<char *> v;
|
||||
bool res = false;
|
||||
for (size_t i = 0; i < 1000U && !res; i++) {
|
||||
v.push_back(new char[size]);
|
||||
if (i == 0) continue;
|
||||
sort(v.begin(), v.end());
|
||||
for (size_t j = 1; j < v.size(); j++) {
|
||||
assert(v[j] > v[j-1]);
|
||||
if ((size_t)(v[j] - v[j-1]) < size * 2) {
|
||||
*x2 = v[j];
|
||||
*x1 = v[j-1];
|
||||
res = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < v.size(); i++) {
|
||||
if (res && v[i] == *x1) continue;
|
||||
if (res && v[i] == *x2) continue;
|
||||
delete [] v[i];
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
TEST(AddressSanitizer, LargeOOBInMemset) {
|
||||
for (size_t size = 200; size < 100000; size += size / 2) {
|
||||
char *x1, *x2;
|
||||
if (!Ident(AllocateTwoAdjacentArrays)(&x1, &x2, size))
|
||||
continue;
|
||||
// fprintf(stderr, " large oob memset: %p %p %zd\n", x1, x2, size);
|
||||
// Do a memset on x1 with huge out-of-bound access that will end up in x2.
|
||||
EXPECT_DEATH(Ident(memset)(x1, 0, size * 2),
|
||||
"is located 0 bytes to the right");
|
||||
delete [] x1;
|
||||
delete [] x2;
|
||||
return;
|
||||
}
|
||||
assert(0 && "Did not find two adjacent malloc-ed pointers");
|
||||
}
|
||||
|
||||
// Same test for memcpy and memmove functions
|
||||
template <typename T, class M>
|
||||
void MemTransferOOBTestTemplate(size_t length) {
|
||||
@ -877,27 +990,27 @@ void MemTransferOOBTestTemplate(size_t length) {
|
||||
|
||||
// try to change mem to the right of dest
|
||||
EXPECT_DEATH(M::transfer(dest + 1, src, size),
|
||||
RightOOBErrorMessage(sizeof(T) - 1));
|
||||
RightOOBWriteMessage(0));
|
||||
EXPECT_DEATH(M::transfer((char*)(dest + length) - 1, src, 5),
|
||||
RightOOBErrorMessage(3));
|
||||
RightOOBWriteMessage(0));
|
||||
|
||||
// try to change mem to the left of dest
|
||||
EXPECT_DEATH(M::transfer(dest - 2, src, size),
|
||||
LeftOOBErrorMessage(2 * sizeof(T)));
|
||||
LeftOOBWriteMessage(2 * sizeof(T)));
|
||||
EXPECT_DEATH(M::transfer((char*)dest - 3, src, 4),
|
||||
LeftOOBErrorMessage(3));
|
||||
LeftOOBWriteMessage(3));
|
||||
|
||||
// try to access mem to the right of src
|
||||
EXPECT_DEATH(M::transfer(dest, src + 2, size),
|
||||
RightOOBErrorMessage(2 * sizeof(T) - 1));
|
||||
RightOOBReadMessage(0));
|
||||
EXPECT_DEATH(M::transfer(dest, (char*)(src + length) - 3, 6),
|
||||
RightOOBErrorMessage(2));
|
||||
RightOOBReadMessage(0));
|
||||
|
||||
// try to access mem to the left of src
|
||||
EXPECT_DEATH(M::transfer(dest, src - 1, size),
|
||||
LeftOOBErrorMessage(sizeof(T)));
|
||||
LeftOOBReadMessage(sizeof(T)));
|
||||
EXPECT_DEATH(M::transfer(dest, (char*)src - 6, 7),
|
||||
LeftOOBErrorMessage(6));
|
||||
LeftOOBReadMessage(6));
|
||||
|
||||
// Generally we don't need to test cases where both accessing src and writing
|
||||
// to dest address to poisoned memory.
|
||||
@ -906,10 +1019,10 @@ void MemTransferOOBTestTemplate(size_t length) {
|
||||
T *big_dest = Ident((T*)malloc(size * 2));
|
||||
// try to change mem to both sides of dest
|
||||
EXPECT_DEATH(M::transfer(dest - 1, big_src, size * 2),
|
||||
LeftOOBErrorMessage(sizeof(T)));
|
||||
LeftOOBWriteMessage(sizeof(T)));
|
||||
// try to access mem to both sides of src
|
||||
EXPECT_DEATH(M::transfer(big_dest, src - 2, size * 2),
|
||||
LeftOOBErrorMessage(2 * sizeof(T)));
|
||||
LeftOOBReadMessage(2 * sizeof(T)));
|
||||
|
||||
free(src);
|
||||
free(dest);
|
||||
@ -920,7 +1033,7 @@ void MemTransferOOBTestTemplate(size_t length) {
|
||||
class MemCpyWrapper {
|
||||
public:
|
||||
static void* transfer(void *to, const void *from, size_t size) {
|
||||
return memcpy(to, from, size);
|
||||
return Ident(memcpy)(to, from, size);
|
||||
}
|
||||
};
|
||||
TEST(AddressSanitizer, MemCpyOOBTest) {
|
||||
@ -931,7 +1044,7 @@ TEST(AddressSanitizer, MemCpyOOBTest) {
|
||||
class MemMoveWrapper {
|
||||
public:
|
||||
static void* transfer(void *to, const void *from, size_t size) {
|
||||
return memmove(to, from, size);
|
||||
return Ident(memmove)(to, from, size);
|
||||
}
|
||||
};
|
||||
TEST(AddressSanitizer, MemMoveOOBTest) {
|
||||
@ -958,15 +1071,15 @@ void StrLenOOBTestTemplate(char *str, size_t length, bool is_global) {
|
||||
// Arg of strlen is not malloced, OOB access
|
||||
if (!is_global) {
|
||||
// We don't insert RedZones to the left of global variables
|
||||
EXPECT_DEATH(Ident(strlen(str - 1)), LeftOOBErrorMessage(1));
|
||||
EXPECT_DEATH(Ident(strlen(str - 5)), LeftOOBErrorMessage(5));
|
||||
EXPECT_DEATH(Ident(strlen(str - 1)), LeftOOBReadMessage(1));
|
||||
EXPECT_DEATH(Ident(strlen(str - 5)), LeftOOBReadMessage(5));
|
||||
}
|
||||
EXPECT_DEATH(Ident(strlen(str + length + 1)), RightOOBErrorMessage(0));
|
||||
EXPECT_DEATH(Ident(strlen(str + length + 1)), RightOOBReadMessage(0));
|
||||
// Overwrite terminator
|
||||
str[length] = 'a';
|
||||
// String is not zero-terminated, strlen will lead to OOB access
|
||||
EXPECT_DEATH(Ident(strlen(str)), RightOOBErrorMessage(0));
|
||||
EXPECT_DEATH(Ident(strlen(str + length)), RightOOBErrorMessage(0));
|
||||
EXPECT_DEATH(Ident(strlen(str)), RightOOBReadMessage(0));
|
||||
EXPECT_DEATH(Ident(strlen(str + length)), RightOOBReadMessage(0));
|
||||
// Restore terminator
|
||||
str[length] = 0;
|
||||
}
|
||||
@ -1010,11 +1123,11 @@ TEST(AddressSanitizer, StrNLenOOBTest) {
|
||||
str[size - 1] = '\0';
|
||||
Ident(strnlen(str, 2 * size));
|
||||
// Argument points to not allocated memory.
|
||||
EXPECT_DEATH(Ident(strnlen(str - 1, 1)), LeftOOBErrorMessage(1));
|
||||
EXPECT_DEATH(Ident(strnlen(str + size, 1)), RightOOBErrorMessage(0));
|
||||
EXPECT_DEATH(Ident(strnlen(str - 1, 1)), LeftOOBReadMessage(1));
|
||||
EXPECT_DEATH(Ident(strnlen(str + size, 1)), RightOOBReadMessage(0));
|
||||
// Overwrite the terminating '\0' and hit unallocated memory.
|
||||
str[size - 1] = 'z';
|
||||
EXPECT_DEATH(Ident(strnlen(str, size + 1)), RightOOBErrorMessage(0));
|
||||
EXPECT_DEATH(Ident(strnlen(str, size + 1)), RightOOBReadMessage(0));
|
||||
free(str);
|
||||
}
|
||||
#endif
|
||||
@ -1030,11 +1143,11 @@ TEST(AddressSanitizer, StrDupOOBTest) {
|
||||
new_str = strdup(str + size - 1);
|
||||
free(new_str);
|
||||
// Argument points to not allocated memory.
|
||||
EXPECT_DEATH(Ident(strdup(str - 1)), LeftOOBErrorMessage(1));
|
||||
EXPECT_DEATH(Ident(strdup(str + size)), RightOOBErrorMessage(0));
|
||||
EXPECT_DEATH(Ident(strdup(str - 1)), LeftOOBReadMessage(1));
|
||||
EXPECT_DEATH(Ident(strdup(str + size)), RightOOBReadMessage(0));
|
||||
// Overwrite the terminating '\0' and hit unallocated memory.
|
||||
str[size - 1] = 'z';
|
||||
EXPECT_DEATH(Ident(strdup(str)), RightOOBErrorMessage(0));
|
||||
EXPECT_DEATH(Ident(strdup(str)), RightOOBReadMessage(0));
|
||||
free(str);
|
||||
}
|
||||
|
||||
@ -1048,15 +1161,15 @@ TEST(AddressSanitizer, StrCpyOOBTest) {
|
||||
strcpy(to, from);
|
||||
strcpy(to + to_size - from_size, from);
|
||||
// Length of "from" is too small.
|
||||
EXPECT_DEATH(Ident(strcpy(from, "hello2")), RightOOBErrorMessage(0));
|
||||
EXPECT_DEATH(Ident(strcpy(from, "hello2")), RightOOBWriteMessage(0));
|
||||
// "to" or "from" points to not allocated memory.
|
||||
EXPECT_DEATH(Ident(strcpy(to - 1, from)), LeftOOBErrorMessage(1));
|
||||
EXPECT_DEATH(Ident(strcpy(to, from - 1)), LeftOOBErrorMessage(1));
|
||||
EXPECT_DEATH(Ident(strcpy(to, from + from_size)), RightOOBErrorMessage(0));
|
||||
EXPECT_DEATH(Ident(strcpy(to + to_size, from)), RightOOBErrorMessage(0));
|
||||
EXPECT_DEATH(Ident(strcpy(to - 1, from)), LeftOOBWriteMessage(1));
|
||||
EXPECT_DEATH(Ident(strcpy(to, from - 1)), LeftOOBReadMessage(1));
|
||||
EXPECT_DEATH(Ident(strcpy(to, from + from_size)), RightOOBReadMessage(0));
|
||||
EXPECT_DEATH(Ident(strcpy(to + to_size, from)), RightOOBWriteMessage(0));
|
||||
// Overwrite the terminating '\0' character and hit unallocated memory.
|
||||
from[from_size - 1] = '!';
|
||||
EXPECT_DEATH(Ident(strcpy(to, from)), RightOOBErrorMessage(0));
|
||||
EXPECT_DEATH(Ident(strcpy(to, from)), RightOOBReadMessage(0));
|
||||
free(to);
|
||||
free(from);
|
||||
}
|
||||
@ -1078,25 +1191,25 @@ TEST(AddressSanitizer, StrNCpyOOBTest) {
|
||||
strncpy(to + to_size - 1, from, 1);
|
||||
// One of {to, from} points to not allocated memory
|
||||
EXPECT_DEATH(Ident(strncpy(to, from - 1, from_size)),
|
||||
LeftOOBErrorMessage(1));
|
||||
LeftOOBReadMessage(1));
|
||||
EXPECT_DEATH(Ident(strncpy(to - 1, from, from_size)),
|
||||
LeftOOBErrorMessage(1));
|
||||
LeftOOBWriteMessage(1));
|
||||
EXPECT_DEATH(Ident(strncpy(to, from + from_size, 1)),
|
||||
RightOOBErrorMessage(0));
|
||||
RightOOBReadMessage(0));
|
||||
EXPECT_DEATH(Ident(strncpy(to + to_size, from, 1)),
|
||||
RightOOBErrorMessage(0));
|
||||
RightOOBWriteMessage(0));
|
||||
// Length of "to" is too small
|
||||
EXPECT_DEATH(Ident(strncpy(to + to_size - from_size + 1, from, from_size)),
|
||||
RightOOBErrorMessage(0));
|
||||
RightOOBWriteMessage(0));
|
||||
EXPECT_DEATH(Ident(strncpy(to + 1, from, to_size)),
|
||||
RightOOBErrorMessage(0));
|
||||
RightOOBWriteMessage(0));
|
||||
// Overwrite terminator in from
|
||||
from[from_size - 1] = '!';
|
||||
// normal strncpy call
|
||||
strncpy(to, from, from_size);
|
||||
// Length of "from" is too small
|
||||
EXPECT_DEATH(Ident(strncpy(to, from, to_size)),
|
||||
RightOOBErrorMessage(0));
|
||||
RightOOBReadMessage(0));
|
||||
free(to);
|
||||
free(from);
|
||||
}
|
||||
@ -1117,11 +1230,11 @@ USED static void RunStrChrTest(PointerToStrChr1 StrChr) {
|
||||
EXPECT_EQ(str + 10, StrChr(str, 'q'));
|
||||
EXPECT_EQ(NULL, StrChr(str, 'a'));
|
||||
// StrChr argument points to not allocated memory.
|
||||
EXPECT_DEATH(Ident(StrChr(str - 1, 'z')), LeftOOBErrorMessage(1));
|
||||
EXPECT_DEATH(Ident(StrChr(str + size, 'z')), RightOOBErrorMessage(0));
|
||||
EXPECT_DEATH(Ident(StrChr(str - 1, 'z')), LeftOOBReadMessage(1));
|
||||
EXPECT_DEATH(Ident(StrChr(str + size, 'z')), RightOOBReadMessage(0));
|
||||
// Overwrite the terminator and hit not allocated memory.
|
||||
str[11] = 'z';
|
||||
EXPECT_DEATH(Ident(StrChr(str, 'a')), RightOOBErrorMessage(0));
|
||||
EXPECT_DEATH(Ident(StrChr(str, 'a')), RightOOBReadMessage(0));
|
||||
free(str);
|
||||
}
|
||||
USED static void RunStrChrTest(PointerToStrChr2 StrChr) {
|
||||
@ -1133,11 +1246,11 @@ USED static void RunStrChrTest(PointerToStrChr2 StrChr) {
|
||||
EXPECT_EQ(str + 10, StrChr(str, 'q'));
|
||||
EXPECT_EQ(NULL, StrChr(str, 'a'));
|
||||
// StrChr argument points to not allocated memory.
|
||||
EXPECT_DEATH(Ident(StrChr(str - 1, 'z')), LeftOOBErrorMessage(1));
|
||||
EXPECT_DEATH(Ident(StrChr(str + size, 'z')), RightOOBErrorMessage(0));
|
||||
EXPECT_DEATH(Ident(StrChr(str - 1, 'z')), LeftOOBReadMessage(1));
|
||||
EXPECT_DEATH(Ident(StrChr(str + size, 'z')), RightOOBReadMessage(0));
|
||||
// Overwrite the terminator and hit not allocated memory.
|
||||
str[11] = 'z';
|
||||
EXPECT_DEATH(Ident(StrChr(str, 'a')), RightOOBErrorMessage(0));
|
||||
EXPECT_DEATH(Ident(StrChr(str, 'a')), RightOOBReadMessage(0));
|
||||
free(str);
|
||||
}
|
||||
|
||||
@ -1198,8 +1311,9 @@ TEST(AddressSanitizer, StrCmpAndFriendsLogicTest) {
|
||||
typedef int(*PointerToStrCmp)(const char*, const char*);
|
||||
void RunStrCmpTest(PointerToStrCmp StrCmp) {
|
||||
size_t size = Ident(100);
|
||||
char *s1 = MallocAndMemsetString(size);
|
||||
char *s2 = MallocAndMemsetString(size);
|
||||
int fill = 'o';
|
||||
char *s1 = MallocAndMemsetString(size, fill);
|
||||
char *s2 = MallocAndMemsetString(size, fill);
|
||||
s1[size - 1] = '\0';
|
||||
s2[size - 1] = '\0';
|
||||
// Normal StrCmp calls
|
||||
@ -1210,14 +1324,14 @@ void RunStrCmpTest(PointerToStrCmp StrCmp) {
|
||||
s2[size - 1] = 'x';
|
||||
Ident(StrCmp(s1, s2));
|
||||
// One of arguments points to not allocated memory.
|
||||
EXPECT_DEATH(Ident(StrCmp)(s1 - 1, s2), LeftOOBErrorMessage(1));
|
||||
EXPECT_DEATH(Ident(StrCmp)(s1, s2 - 1), LeftOOBErrorMessage(1));
|
||||
EXPECT_DEATH(Ident(StrCmp)(s1 + size, s2), RightOOBErrorMessage(0));
|
||||
EXPECT_DEATH(Ident(StrCmp)(s1, s2 + size), RightOOBErrorMessage(0));
|
||||
EXPECT_DEATH(Ident(StrCmp)(s1 - 1, s2), LeftOOBReadMessage(1));
|
||||
EXPECT_DEATH(Ident(StrCmp)(s1, s2 - 1), LeftOOBReadMessage(1));
|
||||
EXPECT_DEATH(Ident(StrCmp)(s1 + size, s2), RightOOBReadMessage(0));
|
||||
EXPECT_DEATH(Ident(StrCmp)(s1, s2 + size), RightOOBReadMessage(0));
|
||||
// Hit unallocated memory and die.
|
||||
s2[size - 1] = 'z';
|
||||
EXPECT_DEATH(Ident(StrCmp)(s1, s1), RightOOBErrorMessage(0));
|
||||
EXPECT_DEATH(Ident(StrCmp)(s1 + size - 1, s2), RightOOBErrorMessage(0));
|
||||
s1[size - 1] = fill;
|
||||
EXPECT_DEATH(Ident(StrCmp)(s1, s1), RightOOBReadMessage(0));
|
||||
EXPECT_DEATH(Ident(StrCmp)(s1 + size - 1, s2), RightOOBReadMessage(0));
|
||||
free(s1);
|
||||
free(s2);
|
||||
}
|
||||
@ -1246,13 +1360,13 @@ void RunStrNCmpTest(PointerToStrNCmp StrNCmp) {
|
||||
Ident(StrNCmp(s1 - 1, s2 - 1, 0));
|
||||
Ident(StrNCmp(s1 + size - 1, s2 + size - 1, 1));
|
||||
// One of arguments points to not allocated memory.
|
||||
EXPECT_DEATH(Ident(StrNCmp)(s1 - 1, s2, 1), LeftOOBErrorMessage(1));
|
||||
EXPECT_DEATH(Ident(StrNCmp)(s1, s2 - 1, 1), LeftOOBErrorMessage(1));
|
||||
EXPECT_DEATH(Ident(StrNCmp)(s1 + size, s2, 1), RightOOBErrorMessage(0));
|
||||
EXPECT_DEATH(Ident(StrNCmp)(s1, s2 + size, 1), RightOOBErrorMessage(0));
|
||||
EXPECT_DEATH(Ident(StrNCmp)(s1 - 1, s2, 1), LeftOOBReadMessage(1));
|
||||
EXPECT_DEATH(Ident(StrNCmp)(s1, s2 - 1, 1), LeftOOBReadMessage(1));
|
||||
EXPECT_DEATH(Ident(StrNCmp)(s1 + size, s2, 1), RightOOBReadMessage(0));
|
||||
EXPECT_DEATH(Ident(StrNCmp)(s1, s2 + size, 1), RightOOBReadMessage(0));
|
||||
// Hit unallocated memory and die.
|
||||
EXPECT_DEATH(Ident(StrNCmp)(s1 + 1, s2 + 1, size), RightOOBErrorMessage(0));
|
||||
EXPECT_DEATH(Ident(StrNCmp)(s1 + size - 1, s2, 2), RightOOBErrorMessage(0));
|
||||
EXPECT_DEATH(Ident(StrNCmp)(s1 + 1, s2 + 1, size), RightOOBReadMessage(0));
|
||||
EXPECT_DEATH(Ident(StrNCmp)(s1 + size - 1, s2, 2), RightOOBReadMessage(0));
|
||||
free(s1);
|
||||
free(s2);
|
||||
}
|
||||
@ -1274,22 +1388,23 @@ TEST(AddressSanitizer, MemCmpOOBTest) {
|
||||
Ident(memcmp(s1 + size - 1, s2 + size - 1, 1));
|
||||
Ident(memcmp(s1 - 1, s2 - 1, 0));
|
||||
// One of arguments points to not allocated memory.
|
||||
EXPECT_DEATH(Ident(memcmp)(s1 - 1, s2, 1), LeftOOBErrorMessage(1));
|
||||
EXPECT_DEATH(Ident(memcmp)(s1, s2 - 1, 1), LeftOOBErrorMessage(1));
|
||||
EXPECT_DEATH(Ident(memcmp)(s1 + size, s2, 1), RightOOBErrorMessage(0));
|
||||
EXPECT_DEATH(Ident(memcmp)(s1, s2 + size, 1), RightOOBErrorMessage(0));
|
||||
EXPECT_DEATH(Ident(memcmp)(s1 - 1, s2, 1), LeftOOBReadMessage(1));
|
||||
EXPECT_DEATH(Ident(memcmp)(s1, s2 - 1, 1), LeftOOBReadMessage(1));
|
||||
EXPECT_DEATH(Ident(memcmp)(s1 + size, s2, 1), RightOOBReadMessage(0));
|
||||
EXPECT_DEATH(Ident(memcmp)(s1, s2 + size, 1), RightOOBReadMessage(0));
|
||||
// Hit unallocated memory and die.
|
||||
EXPECT_DEATH(Ident(memcmp)(s1 + 1, s2 + 1, size), RightOOBErrorMessage(0));
|
||||
EXPECT_DEATH(Ident(memcmp)(s1 + size - 1, s2, 2), RightOOBErrorMessage(0));
|
||||
EXPECT_DEATH(Ident(memcmp)(s1 + 1, s2 + 1, size), RightOOBReadMessage(0));
|
||||
EXPECT_DEATH(Ident(memcmp)(s1 + size - 1, s2, 2), RightOOBReadMessage(0));
|
||||
// Zero bytes are not terminators and don't prevent from OOB.
|
||||
s1[size - 1] = '\0';
|
||||
s2[size - 1] = '\0';
|
||||
EXPECT_DEATH(Ident(memcmp)(s1, s2, size + 1), RightOOBErrorMessage(0));
|
||||
EXPECT_DEATH(Ident(memcmp)(s1, s2, size + 1), RightOOBReadMessage(0));
|
||||
free(s1);
|
||||
free(s2);
|
||||
}
|
||||
|
||||
TEST(AddressSanitizer, StrCatOOBTest) {
|
||||
// strcat() reads strlen(to) bytes from |to| before concatenating.
|
||||
size_t to_size = Ident(100);
|
||||
char *to = MallocAndMemsetString(to_size);
|
||||
to[0] = '\0';
|
||||
@ -1302,23 +1417,23 @@ TEST(AddressSanitizer, StrCatOOBTest) {
|
||||
strcat(to + from_size, from + from_size - 2);
|
||||
// Passing an invalid pointer is an error even when concatenating an empty
|
||||
// string.
|
||||
EXPECT_DEATH(strcat(to - 1, from + from_size - 1), LeftOOBErrorMessage(1));
|
||||
EXPECT_DEATH(strcat(to - 1, from + from_size - 1), LeftOOBAccessMessage(1));
|
||||
// One of arguments points to not allocated memory.
|
||||
EXPECT_DEATH(strcat(to - 1, from), LeftOOBErrorMessage(1));
|
||||
EXPECT_DEATH(strcat(to, from - 1), LeftOOBErrorMessage(1));
|
||||
EXPECT_DEATH(strcat(to + to_size, from), RightOOBErrorMessage(0));
|
||||
EXPECT_DEATH(strcat(to, from + from_size), RightOOBErrorMessage(0));
|
||||
EXPECT_DEATH(strcat(to - 1, from), LeftOOBAccessMessage(1));
|
||||
EXPECT_DEATH(strcat(to, from - 1), LeftOOBReadMessage(1));
|
||||
EXPECT_DEATH(strcat(to + to_size, from), RightOOBWriteMessage(0));
|
||||
EXPECT_DEATH(strcat(to, from + from_size), RightOOBReadMessage(0));
|
||||
|
||||
// "from" is not zero-terminated.
|
||||
from[from_size - 1] = 'z';
|
||||
EXPECT_DEATH(strcat(to, from), RightOOBErrorMessage(0));
|
||||
EXPECT_DEATH(strcat(to, from), RightOOBReadMessage(0));
|
||||
from[from_size - 1] = '\0';
|
||||
// "to" is not zero-terminated.
|
||||
memset(to, 'z', to_size);
|
||||
EXPECT_DEATH(strcat(to, from), RightOOBErrorMessage(0));
|
||||
EXPECT_DEATH(strcat(to, from), RightOOBWriteMessage(0));
|
||||
// "to" is too short to fit "from".
|
||||
to[to_size - from_size + 1] = '\0';
|
||||
EXPECT_DEATH(strcat(to, from), RightOOBErrorMessage(0));
|
||||
EXPECT_DEATH(strcat(to, from), RightOOBWriteMessage(0));
|
||||
// length of "to" is just enough.
|
||||
strcat(to, from + 1);
|
||||
|
||||
@ -1327,6 +1442,7 @@ TEST(AddressSanitizer, StrCatOOBTest) {
|
||||
}
|
||||
|
||||
TEST(AddressSanitizer, StrNCatOOBTest) {
|
||||
// strncat() reads strlen(to) bytes from |to| before concatenating.
|
||||
size_t to_size = Ident(100);
|
||||
char *to = MallocAndMemsetString(to_size);
|
||||
to[0] = '\0';
|
||||
@ -1338,25 +1454,25 @@ TEST(AddressSanitizer, StrNCatOOBTest) {
|
||||
from[from_size - 1] = '\0';
|
||||
strncat(to, from, 2 * from_size);
|
||||
// Catenating empty string with an invalid string is still an error.
|
||||
EXPECT_DEATH(strncat(to - 1, from, 0), LeftOOBErrorMessage(1));
|
||||
EXPECT_DEATH(strncat(to - 1, from, 0), LeftOOBAccessMessage(1));
|
||||
strncat(to, from + from_size - 1, 10);
|
||||
// One of arguments points to not allocated memory.
|
||||
EXPECT_DEATH(strncat(to - 1, from, 2), LeftOOBErrorMessage(1));
|
||||
EXPECT_DEATH(strncat(to, from - 1, 2), LeftOOBErrorMessage(1));
|
||||
EXPECT_DEATH(strncat(to + to_size, from, 2), RightOOBErrorMessage(0));
|
||||
EXPECT_DEATH(strncat(to, from + from_size, 2), RightOOBErrorMessage(0));
|
||||
EXPECT_DEATH(strncat(to - 1, from, 2), LeftOOBAccessMessage(1));
|
||||
EXPECT_DEATH(strncat(to, from - 1, 2), LeftOOBReadMessage(1));
|
||||
EXPECT_DEATH(strncat(to + to_size, from, 2), RightOOBWriteMessage(0));
|
||||
EXPECT_DEATH(strncat(to, from + from_size, 2), RightOOBReadMessage(0));
|
||||
|
||||
memset(from, 'z', from_size);
|
||||
memset(to, 'z', to_size);
|
||||
to[0] = '\0';
|
||||
// "from" is too short.
|
||||
EXPECT_DEATH(strncat(to, from, from_size + 1), RightOOBErrorMessage(0));
|
||||
EXPECT_DEATH(strncat(to, from, from_size + 1), RightOOBReadMessage(0));
|
||||
// "to" is not zero-terminated.
|
||||
EXPECT_DEATH(strncat(to + 1, from, 1), RightOOBErrorMessage(0));
|
||||
EXPECT_DEATH(strncat(to + 1, from, 1), RightOOBWriteMessage(0));
|
||||
// "to" is too short to fit "from".
|
||||
to[0] = 'z';
|
||||
to[to_size - from_size + 1] = '\0';
|
||||
EXPECT_DEATH(strncat(to, from, from_size - 1), RightOOBErrorMessage(0));
|
||||
EXPECT_DEATH(strncat(to, from, from_size - 1), RightOOBWriteMessage(0));
|
||||
// "to" is just enough.
|
||||
strncat(to, from, from_size - 2);
|
||||
|
||||
@ -1447,10 +1563,10 @@ typedef void(*PointerToCallAtoi)(const char*);
|
||||
void RunAtoiOOBTest(PointerToCallAtoi Atoi) {
|
||||
char *array = MallocAndMemsetString(10, '1');
|
||||
// Invalid pointer to the string.
|
||||
EXPECT_DEATH(Atoi(array + 11), RightOOBErrorMessage(1));
|
||||
EXPECT_DEATH(Atoi(array - 1), LeftOOBErrorMessage(1));
|
||||
EXPECT_DEATH(Atoi(array + 11), RightOOBReadMessage(1));
|
||||
EXPECT_DEATH(Atoi(array - 1), LeftOOBReadMessage(1));
|
||||
// Die if a buffer doesn't have terminating NULL.
|
||||
EXPECT_DEATH(Atoi(array), RightOOBErrorMessage(0));
|
||||
EXPECT_DEATH(Atoi(array), RightOOBReadMessage(0));
|
||||
// Make last symbol a terminating NULL or other non-digit.
|
||||
array[9] = '\0';
|
||||
Atoi(array);
|
||||
@ -1459,13 +1575,13 @@ void RunAtoiOOBTest(PointerToCallAtoi Atoi) {
|
||||
Atoi(array + 9);
|
||||
// Sometimes we need to detect overflow if no digits are found.
|
||||
memset(array, ' ', 10);
|
||||
EXPECT_DEATH(Atoi(array), RightOOBErrorMessage(0));
|
||||
EXPECT_DEATH(Atoi(array), RightOOBReadMessage(0));
|
||||
array[9] = '-';
|
||||
EXPECT_DEATH(Atoi(array), RightOOBErrorMessage(0));
|
||||
EXPECT_DEATH(Atoi(array + 9), RightOOBErrorMessage(0));
|
||||
EXPECT_DEATH(Atoi(array), RightOOBReadMessage(0));
|
||||
EXPECT_DEATH(Atoi(array + 9), RightOOBReadMessage(0));
|
||||
array[8] = '-';
|
||||
Atoi(array);
|
||||
delete array;
|
||||
free(array);
|
||||
}
|
||||
|
||||
TEST(AddressSanitizer, AtoiAndFriendsOOBTest) {
|
||||
@ -1489,16 +1605,16 @@ void RunStrtolOOBTest(PointerToCallStrtol Strtol) {
|
||||
array[1] = '2';
|
||||
array[2] = '3';
|
||||
// Invalid pointer to the string.
|
||||
EXPECT_DEATH(Strtol(array + 3, NULL, 0), RightOOBErrorMessage(0));
|
||||
EXPECT_DEATH(Strtol(array - 1, NULL, 0), LeftOOBErrorMessage(1));
|
||||
EXPECT_DEATH(Strtol(array + 3, NULL, 0), RightOOBReadMessage(0));
|
||||
EXPECT_DEATH(Strtol(array - 1, NULL, 0), LeftOOBReadMessage(1));
|
||||
// Buffer overflow if there is no terminating null (depends on base).
|
||||
Strtol(array, &endptr, 3);
|
||||
EXPECT_EQ(array + 2, endptr);
|
||||
EXPECT_DEATH(Strtol(array, NULL, 0), RightOOBErrorMessage(0));
|
||||
EXPECT_DEATH(Strtol(array, NULL, 0), RightOOBReadMessage(0));
|
||||
array[2] = 'z';
|
||||
Strtol(array, &endptr, 35);
|
||||
EXPECT_EQ(array + 2, endptr);
|
||||
EXPECT_DEATH(Strtol(array, NULL, 36), RightOOBErrorMessage(0));
|
||||
EXPECT_DEATH(Strtol(array, NULL, 36), RightOOBReadMessage(0));
|
||||
// Add terminating zero to get rid of overflow.
|
||||
array[2] = '\0';
|
||||
Strtol(array, NULL, 36);
|
||||
@ -1507,11 +1623,11 @@ void RunStrtolOOBTest(PointerToCallStrtol Strtol) {
|
||||
Strtol(array + 3, NULL, 1);
|
||||
// Sometimes we need to detect overflow if no digits are found.
|
||||
array[0] = array[1] = array[2] = ' ';
|
||||
EXPECT_DEATH(Strtol(array, NULL, 0), RightOOBErrorMessage(0));
|
||||
EXPECT_DEATH(Strtol(array, NULL, 0), RightOOBReadMessage(0));
|
||||
array[2] = '+';
|
||||
EXPECT_DEATH(Strtol(array, NULL, 0), RightOOBErrorMessage(0));
|
||||
EXPECT_DEATH(Strtol(array, NULL, 0), RightOOBReadMessage(0));
|
||||
array[2] = '-';
|
||||
EXPECT_DEATH(Strtol(array, NULL, 0), RightOOBErrorMessage(0));
|
||||
EXPECT_DEATH(Strtol(array, NULL, 0), RightOOBReadMessage(0));
|
||||
array[1] = '+';
|
||||
Strtol(array, NULL, 0);
|
||||
array[1] = array[2] = 'z';
|
||||
@ -1519,7 +1635,7 @@ void RunStrtolOOBTest(PointerToCallStrtol Strtol) {
|
||||
EXPECT_EQ(array, endptr);
|
||||
Strtol(array + 2, NULL, 0);
|
||||
EXPECT_EQ(array, endptr);
|
||||
delete array;
|
||||
free(array);
|
||||
}
|
||||
|
||||
TEST(AddressSanitizer, StrtollOOBTest) {
|
||||
@ -1538,7 +1654,7 @@ typedef void*(*PointerToMemSet)(void*, int, size_t);
|
||||
void CallMemSetByPointer(PointerToMemSet MemSet) {
|
||||
size_t size = Ident(100);
|
||||
char *array = Ident((char*)malloc(size));
|
||||
EXPECT_DEATH(MemSet(array, 0, 101), RightOOBErrorMessage(0));
|
||||
EXPECT_DEATH(MemSet(array, 0, 101), RightOOBWriteMessage(0));
|
||||
free(array);
|
||||
}
|
||||
|
||||
@ -1546,7 +1662,7 @@ void CallMemTransferByPointer(PointerToMemTransfer MemTransfer) {
|
||||
size_t size = Ident(100);
|
||||
char *src = Ident((char*)malloc(size));
|
||||
char *dst = Ident((char*)malloc(size));
|
||||
EXPECT_DEATH(MemTransfer(dst, src, 101), RightOOBErrorMessage(0));
|
||||
EXPECT_DEATH(MemTransfer(dst, src, 101), RightOOBWriteMessage(0));
|
||||
free(src);
|
||||
free(dst);
|
||||
}
|
||||
@ -1557,12 +1673,51 @@ TEST(AddressSanitizer, DISABLED_MemIntrinsicCallByPointerTest) {
|
||||
CallMemTransferByPointer(&memmove);
|
||||
}
|
||||
|
||||
#if defined(__linux__) && !defined(ANDROID) && !defined(__ANDROID__)
|
||||
TEST(AddressSanitizer, pread) {
|
||||
char *x = new char[10];
|
||||
int fd = open("/proc/self/stat", O_RDONLY);
|
||||
ASSERT_GT(fd, 0);
|
||||
EXPECT_DEATH(pread(fd, x, 15, 0),
|
||||
ASAN_PCRE_DOTALL
|
||||
"AddressSanitizer: heap-buffer-overflow"
|
||||
".* is located 0 bytes to the right of 10-byte region");
|
||||
close(fd);
|
||||
delete [] x;
|
||||
}
|
||||
|
||||
TEST(AddressSanitizer, pread64) {
|
||||
char *x = new char[10];
|
||||
int fd = open("/proc/self/stat", O_RDONLY);
|
||||
ASSERT_GT(fd, 0);
|
||||
EXPECT_DEATH(pread64(fd, x, 15, 0),
|
||||
ASAN_PCRE_DOTALL
|
||||
"AddressSanitizer: heap-buffer-overflow"
|
||||
".* is located 0 bytes to the right of 10-byte region");
|
||||
close(fd);
|
||||
delete [] x;
|
||||
}
|
||||
|
||||
TEST(AddressSanitizer, read) {
|
||||
char *x = new char[10];
|
||||
int fd = open("/proc/self/stat", O_RDONLY);
|
||||
ASSERT_GT(fd, 0);
|
||||
EXPECT_DEATH(read(fd, x, 15),
|
||||
ASAN_PCRE_DOTALL
|
||||
"AddressSanitizer: heap-buffer-overflow"
|
||||
".* is located 0 bytes to the right of 10-byte region");
|
||||
close(fd);
|
||||
delete [] x;
|
||||
}
|
||||
|
||||
#endif // defined(__linux__) && !defined(ANDROID) && !defined(__ANDROID__)
|
||||
|
||||
// This test case fails
|
||||
// Clang optimizes memcpy/memset calls which lead to unaligned access
|
||||
TEST(AddressSanitizer, DISABLED_MemIntrinsicUnalignedAccessTest) {
|
||||
int size = Ident(4096);
|
||||
char *s = Ident((char*)malloc(size));
|
||||
EXPECT_DEATH(memset(s + size - 1, 0, 2), RightOOBErrorMessage(0));
|
||||
EXPECT_DEATH(memset(s + size - 1, 0, 2), RightOOBWriteMessage(0));
|
||||
free(s);
|
||||
}
|
||||
|
||||
@ -1617,19 +1772,30 @@ TEST(AddressSanitizer, DISABLED_MallocFreeUnwindAndSymbolizeTest) {
|
||||
"malloc_fff.*malloc_eee.*malloc_ddd");
|
||||
}
|
||||
|
||||
static bool TryToSetThreadName(const char *name) {
|
||||
#if defined(__linux__) && defined(PR_SET_NAME)
|
||||
return 0 == prctl(PR_SET_NAME, (unsigned long)name, 0, 0, 0);
|
||||
#else
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
void *ThreadedTestAlloc(void *a) {
|
||||
EXPECT_EQ(true, TryToSetThreadName("AllocThr"));
|
||||
int **p = (int**)a;
|
||||
*p = new int;
|
||||
return 0;
|
||||
}
|
||||
|
||||
void *ThreadedTestFree(void *a) {
|
||||
EXPECT_EQ(true, TryToSetThreadName("FreeThr"));
|
||||
int **p = (int**)a;
|
||||
delete *p;
|
||||
return 0;
|
||||
}
|
||||
|
||||
void *ThreadedTestUse(void *a) {
|
||||
EXPECT_EQ(true, TryToSetThreadName("UseThr"));
|
||||
int **p = (int**)a;
|
||||
**p = 1;
|
||||
return 0;
|
||||
@ -1654,6 +1820,30 @@ TEST(AddressSanitizer, ThreadedTest) {
|
||||
".*Thread T.*created");
|
||||
}
|
||||
|
||||
void *ThreadedTestFunc(void *unused) {
|
||||
// Check if prctl(PR_SET_NAME) is supported. Return if not.
|
||||
if (!TryToSetThreadName("TestFunc"))
|
||||
return 0;
|
||||
EXPECT_DEATH(ThreadedTestSpawn(),
|
||||
ASAN_PCRE_DOTALL
|
||||
"WRITE .*thread T. .UseThr."
|
||||
".*freed by thread T. .FreeThr. here:"
|
||||
".*previously allocated by thread T. .AllocThr. here:"
|
||||
".*Thread T. .UseThr. created by T.*TestFunc"
|
||||
".*Thread T. .FreeThr. created by T"
|
||||
".*Thread T. .AllocThr. created by T"
|
||||
"");
|
||||
return 0;
|
||||
}
|
||||
|
||||
TEST(AddressSanitizer, ThreadNamesTest) {
|
||||
// Run ThreadedTestFunc in a separate thread because it tries to set a
|
||||
// thread name and we don't want to change the main thread's name.
|
||||
pthread_t t;
|
||||
PTHREAD_CREATE(&t, 0, ThreadedTestFunc, 0);
|
||||
PTHREAD_JOIN(t, 0);
|
||||
}
|
||||
|
||||
#if ASAN_NEEDS_SEGV
|
||||
TEST(AddressSanitizer, ShadowGapTest) {
|
||||
#if SANITIZER_WORDSIZE == 32
|
||||
@ -1868,6 +2058,27 @@ TEST(AddressSanitizer, AttributeNoAddressSafetyTest) {
|
||||
Ident(NoAddressSafety)();
|
||||
}
|
||||
|
||||
static string MismatchStr(const string &str) {
|
||||
return string("AddressSanitizer: alloc-dealloc-mismatch \\(") + str;
|
||||
}
|
||||
|
||||
// This test is disabled until we enable alloc_dealloc_mismatch by default.
|
||||
// The feature is also tested by lit tests.
|
||||
TEST(AddressSanitizer, DISABLED_AllocDeallocMismatch) {
|
||||
EXPECT_DEATH(free(Ident(new int)),
|
||||
MismatchStr("operator new vs free"));
|
||||
EXPECT_DEATH(free(Ident(new int[2])),
|
||||
MismatchStr("operator new \\[\\] vs free"));
|
||||
EXPECT_DEATH(delete (Ident(new int[2])),
|
||||
MismatchStr("operator new \\[\\] vs operator delete"));
|
||||
EXPECT_DEATH(delete (Ident((int*)malloc(2 * sizeof(int)))),
|
||||
MismatchStr("malloc vs operator delete"));
|
||||
EXPECT_DEATH(delete [] (Ident(new int)),
|
||||
MismatchStr("operator new vs operator delete \\[\\]"));
|
||||
EXPECT_DEATH(delete [] (Ident((int*)malloc(2 * sizeof(int)))),
|
||||
MismatchStr("malloc vs operator delete \\[\\]"));
|
||||
}
|
||||
|
||||
// ------------------ demo tests; run each one-by-one -------------
|
||||
// e.g. --gtest_filter=*DemoOOBLeftHigh --gtest_also_run_disabled_tests
|
||||
TEST(AddressSanitizer, DISABLED_DemoThreadedTest) {
|
||||
@ -2033,53 +2244,56 @@ TEST(AddressSanitizerMac, DISABLED_CFAllocatorMallocZoneDoubleFree) {
|
||||
EXPECT_DEATH(CFAllocatorMallocZoneDoubleFree(), "attempting double-free");
|
||||
}
|
||||
|
||||
// For libdispatch tests below we check that ASan got to the shadow byte
|
||||
// legend, i.e. managed to print the thread stacks (this almost certainly
|
||||
// means that the libdispatch task creation has been intercepted correctly).
|
||||
TEST(AddressSanitizerMac, GCDDispatchAsync) {
|
||||
// Make sure the whole ASan report is printed, i.e. that we don't die
|
||||
// on a CHECK.
|
||||
EXPECT_DEATH(TestGCDDispatchAsync(), "Shadow byte and word");
|
||||
EXPECT_DEATH(TestGCDDispatchAsync(), "Shadow byte legend");
|
||||
}
|
||||
|
||||
TEST(AddressSanitizerMac, GCDDispatchSync) {
|
||||
// Make sure the whole ASan report is printed, i.e. that we don't die
|
||||
// on a CHECK.
|
||||
EXPECT_DEATH(TestGCDDispatchSync(), "Shadow byte and word");
|
||||
EXPECT_DEATH(TestGCDDispatchSync(), "Shadow byte legend");
|
||||
}
|
||||
|
||||
|
||||
TEST(AddressSanitizerMac, GCDReuseWqthreadsAsync) {
|
||||
// Make sure the whole ASan report is printed, i.e. that we don't die
|
||||
// on a CHECK.
|
||||
EXPECT_DEATH(TestGCDReuseWqthreadsAsync(), "Shadow byte and word");
|
||||
EXPECT_DEATH(TestGCDReuseWqthreadsAsync(), "Shadow byte legend");
|
||||
}
|
||||
|
||||
TEST(AddressSanitizerMac, GCDReuseWqthreadsSync) {
|
||||
// Make sure the whole ASan report is printed, i.e. that we don't die
|
||||
// on a CHECK.
|
||||
EXPECT_DEATH(TestGCDReuseWqthreadsSync(), "Shadow byte and word");
|
||||
EXPECT_DEATH(TestGCDReuseWqthreadsSync(), "Shadow byte legend");
|
||||
}
|
||||
|
||||
TEST(AddressSanitizerMac, GCDDispatchAfter) {
|
||||
// Make sure the whole ASan report is printed, i.e. that we don't die
|
||||
// on a CHECK.
|
||||
EXPECT_DEATH(TestGCDDispatchAfter(), "Shadow byte and word");
|
||||
EXPECT_DEATH(TestGCDDispatchAfter(), "Shadow byte legend");
|
||||
}
|
||||
|
||||
TEST(AddressSanitizerMac, GCDSourceEvent) {
|
||||
// Make sure the whole ASan report is printed, i.e. that we don't die
|
||||
// on a CHECK.
|
||||
EXPECT_DEATH(TestGCDSourceEvent(), "Shadow byte and word");
|
||||
EXPECT_DEATH(TestGCDSourceEvent(), "Shadow byte legend");
|
||||
}
|
||||
|
||||
TEST(AddressSanitizerMac, GCDSourceCancel) {
|
||||
// Make sure the whole ASan report is printed, i.e. that we don't die
|
||||
// on a CHECK.
|
||||
EXPECT_DEATH(TestGCDSourceCancel(), "Shadow byte and word");
|
||||
EXPECT_DEATH(TestGCDSourceCancel(), "Shadow byte legend");
|
||||
}
|
||||
|
||||
TEST(AddressSanitizerMac, GCDGroupAsync) {
|
||||
// Make sure the whole ASan report is printed, i.e. that we don't die
|
||||
// on a CHECK.
|
||||
EXPECT_DEATH(TestGCDGroupAsync(), "Shadow byte and word");
|
||||
EXPECT_DEATH(TestGCDGroupAsync(), "Shadow byte legend");
|
||||
}
|
||||
|
||||
void *MallocIntrospectionLockWorker(void *_) {
|
||||
@ -2172,7 +2386,7 @@ TEST(AddressSanitizerMac, NSURLDeallocation) {
|
||||
TEST(AddressSanitizerMac, Mstats) {
|
||||
malloc_statistics_t stats1, stats2;
|
||||
malloc_zone_statistics(/*all zones*/NULL, &stats1);
|
||||
const int kMallocSize = 100000;
|
||||
const size_t kMallocSize = 100000;
|
||||
void *alloc = Ident(malloc(kMallocSize));
|
||||
malloc_zone_statistics(/*all zones*/NULL, &stats2);
|
||||
EXPECT_GT(stats2.blocks_in_use, stats1.blocks_in_use);
|
||||
|
@ -1,3 +1,13 @@
|
||||
2013-01-10 Kostya Serebryany <kcc@google.com>
|
||||
|
||||
* All source files: Merge from upstream r171973.
|
||||
* sanitizer_common/Makefile.am: Added new files.
|
||||
* asan/Makefile.am: Likewise.
|
||||
* tsan/Makefile.am: Likewise.
|
||||
* sanitizer_common/Makefile.in: Regenerated.
|
||||
* asan/Makefile.in: Likewise.
|
||||
* tsan/Makefile.in: Likewise.
|
||||
|
||||
2013-01-07 H.J. Lu <hongjiu.lu@intel.com>
|
||||
|
||||
* asan/Makefile.am (libasan_la_LIBADD): Replace
|
||||
|
@ -1,4 +1,4 @@
|
||||
169392
|
||||
171973
|
||||
|
||||
The first line of this file holds the svn revision number of the
|
||||
last merge done from the master library sources.
|
||||
|
@ -15,6 +15,7 @@ toolexeclib_LTLIBRARIES = libasan.la
|
||||
|
||||
asan_files = \
|
||||
asan_allocator.cc \
|
||||
asan_allocator2.cc \
|
||||
asan_interceptors.cc \
|
||||
asan_mac.cc \
|
||||
asan_malloc_mac.cc \
|
||||
@ -23,6 +24,7 @@ asan_files = \
|
||||
asan_rtl.cc \
|
||||
asan_stats.cc \
|
||||
asan_thread_registry.cc \
|
||||
asan_fake_stack.cc \
|
||||
asan_globals.cc \
|
||||
asan_linux.cc \
|
||||
asan_malloc_linux.cc \
|
||||
|
@ -84,19 +84,20 @@ am__DEPENDENCIES_1 =
|
||||
@USING_MAC_INTERPOSE_FALSE@ $(am__DEPENDENCIES_1)
|
||||
@USING_MAC_INTERPOSE_TRUE@libasan_la_DEPENDENCIES = $(top_builddir)/sanitizer_common/libsanitizer_common.la \
|
||||
@USING_MAC_INTERPOSE_TRUE@ $(am__DEPENDENCIES_1)
|
||||
am__libasan_la_SOURCES_DIST = asan_allocator.cc asan_interceptors.cc \
|
||||
asan_mac.cc asan_malloc_mac.cc asan_new_delete.cc \
|
||||
asan_posix.cc asan_rtl.cc asan_stats.cc \
|
||||
asan_thread_registry.cc asan_globals.cc asan_linux.cc \
|
||||
asan_malloc_linux.cc asan_malloc_win.cc asan_poisoning.cc \
|
||||
asan_report.cc asan_stack.cc asan_thread.cc asan_win.cc \
|
||||
dynamic/asan_interceptors_dynamic.cc
|
||||
am__objects_1 = asan_allocator.lo asan_interceptors.lo asan_mac.lo \
|
||||
asan_malloc_mac.lo asan_new_delete.lo asan_posix.lo \
|
||||
asan_rtl.lo asan_stats.lo asan_thread_registry.lo \
|
||||
asan_globals.lo asan_linux.lo asan_malloc_linux.lo \
|
||||
asan_malloc_win.lo asan_poisoning.lo asan_report.lo \
|
||||
asan_stack.lo asan_thread.lo asan_win.lo
|
||||
am__libasan_la_SOURCES_DIST = asan_allocator.cc asan_allocator2.cc \
|
||||
asan_interceptors.cc asan_mac.cc asan_malloc_mac.cc \
|
||||
asan_new_delete.cc asan_posix.cc asan_rtl.cc asan_stats.cc \
|
||||
asan_thread_registry.cc asan_fake_stack.cc asan_globals.cc \
|
||||
asan_linux.cc asan_malloc_linux.cc asan_malloc_win.cc \
|
||||
asan_poisoning.cc asan_report.cc asan_stack.cc asan_thread.cc \
|
||||
asan_win.cc dynamic/asan_interceptors_dynamic.cc
|
||||
am__objects_1 = asan_allocator.lo asan_allocator2.lo \
|
||||
asan_interceptors.lo asan_mac.lo asan_malloc_mac.lo \
|
||||
asan_new_delete.lo asan_posix.lo asan_rtl.lo asan_stats.lo \
|
||||
asan_thread_registry.lo asan_fake_stack.lo asan_globals.lo \
|
||||
asan_linux.lo asan_malloc_linux.lo asan_malloc_win.lo \
|
||||
asan_poisoning.lo asan_report.lo asan_stack.lo asan_thread.lo \
|
||||
asan_win.lo
|
||||
@USING_MAC_INTERPOSE_TRUE@am__objects_2 = \
|
||||
@USING_MAC_INTERPOSE_TRUE@ asan_interceptors_dynamic.lo
|
||||
am_libasan_la_OBJECTS = $(am__objects_1) $(am__objects_2)
|
||||
@ -269,6 +270,7 @@ ACLOCAL_AMFLAGS = -I $(top_srcdir) -I $(top_srcdir)/config
|
||||
toolexeclib_LTLIBRARIES = libasan.la
|
||||
asan_files = \
|
||||
asan_allocator.cc \
|
||||
asan_allocator2.cc \
|
||||
asan_interceptors.cc \
|
||||
asan_mac.cc \
|
||||
asan_malloc_mac.cc \
|
||||
@ -277,6 +279,7 @@ asan_files = \
|
||||
asan_rtl.cc \
|
||||
asan_stats.cc \
|
||||
asan_thread_registry.cc \
|
||||
asan_fake_stack.cc \
|
||||
asan_globals.cc \
|
||||
asan_linux.cc \
|
||||
asan_malloc_linux.cc \
|
||||
@ -409,6 +412,8 @@ distclean-compile:
|
||||
-rm -f *.tab.c
|
||||
|
||||
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/asan_allocator.Plo@am__quote@
|
||||
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/asan_allocator2.Plo@am__quote@
|
||||
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/asan_fake_stack.Plo@am__quote@
|
||||
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/asan_globals.Plo@am__quote@
|
||||
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/asan_interceptors.Plo@am__quote@
|
||||
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/asan_interceptors_dynamic.Plo@am__quote@
|
||||
|
@ -22,8 +22,9 @@
|
||||
// Once freed, the body of the chunk contains the stack trace of the free call.
|
||||
//
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
#include "asan_allocator.h"
|
||||
|
||||
#if ASAN_ALLOCATOR_VERSION == 1
|
||||
#include "asan_interceptors.h"
|
||||
#include "asan_internal.h"
|
||||
#include "asan_lock.h"
|
||||
@ -35,10 +36,6 @@
|
||||
#include "sanitizer/asan_interface.h"
|
||||
#include "sanitizer_common/sanitizer_atomic.h"
|
||||
|
||||
#if defined(_WIN32) && !defined(__clang__)
|
||||
#include <intrin.h>
|
||||
#endif
|
||||
|
||||
namespace __asan {
|
||||
|
||||
#define REDZONE ((uptr)(flags()->redzone))
|
||||
@ -58,42 +55,6 @@ static const uptr kMallocSizeClassStep = 1UL << kMallocSizeClassStepLog;
|
||||
static const uptr kMaxAllowedMallocSize =
|
||||
(SANITIZER_WORDSIZE == 32) ? 3UL << 30 : 8UL << 30;
|
||||
|
||||
static inline bool IsAligned(uptr a, uptr alignment) {
|
||||
return (a & (alignment - 1)) == 0;
|
||||
}
|
||||
|
||||
static inline uptr Log2(uptr x) {
|
||||
CHECK(IsPowerOfTwo(x));
|
||||
#if !defined(_WIN32) || defined(__clang__)
|
||||
return __builtin_ctzl(x);
|
||||
#elif defined(_WIN64)
|
||||
unsigned long ret; // NOLINT
|
||||
_BitScanForward64(&ret, x);
|
||||
return ret;
|
||||
#else
|
||||
unsigned long ret; // NOLINT
|
||||
_BitScanForward(&ret, x);
|
||||
return ret;
|
||||
#endif
|
||||
}
|
||||
|
||||
static inline uptr RoundUpToPowerOfTwo(uptr size) {
|
||||
CHECK(size);
|
||||
if (IsPowerOfTwo(size)) return size;
|
||||
|
||||
unsigned long up; // NOLINT
|
||||
#if !defined(_WIN32) || defined(__clang__)
|
||||
up = SANITIZER_WORDSIZE - 1 - __builtin_clzl(size);
|
||||
#elif defined(_WIN64)
|
||||
_BitScanReverse64(&up, size);
|
||||
#else
|
||||
_BitScanReverse(&up, size);
|
||||
#endif
|
||||
CHECK(size < (1ULL << (up + 1)));
|
||||
CHECK(size > (1ULL << up));
|
||||
return 1UL << (up + 1);
|
||||
}
|
||||
|
||||
static inline uptr SizeClassToSize(u8 size_class) {
|
||||
CHECK(size_class < kNumberOfSizeClasses);
|
||||
if (size_class <= kMallocSizeClassStepLog) {
|
||||
@ -165,7 +126,8 @@ struct ChunkBase {
|
||||
|
||||
// Second 8 bytes.
|
||||
uptr alignment_log : 8;
|
||||
uptr used_size : FIRST_32_SECOND_64(32, 56); // Size requested by the user.
|
||||
uptr alloc_type : 2;
|
||||
uptr used_size : FIRST_32_SECOND_64(32, 54); // Size requested by the user.
|
||||
|
||||
// This field may overlap with the user area and thus should not
|
||||
// be used while the chunk is in CHUNK_ALLOCATED state.
|
||||
@ -215,33 +177,6 @@ void AsanChunkView::GetFreeStack(StackTrace *stack) {
|
||||
chunk_->compressed_free_stack_size());
|
||||
}
|
||||
|
||||
bool AsanChunkView::AddrIsInside(uptr addr, uptr access_size, uptr *offset) {
|
||||
if (addr >= Beg() && (addr + access_size) <= End()) {
|
||||
*offset = addr - Beg();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool AsanChunkView::AddrIsAtLeft(uptr addr, uptr access_size, uptr *offset) {
|
||||
if (addr < Beg()) {
|
||||
*offset = Beg() - addr;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool AsanChunkView::AddrIsAtRight(uptr addr, uptr access_size, uptr *offset) {
|
||||
if (addr + access_size >= End()) {
|
||||
if (addr <= End())
|
||||
*offset = 0;
|
||||
else
|
||||
*offset = addr - End();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static AsanChunk *PtrToChunk(uptr ptr) {
|
||||
AsanChunk *m = (AsanChunk*)(ptr - REDZONE);
|
||||
if (m->chunk_state == CHUNK_MEMALIGN) {
|
||||
@ -252,34 +187,13 @@ static AsanChunk *PtrToChunk(uptr ptr) {
|
||||
|
||||
void AsanChunkFifoList::PushList(AsanChunkFifoList *q) {
|
||||
CHECK(q->size() > 0);
|
||||
if (last_) {
|
||||
CHECK(first_);
|
||||
CHECK(!last_->next);
|
||||
last_->next = q->first_;
|
||||
last_ = q->last_;
|
||||
} else {
|
||||
CHECK(!first_);
|
||||
last_ = q->last_;
|
||||
first_ = q->first_;
|
||||
CHECK(first_);
|
||||
}
|
||||
CHECK(last_);
|
||||
CHECK(!last_->next);
|
||||
size_ += q->size();
|
||||
append_back(q);
|
||||
q->clear();
|
||||
}
|
||||
|
||||
void AsanChunkFifoList::Push(AsanChunk *n) {
|
||||
CHECK(n->next == 0);
|
||||
if (last_) {
|
||||
CHECK(first_);
|
||||
CHECK(!last_->next);
|
||||
last_->next = n;
|
||||
last_ = n;
|
||||
} else {
|
||||
CHECK(!first_);
|
||||
last_ = first_ = n;
|
||||
}
|
||||
push_back(n);
|
||||
size_ += n->Size();
|
||||
}
|
||||
|
||||
@ -288,15 +202,9 @@ void AsanChunkFifoList::Push(AsanChunk *n) {
|
||||
// ago. Not sure if we can or want to do anything with this.
|
||||
AsanChunk *AsanChunkFifoList::Pop() {
|
||||
CHECK(first_);
|
||||
AsanChunk *res = first_;
|
||||
first_ = first_->next;
|
||||
if (first_ == 0)
|
||||
last_ = 0;
|
||||
CHECK(size_ >= res->Size());
|
||||
AsanChunk *res = front();
|
||||
size_ -= res->Size();
|
||||
if (last_) {
|
||||
CHECK(!last_->next);
|
||||
}
|
||||
pop_front();
|
||||
return res;
|
||||
}
|
||||
|
||||
@ -588,7 +496,8 @@ AsanChunkView FindHeapChunkByAddress(uptr address) {
|
||||
return AsanChunkView(malloc_info.FindChunkByAddr(address));
|
||||
}
|
||||
|
||||
static u8 *Allocate(uptr alignment, uptr size, StackTrace *stack) {
|
||||
static u8 *Allocate(uptr alignment, uptr size, StackTrace *stack,
|
||||
AllocType alloc_type) {
|
||||
__asan_init();
|
||||
CHECK(stack);
|
||||
if (size == 0) {
|
||||
@ -645,6 +554,7 @@ static u8 *Allocate(uptr alignment, uptr size, StackTrace *stack) {
|
||||
CHECK(m);
|
||||
CHECK(m->chunk_state == CHUNK_AVAILABLE);
|
||||
m->chunk_state = CHUNK_ALLOCATED;
|
||||
m->alloc_type = alloc_type;
|
||||
m->next = 0;
|
||||
CHECK(m->Size() == size_to_allocate);
|
||||
uptr addr = (uptr)m + REDZONE;
|
||||
@ -679,7 +589,7 @@ static u8 *Allocate(uptr alignment, uptr size, StackTrace *stack) {
|
||||
return (u8*)addr;
|
||||
}
|
||||
|
||||
static void Deallocate(u8 *ptr, StackTrace *stack) {
|
||||
static void Deallocate(u8 *ptr, StackTrace *stack, AllocType alloc_type) {
|
||||
if (!ptr) return;
|
||||
CHECK(stack);
|
||||
|
||||
@ -700,6 +610,9 @@ static void Deallocate(u8 *ptr, StackTrace *stack) {
|
||||
ReportFreeNotMalloced((uptr)ptr, stack);
|
||||
}
|
||||
CHECK(old_chunk_state == CHUNK_ALLOCATED);
|
||||
if (m->alloc_type != alloc_type && flags()->alloc_dealloc_mismatch)
|
||||
ReportAllocTypeMismatch((uptr)ptr, stack,
|
||||
(AllocType)m->alloc_type, (AllocType)alloc_type);
|
||||
// With REDZONE==16 m->next is in the user area, otherwise it should be 0.
|
||||
CHECK(REDZONE <= 16 || !m->next);
|
||||
CHECK(m->free_tid == kInvalidTid);
|
||||
@ -744,18 +657,19 @@ static u8 *Reallocate(u8 *old_ptr, uptr new_size,
|
||||
CHECK(m->chunk_state == CHUNK_ALLOCATED);
|
||||
uptr old_size = m->used_size;
|
||||
uptr memcpy_size = Min(new_size, old_size);
|
||||
u8 *new_ptr = Allocate(0, new_size, stack);
|
||||
u8 *new_ptr = Allocate(0, new_size, stack, FROM_MALLOC);
|
||||
if (new_ptr) {
|
||||
CHECK(REAL(memcpy) != 0);
|
||||
REAL(memcpy)(new_ptr, old_ptr, memcpy_size);
|
||||
Deallocate(old_ptr, stack);
|
||||
Deallocate(old_ptr, stack, FROM_MALLOC);
|
||||
}
|
||||
return new_ptr;
|
||||
}
|
||||
|
||||
} // namespace __asan
|
||||
|
||||
// Default (no-op) implementation of malloc hooks.
|
||||
#if !SANITIZER_SUPPORTS_WEAK_HOOKS
|
||||
// Provide default (no-op) implementation of malloc hooks.
|
||||
extern "C" {
|
||||
SANITIZER_WEAK_ATTRIBUTE SANITIZER_INTERFACE_ATTRIBUTE
|
||||
void __asan_malloc_hook(void *ptr, uptr size) {
|
||||
@ -767,53 +681,58 @@ void __asan_free_hook(void *ptr) {
|
||||
(void)ptr;
|
||||
}
|
||||
} // extern "C"
|
||||
#endif
|
||||
|
||||
namespace __asan {
|
||||
|
||||
void PrintInternalAllocatorStats() {
|
||||
}
|
||||
|
||||
SANITIZER_INTERFACE_ATTRIBUTE
|
||||
void *asan_memalign(uptr alignment, uptr size, StackTrace *stack) {
|
||||
void *ptr = (void*)Allocate(alignment, size, stack);
|
||||
__asan_malloc_hook(ptr, size);
|
||||
void *asan_memalign(uptr alignment, uptr size, StackTrace *stack,
|
||||
AllocType alloc_type) {
|
||||
void *ptr = (void*)Allocate(alignment, size, stack, alloc_type);
|
||||
ASAN_MALLOC_HOOK(ptr, size);
|
||||
return ptr;
|
||||
}
|
||||
|
||||
SANITIZER_INTERFACE_ATTRIBUTE
|
||||
void asan_free(void *ptr, StackTrace *stack) {
|
||||
__asan_free_hook(ptr);
|
||||
Deallocate((u8*)ptr, stack);
|
||||
void asan_free(void *ptr, StackTrace *stack, AllocType alloc_type) {
|
||||
ASAN_FREE_HOOK(ptr);
|
||||
Deallocate((u8*)ptr, stack, alloc_type);
|
||||
}
|
||||
|
||||
SANITIZER_INTERFACE_ATTRIBUTE
|
||||
void *asan_malloc(uptr size, StackTrace *stack) {
|
||||
void *ptr = (void*)Allocate(0, size, stack);
|
||||
__asan_malloc_hook(ptr, size);
|
||||
void *ptr = (void*)Allocate(0, size, stack, FROM_MALLOC);
|
||||
ASAN_MALLOC_HOOK(ptr, size);
|
||||
return ptr;
|
||||
}
|
||||
|
||||
void *asan_calloc(uptr nmemb, uptr size, StackTrace *stack) {
|
||||
void *ptr = (void*)Allocate(0, nmemb * size, stack);
|
||||
void *ptr = (void*)Allocate(0, nmemb * size, stack, FROM_MALLOC);
|
||||
if (ptr)
|
||||
REAL(memset)(ptr, 0, nmemb * size);
|
||||
__asan_malloc_hook(ptr, nmemb * size);
|
||||
ASAN_MALLOC_HOOK(ptr, size);
|
||||
return ptr;
|
||||
}
|
||||
|
||||
void *asan_realloc(void *p, uptr size, StackTrace *stack) {
|
||||
if (p == 0) {
|
||||
void *ptr = (void*)Allocate(0, size, stack);
|
||||
__asan_malloc_hook(ptr, size);
|
||||
void *ptr = (void*)Allocate(0, size, stack, FROM_MALLOC);
|
||||
ASAN_MALLOC_HOOK(ptr, size);
|
||||
return ptr;
|
||||
} else if (size == 0) {
|
||||
__asan_free_hook(p);
|
||||
Deallocate((u8*)p, stack);
|
||||
ASAN_FREE_HOOK(p);
|
||||
Deallocate((u8*)p, stack, FROM_MALLOC);
|
||||
return 0;
|
||||
}
|
||||
return Reallocate((u8*)p, size, stack);
|
||||
}
|
||||
|
||||
void *asan_valloc(uptr size, StackTrace *stack) {
|
||||
void *ptr = (void*)Allocate(GetPageSizeCached(), size, stack);
|
||||
__asan_malloc_hook(ptr, size);
|
||||
void *ptr = (void*)Allocate(GetPageSizeCached(), size, stack, FROM_MALLOC);
|
||||
ASAN_MALLOC_HOOK(ptr, size);
|
||||
return ptr;
|
||||
}
|
||||
|
||||
@ -824,16 +743,16 @@ void *asan_pvalloc(uptr size, StackTrace *stack) {
|
||||
// pvalloc(0) should allocate one page.
|
||||
size = PageSize;
|
||||
}
|
||||
void *ptr = (void*)Allocate(PageSize, size, stack);
|
||||
__asan_malloc_hook(ptr, size);
|
||||
void *ptr = (void*)Allocate(PageSize, size, stack, FROM_MALLOC);
|
||||
ASAN_MALLOC_HOOK(ptr, size);
|
||||
return ptr;
|
||||
}
|
||||
|
||||
int asan_posix_memalign(void **memptr, uptr alignment, uptr size,
|
||||
StackTrace *stack) {
|
||||
void *ptr = Allocate(alignment, size, stack);
|
||||
void *ptr = Allocate(alignment, size, stack, FROM_MALLOC);
|
||||
CHECK(IsAligned((uptr)ptr, alignment));
|
||||
__asan_malloc_hook(ptr, size);
|
||||
ASAN_MALLOC_HOOK(ptr, size);
|
||||
*memptr = ptr;
|
||||
return 0;
|
||||
}
|
||||
@ -860,170 +779,11 @@ void asan_mz_force_unlock() {
|
||||
malloc_info.ForceUnlock();
|
||||
}
|
||||
|
||||
// ---------------------- Fake stack-------------------- {{{1
|
||||
FakeStack::FakeStack() {
|
||||
CHECK(REAL(memset) != 0);
|
||||
REAL(memset)(this, 0, sizeof(*this));
|
||||
}
|
||||
|
||||
bool FakeStack::AddrIsInSizeClass(uptr addr, uptr size_class) {
|
||||
uptr mem = allocated_size_classes_[size_class];
|
||||
uptr size = ClassMmapSize(size_class);
|
||||
bool res = mem && addr >= mem && addr < mem + size;
|
||||
return res;
|
||||
}
|
||||
|
||||
uptr FakeStack::AddrIsInFakeStack(uptr addr) {
|
||||
for (uptr i = 0; i < kNumberOfSizeClasses; i++) {
|
||||
if (AddrIsInSizeClass(addr, i)) return allocated_size_classes_[i];
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
// We may want to compute this during compilation.
|
||||
inline uptr FakeStack::ComputeSizeClass(uptr alloc_size) {
|
||||
uptr rounded_size = RoundUpToPowerOfTwo(alloc_size);
|
||||
uptr log = Log2(rounded_size);
|
||||
CHECK(alloc_size <= (1UL << log));
|
||||
if (!(alloc_size > (1UL << (log-1)))) {
|
||||
Printf("alloc_size %zu log %zu\n", alloc_size, log);
|
||||
}
|
||||
CHECK(alloc_size > (1UL << (log-1)));
|
||||
uptr res = log < kMinStackFrameSizeLog ? 0 : log - kMinStackFrameSizeLog;
|
||||
CHECK(res < kNumberOfSizeClasses);
|
||||
CHECK(ClassSize(res) >= rounded_size);
|
||||
return res;
|
||||
}
|
||||
|
||||
void FakeFrameFifo::FifoPush(FakeFrame *node) {
|
||||
CHECK(node);
|
||||
node->next = 0;
|
||||
if (first_ == 0 && last_ == 0) {
|
||||
first_ = last_ = node;
|
||||
} else {
|
||||
CHECK(first_);
|
||||
CHECK(last_);
|
||||
last_->next = node;
|
||||
last_ = node;
|
||||
}
|
||||
}
|
||||
|
||||
FakeFrame *FakeFrameFifo::FifoPop() {
|
||||
CHECK(first_ && last_ && "Exhausted fake stack");
|
||||
FakeFrame *res = 0;
|
||||
if (first_ == last_) {
|
||||
res = first_;
|
||||
first_ = last_ = 0;
|
||||
} else {
|
||||
res = first_;
|
||||
first_ = first_->next;
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
void FakeStack::Init(uptr stack_size) {
|
||||
stack_size_ = stack_size;
|
||||
alive_ = true;
|
||||
}
|
||||
|
||||
void FakeStack::Cleanup() {
|
||||
alive_ = false;
|
||||
for (uptr i = 0; i < kNumberOfSizeClasses; i++) {
|
||||
uptr mem = allocated_size_classes_[i];
|
||||
if (mem) {
|
||||
PoisonShadow(mem, ClassMmapSize(i), 0);
|
||||
allocated_size_classes_[i] = 0;
|
||||
UnmapOrDie((void*)mem, ClassMmapSize(i));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
uptr FakeStack::ClassMmapSize(uptr size_class) {
|
||||
return RoundUpToPowerOfTwo(stack_size_);
|
||||
}
|
||||
|
||||
void FakeStack::AllocateOneSizeClass(uptr size_class) {
|
||||
CHECK(ClassMmapSize(size_class) >= GetPageSizeCached());
|
||||
uptr new_mem = (uptr)MmapOrDie(
|
||||
ClassMmapSize(size_class), __FUNCTION__);
|
||||
// Printf("T%d new_mem[%zu]: %p-%p mmap %zu\n",
|
||||
// asanThreadRegistry().GetCurrent()->tid(),
|
||||
// size_class, new_mem, new_mem + ClassMmapSize(size_class),
|
||||
// ClassMmapSize(size_class));
|
||||
uptr i;
|
||||
for (i = 0; i < ClassMmapSize(size_class);
|
||||
i += ClassSize(size_class)) {
|
||||
size_classes_[size_class].FifoPush((FakeFrame*)(new_mem + i));
|
||||
}
|
||||
CHECK(i == ClassMmapSize(size_class));
|
||||
allocated_size_classes_[size_class] = new_mem;
|
||||
}
|
||||
|
||||
uptr FakeStack::AllocateStack(uptr size, uptr real_stack) {
|
||||
if (!alive_) return real_stack;
|
||||
CHECK(size <= kMaxStackMallocSize && size > 1);
|
||||
uptr size_class = ComputeSizeClass(size);
|
||||
if (!allocated_size_classes_[size_class]) {
|
||||
AllocateOneSizeClass(size_class);
|
||||
}
|
||||
FakeFrame *fake_frame = size_classes_[size_class].FifoPop();
|
||||
CHECK(fake_frame);
|
||||
fake_frame->size_minus_one = size - 1;
|
||||
fake_frame->real_stack = real_stack;
|
||||
while (FakeFrame *top = call_stack_.top()) {
|
||||
if (top->real_stack > real_stack) break;
|
||||
call_stack_.LifoPop();
|
||||
DeallocateFrame(top);
|
||||
}
|
||||
call_stack_.LifoPush(fake_frame);
|
||||
uptr ptr = (uptr)fake_frame;
|
||||
PoisonShadow(ptr, size, 0);
|
||||
return ptr;
|
||||
}
|
||||
|
||||
void FakeStack::DeallocateFrame(FakeFrame *fake_frame) {
|
||||
CHECK(alive_);
|
||||
uptr size = fake_frame->size_minus_one + 1;
|
||||
uptr size_class = ComputeSizeClass(size);
|
||||
CHECK(allocated_size_classes_[size_class]);
|
||||
uptr ptr = (uptr)fake_frame;
|
||||
CHECK(AddrIsInSizeClass(ptr, size_class));
|
||||
CHECK(AddrIsInSizeClass(ptr + size - 1, size_class));
|
||||
size_classes_[size_class].FifoPush(fake_frame);
|
||||
}
|
||||
|
||||
void FakeStack::OnFree(uptr ptr, uptr size, uptr real_stack) {
|
||||
FakeFrame *fake_frame = (FakeFrame*)ptr;
|
||||
CHECK(fake_frame->magic = kRetiredStackFrameMagic);
|
||||
CHECK(fake_frame->descr != 0);
|
||||
CHECK(fake_frame->size_minus_one == size - 1);
|
||||
PoisonShadow(ptr, size, kAsanStackAfterReturnMagic);
|
||||
}
|
||||
|
||||
} // namespace __asan
|
||||
|
||||
// ---------------------- Interface ---------------- {{{1
|
||||
using namespace __asan; // NOLINT
|
||||
|
||||
uptr __asan_stack_malloc(uptr size, uptr real_stack) {
|
||||
if (!flags()->use_fake_stack) return real_stack;
|
||||
AsanThread *t = asanThreadRegistry().GetCurrent();
|
||||
if (!t) {
|
||||
// TSD is gone, use the real stack.
|
||||
return real_stack;
|
||||
}
|
||||
uptr ptr = t->fake_stack().AllocateStack(size, real_stack);
|
||||
// Printf("__asan_stack_malloc %p %zu %p\n", ptr, size, real_stack);
|
||||
return ptr;
|
||||
}
|
||||
|
||||
void __asan_stack_free(uptr ptr, uptr size, uptr real_stack) {
|
||||
if (!flags()->use_fake_stack) return;
|
||||
if (ptr != real_stack) {
|
||||
FakeStack::OnFree(ptr, size, real_stack);
|
||||
}
|
||||
}
|
||||
|
||||
// ASan allocator doesn't reserve extra bytes, so normally we would
|
||||
// just return "size".
|
||||
uptr __asan_get_estimated_allocated_size(uptr size) {
|
||||
@ -1040,8 +800,9 @@ uptr __asan_get_allocated_size(const void *p) {
|
||||
uptr allocated_size = malloc_info.AllocationSize((uptr)p);
|
||||
// Die if p is not malloced or if it is already freed.
|
||||
if (allocated_size == 0) {
|
||||
GET_STACK_TRACE_HERE(kStackTraceMax);
|
||||
GET_STACK_TRACE_FATAL_HERE;
|
||||
ReportAsanGetAllocatedSizeNotOwned((uptr)p, &stack);
|
||||
}
|
||||
return allocated_size;
|
||||
}
|
||||
#endif // ASAN_ALLOCATOR_VERSION
|
||||
|
@ -15,9 +15,22 @@
|
||||
|
||||
#include "asan_internal.h"
|
||||
#include "asan_interceptors.h"
|
||||
#include "sanitizer_common/sanitizer_list.h"
|
||||
|
||||
// We are in the process of transitioning from the old allocator (version 1)
|
||||
// to a new one (version 2). The change is quite intrusive so both allocators
|
||||
// will co-exist in the source base for a while. The actual allocator is chosen
|
||||
// at build time by redefining this macrozz.
|
||||
#define ASAN_ALLOCATOR_VERSION 1
|
||||
|
||||
namespace __asan {
|
||||
|
||||
enum AllocType {
|
||||
FROM_MALLOC = 1, // Memory block came from malloc, calloc, realloc, etc.
|
||||
FROM_NEW = 2, // Memory block came from operator new.
|
||||
FROM_NEW_BR = 3 // Memory block came from operator new [ ]
|
||||
};
|
||||
|
||||
static const uptr kNumberOfSizeClasses = 255;
|
||||
struct AsanChunk;
|
||||
|
||||
@ -32,16 +45,40 @@ class AsanChunkView {
|
||||
uptr FreeTid();
|
||||
void GetAllocStack(StackTrace *stack);
|
||||
void GetFreeStack(StackTrace *stack);
|
||||
bool AddrIsInside(uptr addr, uptr access_size, uptr *offset);
|
||||
bool AddrIsAtLeft(uptr addr, uptr access_size, uptr *offset);
|
||||
bool AddrIsAtRight(uptr addr, uptr access_size, uptr *offset);
|
||||
bool AddrIsInside(uptr addr, uptr access_size, uptr *offset) {
|
||||
if (addr >= Beg() && (addr + access_size) <= End()) {
|
||||
*offset = addr - Beg();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
bool AddrIsAtLeft(uptr addr, uptr access_size, uptr *offset) {
|
||||
(void)access_size;
|
||||
if (addr < Beg()) {
|
||||
*offset = Beg() - addr;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
bool AddrIsAtRight(uptr addr, uptr access_size, uptr *offset) {
|
||||
if (addr + access_size >= End()) {
|
||||
if (addr <= End())
|
||||
*offset = 0;
|
||||
else
|
||||
*offset = addr - End();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private:
|
||||
AsanChunk *const chunk_;
|
||||
};
|
||||
|
||||
AsanChunkView FindHeapChunkByAddress(uptr address);
|
||||
|
||||
class AsanChunkFifoList {
|
||||
// List of AsanChunks with total size.
|
||||
class AsanChunkFifoList: public IntrusiveList<AsanChunk> {
|
||||
public:
|
||||
explicit AsanChunkFifoList(LinkerInitialized) { }
|
||||
AsanChunkFifoList() { clear(); }
|
||||
@ -50,12 +87,10 @@ class AsanChunkFifoList {
|
||||
AsanChunk *Pop();
|
||||
uptr size() { return size_; }
|
||||
void clear() {
|
||||
first_ = last_ = 0;
|
||||
IntrusiveList<AsanChunk>::clear();
|
||||
size_ = 0;
|
||||
}
|
||||
private:
|
||||
AsanChunk *first_;
|
||||
AsanChunk *last_;
|
||||
uptr size_;
|
||||
};
|
||||
|
||||
@ -68,7 +103,11 @@ struct AsanThreadLocalMallocStorage {
|
||||
}
|
||||
|
||||
AsanChunkFifoList quarantine_;
|
||||
#if ASAN_ALLOCATOR_VERSION == 1
|
||||
AsanChunk *free_lists_[kNumberOfSizeClasses];
|
||||
#else
|
||||
uptr allocator2_cache[1024]; // Opaque.
|
||||
#endif
|
||||
void CommitBack();
|
||||
};
|
||||
|
||||
@ -156,8 +195,9 @@ class FakeStack {
|
||||
FakeFrameLifo call_stack_;
|
||||
};
|
||||
|
||||
void *asan_memalign(uptr alignment, uptr size, StackTrace *stack);
|
||||
void asan_free(void *ptr, StackTrace *stack);
|
||||
void *asan_memalign(uptr alignment, uptr size, StackTrace *stack,
|
||||
AllocType alloc_type);
|
||||
void asan_free(void *ptr, StackTrace *stack, AllocType alloc_type);
|
||||
|
||||
void *asan_malloc(uptr size, StackTrace *stack);
|
||||
void *asan_calloc(uptr nmemb, uptr size, StackTrace *stack);
|
||||
@ -173,5 +213,52 @@ uptr asan_mz_size(const void *ptr);
|
||||
void asan_mz_force_lock();
|
||||
void asan_mz_force_unlock();
|
||||
|
||||
void PrintInternalAllocatorStats();
|
||||
|
||||
// Log2 and RoundUpToPowerOfTwo should be inlined for performance.
|
||||
#if defined(_WIN32) && !defined(__clang__)
|
||||
extern "C" {
|
||||
unsigned char _BitScanForward(unsigned long *index, unsigned long mask); // NOLINT
|
||||
unsigned char _BitScanReverse(unsigned long *index, unsigned long mask); // NOLINT
|
||||
#if defined(_WIN64)
|
||||
unsigned char _BitScanForward64(unsigned long *index, unsigned __int64 mask); // NOLINT
|
||||
unsigned char _BitScanReverse64(unsigned long *index, unsigned __int64 mask); // NOLINT
|
||||
#endif
|
||||
}
|
||||
#endif
|
||||
|
||||
static inline uptr Log2(uptr x) {
|
||||
CHECK(IsPowerOfTwo(x));
|
||||
#if !defined(_WIN32) || defined(__clang__)
|
||||
return __builtin_ctzl(x);
|
||||
#elif defined(_WIN64)
|
||||
unsigned long ret; // NOLINT
|
||||
_BitScanForward64(&ret, x);
|
||||
return ret;
|
||||
#else
|
||||
unsigned long ret; // NOLINT
|
||||
_BitScanForward(&ret, x);
|
||||
return ret;
|
||||
#endif
|
||||
}
|
||||
|
||||
static inline uptr RoundUpToPowerOfTwo(uptr size) {
|
||||
CHECK(size);
|
||||
if (IsPowerOfTwo(size)) return size;
|
||||
|
||||
unsigned long up; // NOLINT
|
||||
#if !defined(_WIN32) || defined(__clang__)
|
||||
up = SANITIZER_WORDSIZE - 1 - __builtin_clzl(size);
|
||||
#elif defined(_WIN64)
|
||||
_BitScanReverse64(&up, size);
|
||||
#else
|
||||
_BitScanReverse(&up, size);
|
||||
#endif
|
||||
CHECK(size < (1ULL << (up + 1)));
|
||||
CHECK(size > (1ULL << up));
|
||||
return 1UL << (up + 1);
|
||||
}
|
||||
|
||||
|
||||
} // namespace __asan
|
||||
#endif // ASAN_ALLOCATOR_H
|
||||
|
714
libsanitizer/asan/asan_allocator2.cc
Normal file
714
libsanitizer/asan/asan_allocator2.cc
Normal file
@ -0,0 +1,714 @@
|
||||
//===-- asan_allocator2.cc ------------------------------------------------===//
|
||||
//
|
||||
// This file is distributed under the University of Illinois Open Source
|
||||
// License. See LICENSE.TXT for details.
|
||||
//
|
||||
//===----------------------------------------------------------------------===//
|
||||
//
|
||||
// This file is a part of AddressSanitizer, an address sanity checker.
|
||||
//
|
||||
// Implementation of ASan's memory allocator, 2-nd version.
|
||||
// This variant uses the allocator from sanitizer_common, i.e. the one shared
|
||||
// with ThreadSanitizer and MemorySanitizer.
|
||||
//
|
||||
// Status: under development, not enabled by default yet.
|
||||
//===----------------------------------------------------------------------===//
|
||||
#include "asan_allocator.h"
|
||||
#if ASAN_ALLOCATOR_VERSION == 2
|
||||
|
||||
#include "asan_mapping.h"
|
||||
#include "asan_report.h"
|
||||
#include "asan_thread.h"
|
||||
#include "asan_thread_registry.h"
|
||||
#include "sanitizer/asan_interface.h"
|
||||
#include "sanitizer_common/sanitizer_allocator.h"
|
||||
#include "sanitizer_common/sanitizer_internal_defs.h"
|
||||
#include "sanitizer_common/sanitizer_list.h"
|
||||
#include "sanitizer_common/sanitizer_stackdepot.h"
|
||||
|
||||
namespace __asan {
|
||||
|
||||
struct AsanMapUnmapCallback {
|
||||
void OnMap(uptr p, uptr size) const {
|
||||
PoisonShadow(p, size, kAsanHeapLeftRedzoneMagic);
|
||||
// Statistics.
|
||||
AsanStats &thread_stats = asanThreadRegistry().GetCurrentThreadStats();
|
||||
thread_stats.mmaps++;
|
||||
thread_stats.mmaped += size;
|
||||
}
|
||||
void OnUnmap(uptr p, uptr size) const {
|
||||
PoisonShadow(p, size, 0);
|
||||
// We are about to unmap a chunk of user memory.
|
||||
// Mark the corresponding shadow memory as not needed.
|
||||
// Since asan's mapping is compacting, the shadow chunk may be
|
||||
// not page-aligned, so we only flush the page-aligned portion.
|
||||
uptr page_size = GetPageSizeCached();
|
||||
uptr shadow_beg = RoundUpTo(MemToShadow(p), page_size);
|
||||
uptr shadow_end = RoundDownTo(MemToShadow(p + size), page_size);
|
||||
FlushUnneededShadowMemory(shadow_beg, shadow_end - shadow_beg);
|
||||
// Statistics.
|
||||
AsanStats &thread_stats = asanThreadRegistry().GetCurrentThreadStats();
|
||||
thread_stats.munmaps++;
|
||||
thread_stats.munmaped += size;
|
||||
}
|
||||
};
|
||||
|
||||
#if SANITIZER_WORDSIZE == 64
|
||||
const uptr kAllocatorSpace = 0x600000000000ULL;
|
||||
const uptr kAllocatorSize = 0x10000000000ULL; // 1T.
|
||||
typedef DefaultSizeClassMap SizeClassMap;
|
||||
typedef SizeClassAllocator64<kAllocatorSpace, kAllocatorSize, 0 /*metadata*/,
|
||||
SizeClassMap, AsanMapUnmapCallback> PrimaryAllocator;
|
||||
#elif SANITIZER_WORDSIZE == 32
|
||||
static const u64 kAddressSpaceSize = 1ULL << 32;
|
||||
typedef CompactSizeClassMap SizeClassMap;
|
||||
typedef SizeClassAllocator32<0, kAddressSpaceSize, 16,
|
||||
SizeClassMap, AsanMapUnmapCallback> PrimaryAllocator;
|
||||
#endif
|
||||
|
||||
typedef SizeClassAllocatorLocalCache<PrimaryAllocator> AllocatorCache;
|
||||
typedef LargeMmapAllocator<AsanMapUnmapCallback> SecondaryAllocator;
|
||||
typedef CombinedAllocator<PrimaryAllocator, AllocatorCache,
|
||||
SecondaryAllocator> Allocator;
|
||||
|
||||
// We can not use THREADLOCAL because it is not supported on some of the
|
||||
// platforms we care about (OSX 10.6, Android).
|
||||
// static THREADLOCAL AllocatorCache cache;
|
||||
AllocatorCache *GetAllocatorCache(AsanThreadLocalMallocStorage *ms) {
|
||||
CHECK(ms);
|
||||
CHECK_LE(sizeof(AllocatorCache), sizeof(ms->allocator2_cache));
|
||||
return reinterpret_cast<AllocatorCache *>(ms->allocator2_cache);
|
||||
}
|
||||
|
||||
static Allocator allocator;
|
||||
|
||||
static const uptr kMaxAllowedMallocSize =
|
||||
FIRST_32_SECOND_64(3UL << 30, 8UL << 30);
|
||||
|
||||
static const uptr kMaxThreadLocalQuarantine =
|
||||
FIRST_32_SECOND_64(1 << 18, 1 << 20);
|
||||
|
||||
static const uptr kReturnOnZeroMalloc = 2048; // Zero page is protected.
|
||||
|
||||
static int inited = 0;
|
||||
|
||||
static void Init() {
|
||||
if (inited) return;
|
||||
__asan_init();
|
||||
inited = true; // this must happen before any threads are created.
|
||||
allocator.Init();
|
||||
}
|
||||
|
||||
// Every chunk of memory allocated by this allocator can be in one of 3 states:
|
||||
// CHUNK_AVAILABLE: the chunk is in the free list and ready to be allocated.
|
||||
// CHUNK_ALLOCATED: the chunk is allocated and not yet freed.
|
||||
// CHUNK_QUARANTINE: the chunk was freed and put into quarantine zone.
|
||||
enum {
|
||||
CHUNK_AVAILABLE = 0, // 0 is the default value even if we didn't set it.
|
||||
CHUNK_ALLOCATED = 2,
|
||||
CHUNK_QUARANTINE = 3
|
||||
};
|
||||
|
||||
// Valid redzone sizes are 16, 32, 64, ... 2048, so we encode them in 3 bits.
|
||||
// We use adaptive redzones: for larger allocation larger redzones are used.
|
||||
static u32 RZLog2Size(u32 rz_log) {
|
||||
CHECK_LT(rz_log, 8);
|
||||
return 16 << rz_log;
|
||||
}
|
||||
|
||||
static u32 RZSize2Log(u32 rz_size) {
|
||||
CHECK_GE(rz_size, 16);
|
||||
CHECK_LE(rz_size, 2048);
|
||||
CHECK(IsPowerOfTwo(rz_size));
|
||||
u32 res = __builtin_ctz(rz_size) - 4;
|
||||
CHECK_EQ(rz_size, RZLog2Size(res));
|
||||
return res;
|
||||
}
|
||||
|
||||
static uptr ComputeRZLog(uptr user_requested_size) {
|
||||
u32 rz_log =
|
||||
user_requested_size <= 64 - 16 ? 0 :
|
||||
user_requested_size <= 128 - 32 ? 1 :
|
||||
user_requested_size <= 512 - 64 ? 2 :
|
||||
user_requested_size <= 4096 - 128 ? 3 :
|
||||
user_requested_size <= (1 << 14) - 256 ? 4 :
|
||||
user_requested_size <= (1 << 15) - 512 ? 5 :
|
||||
user_requested_size <= (1 << 16) - 1024 ? 6 : 7;
|
||||
return Max(rz_log, RZSize2Log(flags()->redzone));
|
||||
}
|
||||
|
||||
// The memory chunk allocated from the underlying allocator looks like this:
|
||||
// L L L L L L H H U U U U U U R R
|
||||
// L -- left redzone words (0 or more bytes)
|
||||
// H -- ChunkHeader (16 bytes), which is also a part of the left redzone.
|
||||
// U -- user memory.
|
||||
// R -- right redzone (0 or more bytes)
|
||||
// ChunkBase consists of ChunkHeader and other bytes that overlap with user
|
||||
// memory.
|
||||
|
||||
// If a memory chunk is allocated by memalign and we had to increase the
|
||||
// allocation size to achieve the proper alignment, then we store this magic
|
||||
// value in the first uptr word of the memory block and store the address of
|
||||
// ChunkBase in the next uptr.
|
||||
// M B ? ? ? L L L L L L H H U U U U U U
|
||||
// M -- magic value kMemalignMagic
|
||||
// B -- address of ChunkHeader pointing to the first 'H'
|
||||
static const uptr kMemalignMagic = 0xCC6E96B9;
|
||||
|
||||
struct ChunkHeader {
|
||||
// 1-st 8 bytes.
|
||||
u32 chunk_state : 8; // Must be first.
|
||||
u32 alloc_tid : 24;
|
||||
|
||||
u32 free_tid : 24;
|
||||
u32 from_memalign : 1;
|
||||
u32 alloc_type : 2;
|
||||
u32 rz_log : 3;
|
||||
// 2-nd 8 bytes
|
||||
// This field is used for small sizes. For large sizes it is equal to
|
||||
// SizeClassMap::kMaxSize and the actual size is stored in the
|
||||
// SecondaryAllocator's metadata.
|
||||
u32 user_requested_size;
|
||||
u32 alloc_context_id;
|
||||
};
|
||||
|
||||
struct ChunkBase : ChunkHeader {
|
||||
// Header2, intersects with user memory.
|
||||
AsanChunk *next;
|
||||
u32 free_context_id;
|
||||
};
|
||||
|
||||
static const uptr kChunkHeaderSize = sizeof(ChunkHeader);
|
||||
static const uptr kChunkHeader2Size = sizeof(ChunkBase) - kChunkHeaderSize;
|
||||
COMPILER_CHECK(kChunkHeaderSize == 16);
|
||||
COMPILER_CHECK(kChunkHeader2Size <= 16);
|
||||
|
||||
struct AsanChunk: ChunkBase {
|
||||
uptr Beg() { return reinterpret_cast<uptr>(this) + kChunkHeaderSize; }
|
||||
uptr UsedSize() {
|
||||
if (user_requested_size != SizeClassMap::kMaxSize)
|
||||
return user_requested_size;
|
||||
return *reinterpret_cast<uptr *>(allocator.GetMetaData(AllocBeg()));
|
||||
}
|
||||
void *AllocBeg() {
|
||||
if (from_memalign)
|
||||
return allocator.GetBlockBegin(reinterpret_cast<void *>(this));
|
||||
return reinterpret_cast<void*>(Beg() - RZLog2Size(rz_log));
|
||||
}
|
||||
// We store the alloc/free stack traces in the chunk itself.
|
||||
u32 *AllocStackBeg() {
|
||||
return (u32*)(Beg() - RZLog2Size(rz_log));
|
||||
}
|
||||
uptr AllocStackSize() {
|
||||
CHECK_LE(RZLog2Size(rz_log), kChunkHeaderSize);
|
||||
return (RZLog2Size(rz_log) - kChunkHeaderSize) / sizeof(u32);
|
||||
}
|
||||
u32 *FreeStackBeg() {
|
||||
return (u32*)(Beg() + kChunkHeader2Size);
|
||||
}
|
||||
uptr FreeStackSize() {
|
||||
if (user_requested_size < kChunkHeader2Size) return 0;
|
||||
uptr available = RoundUpTo(user_requested_size, SHADOW_GRANULARITY);
|
||||
return (available - kChunkHeader2Size) / sizeof(u32);
|
||||
}
|
||||
};
|
||||
|
||||
uptr AsanChunkView::Beg() { return chunk_->Beg(); }
|
||||
uptr AsanChunkView::End() { return Beg() + UsedSize(); }
|
||||
uptr AsanChunkView::UsedSize() { return chunk_->UsedSize(); }
|
||||
uptr AsanChunkView::AllocTid() { return chunk_->alloc_tid; }
|
||||
uptr AsanChunkView::FreeTid() { return chunk_->free_tid; }
|
||||
|
||||
static void GetStackTraceFromId(u32 id, StackTrace *stack) {
|
||||
CHECK(id);
|
||||
uptr size = 0;
|
||||
const uptr *trace = StackDepotGet(id, &size);
|
||||
CHECK_LT(size, kStackTraceMax);
|
||||
internal_memcpy(stack->trace, trace, sizeof(uptr) * size);
|
||||
stack->size = size;
|
||||
}
|
||||
|
||||
void AsanChunkView::GetAllocStack(StackTrace *stack) {
|
||||
if (flags()->use_stack_depot)
|
||||
GetStackTraceFromId(chunk_->alloc_context_id, stack);
|
||||
else
|
||||
StackTrace::UncompressStack(stack, chunk_->AllocStackBeg(),
|
||||
chunk_->AllocStackSize());
|
||||
}
|
||||
|
||||
void AsanChunkView::GetFreeStack(StackTrace *stack) {
|
||||
if (flags()->use_stack_depot)
|
||||
GetStackTraceFromId(chunk_->free_context_id, stack);
|
||||
else
|
||||
StackTrace::UncompressStack(stack, chunk_->FreeStackBeg(),
|
||||
chunk_->FreeStackSize());
|
||||
}
|
||||
|
||||
class Quarantine: public AsanChunkFifoList {
|
||||
public:
|
||||
void SwallowThreadLocalQuarantine(AsanThreadLocalMallocStorage *ms) {
|
||||
AsanChunkFifoList *q = &ms->quarantine_;
|
||||
if (!q->size()) return;
|
||||
SpinMutexLock l(&mutex_);
|
||||
PushList(q);
|
||||
PopAndDeallocateLoop(ms);
|
||||
}
|
||||
|
||||
void BypassThreadLocalQuarantine(AsanChunk *m) {
|
||||
SpinMutexLock l(&mutex_);
|
||||
Push(m);
|
||||
}
|
||||
|
||||
private:
|
||||
void PopAndDeallocateLoop(AsanThreadLocalMallocStorage *ms) {
|
||||
while (size() > (uptr)flags()->quarantine_size) {
|
||||
PopAndDeallocate(ms);
|
||||
}
|
||||
}
|
||||
void PopAndDeallocate(AsanThreadLocalMallocStorage *ms) {
|
||||
CHECK_GT(size(), 0);
|
||||
AsanChunk *m = Pop();
|
||||
CHECK(m);
|
||||
CHECK(m->chunk_state == CHUNK_QUARANTINE);
|
||||
m->chunk_state = CHUNK_AVAILABLE;
|
||||
CHECK_NE(m->alloc_tid, kInvalidTid);
|
||||
CHECK_NE(m->free_tid, kInvalidTid);
|
||||
PoisonShadow(m->Beg(),
|
||||
RoundUpTo(m->UsedSize(), SHADOW_GRANULARITY),
|
||||
kAsanHeapLeftRedzoneMagic);
|
||||
void *p = reinterpret_cast<void *>(m->AllocBeg());
|
||||
if (m->from_memalign) {
|
||||
uptr *memalign_magic = reinterpret_cast<uptr *>(p);
|
||||
CHECK_EQ(memalign_magic[0], kMemalignMagic);
|
||||
CHECK_EQ(memalign_magic[1], reinterpret_cast<uptr>(m));
|
||||
}
|
||||
|
||||
// Statistics.
|
||||
AsanStats &thread_stats = asanThreadRegistry().GetCurrentThreadStats();
|
||||
thread_stats.real_frees++;
|
||||
thread_stats.really_freed += m->UsedSize();
|
||||
|
||||
allocator.Deallocate(GetAllocatorCache(ms), p);
|
||||
}
|
||||
SpinMutex mutex_;
|
||||
};
|
||||
|
||||
static Quarantine quarantine;
|
||||
|
||||
void AsanChunkFifoList::PushList(AsanChunkFifoList *q) {
|
||||
CHECK(q->size() > 0);
|
||||
size_ += q->size();
|
||||
append_back(q);
|
||||
q->clear();
|
||||
}
|
||||
|
||||
void AsanChunkFifoList::Push(AsanChunk *n) {
|
||||
push_back(n);
|
||||
size_ += n->UsedSize();
|
||||
}
|
||||
|
||||
// Interesting performance observation: this function takes up to 15% of overal
|
||||
// allocator time. That's because *first_ has been evicted from cache long time
|
||||
// ago. Not sure if we can or want to do anything with this.
|
||||
AsanChunk *AsanChunkFifoList::Pop() {
|
||||
CHECK(first_);
|
||||
AsanChunk *res = front();
|
||||
size_ -= res->UsedSize();
|
||||
pop_front();
|
||||
return res;
|
||||
}
|
||||
|
||||
static void *Allocate(uptr size, uptr alignment, StackTrace *stack,
|
||||
AllocType alloc_type) {
|
||||
Init();
|
||||
CHECK(stack);
|
||||
const uptr min_alignment = SHADOW_GRANULARITY;
|
||||
if (alignment < min_alignment)
|
||||
alignment = min_alignment;
|
||||
if (size == 0) {
|
||||
if (alignment <= kReturnOnZeroMalloc)
|
||||
return reinterpret_cast<void *>(kReturnOnZeroMalloc);
|
||||
else
|
||||
return 0; // 0 bytes with large alignment requested. Just return 0.
|
||||
}
|
||||
CHECK(IsPowerOfTwo(alignment));
|
||||
uptr rz_log = ComputeRZLog(size);
|
||||
uptr rz_size = RZLog2Size(rz_log);
|
||||
uptr rounded_size = RoundUpTo(size, alignment);
|
||||
if (rounded_size < kChunkHeader2Size)
|
||||
rounded_size = kChunkHeader2Size;
|
||||
uptr needed_size = rounded_size + rz_size;
|
||||
if (alignment > min_alignment)
|
||||
needed_size += alignment;
|
||||
bool using_primary_allocator = true;
|
||||
// If we are allocating from the secondary allocator, there will be no
|
||||
// automatic right redzone, so add the right redzone manually.
|
||||
if (!PrimaryAllocator::CanAllocate(needed_size, alignment)) {
|
||||
needed_size += rz_size;
|
||||
using_primary_allocator = false;
|
||||
}
|
||||
CHECK(IsAligned(needed_size, min_alignment));
|
||||
if (size > kMaxAllowedMallocSize || needed_size > kMaxAllowedMallocSize) {
|
||||
Report("WARNING: AddressSanitizer failed to allocate %p bytes\n",
|
||||
(void*)size);
|
||||
return 0;
|
||||
}
|
||||
|
||||
AsanThread *t = asanThreadRegistry().GetCurrent();
|
||||
AllocatorCache *cache = t ? GetAllocatorCache(&t->malloc_storage()) : 0;
|
||||
void *allocated = allocator.Allocate(cache, needed_size, 8, false);
|
||||
uptr alloc_beg = reinterpret_cast<uptr>(allocated);
|
||||
uptr alloc_end = alloc_beg + needed_size;
|
||||
uptr beg_plus_redzone = alloc_beg + rz_size;
|
||||
uptr user_beg = beg_plus_redzone;
|
||||
if (!IsAligned(user_beg, alignment))
|
||||
user_beg = RoundUpTo(user_beg, alignment);
|
||||
uptr user_end = user_beg + size;
|
||||
CHECK_LE(user_end, alloc_end);
|
||||
uptr chunk_beg = user_beg - kChunkHeaderSize;
|
||||
AsanChunk *m = reinterpret_cast<AsanChunk *>(chunk_beg);
|
||||
m->chunk_state = CHUNK_ALLOCATED;
|
||||
m->alloc_type = alloc_type;
|
||||
m->rz_log = rz_log;
|
||||
u32 alloc_tid = t ? t->tid() : 0;
|
||||
m->alloc_tid = alloc_tid;
|
||||
CHECK_EQ(alloc_tid, m->alloc_tid); // Does alloc_tid fit into the bitfield?
|
||||
m->free_tid = kInvalidTid;
|
||||
m->from_memalign = user_beg != beg_plus_redzone;
|
||||
if (m->from_memalign) {
|
||||
CHECK_LE(beg_plus_redzone + 2 * sizeof(uptr), user_beg);
|
||||
uptr *memalign_magic = reinterpret_cast<uptr *>(alloc_beg);
|
||||
memalign_magic[0] = kMemalignMagic;
|
||||
memalign_magic[1] = chunk_beg;
|
||||
}
|
||||
if (using_primary_allocator) {
|
||||
CHECK(size);
|
||||
m->user_requested_size = size;
|
||||
CHECK(allocator.FromPrimary(allocated));
|
||||
} else {
|
||||
CHECK(!allocator.FromPrimary(allocated));
|
||||
m->user_requested_size = SizeClassMap::kMaxSize;
|
||||
uptr *meta = reinterpret_cast<uptr *>(allocator.GetMetaData(allocated));
|
||||
meta[0] = size;
|
||||
meta[1] = chunk_beg;
|
||||
}
|
||||
|
||||
if (flags()->use_stack_depot) {
|
||||
m->alloc_context_id = StackDepotPut(stack->trace, stack->size);
|
||||
} else {
|
||||
m->alloc_context_id = 0;
|
||||
StackTrace::CompressStack(stack, m->AllocStackBeg(), m->AllocStackSize());
|
||||
}
|
||||
|
||||
uptr size_rounded_down_to_granularity = RoundDownTo(size, SHADOW_GRANULARITY);
|
||||
// Unpoison the bulk of the memory region.
|
||||
if (size_rounded_down_to_granularity)
|
||||
PoisonShadow(user_beg, size_rounded_down_to_granularity, 0);
|
||||
// Deal with the end of the region if size is not aligned to granularity.
|
||||
if (size != size_rounded_down_to_granularity && flags()->poison_heap) {
|
||||
u8 *shadow = (u8*)MemToShadow(user_beg + size_rounded_down_to_granularity);
|
||||
*shadow = size & (SHADOW_GRANULARITY - 1);
|
||||
}
|
||||
|
||||
AsanStats &thread_stats = asanThreadRegistry().GetCurrentThreadStats();
|
||||
thread_stats.mallocs++;
|
||||
thread_stats.malloced += size;
|
||||
thread_stats.malloced_redzones += needed_size - size;
|
||||
uptr class_id = Min(kNumberOfSizeClasses, SizeClassMap::ClassID(needed_size));
|
||||
thread_stats.malloced_by_size[class_id]++;
|
||||
if (needed_size > SizeClassMap::kMaxSize)
|
||||
thread_stats.malloc_large++;
|
||||
|
||||
void *res = reinterpret_cast<void *>(user_beg);
|
||||
ASAN_MALLOC_HOOK(res, size);
|
||||
return res;
|
||||
}
|
||||
|
||||
static void Deallocate(void *ptr, StackTrace *stack, AllocType alloc_type) {
|
||||
uptr p = reinterpret_cast<uptr>(ptr);
|
||||
if (p == 0 || p == kReturnOnZeroMalloc) return;
|
||||
uptr chunk_beg = p - kChunkHeaderSize;
|
||||
AsanChunk *m = reinterpret_cast<AsanChunk *>(chunk_beg);
|
||||
|
||||
// Flip the chunk_state atomically to avoid race on double-free.
|
||||
u8 old_chunk_state = atomic_exchange((atomic_uint8_t*)m, CHUNK_QUARANTINE,
|
||||
memory_order_acq_rel);
|
||||
|
||||
if (old_chunk_state == CHUNK_QUARANTINE)
|
||||
ReportDoubleFree((uptr)ptr, stack);
|
||||
else if (old_chunk_state != CHUNK_ALLOCATED)
|
||||
ReportFreeNotMalloced((uptr)ptr, stack);
|
||||
CHECK(old_chunk_state == CHUNK_ALLOCATED);
|
||||
if (m->alloc_type != alloc_type && flags()->alloc_dealloc_mismatch)
|
||||
ReportAllocTypeMismatch((uptr)ptr, stack,
|
||||
(AllocType)m->alloc_type, (AllocType)alloc_type);
|
||||
|
||||
CHECK_GE(m->alloc_tid, 0);
|
||||
if (SANITIZER_WORDSIZE == 64) // On 32-bits this resides in user area.
|
||||
CHECK_EQ(m->free_tid, kInvalidTid);
|
||||
AsanThread *t = asanThreadRegistry().GetCurrent();
|
||||
m->free_tid = t ? t->tid() : 0;
|
||||
if (flags()->use_stack_depot) {
|
||||
m->free_context_id = StackDepotPut(stack->trace, stack->size);
|
||||
} else {
|
||||
m->free_context_id = 0;
|
||||
StackTrace::CompressStack(stack, m->FreeStackBeg(), m->FreeStackSize());
|
||||
}
|
||||
CHECK(m->chunk_state == CHUNK_QUARANTINE);
|
||||
// Poison the region.
|
||||
PoisonShadow(m->Beg(),
|
||||
RoundUpTo(m->UsedSize(), SHADOW_GRANULARITY),
|
||||
kAsanHeapFreeMagic);
|
||||
|
||||
AsanStats &thread_stats = asanThreadRegistry().GetCurrentThreadStats();
|
||||
thread_stats.frees++;
|
||||
thread_stats.freed += m->UsedSize();
|
||||
|
||||
// Push into quarantine.
|
||||
if (t) {
|
||||
AsanChunkFifoList &q = t->malloc_storage().quarantine_;
|
||||
q.Push(m);
|
||||
|
||||
if (q.size() > kMaxThreadLocalQuarantine)
|
||||
quarantine.SwallowThreadLocalQuarantine(&t->malloc_storage());
|
||||
} else {
|
||||
quarantine.BypassThreadLocalQuarantine(m);
|
||||
}
|
||||
|
||||
ASAN_FREE_HOOK(ptr);
|
||||
}
|
||||
|
||||
static void *Reallocate(void *old_ptr, uptr new_size, StackTrace *stack) {
|
||||
CHECK(old_ptr && new_size);
|
||||
uptr p = reinterpret_cast<uptr>(old_ptr);
|
||||
uptr chunk_beg = p - kChunkHeaderSize;
|
||||
AsanChunk *m = reinterpret_cast<AsanChunk *>(chunk_beg);
|
||||
|
||||
AsanStats &thread_stats = asanThreadRegistry().GetCurrentThreadStats();
|
||||
thread_stats.reallocs++;
|
||||
thread_stats.realloced += new_size;
|
||||
|
||||
CHECK(m->chunk_state == CHUNK_ALLOCATED);
|
||||
uptr old_size = m->UsedSize();
|
||||
uptr memcpy_size = Min(new_size, old_size);
|
||||
void *new_ptr = Allocate(new_size, 8, stack, FROM_MALLOC);
|
||||
if (new_ptr) {
|
||||
CHECK(REAL(memcpy) != 0);
|
||||
REAL(memcpy)(new_ptr, old_ptr, memcpy_size);
|
||||
Deallocate(old_ptr, stack, FROM_MALLOC);
|
||||
}
|
||||
return new_ptr;
|
||||
}
|
||||
|
||||
static AsanChunk *GetAsanChunkByAddr(uptr p) {
|
||||
void *ptr = reinterpret_cast<void *>(p);
|
||||
uptr alloc_beg = reinterpret_cast<uptr>(allocator.GetBlockBegin(ptr));
|
||||
if (!alloc_beg) return 0;
|
||||
uptr *memalign_magic = reinterpret_cast<uptr *>(alloc_beg);
|
||||
if (memalign_magic[0] == kMemalignMagic) {
|
||||
AsanChunk *m = reinterpret_cast<AsanChunk *>(memalign_magic[1]);
|
||||
CHECK(m->from_memalign);
|
||||
return m;
|
||||
}
|
||||
if (!allocator.FromPrimary(ptr)) {
|
||||
uptr *meta = reinterpret_cast<uptr *>(
|
||||
allocator.GetMetaData(reinterpret_cast<void *>(alloc_beg)));
|
||||
AsanChunk *m = reinterpret_cast<AsanChunk *>(meta[1]);
|
||||
return m;
|
||||
}
|
||||
uptr actual_size = allocator.GetActuallyAllocatedSize(ptr);
|
||||
CHECK_LE(actual_size, SizeClassMap::kMaxSize);
|
||||
// We know the actually allocted size, but we don't know the redzone size.
|
||||
// Just try all possible redzone sizes.
|
||||
for (u32 rz_log = 0; rz_log < 8; rz_log++) {
|
||||
u32 rz_size = RZLog2Size(rz_log);
|
||||
uptr max_possible_size = actual_size - rz_size;
|
||||
if (ComputeRZLog(max_possible_size) != rz_log)
|
||||
continue;
|
||||
return reinterpret_cast<AsanChunk *>(
|
||||
alloc_beg + rz_size - kChunkHeaderSize);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static uptr AllocationSize(uptr p) {
|
||||
AsanChunk *m = GetAsanChunkByAddr(p);
|
||||
if (!m) return 0;
|
||||
if (m->chunk_state != CHUNK_ALLOCATED) return 0;
|
||||
if (m->Beg() != p) return 0;
|
||||
return m->UsedSize();
|
||||
}
|
||||
|
||||
// We have an address between two chunks, and we want to report just one.
|
||||
AsanChunk *ChooseChunk(uptr addr,
|
||||
AsanChunk *left_chunk, AsanChunk *right_chunk) {
|
||||
// Prefer an allocated chunk over freed chunk and freed chunk
|
||||
// over available chunk.
|
||||
if (left_chunk->chunk_state != right_chunk->chunk_state) {
|
||||
if (left_chunk->chunk_state == CHUNK_ALLOCATED)
|
||||
return left_chunk;
|
||||
if (right_chunk->chunk_state == CHUNK_ALLOCATED)
|
||||
return right_chunk;
|
||||
if (left_chunk->chunk_state == CHUNK_QUARANTINE)
|
||||
return left_chunk;
|
||||
if (right_chunk->chunk_state == CHUNK_QUARANTINE)
|
||||
return right_chunk;
|
||||
}
|
||||
// Same chunk_state: choose based on offset.
|
||||
uptr l_offset = 0, r_offset = 0;
|
||||
CHECK(AsanChunkView(left_chunk).AddrIsAtRight(addr, 1, &l_offset));
|
||||
CHECK(AsanChunkView(right_chunk).AddrIsAtLeft(addr, 1, &r_offset));
|
||||
if (l_offset < r_offset)
|
||||
return left_chunk;
|
||||
return right_chunk;
|
||||
}
|
||||
|
||||
AsanChunkView FindHeapChunkByAddress(uptr addr) {
|
||||
AsanChunk *m1 = GetAsanChunkByAddr(addr);
|
||||
if (!m1) return AsanChunkView(m1);
|
||||
uptr offset = 0;
|
||||
if (AsanChunkView(m1).AddrIsAtLeft(addr, 1, &offset)) {
|
||||
// The address is in the chunk's left redzone, so maybe it is actually
|
||||
// a right buffer overflow from the other chunk to the left.
|
||||
// Search a bit to the left to see if there is another chunk.
|
||||
AsanChunk *m2 = 0;
|
||||
for (uptr l = 1; l < GetPageSizeCached(); l++) {
|
||||
m2 = GetAsanChunkByAddr(addr - l);
|
||||
if (m2 == m1) continue; // Still the same chunk.
|
||||
break;
|
||||
}
|
||||
if (m2 && AsanChunkView(m2).AddrIsAtRight(addr, 1, &offset))
|
||||
m1 = ChooseChunk(addr, m2, m1);
|
||||
}
|
||||
return AsanChunkView(m1);
|
||||
}
|
||||
|
||||
void AsanThreadLocalMallocStorage::CommitBack() {
|
||||
quarantine.SwallowThreadLocalQuarantine(this);
|
||||
allocator.SwallowCache(GetAllocatorCache(this));
|
||||
}
|
||||
|
||||
void PrintInternalAllocatorStats() {
|
||||
allocator.PrintStats();
|
||||
}
|
||||
|
||||
SANITIZER_INTERFACE_ATTRIBUTE
|
||||
void *asan_memalign(uptr alignment, uptr size, StackTrace *stack,
|
||||
AllocType alloc_type) {
|
||||
return Allocate(size, alignment, stack, alloc_type);
|
||||
}
|
||||
|
||||
SANITIZER_INTERFACE_ATTRIBUTE
|
||||
void asan_free(void *ptr, StackTrace *stack, AllocType alloc_type) {
|
||||
Deallocate(ptr, stack, alloc_type);
|
||||
}
|
||||
|
||||
SANITIZER_INTERFACE_ATTRIBUTE
|
||||
void *asan_malloc(uptr size, StackTrace *stack) {
|
||||
return Allocate(size, 8, stack, FROM_MALLOC);
|
||||
}
|
||||
|
||||
void *asan_calloc(uptr nmemb, uptr size, StackTrace *stack) {
|
||||
void *ptr = Allocate(nmemb * size, 8, stack, FROM_MALLOC);
|
||||
if (ptr)
|
||||
REAL(memset)(ptr, 0, nmemb * size);
|
||||
return ptr;
|
||||
}
|
||||
|
||||
void *asan_realloc(void *p, uptr size, StackTrace *stack) {
|
||||
if (p == 0)
|
||||
return Allocate(size, 8, stack, FROM_MALLOC);
|
||||
if (size == 0) {
|
||||
Deallocate(p, stack, FROM_MALLOC);
|
||||
return 0;
|
||||
}
|
||||
return Reallocate(p, size, stack);
|
||||
}
|
||||
|
||||
void *asan_valloc(uptr size, StackTrace *stack) {
|
||||
return Allocate(size, GetPageSizeCached(), stack, FROM_MALLOC);
|
||||
}
|
||||
|
||||
void *asan_pvalloc(uptr size, StackTrace *stack) {
|
||||
uptr PageSize = GetPageSizeCached();
|
||||
size = RoundUpTo(size, PageSize);
|
||||
if (size == 0) {
|
||||
// pvalloc(0) should allocate one page.
|
||||
size = PageSize;
|
||||
}
|
||||
return Allocate(size, PageSize, stack, FROM_MALLOC);
|
||||
}
|
||||
|
||||
int asan_posix_memalign(void **memptr, uptr alignment, uptr size,
|
||||
StackTrace *stack) {
|
||||
void *ptr = Allocate(size, alignment, stack, FROM_MALLOC);
|
||||
CHECK(IsAligned((uptr)ptr, alignment));
|
||||
*memptr = ptr;
|
||||
return 0;
|
||||
}
|
||||
|
||||
uptr asan_malloc_usable_size(void *ptr, StackTrace *stack) {
|
||||
CHECK(stack);
|
||||
if (ptr == 0) return 0;
|
||||
uptr usable_size = AllocationSize(reinterpret_cast<uptr>(ptr));
|
||||
if (flags()->check_malloc_usable_size && (usable_size == 0))
|
||||
ReportMallocUsableSizeNotOwned((uptr)ptr, stack);
|
||||
return usable_size;
|
||||
}
|
||||
|
||||
uptr asan_mz_size(const void *ptr) {
|
||||
UNIMPLEMENTED();
|
||||
return 0;
|
||||
}
|
||||
|
||||
void asan_mz_force_lock() {
|
||||
UNIMPLEMENTED();
|
||||
}
|
||||
|
||||
void asan_mz_force_unlock() {
|
||||
UNIMPLEMENTED();
|
||||
}
|
||||
|
||||
} // namespace __asan
|
||||
|
||||
// ---------------------- Interface ---------------- {{{1
|
||||
using namespace __asan; // NOLINT
|
||||
|
||||
// ASan allocator doesn't reserve extra bytes, so normally we would
|
||||
// just return "size". We don't want to expose our redzone sizes, etc here.
|
||||
uptr __asan_get_estimated_allocated_size(uptr size) {
|
||||
return size;
|
||||
}
|
||||
|
||||
bool __asan_get_ownership(const void *p) {
|
||||
return AllocationSize(reinterpret_cast<uptr>(p)) > 0;
|
||||
}
|
||||
|
||||
uptr __asan_get_allocated_size(const void *p) {
|
||||
if (p == 0) return 0;
|
||||
uptr allocated_size = AllocationSize(reinterpret_cast<uptr>(p));
|
||||
// Die if p is not malloced or if it is already freed.
|
||||
if (allocated_size == 0) {
|
||||
GET_STACK_TRACE_FATAL_HERE;
|
||||
ReportAsanGetAllocatedSizeNotOwned(reinterpret_cast<uptr>(p), &stack);
|
||||
}
|
||||
return allocated_size;
|
||||
}
|
||||
|
||||
#if !SANITIZER_SUPPORTS_WEAK_HOOKS
|
||||
// Provide default (no-op) implementation of malloc hooks.
|
||||
extern "C" {
|
||||
SANITIZER_WEAK_ATTRIBUTE SANITIZER_INTERFACE_ATTRIBUTE
|
||||
void __asan_malloc_hook(void *ptr, uptr size) {
|
||||
(void)ptr;
|
||||
(void)size;
|
||||
}
|
||||
SANITIZER_WEAK_ATTRIBUTE SANITIZER_INTERFACE_ATTRIBUTE
|
||||
void __asan_free_hook(void *ptr) {
|
||||
(void)ptr;
|
||||
}
|
||||
} // extern "C"
|
||||
#endif
|
||||
|
||||
|
||||
#endif // ASAN_ALLOCATOR_VERSION
|
180
libsanitizer/asan/asan_fake_stack.cc
Normal file
180
libsanitizer/asan/asan_fake_stack.cc
Normal file
@ -0,0 +1,180 @@
|
||||
//===-- asan_fake_stack.cc ------------------------------------------------===//
|
||||
//
|
||||
// This file is distributed under the University of Illinois Open Source
|
||||
// License. See LICENSE.TXT for details.
|
||||
//
|
||||
//===----------------------------------------------------------------------===//
|
||||
//
|
||||
// This file is a part of AddressSanitizer, an address sanity checker.
|
||||
//
|
||||
// FakeStack is used to detect use-after-return bugs.
|
||||
//===----------------------------------------------------------------------===//
|
||||
#include "asan_allocator.h"
|
||||
#include "asan_thread.h"
|
||||
#include "asan_thread_registry.h"
|
||||
#include "sanitizer/asan_interface.h"
|
||||
|
||||
namespace __asan {
|
||||
|
||||
FakeStack::FakeStack() {
|
||||
CHECK(REAL(memset) != 0);
|
||||
REAL(memset)(this, 0, sizeof(*this));
|
||||
}
|
||||
|
||||
bool FakeStack::AddrIsInSizeClass(uptr addr, uptr size_class) {
|
||||
uptr mem = allocated_size_classes_[size_class];
|
||||
uptr size = ClassMmapSize(size_class);
|
||||
bool res = mem && addr >= mem && addr < mem + size;
|
||||
return res;
|
||||
}
|
||||
|
||||
uptr FakeStack::AddrIsInFakeStack(uptr addr) {
|
||||
for (uptr i = 0; i < kNumberOfSizeClasses; i++) {
|
||||
if (AddrIsInSizeClass(addr, i)) return allocated_size_classes_[i];
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
// We may want to compute this during compilation.
|
||||
inline uptr FakeStack::ComputeSizeClass(uptr alloc_size) {
|
||||
uptr rounded_size = RoundUpToPowerOfTwo(alloc_size);
|
||||
uptr log = Log2(rounded_size);
|
||||
CHECK(alloc_size <= (1UL << log));
|
||||
if (!(alloc_size > (1UL << (log-1)))) {
|
||||
Printf("alloc_size %zu log %zu\n", alloc_size, log);
|
||||
}
|
||||
CHECK(alloc_size > (1UL << (log-1)));
|
||||
uptr res = log < kMinStackFrameSizeLog ? 0 : log - kMinStackFrameSizeLog;
|
||||
CHECK(res < kNumberOfSizeClasses);
|
||||
CHECK(ClassSize(res) >= rounded_size);
|
||||
return res;
|
||||
}
|
||||
|
||||
void FakeFrameFifo::FifoPush(FakeFrame *node) {
|
||||
CHECK(node);
|
||||
node->next = 0;
|
||||
if (first_ == 0 && last_ == 0) {
|
||||
first_ = last_ = node;
|
||||
} else {
|
||||
CHECK(first_);
|
||||
CHECK(last_);
|
||||
last_->next = node;
|
||||
last_ = node;
|
||||
}
|
||||
}
|
||||
|
||||
FakeFrame *FakeFrameFifo::FifoPop() {
|
||||
CHECK(first_ && last_ && "Exhausted fake stack");
|
||||
FakeFrame *res = 0;
|
||||
if (first_ == last_) {
|
||||
res = first_;
|
||||
first_ = last_ = 0;
|
||||
} else {
|
||||
res = first_;
|
||||
first_ = first_->next;
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
void FakeStack::Init(uptr stack_size) {
|
||||
stack_size_ = stack_size;
|
||||
alive_ = true;
|
||||
}
|
||||
|
||||
void FakeStack::Cleanup() {
|
||||
alive_ = false;
|
||||
for (uptr i = 0; i < kNumberOfSizeClasses; i++) {
|
||||
uptr mem = allocated_size_classes_[i];
|
||||
if (mem) {
|
||||
PoisonShadow(mem, ClassMmapSize(i), 0);
|
||||
allocated_size_classes_[i] = 0;
|
||||
UnmapOrDie((void*)mem, ClassMmapSize(i));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
uptr FakeStack::ClassMmapSize(uptr size_class) {
|
||||
return RoundUpToPowerOfTwo(stack_size_);
|
||||
}
|
||||
|
||||
void FakeStack::AllocateOneSizeClass(uptr size_class) {
|
||||
CHECK(ClassMmapSize(size_class) >= GetPageSizeCached());
|
||||
uptr new_mem = (uptr)MmapOrDie(
|
||||
ClassMmapSize(size_class), __FUNCTION__);
|
||||
// Printf("T%d new_mem[%zu]: %p-%p mmap %zu\n",
|
||||
// asanThreadRegistry().GetCurrent()->tid(),
|
||||
// size_class, new_mem, new_mem + ClassMmapSize(size_class),
|
||||
// ClassMmapSize(size_class));
|
||||
uptr i;
|
||||
for (i = 0; i < ClassMmapSize(size_class);
|
||||
i += ClassSize(size_class)) {
|
||||
size_classes_[size_class].FifoPush((FakeFrame*)(new_mem + i));
|
||||
}
|
||||
CHECK(i == ClassMmapSize(size_class));
|
||||
allocated_size_classes_[size_class] = new_mem;
|
||||
}
|
||||
|
||||
uptr FakeStack::AllocateStack(uptr size, uptr real_stack) {
|
||||
if (!alive_) return real_stack;
|
||||
CHECK(size <= kMaxStackMallocSize && size > 1);
|
||||
uptr size_class = ComputeSizeClass(size);
|
||||
if (!allocated_size_classes_[size_class]) {
|
||||
AllocateOneSizeClass(size_class);
|
||||
}
|
||||
FakeFrame *fake_frame = size_classes_[size_class].FifoPop();
|
||||
CHECK(fake_frame);
|
||||
fake_frame->size_minus_one = size - 1;
|
||||
fake_frame->real_stack = real_stack;
|
||||
while (FakeFrame *top = call_stack_.top()) {
|
||||
if (top->real_stack > real_stack) break;
|
||||
call_stack_.LifoPop();
|
||||
DeallocateFrame(top);
|
||||
}
|
||||
call_stack_.LifoPush(fake_frame);
|
||||
uptr ptr = (uptr)fake_frame;
|
||||
PoisonShadow(ptr, size, 0);
|
||||
return ptr;
|
||||
}
|
||||
|
||||
void FakeStack::DeallocateFrame(FakeFrame *fake_frame) {
|
||||
CHECK(alive_);
|
||||
uptr size = fake_frame->size_minus_one + 1;
|
||||
uptr size_class = ComputeSizeClass(size);
|
||||
CHECK(allocated_size_classes_[size_class]);
|
||||
uptr ptr = (uptr)fake_frame;
|
||||
CHECK(AddrIsInSizeClass(ptr, size_class));
|
||||
CHECK(AddrIsInSizeClass(ptr + size - 1, size_class));
|
||||
size_classes_[size_class].FifoPush(fake_frame);
|
||||
}
|
||||
|
||||
void FakeStack::OnFree(uptr ptr, uptr size, uptr real_stack) {
|
||||
FakeFrame *fake_frame = (FakeFrame*)ptr;
|
||||
CHECK(fake_frame->magic = kRetiredStackFrameMagic);
|
||||
CHECK(fake_frame->descr != 0);
|
||||
CHECK(fake_frame->size_minus_one == size - 1);
|
||||
PoisonShadow(ptr, size, kAsanStackAfterReturnMagic);
|
||||
}
|
||||
|
||||
} // namespace __asan
|
||||
|
||||
// ---------------------- Interface ---------------- {{{1
|
||||
using namespace __asan; // NOLINT
|
||||
|
||||
uptr __asan_stack_malloc(uptr size, uptr real_stack) {
|
||||
if (!flags()->use_fake_stack) return real_stack;
|
||||
AsanThread *t = asanThreadRegistry().GetCurrent();
|
||||
if (!t) {
|
||||
// TSD is gone, use the real stack.
|
||||
return real_stack;
|
||||
}
|
||||
uptr ptr = t->fake_stack().AllocateStack(size, real_stack);
|
||||
// Printf("__asan_stack_malloc %p %zu %p\n", ptr, size, real_stack);
|
||||
return ptr;
|
||||
}
|
||||
|
||||
void __asan_stack_free(uptr ptr, uptr size, uptr real_stack) {
|
||||
if (!flags()->use_fake_stack) return;
|
||||
if (ptr != real_stack) {
|
||||
FakeStack::OnFree(ptr, size, real_stack);
|
||||
}
|
||||
}
|
@ -43,7 +43,7 @@ struct Flags {
|
||||
int report_globals;
|
||||
// If set, attempts to catch initialization order issues.
|
||||
bool check_initialization_order;
|
||||
// Max number of stack frames kept for each allocation.
|
||||
// Max number of stack frames kept for each allocation/deallocation.
|
||||
int malloc_context_size;
|
||||
// If set, uses custom wrappers and replacements for libc string functions
|
||||
// to find more errors.
|
||||
@ -93,6 +93,17 @@ struct Flags {
|
||||
bool print_full_thread_history;
|
||||
// ASan will write logs to "log_path.pid" instead of stderr.
|
||||
const char *log_path;
|
||||
// Use fast (frame-pointer-based) unwinder on fatal errors (if available).
|
||||
bool fast_unwind_on_fatal;
|
||||
// Use fast (frame-pointer-based) unwinder on malloc/free (if available).
|
||||
bool fast_unwind_on_malloc;
|
||||
// Poison (or not) the heap memory on [de]allocation. Zero value is useful
|
||||
// for benchmarking the allocator or instrumentator.
|
||||
bool poison_heap;
|
||||
// Report errors on malloc/delete, new/free, new/delete[], etc.
|
||||
bool alloc_dealloc_mismatch;
|
||||
// Use stack depot instead of storing stacks in the redzones.
|
||||
bool use_stack_depot;
|
||||
};
|
||||
|
||||
Flags *flags();
|
||||
|
@ -14,6 +14,7 @@
|
||||
|
||||
#include "asan_internal.h"
|
||||
#include "interception/interception.h"
|
||||
#include "sanitizer_common/sanitizer_platform_interceptors.h"
|
||||
|
||||
using __sanitizer::uptr;
|
||||
|
||||
@ -39,8 +40,10 @@ using __sanitizer::uptr;
|
||||
|
||||
#if defined(__linux__)
|
||||
# define ASAN_USE_ALIAS_ATTRIBUTE_FOR_INDEX 1
|
||||
# define ASAN_INTERCEPT_PRCTL 1
|
||||
#else
|
||||
# define ASAN_USE_ALIAS_ATTRIBUTE_FOR_INDEX 0
|
||||
# define ASAN_INTERCEPT_PRCTL 0
|
||||
#endif
|
||||
|
||||
#if !defined(__APPLE__)
|
||||
@ -149,10 +152,23 @@ DECLARE_FUNCTION_AND_WRAPPER(long long, atoll, const char *nptr); // NOLINT
|
||||
DECLARE_FUNCTION_AND_WRAPPER(long long, strtoll, const char *nptr, char **endptr, int base); // NOLINT
|
||||
# endif
|
||||
|
||||
// unistd.h
|
||||
# if SANITIZER_INTERCEPT_READ
|
||||
DECLARE_FUNCTION_AND_WRAPPER(SSIZE_T, read, int fd, void *buf, SIZE_T count);
|
||||
# endif
|
||||
# if SANITIZER_INTERCEPT_PREAD
|
||||
DECLARE_FUNCTION_AND_WRAPPER(SSIZE_T, pread, int fd, void *buf,
|
||||
SIZE_T count, OFF_T offset);
|
||||
# endif
|
||||
# if SANITIZER_INTERCEPT_PREAD64
|
||||
DECLARE_FUNCTION_AND_WRAPPER(SSIZE_T, pread64, int fd, void *buf,
|
||||
SIZE_T count, OFF64_T offset);
|
||||
# endif
|
||||
|
||||
# if ASAN_INTERCEPT_MLOCKX
|
||||
// mlock/munlock
|
||||
DECLARE_FUNCTION_AND_WRAPPER(int, mlock, const void *addr, size_t len);
|
||||
DECLARE_FUNCTION_AND_WRAPPER(int, munlock, const void *addr, size_t len);
|
||||
DECLARE_FUNCTION_AND_WRAPPER(int, mlock, const void *addr, SIZE_T len);
|
||||
DECLARE_FUNCTION_AND_WRAPPER(int, munlock, const void *addr, SIZE_T len);
|
||||
DECLARE_FUNCTION_AND_WRAPPER(int, mlockall, int flags);
|
||||
DECLARE_FUNCTION_AND_WRAPPER(int, munlockall, void);
|
||||
# endif
|
||||
|
@ -25,38 +25,20 @@
|
||||
|
||||
namespace __asan {
|
||||
|
||||
// Instruments read/write access to a single byte in memory.
|
||||
// On error calls __asan_report_error, which aborts the program.
|
||||
#define ACCESS_ADDRESS(address, isWrite) do { \
|
||||
if (!AddrIsInMem(address) || AddressIsPoisoned(address)) { \
|
||||
GET_CURRENT_PC_BP_SP; \
|
||||
__asan_report_error(pc, bp, sp, address, isWrite, /* access_size */ 1); \
|
||||
} \
|
||||
} while (0)
|
||||
|
||||
// We implement ACCESS_MEMORY_RANGE, ASAN_READ_RANGE,
|
||||
// and ASAN_WRITE_RANGE as macro instead of function so
|
||||
// that no extra frames are created, and stack trace contains
|
||||
// relevant information only.
|
||||
|
||||
// Instruments read/write access to a memory range.
|
||||
// More complex implementation is possible, for now just
|
||||
// checking the first and the last byte of a range.
|
||||
#define ACCESS_MEMORY_RANGE(offset, size, isWrite) do { \
|
||||
if (size > 0) { \
|
||||
uptr ptr = (uptr)(offset); \
|
||||
ACCESS_ADDRESS(ptr, isWrite); \
|
||||
ACCESS_ADDRESS(ptr + (size) - 1, isWrite); \
|
||||
} \
|
||||
// We check all shadow bytes.
|
||||
#define ACCESS_MEMORY_RANGE(offset, size, isWrite) do { \
|
||||
if (uptr __ptr = __asan_region_is_poisoned((uptr)(offset), size)) { \
|
||||
GET_CURRENT_PC_BP_SP; \
|
||||
__asan_report_error(pc, bp, sp, __ptr, isWrite, /* access_size */1); \
|
||||
} \
|
||||
} while (0)
|
||||
|
||||
#define ASAN_READ_RANGE(offset, size) do { \
|
||||
ACCESS_MEMORY_RANGE(offset, size, false); \
|
||||
} while (0)
|
||||
|
||||
#define ASAN_WRITE_RANGE(offset, size) do { \
|
||||
ACCESS_MEMORY_RANGE(offset, size, true); \
|
||||
} while (0)
|
||||
#define ASAN_READ_RANGE(offset, size) ACCESS_MEMORY_RANGE(offset, size, false)
|
||||
#define ASAN_WRITE_RANGE(offset, size) ACCESS_MEMORY_RANGE(offset, size, true);
|
||||
|
||||
// Behavior of functions like "memcpy" or "strcpy" is undefined
|
||||
// if memory intervals overlap. We report error in this case.
|
||||
@ -69,7 +51,7 @@ static inline bool RangesOverlap(const char *offset1, uptr length1,
|
||||
const char *offset1 = (const char*)_offset1; \
|
||||
const char *offset2 = (const char*)_offset2; \
|
||||
if (RangesOverlap(offset1, length1, offset2, length2)) { \
|
||||
GET_STACK_TRACE_HERE(kStackTraceMax); \
|
||||
GET_STACK_TRACE_FATAL_HERE; \
|
||||
ReportStringFunctionMemoryRangesOverlap(name, offset1, length1, \
|
||||
offset2, length2, &stack); \
|
||||
} \
|
||||
@ -96,6 +78,11 @@ static inline uptr MaybeRealStrnlen(const char *s, uptr maxlen) {
|
||||
// ---------------------- Wrappers ---------------- {{{1
|
||||
using namespace __asan; // NOLINT
|
||||
|
||||
#define COMMON_INTERCEPTOR_WRITE_RANGE(ptr, size) ASAN_WRITE_RANGE(ptr, size)
|
||||
#define COMMON_INTERCEPTOR_READ_RANGE(ptr, size) ASAN_READ_RANGE(ptr, size)
|
||||
#define COMMON_INTERCEPTOR_ENTER(func, ...) ENSURE_ASAN_INITED()
|
||||
#include "sanitizer_common/sanitizer_common_interceptors.h"
|
||||
|
||||
static thread_return_t THREAD_CALLING_CONV asan_thread_start(void *arg) {
|
||||
AsanThread *t = (AsanThread*)arg;
|
||||
asanThreadRegistry().SetCurrent(t);
|
||||
@ -105,7 +92,7 @@ static thread_return_t THREAD_CALLING_CONV asan_thread_start(void *arg) {
|
||||
#if ASAN_INTERCEPT_PTHREAD_CREATE
|
||||
INTERCEPTOR(int, pthread_create, void *thread,
|
||||
void *attr, void *(*start_routine)(void*), void *arg) {
|
||||
GET_STACK_TRACE_HERE(kStackTraceMax);
|
||||
GET_STACK_TRACE_THREAD;
|
||||
u32 current_tid = asanThreadRegistry().GetCurrentTidOrInvalid();
|
||||
AsanThread *t = AsanThread::Create(current_tid, start_routine, arg, &stack);
|
||||
asanThreadRegistry().RegisterThread(t);
|
||||
@ -175,6 +162,25 @@ INTERCEPTOR(void, siglongjmp, void *env, int val) {
|
||||
}
|
||||
#endif
|
||||
|
||||
#if ASAN_INTERCEPT_PRCTL
|
||||
#define PR_SET_NAME 15
|
||||
INTERCEPTOR(int, prctl, int option,
|
||||
unsigned long arg2, unsigned long arg3, // NOLINT
|
||||
unsigned long arg4, unsigned long arg5) { // NOLINT
|
||||
int res = REAL(prctl(option, arg2, arg3, arg4, arg5));
|
||||
if (option == PR_SET_NAME) {
|
||||
AsanThread *t = asanThreadRegistry().GetCurrent();
|
||||
if (t) {
|
||||
char buff[17];
|
||||
internal_strncpy(buff, (char*)arg2, 16);
|
||||
buff[16] = 0;
|
||||
t->summary()->set_name(buff);
|
||||
}
|
||||
}
|
||||
return res;
|
||||
}
|
||||
#endif
|
||||
|
||||
#if ASAN_INTERCEPT___CXA_THROW
|
||||
INTERCEPTOR(void, __cxa_throw, void *a, void *b, void *c) {
|
||||
CHECK(REAL(__cxa_throw));
|
||||
@ -256,8 +262,8 @@ INTERCEPTOR(void*, memcpy, void *to, const void *from, uptr size) {
|
||||
// See http://llvm.org/bugs/show_bug.cgi?id=11763.
|
||||
CHECK_RANGES_OVERLAP("memcpy", to, size, from, size);
|
||||
}
|
||||
ASAN_WRITE_RANGE(from, size);
|
||||
ASAN_READ_RANGE(to, size);
|
||||
ASAN_READ_RANGE(from, size);
|
||||
ASAN_WRITE_RANGE(to, size);
|
||||
}
|
||||
#if MAC_INTERPOSE_FUNCTIONS
|
||||
// Interposing of resolver functions is broken on Mac OS 10.7 and 10.8.
|
||||
@ -275,8 +281,8 @@ INTERCEPTOR(void*, memmove, void *to, const void *from, uptr size) {
|
||||
}
|
||||
ENSURE_ASAN_INITED();
|
||||
if (flags()->replace_intrin) {
|
||||
ASAN_WRITE_RANGE(from, size);
|
||||
ASAN_READ_RANGE(to, size);
|
||||
ASAN_READ_RANGE(from, size);
|
||||
ASAN_WRITE_RANGE(to, size);
|
||||
}
|
||||
#if MAC_INTERPOSE_FUNCTIONS
|
||||
// Interposing of resolver functions is broken on Mac OS 10.7 and 10.8.
|
||||
@ -621,7 +627,7 @@ INTERCEPTOR_WINAPI(DWORD, CreateThread,
|
||||
void* security, uptr stack_size,
|
||||
DWORD (__stdcall *start_routine)(void*), void* arg,
|
||||
DWORD flags, void* tid) {
|
||||
GET_STACK_TRACE_HERE(kStackTraceMax);
|
||||
GET_STACK_TRACE_THREAD;
|
||||
u32 current_tid = asanThreadRegistry().GetCurrentTidOrInvalid();
|
||||
AsanThread *t = AsanThread::Create(current_tid, start_routine, arg, &stack);
|
||||
asanThreadRegistry().RegisterThread(t);
|
||||
@ -646,6 +652,9 @@ void InitializeAsanInterceptors() {
|
||||
#if MAC_INTERPOSE_FUNCTIONS
|
||||
return;
|
||||
#endif
|
||||
|
||||
SANITIZER_COMMON_INTERCEPTORS_INIT;
|
||||
|
||||
// Intercept mem* functions.
|
||||
ASAN_INTERCEPT_FUNC(memcmp);
|
||||
ASAN_INTERCEPT_FUNC(memmove);
|
||||
@ -718,6 +727,9 @@ void InitializeAsanInterceptors() {
|
||||
#if ASAN_INTERCEPT_SIGLONGJMP
|
||||
ASAN_INTERCEPT_FUNC(siglongjmp);
|
||||
#endif
|
||||
#if ASAN_INTERCEPT_PRCTL
|
||||
ASAN_INTERCEPT_FUNC(prctl);
|
||||
#endif
|
||||
|
||||
// Intercept exception handling functions.
|
||||
#if ASAN_INTERCEPT___CXA_THROW
|
||||
|
@ -81,9 +81,9 @@
|
||||
// If set, values like allocator chunk size, as well as defaults for some flags
|
||||
// will be changed towards less memory overhead.
|
||||
#ifndef ASAN_LOW_MEMORY
|
||||
# ifdef ASAN_ANDROID
|
||||
#if SANITIZER_WORDSIZE == 32
|
||||
# define ASAN_LOW_MEMORY 1
|
||||
# else
|
||||
#else
|
||||
# define ASAN_LOW_MEMORY 0
|
||||
# endif
|
||||
#endif
|
||||
@ -143,6 +143,15 @@ bool PlatformHasDifferentMemcpyAndMemmove();
|
||||
# define PLATFORM_HAS_DIFFERENT_MEMCPY_AND_MEMMOVE true
|
||||
#endif // __APPLE__
|
||||
|
||||
// Add convenient macro for interface functions that may be represented as
|
||||
// weak hooks.
|
||||
#define ASAN_MALLOC_HOOK(ptr, size) \
|
||||
if (&__asan_malloc_hook) __asan_malloc_hook(ptr, size)
|
||||
#define ASAN_FREE_HOOK(ptr) \
|
||||
if (&__asan_free_hook) __asan_free_hook(ptr)
|
||||
#define ASAN_ON_ERROR() \
|
||||
if (&__asan_on_error) __asan_on_error()
|
||||
|
||||
extern int asan_inited;
|
||||
// Used to avoid infinite recursion in __asan_init().
|
||||
extern bool asan_init_is_running;
|
||||
|
@ -120,53 +120,21 @@ void AsanLock::Unlock() {
|
||||
pthread_mutex_unlock((pthread_mutex_t*)&opaque_storage_);
|
||||
}
|
||||
|
||||
#ifdef __arm__
|
||||
#define UNWIND_STOP _URC_END_OF_STACK
|
||||
#define UNWIND_CONTINUE _URC_NO_REASON
|
||||
#else
|
||||
#define UNWIND_STOP _URC_NORMAL_STOP
|
||||
#define UNWIND_CONTINUE _URC_NO_REASON
|
||||
#endif
|
||||
|
||||
uptr Unwind_GetIP(struct _Unwind_Context *ctx) {
|
||||
#ifdef __arm__
|
||||
uptr val;
|
||||
_Unwind_VRS_Result res = _Unwind_VRS_Get(ctx, _UVRSC_CORE,
|
||||
15 /* r15 = PC */, _UVRSD_UINT32, &val);
|
||||
CHECK(res == _UVRSR_OK && "_Unwind_VRS_Get failed");
|
||||
// Clear the Thumb bit.
|
||||
return val & ~(uptr)1;
|
||||
#else
|
||||
return _Unwind_GetIP(ctx);
|
||||
#endif
|
||||
}
|
||||
|
||||
_Unwind_Reason_Code Unwind_Trace(struct _Unwind_Context *ctx,
|
||||
void *param) {
|
||||
StackTrace *b = (StackTrace*)param;
|
||||
CHECK(b->size < b->max_size);
|
||||
uptr pc = Unwind_GetIP(ctx);
|
||||
b->trace[b->size++] = pc;
|
||||
if (b->size == b->max_size) return UNWIND_STOP;
|
||||
return UNWIND_CONTINUE;
|
||||
}
|
||||
|
||||
void GetStackTrace(StackTrace *stack, uptr max_s, uptr pc, uptr bp) {
|
||||
stack->size = 0;
|
||||
stack->trace[0] = pc;
|
||||
if ((max_s) > 1) {
|
||||
stack->max_size = max_s;
|
||||
void GetStackTrace(StackTrace *stack, uptr max_s, uptr pc, uptr bp, bool fast) {
|
||||
#if defined(__arm__) || \
|
||||
defined(__powerpc__) || defined(__powerpc64__) || \
|
||||
defined(__sparc__)
|
||||
_Unwind_Backtrace(Unwind_Trace, stack);
|
||||
// Pop off the two ASAN functions from the backtrace.
|
||||
stack->PopStackFrames(2);
|
||||
#else
|
||||
fast = false;
|
||||
#endif
|
||||
if (!fast)
|
||||
return stack->SlowUnwindStack(pc, max_s);
|
||||
stack->size = 0;
|
||||
stack->trace[0] = pc;
|
||||
if (max_s > 1) {
|
||||
stack->max_size = max_s;
|
||||
if (!asan_inited) return;
|
||||
if (AsanThread *t = asanThreadRegistry().GetCurrent())
|
||||
stack->FastUnwindStack(pc, bp, t->stack_top(), t->stack_bottom());
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -158,7 +158,8 @@ void AsanLock::Unlock() {
|
||||
OSSpinLockUnlock((OSSpinLock*)&opaque_storage_);
|
||||
}
|
||||
|
||||
void GetStackTrace(StackTrace *stack, uptr max_s, uptr pc, uptr bp) {
|
||||
void GetStackTrace(StackTrace *stack, uptr max_s, uptr pc, uptr bp, bool fast) {
|
||||
(void)fast;
|
||||
stack->size = 0;
|
||||
stack->trace[0] = pc;
|
||||
if ((max_s) > 1) {
|
||||
@ -306,7 +307,7 @@ void asan_register_worker_thread(int parent_tid, StackTrace *stack) {
|
||||
// alloc_asan_context().
|
||||
extern "C"
|
||||
void asan_dispatch_call_block_and_release(void *block) {
|
||||
GET_STACK_TRACE_HERE(kStackTraceMax);
|
||||
GET_STACK_TRACE_THREAD;
|
||||
asan_block_context_t *context = (asan_block_context_t*)block;
|
||||
if (flags()->verbosity >= 2) {
|
||||
Report("asan_dispatch_call_block_and_release(): "
|
||||
@ -316,7 +317,7 @@ void asan_dispatch_call_block_and_release(void *block) {
|
||||
asan_register_worker_thread(context->parent_tid, &stack);
|
||||
// Call the original dispatcher for the block.
|
||||
context->func(context->block);
|
||||
asan_free(context, &stack);
|
||||
asan_free(context, &stack, FROM_MALLOC);
|
||||
}
|
||||
|
||||
} // namespace __asan
|
||||
@ -341,7 +342,7 @@ asan_block_context_t *alloc_asan_context(void *ctxt, dispatch_function_t func,
|
||||
#define INTERCEPT_DISPATCH_X_F_3(dispatch_x_f) \
|
||||
INTERCEPTOR(void, dispatch_x_f, dispatch_queue_t dq, void *ctxt, \
|
||||
dispatch_function_t func) { \
|
||||
GET_STACK_TRACE_HERE(kStackTraceMax); \
|
||||
GET_STACK_TRACE_THREAD; \
|
||||
asan_block_context_t *asan_ctxt = alloc_asan_context(ctxt, func, &stack); \
|
||||
if (flags()->verbosity >= 2) { \
|
||||
Report(#dispatch_x_f "(): context: %p, pthread_self: %p\n", \
|
||||
@ -359,7 +360,7 @@ INTERCEPT_DISPATCH_X_F_3(dispatch_barrier_async_f)
|
||||
INTERCEPTOR(void, dispatch_after_f, dispatch_time_t when,
|
||||
dispatch_queue_t dq, void *ctxt,
|
||||
dispatch_function_t func) {
|
||||
GET_STACK_TRACE_HERE(kStackTraceMax);
|
||||
GET_STACK_TRACE_THREAD;
|
||||
asan_block_context_t *asan_ctxt = alloc_asan_context(ctxt, func, &stack);
|
||||
if (flags()->verbosity >= 2) {
|
||||
Report("dispatch_after_f: %p\n", asan_ctxt);
|
||||
@ -372,7 +373,7 @@ INTERCEPTOR(void, dispatch_after_f, dispatch_time_t when,
|
||||
INTERCEPTOR(void, dispatch_group_async_f, dispatch_group_t group,
|
||||
dispatch_queue_t dq, void *ctxt,
|
||||
dispatch_function_t func) {
|
||||
GET_STACK_TRACE_HERE(kStackTraceMax);
|
||||
GET_STACK_TRACE_THREAD;
|
||||
asan_block_context_t *asan_ctxt = alloc_asan_context(ctxt, func, &stack);
|
||||
if (flags()->verbosity >= 2) {
|
||||
Report("dispatch_group_async_f(): context: %p, pthread_self: %p\n",
|
||||
@ -407,7 +408,7 @@ void dispatch_source_set_event_handler(dispatch_source_t ds, void(^work)(void));
|
||||
void (^asan_block)(void); \
|
||||
int parent_tid = asanThreadRegistry().GetCurrentTidOrInvalid(); \
|
||||
asan_block = ^(void) { \
|
||||
GET_STACK_TRACE_HERE(kStackTraceMax); \
|
||||
GET_STACK_TRACE_THREAD; \
|
||||
asan_register_worker_thread(parent_tid, &stack); \
|
||||
work(); \
|
||||
}
|
||||
@ -457,15 +458,15 @@ void *wrap_workitem_func(void *arg) {
|
||||
asan_block_context_t *ctxt = (asan_block_context_t*)arg;
|
||||
worker_t fn = (worker_t)(ctxt->func);
|
||||
void *result = fn(ctxt->block);
|
||||
GET_STACK_TRACE_HERE(kStackTraceMax);
|
||||
asan_free(arg, &stack);
|
||||
GET_STACK_TRACE_THREAD;
|
||||
asan_free(arg, &stack, FROM_MALLOC);
|
||||
return result;
|
||||
}
|
||||
|
||||
INTERCEPTOR(int, pthread_workqueue_additem_np, pthread_workqueue_t workq,
|
||||
void *(*workitem_func)(void *), void * workitem_arg,
|
||||
pthread_workitem_handle_t * itemhandlep, unsigned int *gencountp) {
|
||||
GET_STACK_TRACE_HERE(kStackTraceMax);
|
||||
GET_STACK_TRACE_THREAD;
|
||||
asan_block_context_t *asan_ctxt =
|
||||
(asan_block_context_t*) asan_malloc(sizeof(asan_block_context_t), &stack);
|
||||
asan_ctxt->block = workitem_arg;
|
||||
|
@ -17,6 +17,8 @@
|
||||
#include "asan_interceptors.h"
|
||||
#include "asan_internal.h"
|
||||
#include "asan_stack.h"
|
||||
#include "asan_thread_registry.h"
|
||||
#include "sanitizer/asan_interface.h"
|
||||
|
||||
#if ASAN_ANDROID
|
||||
DECLARE_REAL_AND_INTERCEPTOR(void*, malloc, uptr size)
|
||||
@ -57,17 +59,17 @@ void ReplaceSystemMalloc() {
|
||||
using namespace __asan; // NOLINT
|
||||
|
||||
INTERCEPTOR(void, free, void *ptr) {
|
||||
GET_STACK_TRACE_HERE_FOR_FREE(ptr);
|
||||
asan_free(ptr, &stack);
|
||||
GET_STACK_TRACE_FREE;
|
||||
asan_free(ptr, &stack, FROM_MALLOC);
|
||||
}
|
||||
|
||||
INTERCEPTOR(void, cfree, void *ptr) {
|
||||
GET_STACK_TRACE_HERE_FOR_FREE(ptr);
|
||||
asan_free(ptr, &stack);
|
||||
GET_STACK_TRACE_FREE;
|
||||
asan_free(ptr, &stack, FROM_MALLOC);
|
||||
}
|
||||
|
||||
INTERCEPTOR(void*, malloc, uptr size) {
|
||||
GET_STACK_TRACE_HERE_FOR_MALLOC;
|
||||
GET_STACK_TRACE_MALLOC;
|
||||
return asan_malloc(size, &stack);
|
||||
}
|
||||
|
||||
@ -83,25 +85,25 @@ INTERCEPTOR(void*, calloc, uptr nmemb, uptr size) {
|
||||
CHECK(allocated < kCallocPoolSize);
|
||||
return mem;
|
||||
}
|
||||
GET_STACK_TRACE_HERE_FOR_MALLOC;
|
||||
GET_STACK_TRACE_MALLOC;
|
||||
return asan_calloc(nmemb, size, &stack);
|
||||
}
|
||||
|
||||
INTERCEPTOR(void*, realloc, void *ptr, uptr size) {
|
||||
GET_STACK_TRACE_HERE_FOR_MALLOC;
|
||||
GET_STACK_TRACE_MALLOC;
|
||||
return asan_realloc(ptr, size, &stack);
|
||||
}
|
||||
|
||||
INTERCEPTOR(void*, memalign, uptr boundary, uptr size) {
|
||||
GET_STACK_TRACE_HERE_FOR_MALLOC;
|
||||
return asan_memalign(boundary, size, &stack);
|
||||
GET_STACK_TRACE_MALLOC;
|
||||
return asan_memalign(boundary, size, &stack, FROM_MALLOC);
|
||||
}
|
||||
|
||||
INTERCEPTOR(void*, __libc_memalign, uptr align, uptr s)
|
||||
ALIAS("memalign");
|
||||
|
||||
INTERCEPTOR(uptr, malloc_usable_size, void *ptr) {
|
||||
GET_STACK_TRACE_HERE_FOR_MALLOC;
|
||||
GET_STACK_TRACE_MALLOC;
|
||||
return asan_malloc_usable_size(ptr, &stack);
|
||||
}
|
||||
|
||||
@ -124,19 +126,23 @@ INTERCEPTOR(int, mallopt, int cmd, int value) {
|
||||
}
|
||||
|
||||
INTERCEPTOR(int, posix_memalign, void **memptr, uptr alignment, uptr size) {
|
||||
GET_STACK_TRACE_HERE_FOR_MALLOC;
|
||||
GET_STACK_TRACE_MALLOC;
|
||||
// Printf("posix_memalign: %zx %zu\n", alignment, size);
|
||||
return asan_posix_memalign(memptr, alignment, size, &stack);
|
||||
}
|
||||
|
||||
INTERCEPTOR(void*, valloc, uptr size) {
|
||||
GET_STACK_TRACE_HERE_FOR_MALLOC;
|
||||
GET_STACK_TRACE_MALLOC;
|
||||
return asan_valloc(size, &stack);
|
||||
}
|
||||
|
||||
INTERCEPTOR(void*, pvalloc, uptr size) {
|
||||
GET_STACK_TRACE_HERE_FOR_MALLOC;
|
||||
GET_STACK_TRACE_MALLOC;
|
||||
return asan_pvalloc(size, &stack);
|
||||
}
|
||||
|
||||
INTERCEPTOR(void, malloc_stats, void) {
|
||||
__asan_print_accumulated_stats();
|
||||
}
|
||||
|
||||
#endif // __linux__
|
||||
|
@ -90,8 +90,8 @@ INTERCEPTOR(void, free, void *ptr) {
|
||||
#endif
|
||||
} else {
|
||||
if (!asan_mz_size(ptr)) ptr = get_saved_cfallocator_ref(ptr);
|
||||
GET_STACK_TRACE_HERE_FOR_FREE(ptr);
|
||||
asan_free(ptr, &stack);
|
||||
GET_STACK_TRACE_FREE;
|
||||
asan_free(ptr, &stack, FROM_MALLOC);
|
||||
}
|
||||
}
|
||||
|
||||
@ -128,7 +128,7 @@ void *mz_malloc(malloc_zone_t *zone, size_t size) {
|
||||
CHECK(system_malloc_zone);
|
||||
return malloc_zone_malloc(system_malloc_zone, size);
|
||||
}
|
||||
GET_STACK_TRACE_HERE_FOR_MALLOC;
|
||||
GET_STACK_TRACE_MALLOC;
|
||||
return asan_malloc(size, &stack);
|
||||
}
|
||||
|
||||
@ -137,7 +137,7 @@ void *cf_malloc(CFIndex size, CFOptionFlags hint, void *info) {
|
||||
CHECK(system_malloc_zone);
|
||||
return malloc_zone_malloc(system_malloc_zone, size);
|
||||
}
|
||||
GET_STACK_TRACE_HERE_FOR_MALLOC;
|
||||
GET_STACK_TRACE_MALLOC;
|
||||
return asan_malloc(size, &stack);
|
||||
}
|
||||
|
||||
@ -153,7 +153,7 @@ void *mz_calloc(malloc_zone_t *zone, size_t nmemb, size_t size) {
|
||||
CHECK(allocated < kCallocPoolSize);
|
||||
return mem;
|
||||
}
|
||||
GET_STACK_TRACE_HERE_FOR_MALLOC;
|
||||
GET_STACK_TRACE_MALLOC;
|
||||
return asan_calloc(nmemb, size, &stack);
|
||||
}
|
||||
|
||||
@ -162,8 +162,8 @@ void *mz_valloc(malloc_zone_t *zone, size_t size) {
|
||||
CHECK(system_malloc_zone);
|
||||
return malloc_zone_valloc(system_malloc_zone, size);
|
||||
}
|
||||
GET_STACK_TRACE_HERE_FOR_MALLOC;
|
||||
return asan_memalign(GetPageSizeCached(), size, &stack);
|
||||
GET_STACK_TRACE_MALLOC;
|
||||
return asan_memalign(GetPageSizeCached(), size, &stack, FROM_MALLOC);
|
||||
}
|
||||
|
||||
#define GET_ZONE_FOR_PTR(ptr) \
|
||||
@ -173,8 +173,8 @@ void *mz_valloc(malloc_zone_t *zone, size_t size) {
|
||||
void ALWAYS_INLINE free_common(void *context, void *ptr) {
|
||||
if (!ptr) return;
|
||||
if (asan_mz_size(ptr)) {
|
||||
GET_STACK_TRACE_HERE_FOR_FREE(ptr);
|
||||
asan_free(ptr, &stack);
|
||||
GET_STACK_TRACE_FREE;
|
||||
asan_free(ptr, &stack, FROM_MALLOC);
|
||||
} else {
|
||||
// If the pointer does not belong to any of the zones, use one of the
|
||||
// fallback methods to free memory.
|
||||
@ -188,9 +188,9 @@ void ALWAYS_INLINE free_common(void *context, void *ptr) {
|
||||
// If the memory chunk pointer was moved to store additional
|
||||
// CFAllocatorRef, fix it back.
|
||||
ptr = get_saved_cfallocator_ref(ptr);
|
||||
GET_STACK_TRACE_HERE_FOR_FREE(ptr);
|
||||
GET_STACK_TRACE_FREE;
|
||||
if (!flags()->mac_ignore_invalid_free) {
|
||||
asan_free(ptr, &stack);
|
||||
asan_free(ptr, &stack, FROM_MALLOC);
|
||||
} else {
|
||||
GET_ZONE_FOR_PTR(ptr);
|
||||
WarnMacFreeUnallocated((uptr)ptr, (uptr)zone_ptr, zone_name, &stack);
|
||||
@ -211,17 +211,17 @@ void cf_free(void *ptr, void *info) {
|
||||
|
||||
void *mz_realloc(malloc_zone_t *zone, void *ptr, size_t size) {
|
||||
if (!ptr) {
|
||||
GET_STACK_TRACE_HERE_FOR_MALLOC;
|
||||
GET_STACK_TRACE_MALLOC;
|
||||
return asan_malloc(size, &stack);
|
||||
} else {
|
||||
if (asan_mz_size(ptr)) {
|
||||
GET_STACK_TRACE_HERE_FOR_MALLOC;
|
||||
GET_STACK_TRACE_MALLOC;
|
||||
return asan_realloc(ptr, size, &stack);
|
||||
} else {
|
||||
// We can't recover from reallocating an unknown address, because
|
||||
// this would require reading at most |size| bytes from
|
||||
// potentially unaccessible memory.
|
||||
GET_STACK_TRACE_HERE_FOR_FREE(ptr);
|
||||
GET_STACK_TRACE_FREE;
|
||||
GET_ZONE_FOR_PTR(ptr);
|
||||
ReportMacMzReallocUnknown((uptr)ptr, (uptr)zone_ptr, zone_name, &stack);
|
||||
}
|
||||
@ -230,17 +230,17 @@ void *mz_realloc(malloc_zone_t *zone, void *ptr, size_t size) {
|
||||
|
||||
void *cf_realloc(void *ptr, CFIndex size, CFOptionFlags hint, void *info) {
|
||||
if (!ptr) {
|
||||
GET_STACK_TRACE_HERE_FOR_MALLOC;
|
||||
GET_STACK_TRACE_MALLOC;
|
||||
return asan_malloc(size, &stack);
|
||||
} else {
|
||||
if (asan_mz_size(ptr)) {
|
||||
GET_STACK_TRACE_HERE_FOR_MALLOC;
|
||||
GET_STACK_TRACE_MALLOC;
|
||||
return asan_realloc(ptr, size, &stack);
|
||||
} else {
|
||||
// We can't recover from reallocating an unknown address, because
|
||||
// this would require reading at most |size| bytes from
|
||||
// potentially unaccessible memory.
|
||||
GET_STACK_TRACE_HERE_FOR_FREE(ptr);
|
||||
GET_STACK_TRACE_FREE;
|
||||
GET_ZONE_FOR_PTR(ptr);
|
||||
ReportMacCfReallocUnknown((uptr)ptr, (uptr)zone_ptr, zone_name, &stack);
|
||||
}
|
||||
@ -259,8 +259,8 @@ void *mz_memalign(malloc_zone_t *zone, size_t align, size_t size) {
|
||||
CHECK(system_malloc_zone);
|
||||
return malloc_zone_memalign(system_malloc_zone, align, size);
|
||||
}
|
||||
GET_STACK_TRACE_HERE_FOR_MALLOC;
|
||||
return asan_memalign(align, size, &stack);
|
||||
GET_STACK_TRACE_MALLOC;
|
||||
return asan_memalign(align, size, &stack, FROM_MALLOC);
|
||||
}
|
||||
|
||||
// This function is currently unused, and we build with -Werror.
|
||||
|
@ -29,8 +29,8 @@ using namespace __asan; // NOLINT
|
||||
|
||||
extern "C" {
|
||||
void free(void *ptr) {
|
||||
GET_STACK_TRACE_HERE_FOR_FREE(ptr);
|
||||
return asan_free(ptr, &stack);
|
||||
GET_STACK_TRACE_FREE;
|
||||
return asan_free(ptr, &stack, FROM_MALLOC);
|
||||
}
|
||||
|
||||
void _free_dbg(void* ptr, int) {
|
||||
@ -42,7 +42,7 @@ void cfree(void *ptr) {
|
||||
}
|
||||
|
||||
void *malloc(size_t size) {
|
||||
GET_STACK_TRACE_HERE_FOR_MALLOC;
|
||||
GET_STACK_TRACE_MALLOC;
|
||||
return asan_malloc(size, &stack);
|
||||
}
|
||||
|
||||
@ -51,7 +51,7 @@ void* _malloc_dbg(size_t size, int , const char*, int) {
|
||||
}
|
||||
|
||||
void *calloc(size_t nmemb, size_t size) {
|
||||
GET_STACK_TRACE_HERE_FOR_MALLOC;
|
||||
GET_STACK_TRACE_MALLOC;
|
||||
return asan_calloc(nmemb, size, &stack);
|
||||
}
|
||||
|
||||
@ -64,7 +64,7 @@ void *_calloc_impl(size_t nmemb, size_t size, int *errno_tmp) {
|
||||
}
|
||||
|
||||
void *realloc(void *ptr, size_t size) {
|
||||
GET_STACK_TRACE_HERE_FOR_MALLOC;
|
||||
GET_STACK_TRACE_MALLOC;
|
||||
return asan_realloc(ptr, size, &stack);
|
||||
}
|
||||
|
||||
@ -83,7 +83,7 @@ void* _recalloc(void* p, size_t n, size_t elem_size) {
|
||||
}
|
||||
|
||||
size_t _msize(void *ptr) {
|
||||
GET_STACK_TRACE_HERE_FOR_MALLOC;
|
||||
GET_STACK_TRACE_MALLOC;
|
||||
return asan_malloc_usable_size(ptr, &stack);
|
||||
}
|
||||
|
||||
|
@ -18,8 +18,8 @@
|
||||
// http://code.google.com/p/address-sanitizer/wiki/AddressSanitizerAlgorithm
|
||||
|
||||
#if ASAN_FLEXIBLE_MAPPING_AND_OFFSET == 1
|
||||
extern __attribute__((visibility("default"))) uptr __asan_mapping_scale;
|
||||
extern __attribute__((visibility("default"))) uptr __asan_mapping_offset;
|
||||
extern SANITIZER_INTERFACE_ATTRIBUTE uptr __asan_mapping_scale;
|
||||
extern SANITIZER_INTERFACE_ATTRIBUTE uptr __asan_mapping_offset;
|
||||
# define SHADOW_SCALE (__asan_mapping_scale)
|
||||
# define SHADOW_OFFSET (__asan_mapping_offset)
|
||||
#else
|
||||
|
@ -33,32 +33,34 @@ namespace std {
|
||||
struct nothrow_t {};
|
||||
} // namespace std
|
||||
|
||||
#define OPERATOR_NEW_BODY \
|
||||
GET_STACK_TRACE_HERE_FOR_MALLOC;\
|
||||
return asan_memalign(0, size, &stack);
|
||||
#define OPERATOR_NEW_BODY(type) \
|
||||
GET_STACK_TRACE_MALLOC;\
|
||||
return asan_memalign(0, size, &stack, type);
|
||||
|
||||
INTERCEPTOR_ATTRIBUTE
|
||||
void *operator new(size_t size) { OPERATOR_NEW_BODY; }
|
||||
void *operator new(size_t size) { OPERATOR_NEW_BODY(FROM_NEW); }
|
||||
INTERCEPTOR_ATTRIBUTE
|
||||
void *operator new[](size_t size) { OPERATOR_NEW_BODY; }
|
||||
void *operator new[](size_t size) { OPERATOR_NEW_BODY(FROM_NEW_BR); }
|
||||
INTERCEPTOR_ATTRIBUTE
|
||||
void *operator new(size_t size, std::nothrow_t const&) { OPERATOR_NEW_BODY; }
|
||||
void *operator new(size_t size, std::nothrow_t const&)
|
||||
{ OPERATOR_NEW_BODY(FROM_NEW); }
|
||||
INTERCEPTOR_ATTRIBUTE
|
||||
void *operator new[](size_t size, std::nothrow_t const&) { OPERATOR_NEW_BODY; }
|
||||
void *operator new[](size_t size, std::nothrow_t const&)
|
||||
{ OPERATOR_NEW_BODY(FROM_NEW_BR); }
|
||||
|
||||
#define OPERATOR_DELETE_BODY \
|
||||
GET_STACK_TRACE_HERE_FOR_FREE(ptr);\
|
||||
asan_free(ptr, &stack);
|
||||
#define OPERATOR_DELETE_BODY(type) \
|
||||
GET_STACK_TRACE_FREE;\
|
||||
asan_free(ptr, &stack, type);
|
||||
|
||||
INTERCEPTOR_ATTRIBUTE
|
||||
void operator delete(void *ptr) { OPERATOR_DELETE_BODY; }
|
||||
void operator delete(void *ptr) { OPERATOR_DELETE_BODY(FROM_NEW); }
|
||||
INTERCEPTOR_ATTRIBUTE
|
||||
void operator delete[](void *ptr) { OPERATOR_DELETE_BODY; }
|
||||
void operator delete[](void *ptr) { OPERATOR_DELETE_BODY(FROM_NEW_BR); }
|
||||
INTERCEPTOR_ATTRIBUTE
|
||||
void operator delete(void *ptr, std::nothrow_t const&)
|
||||
{ OPERATOR_DELETE_BODY; }
|
||||
{ OPERATOR_DELETE_BODY(FROM_NEW); }
|
||||
INTERCEPTOR_ATTRIBUTE
|
||||
void operator delete[](void *ptr, std::nothrow_t const&)
|
||||
{ OPERATOR_DELETE_BODY; }
|
||||
{ OPERATOR_DELETE_BODY(FROM_NEW_BR); }
|
||||
|
||||
#endif
|
||||
|
@ -14,10 +14,12 @@
|
||||
#include "asan_internal.h"
|
||||
#include "asan_mapping.h"
|
||||
#include "sanitizer/asan_interface.h"
|
||||
#include "sanitizer_common/sanitizer_libc.h"
|
||||
|
||||
namespace __asan {
|
||||
|
||||
void PoisonShadow(uptr addr, uptr size, u8 value) {
|
||||
if (!flags()->poison_heap) return;
|
||||
CHECK(AddrIsAlignedByGranularity(addr));
|
||||
CHECK(AddrIsAlignedByGranularity(addr + size));
|
||||
uptr shadow_beg = MemToShadow(addr);
|
||||
@ -30,6 +32,7 @@ void PoisonShadowPartialRightRedzone(uptr addr,
|
||||
uptr size,
|
||||
uptr redzone_size,
|
||||
u8 value) {
|
||||
if (!flags()->poison_heap) return;
|
||||
CHECK(AddrIsAlignedByGranularity(addr));
|
||||
u8 *shadow = (u8*)MemToShadow(addr);
|
||||
for (uptr i = 0; i < redzone_size;
|
||||
@ -150,6 +153,33 @@ bool __asan_address_is_poisoned(void const volatile *addr) {
|
||||
return __asan::AddressIsPoisoned((uptr)addr);
|
||||
}
|
||||
|
||||
uptr __asan_region_is_poisoned(uptr beg, uptr size) {
|
||||
if (!size) return 0;
|
||||
uptr end = beg + size;
|
||||
if (!AddrIsInMem(beg)) return beg;
|
||||
if (!AddrIsInMem(end)) return end;
|
||||
uptr aligned_b = RoundUpTo(beg, SHADOW_GRANULARITY);
|
||||
uptr aligned_e = RoundDownTo(end, SHADOW_GRANULARITY);
|
||||
uptr shadow_beg = MemToShadow(aligned_b);
|
||||
uptr shadow_end = MemToShadow(aligned_e);
|
||||
// First check the first and the last application bytes,
|
||||
// then check the SHADOW_GRANULARITY-aligned region by calling
|
||||
// mem_is_zero on the corresponding shadow.
|
||||
if (!__asan::AddressIsPoisoned(beg) &&
|
||||
!__asan::AddressIsPoisoned(end - 1) &&
|
||||
(shadow_end <= shadow_beg ||
|
||||
__sanitizer::mem_is_zero((const char *)shadow_beg,
|
||||
shadow_end - shadow_beg)))
|
||||
return 0;
|
||||
// The fast check failed, so we have a poisoned byte somewhere.
|
||||
// Find it slowly.
|
||||
for (; beg < end; beg++)
|
||||
if (__asan::AddressIsPoisoned(beg))
|
||||
return beg;
|
||||
UNREACHABLE("mem_is_zero returned false, but poisoned byte was not found");
|
||||
return 0;
|
||||
}
|
||||
|
||||
// This is a simplified version of __asan_(un)poison_memory_region, which
|
||||
// assumes that left border of region to be poisoned is properly aligned.
|
||||
static void PoisonAlignedStackMemory(uptr addr, uptr size, bool do_poison) {
|
||||
@ -166,7 +196,7 @@ static void PoisonAlignedStackMemory(uptr addr, uptr size, bool do_poison) {
|
||||
// If possible, mark all the bytes mapping to last shadow byte as
|
||||
// unaddressable.
|
||||
if (end_value > 0 && end_value <= end_offset)
|
||||
*shadow_end = kAsanStackUseAfterScopeMagic;
|
||||
*shadow_end = (s8)kAsanStackUseAfterScopeMagic;
|
||||
} else {
|
||||
// If necessary, mark few first bytes mapping to last shadow byte
|
||||
// as addressable
|
||||
|
@ -16,6 +16,9 @@
|
||||
#include "asan_stack.h"
|
||||
#include "asan_thread.h"
|
||||
#include "asan_thread_registry.h"
|
||||
#include "sanitizer_common/sanitizer_common.h"
|
||||
#include "sanitizer_common/sanitizer_report_decorator.h"
|
||||
#include "sanitizer_common/sanitizer_symbolizer.h"
|
||||
|
||||
namespace __asan {
|
||||
|
||||
@ -38,14 +41,79 @@ void AppendToErrorMessageBuffer(const char *buffer) {
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------- Decorator ------------------------------ {{{1
|
||||
bool PrintsToTtyCached() {
|
||||
static int cached = 0;
|
||||
static bool prints_to_tty;
|
||||
if (!cached) { // Ok wrt threads since we are printing only from one thread.
|
||||
prints_to_tty = PrintsToTty();
|
||||
cached = 1;
|
||||
}
|
||||
return prints_to_tty;
|
||||
}
|
||||
class Decorator: private __sanitizer::AnsiColorDecorator {
|
||||
public:
|
||||
Decorator() : __sanitizer::AnsiColorDecorator(PrintsToTtyCached()) { }
|
||||
const char *Warning() { return Red(); }
|
||||
const char *EndWarning() { return Default(); }
|
||||
const char *Access() { return Blue(); }
|
||||
const char *EndAccess() { return Default(); }
|
||||
const char *Location() { return Green(); }
|
||||
const char *EndLocation() { return Default(); }
|
||||
const char *Allocation() { return Magenta(); }
|
||||
const char *EndAllocation() { return Default(); }
|
||||
|
||||
const char *ShadowByte(u8 byte) {
|
||||
switch (byte) {
|
||||
case kAsanHeapLeftRedzoneMagic:
|
||||
case kAsanHeapRightRedzoneMagic:
|
||||
return Red();
|
||||
case kAsanHeapFreeMagic:
|
||||
return Magenta();
|
||||
case kAsanStackLeftRedzoneMagic:
|
||||
case kAsanStackMidRedzoneMagic:
|
||||
case kAsanStackRightRedzoneMagic:
|
||||
case kAsanStackPartialRedzoneMagic:
|
||||
return Red();
|
||||
case kAsanStackAfterReturnMagic:
|
||||
return Magenta();
|
||||
case kAsanInitializationOrderMagic:
|
||||
return Cyan();
|
||||
case kAsanUserPoisonedMemoryMagic:
|
||||
return Blue();
|
||||
case kAsanStackUseAfterScopeMagic:
|
||||
return Magenta();
|
||||
case kAsanGlobalRedzoneMagic:
|
||||
return Red();
|
||||
case kAsanInternalHeapMagic:
|
||||
return Yellow();
|
||||
default:
|
||||
return Default();
|
||||
}
|
||||
}
|
||||
const char *EndShadowByte() { return Default(); }
|
||||
};
|
||||
|
||||
// ---------------------- Helper functions ----------------------- {{{1
|
||||
|
||||
static void PrintBytes(const char *before, uptr *a) {
|
||||
u8 *bytes = (u8*)a;
|
||||
uptr byte_num = (SANITIZER_WORDSIZE) / 8;
|
||||
Printf("%s%p:", before, (void*)a);
|
||||
for (uptr i = 0; i < byte_num; i++) {
|
||||
Printf(" %x%x", bytes[i] >> 4, bytes[i] & 15);
|
||||
static void PrintShadowByte(const char *before, u8 byte,
|
||||
const char *after = "\n") {
|
||||
Decorator d;
|
||||
Printf("%s%s%x%x%s%s", before,
|
||||
d.ShadowByte(byte), byte >> 4, byte & 15, d.EndShadowByte(), after);
|
||||
}
|
||||
|
||||
static void PrintShadowBytes(const char *before, u8 *bytes,
|
||||
u8 *guilty, uptr n) {
|
||||
Decorator d;
|
||||
if (before)
|
||||
Printf("%s%p:", before, bytes);
|
||||
for (uptr i = 0; i < n; i++) {
|
||||
u8 *p = bytes + i;
|
||||
const char *before = p == guilty ? "[" :
|
||||
p - 1 == guilty ? "" : " ";
|
||||
const char *after = p == guilty ? "]" : "";
|
||||
PrintShadowByte(before, *p, after);
|
||||
}
|
||||
Printf("\n");
|
||||
}
|
||||
@ -54,15 +122,35 @@ static void PrintShadowMemoryForAddress(uptr addr) {
|
||||
if (!AddrIsInMem(addr))
|
||||
return;
|
||||
uptr shadow_addr = MemToShadow(addr);
|
||||
Printf("Shadow byte and word:\n");
|
||||
Printf(" %p: %x\n", (void*)shadow_addr, *(unsigned char*)shadow_addr);
|
||||
uptr aligned_shadow = shadow_addr & ~(kWordSize - 1);
|
||||
PrintBytes(" ", (uptr*)(aligned_shadow));
|
||||
Printf("More shadow bytes:\n");
|
||||
for (int i = -4; i <= 4; i++) {
|
||||
const uptr n_bytes_per_row = 16;
|
||||
uptr aligned_shadow = shadow_addr & ~(n_bytes_per_row - 1);
|
||||
Printf("Shadow bytes around the buggy address:\n");
|
||||
for (int i = -5; i <= 5; i++) {
|
||||
const char *prefix = (i == 0) ? "=>" : " ";
|
||||
PrintBytes(prefix, (uptr*)(aligned_shadow + i * kWordSize));
|
||||
PrintShadowBytes(prefix,
|
||||
(u8*)(aligned_shadow + i * n_bytes_per_row),
|
||||
(u8*)shadow_addr, n_bytes_per_row);
|
||||
}
|
||||
Printf("Shadow byte legend (one shadow byte represents %d "
|
||||
"application bytes):\n", (int)SHADOW_GRANULARITY);
|
||||
PrintShadowByte(" Addressable: ", 0);
|
||||
Printf(" Partially addressable: ");
|
||||
for (uptr i = 1; i < SHADOW_GRANULARITY; i++)
|
||||
PrintShadowByte("", i, " ");
|
||||
Printf("\n");
|
||||
PrintShadowByte(" Heap left redzone: ", kAsanHeapLeftRedzoneMagic);
|
||||
PrintShadowByte(" Heap righ redzone: ", kAsanHeapRightRedzoneMagic);
|
||||
PrintShadowByte(" Freed Heap region: ", kAsanHeapFreeMagic);
|
||||
PrintShadowByte(" Stack left redzone: ", kAsanStackLeftRedzoneMagic);
|
||||
PrintShadowByte(" Stack mid redzone: ", kAsanStackMidRedzoneMagic);
|
||||
PrintShadowByte(" Stack right redzone: ", kAsanStackRightRedzoneMagic);
|
||||
PrintShadowByte(" Stack partial redzone: ", kAsanStackPartialRedzoneMagic);
|
||||
PrintShadowByte(" Stack after return: ", kAsanStackAfterReturnMagic);
|
||||
PrintShadowByte(" Stack use after scope: ", kAsanStackUseAfterScopeMagic);
|
||||
PrintShadowByte(" Global redzone: ", kAsanGlobalRedzoneMagic);
|
||||
PrintShadowByte(" Global init order: ", kAsanInitializationOrderMagic);
|
||||
PrintShadowByte(" Poisoned by user: ", kAsanUserPoisonedMemoryMagic);
|
||||
PrintShadowByte(" ASan internal: ", kAsanInternalHeapMagic);
|
||||
}
|
||||
|
||||
static void PrintZoneForPointer(uptr ptr, uptr zone_ptr,
|
||||
@ -98,6 +186,8 @@ static void PrintGlobalNameIfASCII(const __asan_global &g) {
|
||||
bool DescribeAddressRelativeToGlobal(uptr addr, const __asan_global &g) {
|
||||
if (addr < g.beg - kGlobalAndStackRedzone) return false;
|
||||
if (addr >= g.beg + g.size_with_redzone) return false;
|
||||
Decorator d;
|
||||
Printf("%s", d.Location());
|
||||
Printf("%p is located ", (void*)addr);
|
||||
if (addr < g.beg) {
|
||||
Printf("%zd bytes to the left", g.beg - addr);
|
||||
@ -108,6 +198,7 @@ bool DescribeAddressRelativeToGlobal(uptr addr, const __asan_global &g) {
|
||||
}
|
||||
Printf(" of global variable '%s' (0x%zx) of size %zu\n",
|
||||
g.name, g.beg, g.size);
|
||||
Printf("%s", d.EndLocation());
|
||||
PrintGlobalNameIfASCII(g);
|
||||
return true;
|
||||
}
|
||||
@ -151,9 +242,12 @@ bool DescribeAddressIfStack(uptr addr, uptr access_size) {
|
||||
internal_strncat(buf, frame_descr,
|
||||
Min(kBufSize,
|
||||
static_cast<sptr>(name_end - frame_descr)));
|
||||
Decorator d;
|
||||
Printf("%s", d.Location());
|
||||
Printf("Address %p is located at offset %zu "
|
||||
"in frame <%s> of T%d's stack:\n",
|
||||
(void*)addr, offset, buf, t->tid());
|
||||
(void*)addr, offset, Demangle(buf), t->tid());
|
||||
Printf("%s", d.EndLocation());
|
||||
// Report the number of stack objects.
|
||||
char *p;
|
||||
uptr n_objects = internal_simple_strtoll(name_end, &p, 10);
|
||||
@ -187,6 +281,8 @@ bool DescribeAddressIfStack(uptr addr, uptr access_size) {
|
||||
static void DescribeAccessToHeapChunk(AsanChunkView chunk, uptr addr,
|
||||
uptr access_size) {
|
||||
uptr offset;
|
||||
Decorator d;
|
||||
Printf("%s", d.Location());
|
||||
Printf("%p is located ", (void*)addr);
|
||||
if (chunk.AddrIsInside(addr, access_size, &offset)) {
|
||||
Printf("%zu bytes inside of", offset);
|
||||
@ -199,6 +295,26 @@ static void DescribeAccessToHeapChunk(AsanChunkView chunk, uptr addr,
|
||||
}
|
||||
Printf(" %zu-byte region [%p,%p)\n", chunk.UsedSize(),
|
||||
(void*)(chunk.Beg()), (void*)(chunk.End()));
|
||||
Printf("%s", d.EndLocation());
|
||||
}
|
||||
|
||||
// Return " (thread_name) " or an empty string if the name is empty.
|
||||
const char *ThreadNameWithParenthesis(AsanThreadSummary *t, char buff[],
|
||||
uptr buff_len) {
|
||||
const char *name = t->name();
|
||||
if (*name == 0) return "";
|
||||
buff[0] = 0;
|
||||
internal_strncat(buff, " (", 3);
|
||||
internal_strncat(buff, name, buff_len - 4);
|
||||
internal_strncat(buff, ")", 2);
|
||||
return buff;
|
||||
}
|
||||
|
||||
const char *ThreadNameWithParenthesis(u32 tid, char buff[],
|
||||
uptr buff_len) {
|
||||
if (tid == kInvalidTid) return "";
|
||||
AsanThreadSummary *t = asanThreadRegistry().FindByTid(tid);
|
||||
return ThreadNameWithParenthesis(t, buff, buff_len);
|
||||
}
|
||||
|
||||
void DescribeHeapAddress(uptr addr, uptr access_size) {
|
||||
@ -212,20 +328,31 @@ void DescribeHeapAddress(uptr addr, uptr access_size) {
|
||||
chunk.GetAllocStack(&alloc_stack);
|
||||
AsanThread *t = asanThreadRegistry().GetCurrent();
|
||||
CHECK(t);
|
||||
char tname[128];
|
||||
Decorator d;
|
||||
if (chunk.FreeTid() != kInvalidTid) {
|
||||
AsanThreadSummary *free_thread =
|
||||
asanThreadRegistry().FindByTid(chunk.FreeTid());
|
||||
Printf("freed by thread T%d here:\n", free_thread->tid());
|
||||
Printf("%sfreed by thread T%d%s here:%s\n", d.Allocation(),
|
||||
free_thread->tid(),
|
||||
ThreadNameWithParenthesis(free_thread, tname, sizeof(tname)),
|
||||
d.EndAllocation());
|
||||
StackTrace free_stack;
|
||||
chunk.GetFreeStack(&free_stack);
|
||||
PrintStack(&free_stack);
|
||||
Printf("previously allocated by thread T%d here:\n", alloc_thread->tid());
|
||||
Printf("%spreviously allocated by thread T%d%s here:%s\n",
|
||||
d.Allocation(), alloc_thread->tid(),
|
||||
ThreadNameWithParenthesis(alloc_thread, tname, sizeof(tname)),
|
||||
d.EndAllocation());
|
||||
PrintStack(&alloc_stack);
|
||||
DescribeThread(t->summary());
|
||||
DescribeThread(free_thread);
|
||||
DescribeThread(alloc_thread);
|
||||
} else {
|
||||
Printf("allocated by thread T%d here:\n", alloc_thread->tid());
|
||||
Printf("%sallocated by thread T%d%s here:%s\n", d.Allocation(),
|
||||
alloc_thread->tid(),
|
||||
ThreadNameWithParenthesis(alloc_thread, tname, sizeof(tname)),
|
||||
d.EndAllocation());
|
||||
PrintStack(&alloc_stack);
|
||||
DescribeThread(t->summary());
|
||||
DescribeThread(alloc_thread);
|
||||
@ -254,8 +381,13 @@ void DescribeThread(AsanThreadSummary *summary) {
|
||||
return;
|
||||
}
|
||||
summary->set_announced(true);
|
||||
Printf("Thread T%d created by T%d here:\n",
|
||||
summary->tid(), summary->parent_tid());
|
||||
char tname[128];
|
||||
Printf("Thread T%d%s", summary->tid(),
|
||||
ThreadNameWithParenthesis(summary->tid(), tname, sizeof(tname)));
|
||||
Printf(" created by T%d%s here:\n",
|
||||
summary->parent_tid(),
|
||||
ThreadNameWithParenthesis(summary->parent_tid(),
|
||||
tname, sizeof(tname)));
|
||||
PrintStack(summary->stack());
|
||||
// Recursively described parent thread if needed.
|
||||
if (flags()->print_full_thread_history) {
|
||||
@ -291,7 +423,7 @@ class ScopedInErrorReport {
|
||||
// Die() to bypass any additional checks.
|
||||
Exit(flags()->exitcode);
|
||||
}
|
||||
__asan_on_error();
|
||||
ASAN_ON_ERROR();
|
||||
reporting_thread_tid = asanThreadRegistry().GetCurrentTidOrInvalid();
|
||||
Printf("===================================================="
|
||||
"=============\n");
|
||||
@ -322,44 +454,79 @@ class ScopedInErrorReport {
|
||||
|
||||
void ReportSIGSEGV(uptr pc, uptr sp, uptr bp, uptr addr) {
|
||||
ScopedInErrorReport in_report;
|
||||
Decorator d;
|
||||
Printf("%s", d.Warning());
|
||||
Report("ERROR: AddressSanitizer: SEGV on unknown address %p"
|
||||
" (pc %p sp %p bp %p T%d)\n",
|
||||
(void*)addr, (void*)pc, (void*)sp, (void*)bp,
|
||||
asanThreadRegistry().GetCurrentTidOrInvalid());
|
||||
Printf("%s", d.EndWarning());
|
||||
Printf("AddressSanitizer can not provide additional info.\n");
|
||||
GET_STACK_TRACE_WITH_PC_AND_BP(kStackTraceMax, pc, bp);
|
||||
GET_STACK_TRACE_FATAL(pc, bp);
|
||||
PrintStack(&stack);
|
||||
}
|
||||
|
||||
void ReportDoubleFree(uptr addr, StackTrace *stack) {
|
||||
ScopedInErrorReport in_report;
|
||||
Decorator d;
|
||||
Printf("%s", d.Warning());
|
||||
Report("ERROR: AddressSanitizer: attempting double-free on %p:\n", addr);
|
||||
Printf("%s", d.EndWarning());
|
||||
PrintStack(stack);
|
||||
DescribeHeapAddress(addr, 1);
|
||||
}
|
||||
|
||||
void ReportFreeNotMalloced(uptr addr, StackTrace *stack) {
|
||||
ScopedInErrorReport in_report;
|
||||
Decorator d;
|
||||
Printf("%s", d.Warning());
|
||||
Report("ERROR: AddressSanitizer: attempting free on address "
|
||||
"which was not malloc()-ed: %p\n", addr);
|
||||
Printf("%s", d.EndWarning());
|
||||
PrintStack(stack);
|
||||
DescribeHeapAddress(addr, 1);
|
||||
}
|
||||
|
||||
void ReportAllocTypeMismatch(uptr addr, StackTrace *stack,
|
||||
AllocType alloc_type,
|
||||
AllocType dealloc_type) {
|
||||
static const char *alloc_names[] =
|
||||
{"INVALID", "malloc", "operator new", "operator new []"};
|
||||
static const char *dealloc_names[] =
|
||||
{"INVALID", "free", "operator delete", "operator delete []"};
|
||||
CHECK_NE(alloc_type, dealloc_type);
|
||||
ScopedInErrorReport in_report;
|
||||
Decorator d;
|
||||
Printf("%s", d.Warning());
|
||||
Report("ERROR: AddressSanitizer: alloc-dealloc-mismatch (%s vs %s) on %p\n",
|
||||
alloc_names[alloc_type], dealloc_names[dealloc_type], addr);
|
||||
Printf("%s", d.EndWarning());
|
||||
PrintStack(stack);
|
||||
DescribeHeapAddress(addr, 1);
|
||||
Report("HINT: if you don't care about these warnings you may set "
|
||||
"ASAN_OPTIONS=alloc_dealloc_mismatch=0\n");
|
||||
}
|
||||
|
||||
void ReportMallocUsableSizeNotOwned(uptr addr, StackTrace *stack) {
|
||||
ScopedInErrorReport in_report;
|
||||
Decorator d;
|
||||
Printf("%s", d.Warning());
|
||||
Report("ERROR: AddressSanitizer: attempting to call "
|
||||
"malloc_usable_size() for pointer which is "
|
||||
"not owned: %p\n", addr);
|
||||
Printf("%s", d.EndWarning());
|
||||
PrintStack(stack);
|
||||
DescribeHeapAddress(addr, 1);
|
||||
}
|
||||
|
||||
void ReportAsanGetAllocatedSizeNotOwned(uptr addr, StackTrace *stack) {
|
||||
ScopedInErrorReport in_report;
|
||||
Decorator d;
|
||||
Printf("%s", d.Warning());
|
||||
Report("ERROR: AddressSanitizer: attempting to call "
|
||||
"__asan_get_allocated_size() for pointer which is "
|
||||
"not owned: %p\n", addr);
|
||||
Printf("%s", d.EndWarning());
|
||||
PrintStack(stack);
|
||||
DescribeHeapAddress(addr, 1);
|
||||
}
|
||||
@ -368,9 +535,12 @@ void ReportStringFunctionMemoryRangesOverlap(
|
||||
const char *function, const char *offset1, uptr length1,
|
||||
const char *offset2, uptr length2, StackTrace *stack) {
|
||||
ScopedInErrorReport in_report;
|
||||
Decorator d;
|
||||
Printf("%s", d.Warning());
|
||||
Report("ERROR: AddressSanitizer: %s-param-overlap: "
|
||||
"memory ranges [%p,%p) and [%p, %p) overlap\n", \
|
||||
function, offset1, offset1 + length1, offset2, offset2 + length2);
|
||||
Printf("%s", d.EndWarning());
|
||||
PrintStack(stack);
|
||||
DescribeAddress((uptr)offset1, length1);
|
||||
DescribeAddress((uptr)offset2, length2);
|
||||
@ -463,17 +633,23 @@ void __asan_report_error(uptr pc, uptr bp, uptr sp,
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
Decorator d;
|
||||
Printf("%s", d.Warning());
|
||||
Report("ERROR: AddressSanitizer: %s on address "
|
||||
"%p at pc 0x%zx bp 0x%zx sp 0x%zx\n",
|
||||
bug_descr, (void*)addr, pc, bp, sp);
|
||||
Printf("%s", d.EndWarning());
|
||||
|
||||
u32 curr_tid = asanThreadRegistry().GetCurrentTidOrInvalid();
|
||||
Printf("%s of size %zu at %p thread T%d\n",
|
||||
access_size ? (is_write ? "WRITE" : "READ") : "ACCESS",
|
||||
access_size, (void*)addr, curr_tid);
|
||||
char tname[128];
|
||||
Printf("%s%s of size %zu at %p thread T%d%s%s\n",
|
||||
d.Access(),
|
||||
access_size ? (is_write ? "WRITE" : "READ") : "ACCESS",
|
||||
access_size, (void*)addr, curr_tid,
|
||||
ThreadNameWithParenthesis(curr_tid, tname, sizeof(tname)),
|
||||
d.EndAccess());
|
||||
|
||||
GET_STACK_TRACE_WITH_PC_AND_BP(kStackTraceMax, pc, bp);
|
||||
GET_STACK_TRACE_FATAL(pc, bp);
|
||||
PrintStack(&stack);
|
||||
|
||||
DescribeAddress(addr, access_size);
|
||||
@ -491,7 +667,13 @@ void NOINLINE __asan_set_error_report_callback(void (*callback)(const char*)) {
|
||||
}
|
||||
}
|
||||
|
||||
void __asan_describe_address(uptr addr) {
|
||||
DescribeAddress(addr, 1);
|
||||
}
|
||||
|
||||
#if !SANITIZER_SUPPORTS_WEAK_HOOKS
|
||||
// Provide default implementation of __asan_on_error that does nothing
|
||||
// and may be overriden by user.
|
||||
SANITIZER_WEAK_ATTRIBUTE SANITIZER_INTERFACE_ATTRIBUTE NOINLINE
|
||||
void __asan_on_error() {}
|
||||
#endif
|
||||
|
@ -10,6 +10,7 @@
|
||||
// ASan-private header for error reporting functions.
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
#include "asan_allocator.h"
|
||||
#include "asan_internal.h"
|
||||
#include "asan_thread.h"
|
||||
#include "sanitizer/asan_interface.h"
|
||||
@ -32,6 +33,9 @@ void DescribeThread(AsanThreadSummary *summary);
|
||||
void NORETURN ReportSIGSEGV(uptr pc, uptr sp, uptr bp, uptr addr);
|
||||
void NORETURN ReportDoubleFree(uptr addr, StackTrace *stack);
|
||||
void NORETURN ReportFreeNotMalloced(uptr addr, StackTrace *stack);
|
||||
void NORETURN ReportAllocTypeMismatch(uptr addr, StackTrace *stack,
|
||||
AllocType alloc_type,
|
||||
AllocType dealloc_type);
|
||||
void NORETURN ReportMallocUsableSizeNotOwned(uptr addr,
|
||||
StackTrace *stack);
|
||||
void NORETURN ReportAsanGetAllocatedSizeNotOwned(uptr addr,
|
||||
|
@ -52,7 +52,7 @@ static void AsanCheckFailed(const char *file, int line, const char *cond,
|
||||
file, line, cond, (uptr)v1, (uptr)v2);
|
||||
// FIXME: check for infinite recursion without a thread-local counter here.
|
||||
PRINT_CURRENT_STACK();
|
||||
ShowStatsAndAbort();
|
||||
Die();
|
||||
}
|
||||
|
||||
// -------------------------- Flags ------------------------- {{{1
|
||||
@ -64,6 +64,10 @@ Flags *flags() {
|
||||
return &asan_flags;
|
||||
}
|
||||
|
||||
static const char *MaybeCallAsanDefaultOptions() {
|
||||
return (&__asan_default_options) ? __asan_default_options() : "";
|
||||
}
|
||||
|
||||
static void ParseFlagsFromString(Flags *f, const char *str) {
|
||||
ParseFlag(str, &f->quarantine_size, "quarantine_size");
|
||||
ParseFlag(str, &f->symbolize, "symbolize");
|
||||
@ -98,21 +102,20 @@ static void ParseFlagsFromString(Flags *f, const char *str) {
|
||||
ParseFlag(str, &f->allow_reexec, "allow_reexec");
|
||||
ParseFlag(str, &f->print_full_thread_history, "print_full_thread_history");
|
||||
ParseFlag(str, &f->log_path, "log_path");
|
||||
ParseFlag(str, &f->fast_unwind_on_fatal, "fast_unwind_on_fatal");
|
||||
ParseFlag(str, &f->fast_unwind_on_malloc, "fast_unwind_on_malloc");
|
||||
ParseFlag(str, &f->poison_heap, "poison_heap");
|
||||
ParseFlag(str, &f->alloc_dealloc_mismatch, "alloc_dealloc_mismatch");
|
||||
ParseFlag(str, &f->use_stack_depot, "use_stack_depot");
|
||||
}
|
||||
|
||||
extern "C" {
|
||||
SANITIZER_WEAK_ATTRIBUTE
|
||||
SANITIZER_INTERFACE_ATTRIBUTE
|
||||
const char* __asan_default_options() { return ""; }
|
||||
} // extern "C"
|
||||
|
||||
void InitializeFlags(Flags *f, const char *env) {
|
||||
internal_memset(f, 0, sizeof(*f));
|
||||
|
||||
f->quarantine_size = (ASAN_LOW_MEMORY) ? 1UL << 26 : 1UL << 28;
|
||||
f->symbolize = false;
|
||||
f->verbosity = 0;
|
||||
f->redzone = (ASAN_LOW_MEMORY) ? 64 : 128;
|
||||
f->redzone = ASAN_ALLOCATOR_VERSION == 2 ? 16 : (ASAN_LOW_MEMORY) ? 64 : 128;
|
||||
f->debug = false;
|
||||
f->report_globals = 1;
|
||||
f->check_initialization_order = true;
|
||||
@ -137,12 +140,17 @@ void InitializeFlags(Flags *f, const char *env) {
|
||||
f->allow_reexec = true;
|
||||
f->print_full_thread_history = true;
|
||||
f->log_path = 0;
|
||||
f->fast_unwind_on_fatal = true;
|
||||
f->fast_unwind_on_malloc = true;
|
||||
f->poison_heap = true;
|
||||
f->alloc_dealloc_mismatch = true;
|
||||
f->use_stack_depot = true; // Only affects allocator2.
|
||||
|
||||
// Override from user-specified string.
|
||||
ParseFlagsFromString(f, __asan_default_options());
|
||||
ParseFlagsFromString(f, MaybeCallAsanDefaultOptions());
|
||||
if (flags()->verbosity) {
|
||||
Report("Using the defaults from __asan_default_options: %s\n",
|
||||
__asan_default_options());
|
||||
MaybeCallAsanDefaultOptions());
|
||||
}
|
||||
|
||||
// Override from command line.
|
||||
@ -239,15 +247,12 @@ static NOINLINE void force_interface_symbols() {
|
||||
case 27: __asan_set_error_exit_code(0); break;
|
||||
case 28: __asan_stack_free(0, 0, 0); break;
|
||||
case 29: __asan_stack_malloc(0, 0); break;
|
||||
case 30: __asan_on_error(); break;
|
||||
case 31: __asan_default_options(); break;
|
||||
case 32: __asan_before_dynamic_init(0, 0); break;
|
||||
case 33: __asan_after_dynamic_init(); break;
|
||||
case 34: __asan_malloc_hook(0, 0); break;
|
||||
case 35: __asan_free_hook(0); break;
|
||||
case 36: __asan_symbolize(0, 0, 0); break;
|
||||
case 37: __asan_poison_stack_memory(0, 0); break;
|
||||
case 38: __asan_unpoison_stack_memory(0, 0); break;
|
||||
case 30: __asan_before_dynamic_init(0, 0); break;
|
||||
case 31: __asan_after_dynamic_init(); break;
|
||||
case 32: __asan_poison_stack_memory(0, 0); break;
|
||||
case 33: __asan_unpoison_stack_memory(0, 0); break;
|
||||
case 34: __asan_region_is_poisoned(0, 0); break;
|
||||
case 35: __asan_describe_address(0); break;
|
||||
}
|
||||
}
|
||||
|
||||
@ -261,6 +266,13 @@ static void asan_atexit() {
|
||||
// ---------------------- Interface ---------------- {{{1
|
||||
using namespace __asan; // NOLINT
|
||||
|
||||
#if !SANITIZER_SUPPORTS_WEAK_HOOKS
|
||||
extern "C" {
|
||||
SANITIZER_WEAK_ATTRIBUTE SANITIZER_INTERFACE_ATTRIBUTE
|
||||
const char* __asan_default_options() { return ""; }
|
||||
} // extern "C"
|
||||
#endif
|
||||
|
||||
int NOINLINE __asan_set_error_exit_code(int exit_code) {
|
||||
int old = flags()->exitcode;
|
||||
flags()->exitcode = exit_code;
|
||||
|
@ -15,9 +15,15 @@
|
||||
|
||||
namespace __asan {
|
||||
|
||||
static bool MaybeCallAsanSymbolize(const void *pc, char *out_buffer,
|
||||
int out_size) {
|
||||
return (&__asan_symbolize) ? __asan_symbolize(pc, out_buffer, out_size)
|
||||
: false;
|
||||
}
|
||||
|
||||
void PrintStack(StackTrace *stack) {
|
||||
stack->PrintStack(stack->trace, stack->size, flags()->symbolize,
|
||||
flags()->strip_path_prefix, __asan_symbolize);
|
||||
flags()->strip_path_prefix, MaybeCallAsanSymbolize);
|
||||
}
|
||||
|
||||
} // namespace __asan
|
||||
@ -27,7 +33,7 @@ void PrintStack(StackTrace *stack) {
|
||||
// Provide default implementation of __asan_symbolize that does nothing
|
||||
// and may be overriden by user if he wants to use his own symbolization.
|
||||
// ASan on Windows has its own implementation of this.
|
||||
#ifndef _WIN32
|
||||
#if !defined(_WIN32) && !SANITIZER_SUPPORTS_WEAK_HOOKS
|
||||
SANITIZER_WEAK_ATTRIBUTE SANITIZER_INTERFACE_ATTRIBUTE NOINLINE
|
||||
bool __asan_symbolize(const void *pc, char *out_buffer, int out_size) {
|
||||
return false;
|
||||
|
@ -13,10 +13,11 @@
|
||||
#define ASAN_STACK_H
|
||||
|
||||
#include "sanitizer_common/sanitizer_stacktrace.h"
|
||||
#include "asan_flags.h"
|
||||
|
||||
namespace __asan {
|
||||
|
||||
void GetStackTrace(StackTrace *stack, uptr max_s, uptr pc, uptr bp);
|
||||
void GetStackTrace(StackTrace *stack, uptr max_s, uptr pc, uptr bp, bool fast);
|
||||
void PrintStack(StackTrace *stack);
|
||||
|
||||
} // namespace __asan
|
||||
@ -25,27 +26,38 @@ void PrintStack(StackTrace *stack);
|
||||
// The pc will be in the position 0 of the resulting stack trace.
|
||||
// The bp may refer to the current frame or to the caller's frame.
|
||||
// fast_unwind is currently unused.
|
||||
#define GET_STACK_TRACE_WITH_PC_AND_BP(max_s, pc, bp) \
|
||||
#define GET_STACK_TRACE_WITH_PC_AND_BP(max_s, pc, bp, fast) \
|
||||
StackTrace stack; \
|
||||
GetStackTrace(&stack, max_s, pc, bp)
|
||||
GetStackTrace(&stack, max_s, pc, bp, fast)
|
||||
|
||||
// NOTE: A Rule of thumb is to retrieve stack trace in the interceptors
|
||||
// as early as possible (in functions exposed to the user), as we generally
|
||||
// don't want stack trace to contain functions from ASan internals.
|
||||
|
||||
#define GET_STACK_TRACE_HERE(max_size) \
|
||||
#define GET_STACK_TRACE(max_size, fast) \
|
||||
GET_STACK_TRACE_WITH_PC_AND_BP(max_size, \
|
||||
StackTrace::GetCurrentPc(), GET_CURRENT_FRAME())
|
||||
StackTrace::GetCurrentPc(), GET_CURRENT_FRAME(), fast)
|
||||
|
||||
#define GET_STACK_TRACE_HERE_FOR_MALLOC \
|
||||
GET_STACK_TRACE_HERE(flags()->malloc_context_size)
|
||||
#define GET_STACK_TRACE_FATAL(pc, bp) \
|
||||
GET_STACK_TRACE_WITH_PC_AND_BP(kStackTraceMax, pc, bp, \
|
||||
flags()->fast_unwind_on_fatal)
|
||||
|
||||
#define GET_STACK_TRACE_HERE_FOR_FREE(ptr) \
|
||||
GET_STACK_TRACE_HERE(flags()->malloc_context_size)
|
||||
#define GET_STACK_TRACE_FATAL_HERE \
|
||||
GET_STACK_TRACE(kStackTraceMax, flags()->fast_unwind_on_fatal)
|
||||
|
||||
#define GET_STACK_TRACE_THREAD \
|
||||
GET_STACK_TRACE(kStackTraceMax, true)
|
||||
|
||||
#define GET_STACK_TRACE_MALLOC \
|
||||
GET_STACK_TRACE(flags()->malloc_context_size, \
|
||||
flags()->fast_unwind_on_malloc)
|
||||
|
||||
#define GET_STACK_TRACE_FREE GET_STACK_TRACE_MALLOC
|
||||
|
||||
#define PRINT_CURRENT_STACK() \
|
||||
{ \
|
||||
GET_STACK_TRACE_HERE(kStackTraceMax); \
|
||||
GET_STACK_TRACE(kStackTraceMax, \
|
||||
flags()->fast_unwind_on_fatal); \
|
||||
PrintStack(&stack); \
|
||||
}
|
||||
|
||||
|
@ -15,6 +15,7 @@
|
||||
#include "asan_stats.h"
|
||||
#include "asan_thread_registry.h"
|
||||
#include "sanitizer/asan_interface.h"
|
||||
#include "sanitizer_common/sanitizer_stackdepot.h"
|
||||
|
||||
namespace __asan {
|
||||
|
||||
@ -40,8 +41,9 @@ void AsanStats::Print() {
|
||||
Printf("Stats: %zuM freed by %zu calls\n", freed>>20, frees);
|
||||
Printf("Stats: %zuM really freed by %zu calls\n",
|
||||
really_freed>>20, real_frees);
|
||||
Printf("Stats: %zuM (%zu full pages) mmaped in %zu calls\n",
|
||||
mmaped>>20, mmaped / GetPageSizeCached(), mmaps);
|
||||
Printf("Stats: %zuM (%zuM-%zuM) mmaped; %zu maps, %zu unmaps\n",
|
||||
(mmaped-munmaped)>>20, mmaped>>20, munmaped>>20,
|
||||
mmaps, munmaps);
|
||||
|
||||
PrintMallocStatsArray(" mmaps by size class: ", mmaped_by_size);
|
||||
PrintMallocStatsArray(" mallocs by size class: ", malloced_by_size);
|
||||
@ -59,6 +61,10 @@ static void PrintAccumulatedStats() {
|
||||
// Use lock to keep reports from mixing up.
|
||||
ScopedLock lock(&print_lock);
|
||||
stats.Print();
|
||||
StackDepotStats *stack_depot_stats = StackDepotGetStats();
|
||||
Printf("Stats: StackDepot: %zd ids; %zdM mapped\n",
|
||||
stack_depot_stats->n_uniq_ids, stack_depot_stats->mapped >> 20);
|
||||
PrintInternalAllocatorStats();
|
||||
}
|
||||
|
||||
} // namespace __asan
|
||||
|
@ -35,6 +35,8 @@ struct AsanStats {
|
||||
uptr realloced;
|
||||
uptr mmaps;
|
||||
uptr mmaped;
|
||||
uptr munmaps;
|
||||
uptr munmaped;
|
||||
uptr mmaped_by_size[kNumberOfSizeClasses];
|
||||
uptr malloced_by_size[kNumberOfSizeClasses];
|
||||
uptr freed_by_size[kNumberOfSizeClasses];
|
||||
|
@ -37,6 +37,7 @@ class AsanThreadSummary {
|
||||
internal_memcpy(&stack_, stack, sizeof(*stack));
|
||||
}
|
||||
thread_ = 0;
|
||||
name_[0] = 0;
|
||||
}
|
||||
u32 tid() { return tid_; }
|
||||
void set_tid(u32 tid) { tid_ = tid; }
|
||||
@ -47,6 +48,10 @@ class AsanThreadSummary {
|
||||
AsanThread *thread() { return thread_; }
|
||||
void set_thread(AsanThread *thread) { thread_ = thread; }
|
||||
static void TSDDtor(void *tsd);
|
||||
void set_name(const char *name) {
|
||||
internal_strncpy(name_, name, sizeof(name_) - 1);
|
||||
}
|
||||
const char *name() { return name_; }
|
||||
|
||||
private:
|
||||
u32 tid_;
|
||||
@ -54,8 +59,12 @@ class AsanThreadSummary {
|
||||
bool announced_;
|
||||
StackTrace stack_;
|
||||
AsanThread *thread_;
|
||||
char name_[128];
|
||||
};
|
||||
|
||||
// AsanThreadSummary objects are never freed, so we need many of them.
|
||||
COMPILER_CHECK(sizeof(AsanThreadSummary) <= 4094);
|
||||
|
||||
// AsanThread are stored in TSD and destroyed when the thread dies.
|
||||
class AsanThread {
|
||||
public:
|
||||
|
@ -121,13 +121,14 @@ uptr AsanThreadRegistry::GetCurrentAllocatedBytes() {
|
||||
uptr AsanThreadRegistry::GetHeapSize() {
|
||||
ScopedLock lock(&mu_);
|
||||
UpdateAccumulatedStatsUnlocked();
|
||||
return accumulated_stats_.mmaped;
|
||||
return accumulated_stats_.mmaped - accumulated_stats_.munmaped;
|
||||
}
|
||||
|
||||
uptr AsanThreadRegistry::GetFreeBytes() {
|
||||
ScopedLock lock(&mu_);
|
||||
UpdateAccumulatedStatsUnlocked();
|
||||
uptr total_free = accumulated_stats_.mmaped
|
||||
- accumulated_stats_.munmaped
|
||||
+ accumulated_stats_.really_freed
|
||||
+ accumulated_stats_.really_freed_redzones;
|
||||
uptr total_used = accumulated_stats_.malloced
|
||||
|
@ -30,7 +30,8 @@ static AsanLock dbghelp_lock(LINKER_INITIALIZED);
|
||||
static bool dbghelp_initialized = false;
|
||||
#pragma comment(lib, "dbghelp.lib")
|
||||
|
||||
void GetStackTrace(StackTrace *stack, uptr max_s, uptr pc, uptr bp) {
|
||||
void GetStackTrace(StackTrace *stack, uptr max_s, uptr pc, uptr bp, bool fast) {
|
||||
(void)fast;
|
||||
stack->max_size = max_s;
|
||||
void *tmp[kStackTraceMax];
|
||||
|
||||
|
@ -115,6 +115,15 @@ extern "C" {
|
||||
bool __asan_address_is_poisoned(void const volatile *addr)
|
||||
SANITIZER_INTERFACE_ATTRIBUTE;
|
||||
|
||||
// If at least on byte in [beg, beg+size) is poisoned, return the address
|
||||
// of the first such byte. Otherwise return 0.
|
||||
uptr __asan_region_is_poisoned(uptr beg, uptr size)
|
||||
SANITIZER_INTERFACE_ATTRIBUTE;
|
||||
|
||||
// Print the description of addr (useful when debugging in gdb).
|
||||
void __asan_describe_address(uptr addr)
|
||||
SANITIZER_INTERFACE_ATTRIBUTE;
|
||||
|
||||
// This is an internal function that is called to report an error.
|
||||
// However it is still a part of the interface because users may want to
|
||||
// set a breakpoint on this function in a debugger.
|
||||
@ -138,7 +147,7 @@ extern "C" {
|
||||
// User may provide function that would be called right when ASan detects
|
||||
// an error. This can be used to notice cases when ASan detects an error, but
|
||||
// the program crashes before ASan report is printed.
|
||||
void __asan_on_error()
|
||||
/* OPTIONAL */ void __asan_on_error()
|
||||
SANITIZER_WEAK_ATTRIBUTE SANITIZER_INTERFACE_ATTRIBUTE;
|
||||
|
||||
// User may provide its own implementation for symbolization function.
|
||||
@ -146,7 +155,8 @@ extern "C" {
|
||||
// "out_buffer". Description should be at most "out_size" bytes long.
|
||||
// User-specified function should return true if symbolization was
|
||||
// successful.
|
||||
bool __asan_symbolize(const void *pc, char *out_buffer, int out_size)
|
||||
/* OPTIONAL */ bool __asan_symbolize(const void *pc, char *out_buffer,
|
||||
int out_size)
|
||||
SANITIZER_WEAK_ATTRIBUTE SANITIZER_INTERFACE_ATTRIBUTE;
|
||||
|
||||
// Returns the estimated number of bytes that will be reserved by allocator
|
||||
@ -186,20 +196,19 @@ extern "C" {
|
||||
void __asan_print_accumulated_stats()
|
||||
SANITIZER_INTERFACE_ATTRIBUTE;
|
||||
|
||||
// This function may be overriden by user to provide a string containing
|
||||
// ASan runtime options. See asan_flags.h for details.
|
||||
const char* __asan_default_options()
|
||||
// This function may be optionally provided by user and should return
|
||||
// a string containing ASan runtime options. See asan_flags.h for details.
|
||||
/* OPTIONAL */ const char* __asan_default_options()
|
||||
SANITIZER_WEAK_ATTRIBUTE SANITIZER_INTERFACE_ATTRIBUTE;
|
||||
|
||||
// Malloc hooks that may be overriden by user.
|
||||
// Malloc hooks that may be optionally provided by user.
|
||||
// __asan_malloc_hook(ptr, size) is called immediately after
|
||||
// allocation of "size" bytes, which returned "ptr".
|
||||
// __asan_free_hook(ptr) is called immediately before
|
||||
// deallocation of "ptr".
|
||||
// If user doesn't provide implementations of these hooks, they are no-op.
|
||||
void __asan_malloc_hook(void *ptr, uptr size)
|
||||
/* OPTIONAL */ void __asan_malloc_hook(void *ptr, uptr size)
|
||||
SANITIZER_WEAK_ATTRIBUTE SANITIZER_INTERFACE_ATTRIBUTE;
|
||||
void __asan_free_hook(void *ptr)
|
||||
/* OPTIONAL */ void __asan_free_hook(void *ptr)
|
||||
SANITIZER_WEAK_ATTRIBUTE SANITIZER_INTERFACE_ATTRIBUTE;
|
||||
} // extern "C"
|
||||
|
||||
|
@ -28,6 +28,12 @@
|
||||
# define SANITIZER_WEAK_ATTRIBUTE __attribute__((weak))
|
||||
#endif
|
||||
|
||||
#ifdef __linux__
|
||||
# define SANITIZER_SUPPORTS_WEAK_HOOKS 1
|
||||
#else
|
||||
# define SANITIZER_SUPPORTS_WEAK_HOOKS 0
|
||||
#endif
|
||||
|
||||
// __has_feature
|
||||
#if !defined(__has_feature)
|
||||
# define __has_feature(x) 0
|
||||
@ -73,6 +79,12 @@ extern "C" {
|
||||
// stderr.
|
||||
void __sanitizer_set_report_fd(int fd)
|
||||
SANITIZER_INTERFACE_ATTRIBUTE;
|
||||
|
||||
// Notify the tools that the sandbox is going to be turned on. The reserved
|
||||
// parameter will be used in the future to hold a structure with functions
|
||||
// that the tools may call to bypass the sandbox.
|
||||
void __sanitizer_sandbox_on_notify(void *reserved)
|
||||
SANITIZER_WEAK_ATTRIBUTE SANITIZER_INTERFACE_ATTRIBUTE;
|
||||
} // extern "C"
|
||||
|
||||
#endif // SANITIZER_COMMON_INTERFACE_DEFS_H
|
||||
|
@ -17,6 +17,15 @@
|
||||
# error "Interception doesn't work on this operating system."
|
||||
#endif
|
||||
|
||||
#include "sanitizer/common_interface_defs.h"
|
||||
|
||||
// These typedefs should be used only in the interceptor definitions to replace
|
||||
// the standard system types (e.g. SSIZE_T instead of ssize_t)
|
||||
typedef __sanitizer::uptr SIZE_T;
|
||||
typedef __sanitizer::sptr SSIZE_T;
|
||||
typedef __sanitizer::u64 OFF_T;
|
||||
typedef __sanitizer::u64 OFF64_T;
|
||||
|
||||
// How to use this library:
|
||||
// 1) Include this header to define your own interceptors
|
||||
// (see details below).
|
||||
|
@ -22,6 +22,7 @@ sanitizer_common_files = \
|
||||
sanitizer_stackdepot.cc \
|
||||
sanitizer_stacktrace.cc \
|
||||
sanitizer_symbolizer.cc \
|
||||
sanitizer_symbolizer_itanium.cc \
|
||||
sanitizer_symbolizer_linux.cc \
|
||||
sanitizer_symbolizer_mac.cc \
|
||||
sanitizer_symbolizer_win.cc \
|
||||
|
@ -59,9 +59,9 @@ am__objects_1 = sanitizer_allocator.lo sanitizer_common.lo \
|
||||
sanitizer_flags.lo sanitizer_libc.lo sanitizer_linux.lo \
|
||||
sanitizer_mac.lo sanitizer_posix.lo sanitizer_printf.lo \
|
||||
sanitizer_stackdepot.lo sanitizer_stacktrace.lo \
|
||||
sanitizer_symbolizer.lo sanitizer_symbolizer_linux.lo \
|
||||
sanitizer_symbolizer_mac.lo sanitizer_symbolizer_win.lo \
|
||||
sanitizer_win.lo
|
||||
sanitizer_symbolizer.lo sanitizer_symbolizer_itanium.lo \
|
||||
sanitizer_symbolizer_linux.lo sanitizer_symbolizer_mac.lo \
|
||||
sanitizer_symbolizer_win.lo sanitizer_win.lo
|
||||
am_libsanitizer_common_la_OBJECTS = $(am__objects_1)
|
||||
libsanitizer_common_la_OBJECTS = $(am_libsanitizer_common_la_OBJECTS)
|
||||
DEFAULT_INCLUDES = -I.@am__isrc@
|
||||
@ -236,6 +236,7 @@ sanitizer_common_files = \
|
||||
sanitizer_stackdepot.cc \
|
||||
sanitizer_stacktrace.cc \
|
||||
sanitizer_symbolizer.cc \
|
||||
sanitizer_symbolizer_itanium.cc \
|
||||
sanitizer_symbolizer_linux.cc \
|
||||
sanitizer_symbolizer_mac.cc \
|
||||
sanitizer_symbolizer_win.cc \
|
||||
@ -345,6 +346,7 @@ distclean-compile:
|
||||
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/sanitizer_stackdepot.Plo@am__quote@
|
||||
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/sanitizer_stacktrace.Plo@am__quote@
|
||||
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/sanitizer_symbolizer.Plo@am__quote@
|
||||
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/sanitizer_symbolizer_itanium.Plo@am__quote@
|
||||
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/sanitizer_symbolizer_linux.Plo@am__quote@
|
||||
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/sanitizer_symbolizer_mac.Plo@am__quote@
|
||||
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/sanitizer_symbolizer_win.Plo@am__quote@
|
||||
|
@ -20,76 +20,140 @@
|
||||
|
||||
namespace __sanitizer {
|
||||
|
||||
// Maps size class id to size and back.
|
||||
template <uptr l0, uptr l1, uptr l2, uptr l3, uptr l4, uptr l5,
|
||||
uptr s0, uptr s1, uptr s2, uptr s3, uptr s4,
|
||||
uptr c0, uptr c1, uptr c2, uptr c3, uptr c4>
|
||||
class SplineSizeClassMap {
|
||||
private:
|
||||
// Here we use a spline composed of 5 polynomials of oder 1.
|
||||
// The first size class is l0, then the classes go with step s0
|
||||
// untill they reach l1, after which they go with step s1 and so on.
|
||||
// Steps should be powers of two for cheap division.
|
||||
// The size of the last size class should be a power of two.
|
||||
// There should be at most 256 size classes.
|
||||
static const uptr u0 = 0 + (l1 - l0) / s0;
|
||||
static const uptr u1 = u0 + (l2 - l1) / s1;
|
||||
static const uptr u2 = u1 + (l3 - l2) / s2;
|
||||
static const uptr u3 = u2 + (l4 - l3) / s3;
|
||||
static const uptr u4 = u3 + (l5 - l4) / s4;
|
||||
// SizeClassMap maps allocation sizes into size classes and back.
|
||||
// Class 0 corresponds to size 0.
|
||||
// Classes 1 - 16 correspond to sizes 8 - 128 (size = class_id * 8).
|
||||
// Next 8 classes: 128 + i * 16 (i = 1 to 8).
|
||||
// Next 8 classes: 256 + i * 32 (i = 1 to 8).
|
||||
// ...
|
||||
// Next 8 classes: 2^k + i * 2^(k-3) (i = 1 to 8).
|
||||
// Last class corresponds to kMaxSize = 1 << kMaxSizeLog.
|
||||
//
|
||||
// This structure of the size class map gives us:
|
||||
// - Efficient table-free class-to-size and size-to-class functions.
|
||||
// - Difference between two consequent size classes is betweed 12% and 6%
|
||||
//
|
||||
// This class also gives a hint to a thread-caching allocator about the amount
|
||||
// of chunks that need to be cached per-thread:
|
||||
// - kMaxNumCached is the maximal number of chunks per size class.
|
||||
// - (1 << kMaxBytesCachedLog) is the maximal number of bytes per size class.
|
||||
//
|
||||
// Part of output of SizeClassMap::Print():
|
||||
// c00 => s: 0 diff: +0 00% l 0 cached: 0 0; id 0
|
||||
// c01 => s: 8 diff: +8 00% l 3 cached: 256 2048; id 1
|
||||
// c02 => s: 16 diff: +8 100% l 4 cached: 256 4096; id 2
|
||||
// ...
|
||||
// c07 => s: 56 diff: +8 16% l 5 cached: 256 14336; id 7
|
||||
//
|
||||
// c08 => s: 64 diff: +8 14% l 6 cached: 256 16384; id 8
|
||||
// ...
|
||||
// c15 => s: 120 diff: +8 07% l 6 cached: 256 30720; id 15
|
||||
//
|
||||
// c16 => s: 128 diff: +8 06% l 7 cached: 256 32768; id 16
|
||||
// c17 => s: 144 diff: +16 12% l 7 cached: 227 32688; id 17
|
||||
// ...
|
||||
// c23 => s: 240 diff: +16 07% l 7 cached: 136 32640; id 23
|
||||
//
|
||||
// c24 => s: 256 diff: +16 06% l 8 cached: 128 32768; id 24
|
||||
// c25 => s: 288 diff: +32 12% l 8 cached: 113 32544; id 25
|
||||
// ...
|
||||
// c31 => s: 480 diff: +32 07% l 8 cached: 68 32640; id 31
|
||||
//
|
||||
// c32 => s: 512 diff: +32 06% l 9 cached: 64 32768; id 32
|
||||
|
||||
|
||||
template <uptr kMaxSizeLog, uptr kMaxNumCached, uptr kMaxBytesCachedLog>
|
||||
class SizeClassMap {
|
||||
static const uptr kMinSizeLog = 3;
|
||||
static const uptr kMidSizeLog = kMinSizeLog + 4;
|
||||
static const uptr kMinSize = 1 << kMinSizeLog;
|
||||
static const uptr kMidSize = 1 << kMidSizeLog;
|
||||
static const uptr kMidClass = kMidSize / kMinSize;
|
||||
static const uptr S = 3;
|
||||
static const uptr M = (1 << S) - 1;
|
||||
|
||||
public:
|
||||
// The number of size classes should be a power of two for fast division.
|
||||
static const uptr kNumClasses = u4 + 1;
|
||||
static const uptr kMaxSize = l5;
|
||||
static const uptr kMinSize = l0;
|
||||
|
||||
COMPILER_CHECK(kNumClasses <= 256);
|
||||
COMPILER_CHECK((kNumClasses & (kNumClasses - 1)) == 0);
|
||||
COMPILER_CHECK((kMaxSize & (kMaxSize - 1)) == 0);
|
||||
static const uptr kMaxSize = 1 << kMaxSizeLog;
|
||||
static const uptr kNumClasses =
|
||||
kMidClass + ((kMaxSizeLog - kMidSizeLog) << S) + 1;
|
||||
COMPILER_CHECK(kNumClasses >= 32 && kNumClasses <= 256);
|
||||
static const uptr kNumClassesRounded =
|
||||
kNumClasses == 32 ? 32 :
|
||||
kNumClasses <= 64 ? 64 :
|
||||
kNumClasses <= 128 ? 128 : 256;
|
||||
|
||||
static uptr Size(uptr class_id) {
|
||||
if (class_id <= u0) return l0 + s0 * (class_id - 0);
|
||||
if (class_id <= u1) return l1 + s1 * (class_id - u0);
|
||||
if (class_id <= u2) return l2 + s2 * (class_id - u1);
|
||||
if (class_id <= u3) return l3 + s3 * (class_id - u2);
|
||||
if (class_id <= u4) return l4 + s4 * (class_id - u3);
|
||||
return 0;
|
||||
if (class_id <= kMidClass)
|
||||
return kMinSize * class_id;
|
||||
class_id -= kMidClass;
|
||||
uptr t = kMidSize << (class_id >> S);
|
||||
return t + (t >> S) * (class_id & M);
|
||||
}
|
||||
|
||||
static uptr ClassID(uptr size) {
|
||||
if (size <= l1) return 0 + (size - l0 + s0 - 1) / s0;
|
||||
if (size <= l2) return u0 + (size - l1 + s1 - 1) / s1;
|
||||
if (size <= l3) return u1 + (size - l2 + s2 - 1) / s2;
|
||||
if (size <= l4) return u2 + (size - l3 + s3 - 1) / s3;
|
||||
if (size <= l5) return u3 + (size - l4 + s4 - 1) / s4;
|
||||
return 0;
|
||||
if (size <= kMidSize)
|
||||
return (size + kMinSize - 1) >> kMinSizeLog;
|
||||
if (size > kMaxSize) return 0;
|
||||
uptr l = SANITIZER_WORDSIZE - 1 - __builtin_clzl(size);
|
||||
uptr hbits = (size >> (l - S)) & M;
|
||||
uptr lbits = size & ((1 << (l - S)) - 1);
|
||||
uptr l1 = l - kMidSizeLog;
|
||||
return kMidClass + (l1 << S) + hbits + (lbits > 0);
|
||||
}
|
||||
|
||||
static uptr MaxCached(uptr class_id) {
|
||||
if (class_id <= u0) return c0;
|
||||
if (class_id <= u1) return c1;
|
||||
if (class_id <= u2) return c2;
|
||||
if (class_id <= u3) return c3;
|
||||
if (class_id <= u4) return c4;
|
||||
return 0;
|
||||
if (class_id == 0) return 0;
|
||||
uptr n = (1UL << kMaxBytesCachedLog) / Size(class_id);
|
||||
return Max(1UL, Min(kMaxNumCached, n));
|
||||
}
|
||||
|
||||
static void Print() {
|
||||
uptr prev_s = 0;
|
||||
uptr total_cached = 0;
|
||||
for (uptr i = 0; i < kNumClasses; i++) {
|
||||
uptr s = Size(i);
|
||||
if (s >= kMidSize / 2 && (s & (s - 1)) == 0)
|
||||
Printf("\n");
|
||||
uptr d = s - prev_s;
|
||||
uptr p = prev_s ? (d * 100 / prev_s) : 0;
|
||||
uptr l = SANITIZER_WORDSIZE - 1 - __builtin_clzl(s);
|
||||
uptr cached = MaxCached(i) * s;
|
||||
Printf("c%02zd => s: %zd diff: +%zd %02zd%% l %zd "
|
||||
"cached: %zd %zd; id %zd\n",
|
||||
i, Size(i), d, p, l, MaxCached(i), cached, ClassID(s));
|
||||
total_cached += cached;
|
||||
prev_s = s;
|
||||
}
|
||||
Printf("Total cached: %zd\n", total_cached);
|
||||
}
|
||||
|
||||
static void Validate() {
|
||||
for (uptr c = 1; c < kNumClasses; c++) {
|
||||
// Printf("Validate: c%zd\n", c);
|
||||
uptr s = Size(c);
|
||||
CHECK_EQ(ClassID(s), c);
|
||||
if (c != kNumClasses - 1)
|
||||
CHECK_EQ(ClassID(s + 1), c + 1);
|
||||
CHECK_EQ(ClassID(s - 1), c);
|
||||
if (c)
|
||||
CHECK_GT(Size(c), Size(c-1));
|
||||
}
|
||||
CHECK_EQ(ClassID(kMaxSize + 1), 0);
|
||||
|
||||
for (uptr s = 1; s <= kMaxSize; s++) {
|
||||
uptr c = ClassID(s);
|
||||
// Printf("s%zd => c%zd\n", s, c);
|
||||
CHECK_LT(c, kNumClasses);
|
||||
CHECK_GE(Size(c), s);
|
||||
if (c > 0)
|
||||
CHECK_LT(Size(c-1), s);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
class DefaultSizeClassMap: public SplineSizeClassMap<
|
||||
/* l: */1 << 4, 1 << 9, 1 << 12, 1 << 15, 1 << 18, 1 << 21,
|
||||
/* s: */1 << 4, 1 << 6, 1 << 9, 1 << 12, 1 << 15,
|
||||
/* c: */256, 64, 16, 4, 1> {
|
||||
private:
|
||||
COMPILER_CHECK(kNumClasses == 256);
|
||||
};
|
||||
typedef SizeClassMap<15, 256, 16> DefaultSizeClassMap;
|
||||
typedef SizeClassMap<15, 64, 14> CompactSizeClassMap;
|
||||
|
||||
class CompactSizeClassMap: public SplineSizeClassMap<
|
||||
/* l: */1 << 3, 1 << 4, 1 << 7, 1 << 8, 1 << 12, 1 << 15,
|
||||
/* s: */1 << 3, 1 << 4, 1 << 7, 1 << 8, 1 << 12,
|
||||
/* c: */256, 64, 16, 4, 1> {
|
||||
private:
|
||||
COMPILER_CHECK(kNumClasses <= 32);
|
||||
};
|
||||
|
||||
struct AllocatorListNode {
|
||||
AllocatorListNode *next;
|
||||
@ -97,11 +161,45 @@ struct AllocatorListNode {
|
||||
|
||||
typedef IntrusiveList<AllocatorListNode> AllocatorFreeList;
|
||||
|
||||
// Move at most max_count chunks from allocate_from to allocate_to.
|
||||
// This function is better be a method of AllocatorFreeList, but we can't
|
||||
// inherit it from IntrusiveList as the ancient gcc complains about non-PODness.
|
||||
static inline uptr BulkMove(uptr max_count,
|
||||
AllocatorFreeList *allocate_from,
|
||||
AllocatorFreeList *allocate_to) {
|
||||
CHECK(!allocate_from->empty());
|
||||
CHECK(allocate_to->empty());
|
||||
uptr res = 0;
|
||||
if (allocate_from->size() <= max_count) {
|
||||
res = allocate_from->size();
|
||||
allocate_to->append_front(allocate_from);
|
||||
CHECK(allocate_from->empty());
|
||||
} else {
|
||||
for (uptr i = 0; i < max_count; i++) {
|
||||
AllocatorListNode *node = allocate_from->front();
|
||||
allocate_from->pop_front();
|
||||
allocate_to->push_front(node);
|
||||
}
|
||||
res = max_count;
|
||||
CHECK(!allocate_from->empty());
|
||||
}
|
||||
CHECK(!allocate_to->empty());
|
||||
return res;
|
||||
}
|
||||
|
||||
// Allocators call these callbacks on mmap/munmap.
|
||||
struct NoOpMapUnmapCallback {
|
||||
void OnMap(uptr p, uptr size) const { }
|
||||
void OnUnmap(uptr p, uptr size) const { }
|
||||
};
|
||||
|
||||
// SizeClassAllocator64 -- allocator for 64-bit address space.
|
||||
//
|
||||
// Space: a portion of address space of kSpaceSize bytes starting at
|
||||
// a fixed address (kSpaceBeg). Both constants are powers of two and
|
||||
// kSpaceBeg is kSpaceSize-aligned.
|
||||
// At the beginning the entire space is mprotect-ed, then small parts of it
|
||||
// are mapped on demand.
|
||||
//
|
||||
// Region: a part of Space dedicated to a single size class.
|
||||
// There are kNumClasses Regions of equal size.
|
||||
@ -112,22 +210,35 @@ typedef IntrusiveList<AllocatorListNode> AllocatorFreeList;
|
||||
// A Region looks like this:
|
||||
// UserChunk1 ... UserChunkN <gap> MetaChunkN ... MetaChunk1
|
||||
template <const uptr kSpaceBeg, const uptr kSpaceSize,
|
||||
const uptr kMetadataSize, class SizeClassMap>
|
||||
const uptr kMetadataSize, class SizeClassMap,
|
||||
class MapUnmapCallback = NoOpMapUnmapCallback>
|
||||
class SizeClassAllocator64 {
|
||||
public:
|
||||
void Init() {
|
||||
CHECK_EQ(AllocBeg(), reinterpret_cast<uptr>(MmapFixedNoReserve(
|
||||
AllocBeg(), AllocSize())));
|
||||
CHECK_EQ(kSpaceBeg,
|
||||
reinterpret_cast<uptr>(Mprotect(kSpaceBeg, kSpaceSize)));
|
||||
MapWithCallback(kSpaceEnd, AdditionalSize());
|
||||
}
|
||||
|
||||
bool CanAllocate(uptr size, uptr alignment) {
|
||||
void MapWithCallback(uptr beg, uptr size) {
|
||||
CHECK_EQ(beg, reinterpret_cast<uptr>(MmapFixedOrDie(beg, size)));
|
||||
MapUnmapCallback().OnMap(beg, size);
|
||||
}
|
||||
|
||||
void UnmapWithCallback(uptr beg, uptr size) {
|
||||
MapUnmapCallback().OnUnmap(beg, size);
|
||||
UnmapOrDie(reinterpret_cast<void *>(beg), size);
|
||||
}
|
||||
|
||||
static bool CanAllocate(uptr size, uptr alignment) {
|
||||
return size <= SizeClassMap::kMaxSize &&
|
||||
alignment <= SizeClassMap::kMaxSize;
|
||||
}
|
||||
|
||||
void *Allocate(uptr size, uptr alignment) {
|
||||
if (size < alignment) size = alignment;
|
||||
CHECK(CanAllocate(size, alignment));
|
||||
return AllocateBySizeClass(SizeClassMap::ClassID(size));
|
||||
return AllocateBySizeClass(ClassID(size));
|
||||
}
|
||||
|
||||
void Deallocate(void *p) {
|
||||
@ -143,18 +254,8 @@ class SizeClassAllocator64 {
|
||||
if (region->free_list.empty()) {
|
||||
PopulateFreeList(class_id, region);
|
||||
}
|
||||
CHECK(!region->free_list.empty());
|
||||
uptr count = SizeClassMap::MaxCached(class_id);
|
||||
if (region->free_list.size() <= count) {
|
||||
free_list->append_front(®ion->free_list);
|
||||
} else {
|
||||
for (uptr i = 0; i < count; i++) {
|
||||
AllocatorListNode *node = region->free_list.front();
|
||||
region->free_list.pop_front();
|
||||
free_list->push_front(node);
|
||||
}
|
||||
}
|
||||
CHECK(!free_list->empty());
|
||||
region->n_allocated += BulkMove(SizeClassMap::MaxCached(class_id),
|
||||
®ion->free_list, free_list);
|
||||
}
|
||||
|
||||
// Swallow the entire free_list for the given class_id.
|
||||
@ -162,6 +263,7 @@ class SizeClassAllocator64 {
|
||||
CHECK_LT(class_id, kNumClasses);
|
||||
RegionInfo *region = GetRegionInfo(class_id);
|
||||
SpinMutexLock l(®ion->mutex);
|
||||
region->n_freed += free_list->size();
|
||||
region->free_list.append_front(free_list);
|
||||
}
|
||||
|
||||
@ -170,16 +272,20 @@ class SizeClassAllocator64 {
|
||||
}
|
||||
|
||||
static uptr GetSizeClass(void *p) {
|
||||
return (reinterpret_cast<uptr>(p) / kRegionSize) % kNumClasses;
|
||||
return (reinterpret_cast<uptr>(p) / kRegionSize) % kNumClassesRounded;
|
||||
}
|
||||
|
||||
static void *GetBlockBegin(void *p) {
|
||||
void *GetBlockBegin(void *p) {
|
||||
uptr class_id = GetSizeClass(p);
|
||||
uptr size = SizeClassMap::Size(class_id);
|
||||
uptr chunk_idx = GetChunkIdx((uptr)p, size);
|
||||
uptr reg_beg = (uptr)p & ~(kRegionSize - 1);
|
||||
uptr begin = reg_beg + chunk_idx * size;
|
||||
return (void*)begin;
|
||||
uptr beg = chunk_idx * size;
|
||||
uptr next_beg = beg + size;
|
||||
RegionInfo *region = GetRegionInfo(class_id);
|
||||
if (region->mapped_user >= next_beg)
|
||||
return reinterpret_cast<void*>(reg_beg + beg);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static uptr GetActuallyAllocatedSize(void *p) {
|
||||
@ -206,39 +312,66 @@ class SizeClassAllocator64 {
|
||||
|
||||
// Test-only.
|
||||
void TestOnlyUnmap() {
|
||||
UnmapOrDie(reinterpret_cast<void*>(AllocBeg()), AllocSize());
|
||||
UnmapWithCallback(kSpaceBeg, kSpaceSize + AdditionalSize());
|
||||
}
|
||||
|
||||
static uptr AllocBeg() { return kSpaceBeg; }
|
||||
static uptr AllocSize() { return kSpaceSize + AdditionalSize(); }
|
||||
void PrintStats() {
|
||||
uptr total_mapped = 0;
|
||||
uptr n_allocated = 0;
|
||||
uptr n_freed = 0;
|
||||
for (uptr class_id = 1; class_id < kNumClasses; class_id++) {
|
||||
RegionInfo *region = GetRegionInfo(class_id);
|
||||
total_mapped += region->mapped_user;
|
||||
n_allocated += region->n_allocated;
|
||||
n_freed += region->n_freed;
|
||||
}
|
||||
Printf("Stats: SizeClassAllocator64: %zdM mapped in %zd allocations; "
|
||||
"remains %zd\n",
|
||||
total_mapped >> 20, n_allocated, n_allocated - n_freed);
|
||||
for (uptr class_id = 1; class_id < kNumClasses; class_id++) {
|
||||
RegionInfo *region = GetRegionInfo(class_id);
|
||||
if (region->mapped_user == 0) continue;
|
||||
Printf(" %02zd (%zd): total: %zd K allocs: %zd remains: %zd\n",
|
||||
class_id,
|
||||
SizeClassMap::Size(class_id),
|
||||
region->mapped_user >> 10,
|
||||
region->n_allocated,
|
||||
region->n_allocated - region->n_freed);
|
||||
}
|
||||
}
|
||||
|
||||
typedef SizeClassMap SizeClassMapT;
|
||||
static const uptr kNumClasses = SizeClassMap::kNumClasses; // 2^k <= 256
|
||||
static const uptr kNumClasses = SizeClassMap::kNumClasses;
|
||||
static const uptr kNumClassesRounded = SizeClassMap::kNumClassesRounded;
|
||||
|
||||
private:
|
||||
static const uptr kRegionSize = kSpaceSize / kNumClasses;
|
||||
static const uptr kRegionSize = kSpaceSize / kNumClassesRounded;
|
||||
static const uptr kSpaceEnd = kSpaceBeg + kSpaceSize;
|
||||
COMPILER_CHECK(kSpaceBeg % kSpaceSize == 0);
|
||||
COMPILER_CHECK(kNumClasses <= SizeClassMap::kNumClasses);
|
||||
// kRegionSize must be >= 2^32.
|
||||
COMPILER_CHECK((kRegionSize) >= (1ULL << (SANITIZER_WORDSIZE / 2)));
|
||||
// Populate the free list with at most this number of bytes at once
|
||||
// or with one element if its size is greater.
|
||||
static const uptr kPopulateSize = 1 << 18;
|
||||
static const uptr kPopulateSize = 1 << 15;
|
||||
// Call mmap for user memory with at least this size.
|
||||
static const uptr kUserMapSize = 1 << 15;
|
||||
// Call mmap for metadata memory with at least this size.
|
||||
static const uptr kMetaMapSize = 1 << 16;
|
||||
|
||||
struct RegionInfo {
|
||||
SpinMutex mutex;
|
||||
AllocatorFreeList free_list;
|
||||
uptr allocated_user; // Bytes allocated for user memory.
|
||||
uptr allocated_meta; // Bytes allocated for metadata.
|
||||
char padding[kCacheLineSize - 3 * sizeof(uptr) - sizeof(AllocatorFreeList)];
|
||||
uptr mapped_user; // Bytes mapped for user memory.
|
||||
uptr mapped_meta; // Bytes mapped for metadata.
|
||||
uptr n_allocated, n_freed; // Just stats.
|
||||
};
|
||||
COMPILER_CHECK(sizeof(RegionInfo) == kCacheLineSize);
|
||||
COMPILER_CHECK(sizeof(RegionInfo) >= kCacheLineSize);
|
||||
|
||||
static uptr AdditionalSize() {
|
||||
uptr PageSize = GetPageSizeCached();
|
||||
uptr res = Max(sizeof(RegionInfo) * kNumClasses, PageSize);
|
||||
CHECK_EQ(res % PageSize, 0);
|
||||
return res;
|
||||
return RoundUpTo(sizeof(RegionInfo) * kNumClassesRounded,
|
||||
GetPageSizeCached());
|
||||
}
|
||||
|
||||
RegionInfo *GetRegionInfo(uptr class_id) {
|
||||
@ -256,11 +389,20 @@ class SizeClassAllocator64 {
|
||||
}
|
||||
|
||||
void PopulateFreeList(uptr class_id, RegionInfo *region) {
|
||||
CHECK(region->free_list.empty());
|
||||
uptr size = SizeClassMap::Size(class_id);
|
||||
uptr beg_idx = region->allocated_user;
|
||||
uptr end_idx = beg_idx + kPopulateSize;
|
||||
region->free_list.clear();
|
||||
uptr region_beg = kSpaceBeg + kRegionSize * class_id;
|
||||
if (end_idx + size > region->mapped_user) {
|
||||
// Do the mmap for the user memory.
|
||||
uptr map_size = kUserMapSize;
|
||||
while (end_idx + size > region->mapped_user + map_size)
|
||||
map_size += kUserMapSize;
|
||||
CHECK_GE(region->mapped_user + map_size, end_idx);
|
||||
MapWithCallback(region_beg + region->mapped_user, map_size);
|
||||
region->mapped_user += map_size;
|
||||
}
|
||||
uptr idx = beg_idx;
|
||||
uptr i = 0;
|
||||
do { // do-while loop because we need to put at least one item.
|
||||
@ -270,7 +412,19 @@ class SizeClassAllocator64 {
|
||||
i++;
|
||||
} while (idx < end_idx);
|
||||
region->allocated_user += idx - beg_idx;
|
||||
CHECK_LE(region->allocated_user, region->mapped_user);
|
||||
region->allocated_meta += i * kMetadataSize;
|
||||
if (region->allocated_meta > region->mapped_meta) {
|
||||
uptr map_size = kMetaMapSize;
|
||||
while (region->allocated_meta > region->mapped_meta + map_size)
|
||||
map_size += kMetaMapSize;
|
||||
// Do the mmap for the metadata.
|
||||
CHECK_GE(region->mapped_meta + map_size, region->allocated_meta);
|
||||
MapWithCallback(region_beg + kRegionSize -
|
||||
region->mapped_meta - map_size, map_size);
|
||||
region->mapped_meta += map_size;
|
||||
}
|
||||
CHECK_LE(region->allocated_meta, region->mapped_meta);
|
||||
if (region->allocated_user + region->allocated_meta > kRegionSize) {
|
||||
Printf("Out of memory. Dying.\n");
|
||||
Printf("The process has exhausted %zuMB for size class %zu.\n",
|
||||
@ -289,6 +443,7 @@ class SizeClassAllocator64 {
|
||||
CHECK(!region->free_list.empty());
|
||||
AllocatorListNode *node = region->free_list.front();
|
||||
region->free_list.pop_front();
|
||||
region->n_allocated++;
|
||||
return reinterpret_cast<void*>(node);
|
||||
}
|
||||
|
||||
@ -296,9 +451,213 @@ class SizeClassAllocator64 {
|
||||
RegionInfo *region = GetRegionInfo(class_id);
|
||||
SpinMutexLock l(®ion->mutex);
|
||||
region->free_list.push_front(reinterpret_cast<AllocatorListNode*>(p));
|
||||
region->n_freed++;
|
||||
}
|
||||
};
|
||||
|
||||
// SizeClassAllocator32 -- allocator for 32-bit address space.
|
||||
// This allocator can theoretically be used on 64-bit arch, but there it is less
|
||||
// efficient than SizeClassAllocator64.
|
||||
//
|
||||
// [kSpaceBeg, kSpaceBeg + kSpaceSize) is the range of addresses which can
|
||||
// be returned by MmapOrDie().
|
||||
//
|
||||
// Region:
|
||||
// a result of a single call to MmapAlignedOrDie(kRegionSize, kRegionSize).
|
||||
// Since the regions are aligned by kRegionSize, there are exactly
|
||||
// kNumPossibleRegions possible regions in the address space and so we keep
|
||||
// an u8 array possible_regions[kNumPossibleRegions] to store the size classes.
|
||||
// 0 size class means the region is not used by the allocator.
|
||||
//
|
||||
// One Region is used to allocate chunks of a single size class.
|
||||
// A Region looks like this:
|
||||
// UserChunk1 .. UserChunkN <gap> MetaChunkN .. MetaChunk1
|
||||
//
|
||||
// In order to avoid false sharing the objects of this class should be
|
||||
// chache-line aligned.
|
||||
template <const uptr kSpaceBeg, const u64 kSpaceSize,
|
||||
const uptr kMetadataSize, class SizeClassMap,
|
||||
class MapUnmapCallback = NoOpMapUnmapCallback>
|
||||
class SizeClassAllocator32 {
|
||||
public:
|
||||
void Init() {
|
||||
state_ = reinterpret_cast<State *>(MapWithCallback(sizeof(State)));
|
||||
}
|
||||
|
||||
void *MapWithCallback(uptr size) {
|
||||
size = RoundUpTo(size, GetPageSizeCached());
|
||||
void *res = MmapOrDie(size, "SizeClassAllocator32");
|
||||
MapUnmapCallback().OnMap((uptr)res, size);
|
||||
return res;
|
||||
}
|
||||
void UnmapWithCallback(uptr beg, uptr size) {
|
||||
MapUnmapCallback().OnUnmap(beg, size);
|
||||
UnmapOrDie(reinterpret_cast<void *>(beg), size);
|
||||
}
|
||||
|
||||
static bool CanAllocate(uptr size, uptr alignment) {
|
||||
return size <= SizeClassMap::kMaxSize &&
|
||||
alignment <= SizeClassMap::kMaxSize;
|
||||
}
|
||||
|
||||
void *Allocate(uptr size, uptr alignment) {
|
||||
if (size < alignment) size = alignment;
|
||||
CHECK(CanAllocate(size, alignment));
|
||||
return AllocateBySizeClass(ClassID(size));
|
||||
}
|
||||
|
||||
void Deallocate(void *p) {
|
||||
CHECK(PointerIsMine(p));
|
||||
DeallocateBySizeClass(p, GetSizeClass(p));
|
||||
}
|
||||
|
||||
void *GetMetaData(void *p) {
|
||||
CHECK(PointerIsMine(p));
|
||||
uptr mem = reinterpret_cast<uptr>(p);
|
||||
uptr beg = ComputeRegionBeg(mem);
|
||||
uptr size = SizeClassMap::Size(GetSizeClass(p));
|
||||
u32 offset = mem - beg;
|
||||
uptr n = offset / (u32)size; // 32-bit division
|
||||
uptr meta = (beg + kRegionSize) - (n + 1) * kMetadataSize;
|
||||
return reinterpret_cast<void*>(meta);
|
||||
}
|
||||
|
||||
// Allocate several chunks of the given class_id.
|
||||
void BulkAllocate(uptr class_id, AllocatorFreeList *free_list) {
|
||||
SizeClassInfo *sci = GetSizeClassInfo(class_id);
|
||||
SpinMutexLock l(&sci->mutex);
|
||||
EnsureSizeClassHasAvailableChunks(sci, class_id);
|
||||
CHECK(!sci->free_list.empty());
|
||||
BulkMove(SizeClassMap::MaxCached(class_id), &sci->free_list, free_list);
|
||||
}
|
||||
|
||||
// Swallow the entire free_list for the given class_id.
|
||||
void BulkDeallocate(uptr class_id, AllocatorFreeList *free_list) {
|
||||
SizeClassInfo *sci = GetSizeClassInfo(class_id);
|
||||
SpinMutexLock l(&sci->mutex);
|
||||
sci->free_list.append_front(free_list);
|
||||
}
|
||||
|
||||
bool PointerIsMine(void *p) {
|
||||
return GetSizeClass(p) != 0;
|
||||
}
|
||||
|
||||
uptr GetSizeClass(void *p) {
|
||||
return state_->possible_regions[ComputeRegionId(reinterpret_cast<uptr>(p))];
|
||||
}
|
||||
|
||||
void *GetBlockBegin(void *p) {
|
||||
CHECK(PointerIsMine(p));
|
||||
uptr mem = reinterpret_cast<uptr>(p);
|
||||
uptr beg = ComputeRegionBeg(mem);
|
||||
uptr size = SizeClassMap::Size(GetSizeClass(p));
|
||||
u32 offset = mem - beg;
|
||||
u32 n = offset / (u32)size; // 32-bit division
|
||||
uptr res = beg + (n * (u32)size);
|
||||
return reinterpret_cast<void*>(res);
|
||||
}
|
||||
|
||||
uptr GetActuallyAllocatedSize(void *p) {
|
||||
CHECK(PointerIsMine(p));
|
||||
return SizeClassMap::Size(GetSizeClass(p));
|
||||
}
|
||||
|
||||
uptr ClassID(uptr size) { return SizeClassMap::ClassID(size); }
|
||||
|
||||
uptr TotalMemoryUsed() {
|
||||
// No need to lock here.
|
||||
uptr res = 0;
|
||||
for (uptr i = 0; i < kNumPossibleRegions; i++)
|
||||
if (state_->possible_regions[i])
|
||||
res += kRegionSize;
|
||||
return res;
|
||||
}
|
||||
|
||||
void TestOnlyUnmap() {
|
||||
for (uptr i = 0; i < kNumPossibleRegions; i++)
|
||||
if (state_->possible_regions[i])
|
||||
UnmapWithCallback((i * kRegionSize), kRegionSize);
|
||||
UnmapWithCallback(reinterpret_cast<uptr>(state_), sizeof(State));
|
||||
}
|
||||
|
||||
void PrintStats() {
|
||||
}
|
||||
|
||||
typedef SizeClassMap SizeClassMapT;
|
||||
static const uptr kNumClasses = SizeClassMap::kNumClasses;
|
||||
|
||||
private:
|
||||
static const uptr kRegionSizeLog = SANITIZER_WORDSIZE == 64 ? 24 : 20;
|
||||
static const uptr kRegionSize = 1 << kRegionSizeLog;
|
||||
static const uptr kNumPossibleRegions = kSpaceSize / kRegionSize;
|
||||
|
||||
struct SizeClassInfo {
|
||||
SpinMutex mutex;
|
||||
AllocatorFreeList free_list;
|
||||
char padding[kCacheLineSize - sizeof(uptr) - sizeof(AllocatorFreeList)];
|
||||
};
|
||||
COMPILER_CHECK(sizeof(SizeClassInfo) == kCacheLineSize);
|
||||
|
||||
uptr ComputeRegionId(uptr mem) {
|
||||
uptr res = mem >> kRegionSizeLog;
|
||||
CHECK_LT(res, kNumPossibleRegions);
|
||||
return res;
|
||||
}
|
||||
|
||||
uptr ComputeRegionBeg(uptr mem) {
|
||||
return mem & ~(kRegionSize - 1);
|
||||
}
|
||||
|
||||
uptr AllocateRegion(uptr class_id) {
|
||||
CHECK_LT(class_id, kNumClasses);
|
||||
uptr res = reinterpret_cast<uptr>(MmapAlignedOrDie(kRegionSize, kRegionSize,
|
||||
"SizeClassAllocator32"));
|
||||
MapUnmapCallback().OnMap(res, kRegionSize);
|
||||
CHECK_EQ(0U, (res & (kRegionSize - 1)));
|
||||
CHECK_EQ(0U, state_->possible_regions[ComputeRegionId(res)]);
|
||||
state_->possible_regions[ComputeRegionId(res)] = class_id;
|
||||
return res;
|
||||
}
|
||||
|
||||
SizeClassInfo *GetSizeClassInfo(uptr class_id) {
|
||||
CHECK_LT(class_id, kNumClasses);
|
||||
return &state_->size_class_info_array[class_id];
|
||||
}
|
||||
|
||||
void EnsureSizeClassHasAvailableChunks(SizeClassInfo *sci, uptr class_id) {
|
||||
if (!sci->free_list.empty()) return;
|
||||
uptr size = SizeClassMap::Size(class_id);
|
||||
uptr reg = AllocateRegion(class_id);
|
||||
uptr n_chunks = kRegionSize / (size + kMetadataSize);
|
||||
for (uptr i = reg; i < reg + n_chunks * size; i += size)
|
||||
sci->free_list.push_back(reinterpret_cast<AllocatorListNode*>(i));
|
||||
}
|
||||
|
||||
void *AllocateBySizeClass(uptr class_id) {
|
||||
CHECK_LT(class_id, kNumClasses);
|
||||
SizeClassInfo *sci = GetSizeClassInfo(class_id);
|
||||
SpinMutexLock l(&sci->mutex);
|
||||
EnsureSizeClassHasAvailableChunks(sci, class_id);
|
||||
CHECK(!sci->free_list.empty());
|
||||
AllocatorListNode *node = sci->free_list.front();
|
||||
sci->free_list.pop_front();
|
||||
return reinterpret_cast<void*>(node);
|
||||
}
|
||||
|
||||
void DeallocateBySizeClass(void *p, uptr class_id) {
|
||||
CHECK_LT(class_id, kNumClasses);
|
||||
SizeClassInfo *sci = GetSizeClassInfo(class_id);
|
||||
SpinMutexLock l(&sci->mutex);
|
||||
sci->free_list.push_front(reinterpret_cast<AllocatorListNode*>(p));
|
||||
}
|
||||
|
||||
struct State {
|
||||
u8 possible_regions[kNumPossibleRegions];
|
||||
SizeClassInfo size_class_info_array[kNumClasses];
|
||||
};
|
||||
State *state_;
|
||||
};
|
||||
|
||||
// Objects of this type should be used as local caches for SizeClassAllocator64.
|
||||
// Since the typical use of this class is to have one object per thread in TLS,
|
||||
// is has to be POD.
|
||||
@ -312,6 +671,7 @@ struct SizeClassAllocatorLocalCache {
|
||||
}
|
||||
|
||||
void *Allocate(SizeClassAllocator *allocator, uptr class_id) {
|
||||
CHECK_NE(class_id, 0UL);
|
||||
CHECK_LT(class_id, kNumClasses);
|
||||
AllocatorFreeList *free_list = &free_lists_[class_id];
|
||||
if (free_list->empty())
|
||||
@ -323,6 +683,7 @@ struct SizeClassAllocatorLocalCache {
|
||||
}
|
||||
|
||||
void Deallocate(SizeClassAllocator *allocator, uptr class_id, void *p) {
|
||||
CHECK_NE(class_id, 0UL);
|
||||
CHECK_LT(class_id, kNumClasses);
|
||||
AllocatorFreeList *free_list = &free_lists_[class_id];
|
||||
free_list->push_front(reinterpret_cast<AllocatorListNode*>(p));
|
||||
@ -358,6 +719,7 @@ struct SizeClassAllocatorLocalCache {
|
||||
// This class can (de)allocate only large chunks of memory using mmap/unmap.
|
||||
// The main purpose of this allocator is to cover large and rare allocation
|
||||
// sizes not covered by more efficient allocators (e.g. SizeClassAllocator64).
|
||||
template <class MapUnmapCallback = NoOpMapUnmapCallback>
|
||||
class LargeMmapAllocator {
|
||||
public:
|
||||
void Init() {
|
||||
@ -372,6 +734,7 @@ class LargeMmapAllocator {
|
||||
if (map_size < size) return 0; // Overflow.
|
||||
uptr map_beg = reinterpret_cast<uptr>(
|
||||
MmapOrDie(map_size, "LargeMmapAllocator"));
|
||||
MapUnmapCallback().OnMap(map_beg, map_size);
|
||||
uptr map_end = map_beg + map_size;
|
||||
uptr res = map_beg + page_size_;
|
||||
if (res & (alignment - 1)) // Align.
|
||||
@ -384,11 +747,13 @@ class LargeMmapAllocator {
|
||||
h->map_size = map_size;
|
||||
{
|
||||
SpinMutexLock l(&mutex_);
|
||||
h->next = list_;
|
||||
h->prev = 0;
|
||||
if (list_)
|
||||
list_->prev = h;
|
||||
list_ = h;
|
||||
uptr idx = n_chunks_++;
|
||||
CHECK_LT(idx, kMaxNumChunks);
|
||||
h->chunk_idx = idx;
|
||||
chunks_[idx] = h;
|
||||
stats.n_allocs++;
|
||||
stats.currently_allocated += map_size;
|
||||
stats.max_allocated = Max(stats.max_allocated, stats.currently_allocated);
|
||||
}
|
||||
return reinterpret_cast<void*>(res);
|
||||
}
|
||||
@ -397,63 +762,81 @@ class LargeMmapAllocator {
|
||||
Header *h = GetHeader(p);
|
||||
{
|
||||
SpinMutexLock l(&mutex_);
|
||||
Header *prev = h->prev;
|
||||
Header *next = h->next;
|
||||
if (prev)
|
||||
prev->next = next;
|
||||
if (next)
|
||||
next->prev = prev;
|
||||
if (h == list_)
|
||||
list_ = next;
|
||||
uptr idx = h->chunk_idx;
|
||||
CHECK_EQ(chunks_[idx], h);
|
||||
CHECK_LT(idx, n_chunks_);
|
||||
chunks_[idx] = chunks_[n_chunks_ - 1];
|
||||
chunks_[idx]->chunk_idx = idx;
|
||||
n_chunks_--;
|
||||
stats.n_frees++;
|
||||
stats.currently_allocated -= h->map_size;
|
||||
}
|
||||
MapUnmapCallback().OnUnmap(h->map_beg, h->map_size);
|
||||
UnmapOrDie(reinterpret_cast<void*>(h->map_beg), h->map_size);
|
||||
}
|
||||
|
||||
uptr TotalMemoryUsed() {
|
||||
SpinMutexLock l(&mutex_);
|
||||
uptr res = 0;
|
||||
for (Header *l = list_; l; l = l->next) {
|
||||
res += RoundUpMapSize(l->size);
|
||||
for (uptr i = 0; i < n_chunks_; i++) {
|
||||
Header *h = chunks_[i];
|
||||
CHECK_EQ(h->chunk_idx, i);
|
||||
res += RoundUpMapSize(h->size);
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
bool PointerIsMine(void *p) {
|
||||
// Fast check.
|
||||
if ((reinterpret_cast<uptr>(p) & (page_size_ - 1))) return false;
|
||||
SpinMutexLock l(&mutex_);
|
||||
for (Header *l = list_; l; l = l->next) {
|
||||
if (GetUser(l) == p) return true;
|
||||
}
|
||||
return false;
|
||||
return GetBlockBegin(p) != 0;
|
||||
}
|
||||
|
||||
uptr GetActuallyAllocatedSize(void *p) {
|
||||
return RoundUpMapSize(GetHeader(p)->size) - page_size_;
|
||||
return RoundUpTo(GetHeader(p)->size, page_size_);
|
||||
}
|
||||
|
||||
// At least page_size_/2 metadata bytes is available.
|
||||
void *GetMetaData(void *p) {
|
||||
// Too slow: CHECK_EQ(p, GetBlockBegin(p));
|
||||
CHECK(IsAligned(reinterpret_cast<uptr>(p), page_size_));
|
||||
return GetHeader(p) + 1;
|
||||
}
|
||||
|
||||
void *GetBlockBegin(void *p) {
|
||||
void *GetBlockBegin(void *ptr) {
|
||||
uptr p = reinterpret_cast<uptr>(ptr);
|
||||
SpinMutexLock l(&mutex_);
|
||||
for (Header *l = list_; l; l = l->next) {
|
||||
void *b = GetUser(l);
|
||||
if (p >= b && p < (u8*)b + l->size)
|
||||
return b;
|
||||
uptr nearest_chunk = 0;
|
||||
// Cache-friendly linear search.
|
||||
for (uptr i = 0; i < n_chunks_; i++) {
|
||||
uptr ch = reinterpret_cast<uptr>(chunks_[i]);
|
||||
if (p < ch) continue; // p is at left to this chunk, skip it.
|
||||
if (p - ch < p - nearest_chunk)
|
||||
nearest_chunk = ch;
|
||||
}
|
||||
return 0;
|
||||
if (!nearest_chunk)
|
||||
return 0;
|
||||
Header *h = reinterpret_cast<Header *>(nearest_chunk);
|
||||
CHECK_GE(nearest_chunk, h->map_beg);
|
||||
CHECK_LT(nearest_chunk, h->map_beg + h->map_size);
|
||||
CHECK_LE(nearest_chunk, p);
|
||||
if (h->map_beg + h->map_size < p)
|
||||
return 0;
|
||||
return GetUser(h);
|
||||
}
|
||||
|
||||
void PrintStats() {
|
||||
Printf("Stats: LargeMmapAllocator: allocated %zd times, "
|
||||
"remains %zd (%zd K) max %zd M\n",
|
||||
stats.n_allocs, stats.n_allocs - stats.n_frees,
|
||||
stats.currently_allocated >> 10, stats.max_allocated >> 20);
|
||||
}
|
||||
|
||||
private:
|
||||
static const int kMaxNumChunks = 1 << FIRST_32_SECOND_64(15, 18);
|
||||
struct Header {
|
||||
uptr map_beg;
|
||||
uptr map_size;
|
||||
uptr size;
|
||||
Header *next;
|
||||
Header *prev;
|
||||
uptr chunk_idx;
|
||||
};
|
||||
|
||||
Header *GetHeader(uptr p) {
|
||||
@ -472,7 +855,11 @@ class LargeMmapAllocator {
|
||||
}
|
||||
|
||||
uptr page_size_;
|
||||
Header *list_;
|
||||
Header *chunks_[kMaxNumChunks];
|
||||
uptr n_chunks_;
|
||||
struct Stats {
|
||||
uptr n_allocs, n_frees, currently_allocated, max_allocated;
|
||||
} stats;
|
||||
SpinMutex mutex_;
|
||||
};
|
||||
|
||||
@ -501,10 +888,14 @@ class CombinedAllocator {
|
||||
if (alignment > 8)
|
||||
size = RoundUpTo(size, alignment);
|
||||
void *res;
|
||||
if (primary_.CanAllocate(size, alignment))
|
||||
res = cache->Allocate(&primary_, primary_.ClassID(size));
|
||||
else
|
||||
if (primary_.CanAllocate(size, alignment)) {
|
||||
if (cache) // Allocate from cache.
|
||||
res = cache->Allocate(&primary_, primary_.ClassID(size));
|
||||
else // No thread-local cache, allocate directly from primary allocator.
|
||||
res = primary_.Allocate(size, alignment);
|
||||
} else { // Secondary allocator does not use cache.
|
||||
res = secondary_.Allocate(size, alignment);
|
||||
}
|
||||
if (alignment > 8)
|
||||
CHECK_EQ(reinterpret_cast<uptr>(res) & (alignment - 1), 0);
|
||||
if (cleared && res)
|
||||
@ -544,6 +935,10 @@ class CombinedAllocator {
|
||||
return secondary_.PointerIsMine(p);
|
||||
}
|
||||
|
||||
bool FromPrimary(void *p) {
|
||||
return primary_.PointerIsMine(p);
|
||||
}
|
||||
|
||||
void *GetMetaData(void *p) {
|
||||
if (primary_.PointerIsMine(p))
|
||||
return primary_.GetMetaData(p);
|
||||
@ -572,6 +967,11 @@ class CombinedAllocator {
|
||||
cache->Drain(&primary_);
|
||||
}
|
||||
|
||||
void PrintStats() {
|
||||
primary_.PrintStats();
|
||||
secondary_.PrintStats();
|
||||
}
|
||||
|
||||
private:
|
||||
PrimaryAllocator primary_;
|
||||
SecondaryAllocator secondary_;
|
||||
|
@ -22,9 +22,31 @@ extern "C" void _mm_pause();
|
||||
extern "C" long _InterlockedExchangeAdd( // NOLINT
|
||||
long volatile * Addend, long Value); // NOLINT
|
||||
#pragma intrinsic(_InterlockedExchangeAdd)
|
||||
extern "C" void *InterlockedCompareExchangePointer(
|
||||
|
||||
#ifdef _WIN64
|
||||
extern "C" void *_InterlockedCompareExchangePointer(
|
||||
void *volatile *Destination,
|
||||
void *Exchange, void *Comparand);
|
||||
#pragma intrinsic(_InterlockedCompareExchangePointer)
|
||||
#else
|
||||
// There's no _InterlockedCompareExchangePointer intrinsic on x86,
|
||||
// so call _InterlockedCompareExchange instead.
|
||||
extern "C"
|
||||
long __cdecl _InterlockedCompareExchange( // NOLINT
|
||||
long volatile *Destination, // NOLINT
|
||||
long Exchange, long Comparand); // NOLINT
|
||||
#pragma intrinsic(_InterlockedCompareExchange)
|
||||
|
||||
inline static void *_InterlockedCompareExchangePointer(
|
||||
void *volatile *Destination,
|
||||
void *Exchange, void *Comparand) {
|
||||
return reinterpret_cast<void*>(
|
||||
_InterlockedCompareExchange(
|
||||
reinterpret_cast<long volatile*>(Destination), // NOLINT
|
||||
reinterpret_cast<long>(Exchange), // NOLINT
|
||||
reinterpret_cast<long>(Comparand))); // NOLINT
|
||||
}
|
||||
#endif
|
||||
|
||||
namespace __sanitizer {
|
||||
|
||||
@ -113,7 +135,7 @@ INLINE bool atomic_compare_exchange_strong(volatile atomic_uintptr_t *a,
|
||||
uptr xchg,
|
||||
memory_order mo) {
|
||||
uptr cmpv = *cmp;
|
||||
uptr prev = (uptr)InterlockedCompareExchangePointer(
|
||||
uptr prev = (uptr)_InterlockedCompareExchangePointer(
|
||||
(void*volatile*)&a->val_dont_use, (void*)xchg, (void*)cmpv);
|
||||
if (prev == cmpv)
|
||||
return true;
|
||||
|
@ -153,6 +153,27 @@ void SortArray(uptr *array, uptr size) {
|
||||
}
|
||||
}
|
||||
|
||||
// We want to map a chunk of address space aligned to 'alignment'.
|
||||
// We do it by maping a bit more and then unmaping redundant pieces.
|
||||
// We probably can do it with fewer syscalls in some OS-dependent way.
|
||||
void *MmapAlignedOrDie(uptr size, uptr alignment, const char *mem_type) {
|
||||
// uptr PageSize = GetPageSizeCached();
|
||||
CHECK(IsPowerOfTwo(size));
|
||||
CHECK(IsPowerOfTwo(alignment));
|
||||
uptr map_size = size + alignment;
|
||||
uptr map_res = (uptr)MmapOrDie(map_size, mem_type);
|
||||
uptr map_end = map_res + map_size;
|
||||
uptr res = map_res;
|
||||
if (res & (alignment - 1)) // Not aligned.
|
||||
res = (map_res + alignment) & ~(alignment - 1);
|
||||
uptr end = res + size;
|
||||
if (res != map_res)
|
||||
UnmapOrDie((void*)map_res, res - map_res);
|
||||
if (end != map_end)
|
||||
UnmapOrDie((void*)end, map_end - end);
|
||||
return (void*)res;
|
||||
}
|
||||
|
||||
} // namespace __sanitizer
|
||||
|
||||
using namespace __sanitizer; // NOLINT
|
||||
@ -178,4 +199,9 @@ void __sanitizer_set_report_fd(int fd) {
|
||||
internal_close(report_fd);
|
||||
report_fd = fd;
|
||||
}
|
||||
|
||||
void NOINLINE __sanitizer_sandbox_on_notify(void *reserved) {
|
||||
(void)reserved;
|
||||
PrepareForSandboxing();
|
||||
}
|
||||
} // extern "C"
|
||||
|
@ -42,9 +42,13 @@ void GetThreadStackTopAndBottom(bool at_initialization, uptr *stack_top,
|
||||
void *MmapOrDie(uptr size, const char *mem_type);
|
||||
void UnmapOrDie(void *addr, uptr size);
|
||||
void *MmapFixedNoReserve(uptr fixed_addr, uptr size);
|
||||
void *MmapFixedOrDie(uptr fixed_addr, uptr size);
|
||||
void *Mprotect(uptr fixed_addr, uptr size);
|
||||
// Map aligned chunk of address space; size and alignment are powers of two.
|
||||
void *MmapAlignedOrDie(uptr size, uptr alignment, const char *mem_type);
|
||||
// Used to check if we can map shadow memory to a fixed location.
|
||||
bool MemoryRangeIsAvailable(uptr range_start, uptr range_end);
|
||||
void FlushUnneededShadowMemory(uptr addr, uptr size);
|
||||
|
||||
// Internal allocator
|
||||
void *InternalAlloc(uptr size);
|
||||
@ -119,6 +123,7 @@ const char *GetPwd();
|
||||
void ReExec();
|
||||
bool StackSizeIsUnlimited();
|
||||
void SetStackSizeLimitInBytes(uptr limit);
|
||||
void PrepareForSandboxing();
|
||||
|
||||
// Other
|
||||
void SleepForSeconds(int seconds);
|
||||
@ -133,6 +138,13 @@ void NORETURN Die();
|
||||
void NORETURN SANITIZER_INTERFACE_ATTRIBUTE
|
||||
CheckFailed(const char *file, int line, const char *cond, u64 v1, u64 v2);
|
||||
|
||||
// Set the name of the current thread to 'name', return true on succees.
|
||||
// The name may be truncated to a system-dependent limit.
|
||||
bool SanitizerSetThreadName(const char *name);
|
||||
// Get the name of the current thread (no more than max_len bytes),
|
||||
// return true on succees. name should have space for at least max_len+1 bytes.
|
||||
bool SanitizerGetThreadName(char *name, int max_len);
|
||||
|
||||
// Specific tools may override behavior of "Die" and "CheckFailed" functions
|
||||
// to do tool-specific job.
|
||||
void SetDieCallback(void (*callback)(void));
|
||||
@ -148,6 +160,12 @@ INLINE uptr RoundUpTo(uptr size, uptr boundary) {
|
||||
CHECK(IsPowerOfTwo(boundary));
|
||||
return (size + boundary - 1) & ~(boundary - 1);
|
||||
}
|
||||
INLINE uptr RoundDownTo(uptr x, uptr boundary) {
|
||||
return x & ~(boundary - 1);
|
||||
}
|
||||
INLINE bool IsAligned(uptr a, uptr alignment) {
|
||||
return (a & (alignment - 1)) == 0;
|
||||
}
|
||||
// Don't use std::min, std::max or std::swap, to minimize dependency
|
||||
// on libstdc++.
|
||||
template<class T> T Min(T a, T b) { return a < b ? a : b; }
|
||||
|
@ -0,0 +1,77 @@
|
||||
//===-- sanitizer_common_interceptors.h -------------------------*- C++ -*-===//
|
||||
//
|
||||
// This file is distributed under the University of Illinois Open Source
|
||||
// License. See LICENSE.TXT for details.
|
||||
//
|
||||
//===----------------------------------------------------------------------===//
|
||||
//
|
||||
// Common function interceptors for tools like AddressSanitizer,
|
||||
// ThreadSanitizer, MemorySanitizer, etc.
|
||||
//
|
||||
// This file should be included into the tool's interceptor file,
|
||||
// which has to define it's own macros:
|
||||
// COMMON_INTERCEPTOR_ENTER
|
||||
// COMMON_INTERCEPTOR_READ_RANGE
|
||||
// COMMON_INTERCEPTOR_WRITE_RANGE
|
||||
//
|
||||
//===----------------------------------------------------------------------===//
|
||||
#ifndef SANITIZER_COMMON_INTERCEPTORS_H
|
||||
#define SANITIZER_COMMON_INTERCEPTORS_H
|
||||
|
||||
#include "interception/interception.h"
|
||||
#include "sanitizer_platform_interceptors.h"
|
||||
|
||||
#if SANITIZER_INTERCEPT_READ
|
||||
INTERCEPTOR(SSIZE_T, read, int fd, void *ptr, SIZE_T count) {
|
||||
COMMON_INTERCEPTOR_ENTER(read, fd, ptr, count);
|
||||
SSIZE_T res = REAL(read)(fd, ptr, count);
|
||||
if (res > 0)
|
||||
COMMON_INTERCEPTOR_WRITE_RANGE(ptr, res);
|
||||
return res;
|
||||
}
|
||||
#endif
|
||||
|
||||
#if SANITIZER_INTERCEPT_PREAD
|
||||
INTERCEPTOR(SSIZE_T, pread, int fd, void *ptr, SIZE_T count, OFF_T offset) {
|
||||
COMMON_INTERCEPTOR_ENTER(pread, fd, ptr, count, offset);
|
||||
SSIZE_T res = REAL(pread)(fd, ptr, count, offset);
|
||||
if (res > 0)
|
||||
COMMON_INTERCEPTOR_WRITE_RANGE(ptr, res);
|
||||
return res;
|
||||
}
|
||||
#endif
|
||||
|
||||
#if SANITIZER_INTERCEPT_PREAD64
|
||||
INTERCEPTOR(SSIZE_T, pread64, int fd, void *ptr, SIZE_T count, OFF64_T offset) {
|
||||
COMMON_INTERCEPTOR_ENTER(pread64, fd, ptr, count, offset);
|
||||
SSIZE_T res = REAL(pread64)(fd, ptr, count, offset);
|
||||
if (res > 0)
|
||||
COMMON_INTERCEPTOR_WRITE_RANGE(ptr, res);
|
||||
return res;
|
||||
}
|
||||
#endif
|
||||
|
||||
#if SANITIZER_INTERCEPT_READ
|
||||
# define INIT_READ INTERCEPT_FUNCTION(read)
|
||||
#else
|
||||
# define INIT_READ
|
||||
#endif
|
||||
|
||||
#if SANITIZER_INTERCEPT_PREAD
|
||||
# define INIT_PREAD INTERCEPT_FUNCTION(pread)
|
||||
#else
|
||||
# define INIT_PREAD
|
||||
#endif
|
||||
|
||||
#if SANITIZER_INTERCEPT_PREAD64
|
||||
# define INIT_PREAD64 INTERCEPT_FUNCTION(pread64)
|
||||
#else
|
||||
# define INIT_PREAD64
|
||||
#endif
|
||||
|
||||
#define SANITIZER_COMMON_INTERCEPTORS_INIT \
|
||||
INIT_READ; \
|
||||
INIT_PREAD; \
|
||||
INIT_PREAD64; \
|
||||
|
||||
#endif // SANITIZER_COMMON_INTERCEPTORS_H
|
@ -203,4 +203,23 @@ s64 internal_simple_strtoll(const char *nptr, char **endptr, int base) {
|
||||
}
|
||||
}
|
||||
|
||||
bool mem_is_zero(const char *beg, uptr size) {
|
||||
CHECK_LE(size, 1UL << FIRST_32_SECOND_64(30, 40)); // Sanity check.
|
||||
const char *end = beg + size;
|
||||
uptr *aligned_beg = (uptr *)RoundUpTo((uptr)beg, sizeof(uptr));
|
||||
uptr *aligned_end = (uptr *)RoundDownTo((uptr)end, sizeof(uptr));
|
||||
uptr all = 0;
|
||||
// Prologue.
|
||||
for (const char *mem = beg; mem < (char*)aligned_beg && mem < end; mem++)
|
||||
all |= *mem;
|
||||
// Aligned loop.
|
||||
for (; aligned_beg < aligned_end; aligned_beg++)
|
||||
all |= *aligned_beg;
|
||||
// Epilogue.
|
||||
if ((char*)aligned_end >= beg)
|
||||
for (const char *mem = (char*)aligned_end; mem < end; mem++)
|
||||
all |= *mem;
|
||||
return all == 0;
|
||||
}
|
||||
|
||||
} // namespace __sanitizer
|
||||
|
@ -45,6 +45,11 @@ char *internal_strstr(const char *haystack, const char *needle);
|
||||
// Works only for base=10 and doesn't set errno.
|
||||
s64 internal_simple_strtoll(const char *nptr, char **endptr, int base);
|
||||
|
||||
// Return true if all bytes in [mem, mem+size) are zero.
|
||||
// Optimized for the case when the result is true.
|
||||
bool mem_is_zero(const char *mem, uptr size);
|
||||
|
||||
|
||||
// Memory
|
||||
void *internal_mmap(void *addr, uptr length, int prot, int flags,
|
||||
int fd, u64 offset);
|
||||
|
@ -17,6 +17,7 @@
|
||||
#include "sanitizer_mutex.h"
|
||||
#include "sanitizer_placement_new.h"
|
||||
#include "sanitizer_procmaps.h"
|
||||
#include "sanitizer_stacktrace.h"
|
||||
|
||||
#include <fcntl.h>
|
||||
#include <pthread.h>
|
||||
@ -28,7 +29,9 @@
|
||||
#include <sys/time.h>
|
||||
#include <sys/types.h>
|
||||
#include <unistd.h>
|
||||
#include <unwind.h>
|
||||
#include <errno.h>
|
||||
#include <sys/prctl.h>
|
||||
|
||||
// Are we using 32-bit or 64-bit syscalls?
|
||||
// x32 (which defines __x86_64__) has SANITIZER_WORDSIZE == 32
|
||||
@ -215,6 +218,14 @@ void ReExec() {
|
||||
execv(argv[0], argv.data());
|
||||
}
|
||||
|
||||
void PrepareForSandboxing() {
|
||||
// Some kinds of sandboxes may forbid filesystem access, so we won't be able
|
||||
// to read the file mappings from /proc/self/maps. Luckily, neither the
|
||||
// process will be able to load additional libraries, so it's fine to use the
|
||||
// cached mappings.
|
||||
MemoryMappingLayout::CacheMemoryMappings();
|
||||
}
|
||||
|
||||
// ----------------- sanitizer_procmaps.h
|
||||
// Linker initialized.
|
||||
ProcSelfMapsBuff MemoryMappingLayout::cached_proc_self_maps_;
|
||||
@ -354,6 +365,75 @@ bool MemoryMappingLayout::GetObjectNameAndOffset(uptr addr, uptr *offset,
|
||||
return IterateForObjectNameAndOffset(addr, offset, filename, filename_size);
|
||||
}
|
||||
|
||||
bool SanitizerSetThreadName(const char *name) {
|
||||
return 0 == prctl(PR_SET_NAME, (unsigned long)name, 0, 0, 0); // NOLINT
|
||||
}
|
||||
|
||||
bool SanitizerGetThreadName(char *name, int max_len) {
|
||||
char buff[17];
|
||||
if (prctl(PR_GET_NAME, (unsigned long)buff, 0, 0, 0)) // NOLINT
|
||||
return false;
|
||||
internal_strncpy(name, buff, max_len);
|
||||
name[max_len] = 0;
|
||||
return true;
|
||||
}
|
||||
|
||||
#ifndef SANITIZER_GO
|
||||
//------------------------- SlowUnwindStack -----------------------------------
|
||||
#ifdef __arm__
|
||||
#define UNWIND_STOP _URC_END_OF_STACK
|
||||
#define UNWIND_CONTINUE _URC_NO_REASON
|
||||
#else
|
||||
#define UNWIND_STOP _URC_NORMAL_STOP
|
||||
#define UNWIND_CONTINUE _URC_NO_REASON
|
||||
#endif
|
||||
|
||||
uptr Unwind_GetIP(struct _Unwind_Context *ctx) {
|
||||
#ifdef __arm__
|
||||
uptr val;
|
||||
_Unwind_VRS_Result res = _Unwind_VRS_Get(ctx, _UVRSC_CORE,
|
||||
15 /* r15 = PC */, _UVRSD_UINT32, &val);
|
||||
CHECK(res == _UVRSR_OK && "_Unwind_VRS_Get failed");
|
||||
// Clear the Thumb bit.
|
||||
return val & ~(uptr)1;
|
||||
#else
|
||||
return _Unwind_GetIP(ctx);
|
||||
#endif
|
||||
}
|
||||
|
||||
_Unwind_Reason_Code Unwind_Trace(struct _Unwind_Context *ctx, void *param) {
|
||||
StackTrace *b = (StackTrace*)param;
|
||||
CHECK(b->size < b->max_size);
|
||||
uptr pc = Unwind_GetIP(ctx);
|
||||
b->trace[b->size++] = pc;
|
||||
if (b->size == b->max_size) return UNWIND_STOP;
|
||||
return UNWIND_CONTINUE;
|
||||
}
|
||||
|
||||
static bool MatchPc(uptr cur_pc, uptr trace_pc) {
|
||||
return cur_pc - trace_pc <= 64 || trace_pc - cur_pc <= 64;
|
||||
}
|
||||
|
||||
void StackTrace::SlowUnwindStack(uptr pc, uptr max_depth) {
|
||||
this->size = 0;
|
||||
this->max_size = max_depth;
|
||||
if (max_depth > 1) {
|
||||
_Unwind_Backtrace(Unwind_Trace, this);
|
||||
// We need to pop a few frames so that pc is on top.
|
||||
// trace[0] belongs to the current function so we always pop it.
|
||||
int to_pop = 1;
|
||||
/**/ if (size > 1 && MatchPc(pc, trace[1])) to_pop = 1;
|
||||
else if (size > 2 && MatchPc(pc, trace[2])) to_pop = 2;
|
||||
else if (size > 3 && MatchPc(pc, trace[3])) to_pop = 3;
|
||||
else if (size > 4 && MatchPc(pc, trace[4])) to_pop = 4;
|
||||
else if (size > 5 && MatchPc(pc, trace[5])) to_pop = 5;
|
||||
this->PopStackFrames(to_pop);
|
||||
}
|
||||
this->trace[0] = pc;
|
||||
}
|
||||
|
||||
#endif // #ifndef SANITIZER_GO
|
||||
|
||||
} // namespace __sanitizer
|
||||
|
||||
#endif // __linux__
|
||||
|
@ -124,6 +124,10 @@ void ReExec() {
|
||||
UNIMPLEMENTED();
|
||||
}
|
||||
|
||||
void PrepareForSandboxing() {
|
||||
// Nothing here for now.
|
||||
}
|
||||
|
||||
// ----------------- sanitizer_procmaps.h
|
||||
|
||||
MemoryMappingLayout::MemoryMappingLayout() {
|
||||
|
@ -0,0 +1,27 @@
|
||||
//===-- sanitizer_platform_interceptors.h -----------------------*- C++ -*-===//
|
||||
//
|
||||
// This file is distributed under the University of Illinois Open Source
|
||||
// License. See LICENSE.TXT for details.
|
||||
//
|
||||
//===----------------------------------------------------------------------===//
|
||||
//
|
||||
// This file defines macro telling whether sanitizer tools can/should intercept
|
||||
// given library functions on a given platform.
|
||||
//
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
#include "sanitizer_internal_defs.h"
|
||||
|
||||
#if !defined(_WIN32)
|
||||
# define SANITIZER_INTERCEPT_READ 1
|
||||
# define SANITIZER_INTERCEPT_PREAD 1
|
||||
#else
|
||||
# define SANITIZER_INTERCEPT_READ 0
|
||||
# define SANITIZER_INTERCEPT_PREAD 0
|
||||
#endif
|
||||
|
||||
#if defined(__linux__) && !defined(ANDROID)
|
||||
# define SANITIZER_INTERCEPT_PREAD64 1
|
||||
#else
|
||||
# define SANITIZER_INTERCEPT_PREAD64 0
|
||||
#endif
|
@ -91,6 +91,21 @@ void *MmapFixedNoReserve(uptr fixed_addr, uptr size) {
|
||||
return p;
|
||||
}
|
||||
|
||||
void *MmapFixedOrDie(uptr fixed_addr, uptr size) {
|
||||
uptr PageSize = GetPageSizeCached();
|
||||
void *p = internal_mmap((void*)(fixed_addr & ~(PageSize - 1)),
|
||||
RoundUpTo(size, PageSize),
|
||||
PROT_READ | PROT_WRITE,
|
||||
MAP_PRIVATE | MAP_ANON | MAP_FIXED,
|
||||
-1, 0);
|
||||
if (p == (void*)-1) {
|
||||
Report("ERROR: Failed to allocate 0x%zx (%zd) bytes at address %p (%d)\n",
|
||||
size, size, fixed_addr, errno);
|
||||
CHECK("unable to mmap" && 0);
|
||||
}
|
||||
return p;
|
||||
}
|
||||
|
||||
void *Mprotect(uptr fixed_addr, uptr size) {
|
||||
return internal_mmap((void*)fixed_addr, size,
|
||||
PROT_NONE,
|
||||
@ -98,6 +113,10 @@ void *Mprotect(uptr fixed_addr, uptr size) {
|
||||
-1, 0);
|
||||
}
|
||||
|
||||
void FlushUnneededShadowMemory(uptr addr, uptr size) {
|
||||
madvise((void*)addr, size, MADV_DONTNEED);
|
||||
}
|
||||
|
||||
void *MapFileToMemory(const char *file_name, uptr *buff_size) {
|
||||
fd_t fd = internal_open(file_name, false);
|
||||
CHECK_NE(fd, kInvalidFd);
|
||||
|
@ -92,7 +92,7 @@ static int AppendPointer(char **buff, const char *buff_end, u64 ptr_value) {
|
||||
int VSNPrintf(char *buff, int buff_length,
|
||||
const char *format, va_list args) {
|
||||
static const char *kPrintfFormatsHelp =
|
||||
"Supported Printf formats: %%(0[0-9]*)?(z|ll)?{d,u,x}; %%p; %%s; %%c\n";
|
||||
"Supported Printf formats: %(0[0-9]*)?(z|ll)?{d,u,x}; %p; %s; %c\n";
|
||||
RAW_CHECK(format);
|
||||
RAW_CHECK(buff_length > 0);
|
||||
const char *buff_end = &buff[buff_length - 1];
|
||||
|
35
libsanitizer/sanitizer_common/sanitizer_report_decorator.h
Normal file
35
libsanitizer/sanitizer_common/sanitizer_report_decorator.h
Normal file
@ -0,0 +1,35 @@
|
||||
//===-- sanitizer_report_decorator.h ----------------------------*- C++ -*-===//
|
||||
//
|
||||
// This file is distributed under the University of Illinois Open Source
|
||||
// License. See LICENSE.TXT for details.
|
||||
//
|
||||
//===----------------------------------------------------------------------===//
|
||||
//
|
||||
// Tags to decorate the sanitizer reports.
|
||||
// Currently supported tags:
|
||||
// * None.
|
||||
// * ANSI color sequences.
|
||||
//
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
#ifndef SANITIZER_ALLOCATOR_H
|
||||
#define SANITIZER_ALLOCATOR_H
|
||||
|
||||
namespace __sanitizer {
|
||||
class AnsiColorDecorator {
|
||||
public:
|
||||
explicit AnsiColorDecorator(bool use_ansi_colors) : ansi_(use_ansi_colors) { }
|
||||
const char *Black() { return ansi_ ? "\033[1m\033[30m" : ""; }
|
||||
const char *Red() { return ansi_ ? "\033[1m\033[31m" : ""; }
|
||||
const char *Green() { return ansi_ ? "\033[1m\033[32m" : ""; }
|
||||
const char *Yellow() { return ansi_ ? "\033[1m\033[33m" : ""; }
|
||||
const char *Blue() { return ansi_ ? "\033[1m\033[34m" : ""; }
|
||||
const char *Magenta() { return ansi_ ? "\033[1m\033[35m" : ""; }
|
||||
const char *Cyan() { return ansi_ ? "\033[1m\033[36m" : ""; }
|
||||
const char *White() { return ansi_ ? "\033[1m\033[37m" : ""; }
|
||||
const char *Default() { return ansi_ ? "\033[1m\033[0m" : ""; }
|
||||
private:
|
||||
bool ansi_;
|
||||
};
|
||||
} // namespace __sanitizer
|
||||
#endif // SANITIZER_ALLOCATOR_H
|
@ -40,6 +40,12 @@ static struct {
|
||||
atomic_uint32_t seq[kPartCount]; // Unique id generators.
|
||||
} depot;
|
||||
|
||||
static StackDepotStats stats;
|
||||
|
||||
StackDepotStats *StackDepotGetStats() {
|
||||
return &stats;
|
||||
}
|
||||
|
||||
static u32 hash(const uptr *stack, uptr size) {
|
||||
// murmur2
|
||||
const u32 m = 0x5bd1e995;
|
||||
@ -75,7 +81,7 @@ static StackDesc *tryallocDesc(uptr memsz) {
|
||||
}
|
||||
|
||||
static StackDesc *allocDesc(uptr size) {
|
||||
// Frist, try to allocate optimisitically.
|
||||
// First, try to allocate optimisitically.
|
||||
uptr memsz = sizeof(StackDesc) + (size - 1) * sizeof(uptr);
|
||||
StackDesc *s = tryallocDesc(memsz);
|
||||
if (s)
|
||||
@ -91,6 +97,7 @@ static StackDesc *allocDesc(uptr size) {
|
||||
if (allocsz < memsz)
|
||||
allocsz = memsz;
|
||||
uptr mem = (uptr)MmapOrDie(allocsz, "stack depot");
|
||||
stats.mapped += allocsz;
|
||||
atomic_store(&depot.region_end, mem + allocsz, memory_order_release);
|
||||
atomic_store(&depot.region_pos, mem, memory_order_release);
|
||||
}
|
||||
@ -154,6 +161,7 @@ u32 StackDepotPut(const uptr *stack, uptr size) {
|
||||
}
|
||||
uptr part = (h % kTabSize) / kPartSize;
|
||||
id = atomic_fetch_add(&depot.seq[part], 1, memory_order_relaxed) + 1;
|
||||
stats.n_uniq_ids++;
|
||||
CHECK_LT(id, kMaxId);
|
||||
id |= part << kPartShift;
|
||||
CHECK_NE(id, 0);
|
||||
|
@ -22,6 +22,13 @@ u32 StackDepotPut(const uptr *stack, uptr size);
|
||||
// Retrieves a stored stack trace by the id.
|
||||
const uptr *StackDepotGet(u32 id, uptr *size);
|
||||
|
||||
struct StackDepotStats {
|
||||
uptr n_uniq_ids;
|
||||
uptr mapped;
|
||||
};
|
||||
|
||||
StackDepotStats *StackDepotGetStats();
|
||||
|
||||
} // namespace __sanitizer
|
||||
|
||||
#endif // SANITIZER_STACKDEPOT_H
|
||||
|
@ -23,10 +23,7 @@ static const char *StripPathPrefix(const char *filepath,
|
||||
}
|
||||
|
||||
// ----------------------- StackTrace ----------------------------- {{{1
|
||||
// PCs in stack traces are actually the return addresses, that is,
|
||||
// addresses of the next instructions after the call. That's why we
|
||||
// decrement them.
|
||||
static uptr patch_pc(uptr pc) {
|
||||
uptr StackTrace::GetPreviousInstructionPc(uptr pc) {
|
||||
#ifdef __arm__
|
||||
// Cancel Thumb bit.
|
||||
pc = pc & (~1);
|
||||
@ -69,7 +66,9 @@ void StackTrace::PrintStack(const uptr *addr, uptr size,
|
||||
InternalScopedBuffer<AddressInfo> addr_frames(64);
|
||||
uptr frame_num = 0;
|
||||
for (uptr i = 0; i < size && addr[i]; i++) {
|
||||
uptr pc = patch_pc(addr[i]);
|
||||
// PCs in stack traces are actually the return addresses, that is,
|
||||
// addresses of the next instructions after the call.
|
||||
uptr pc = GetPreviousInstructionPc(addr[i]);
|
||||
uptr addr_frames_num = 0; // The number of stack frames for current
|
||||
// instruction address.
|
||||
if (symbolize_callback) {
|
||||
|
@ -42,10 +42,12 @@ struct StackTrace {
|
||||
}
|
||||
|
||||
void FastUnwindStack(uptr pc, uptr bp, uptr stack_top, uptr stack_bottom);
|
||||
void SlowUnwindStack(uptr pc, uptr max_depth);
|
||||
|
||||
void PopStackFrames(uptr count);
|
||||
|
||||
static uptr GetCurrentPc();
|
||||
static uptr GetPreviousInstructionPc(uptr pc);
|
||||
|
||||
static uptr CompressStack(StackTrace *stack,
|
||||
u32 *compressed, uptr size);
|
||||
|
@ -58,6 +58,9 @@ struct AddressInfo {
|
||||
uptr SymbolizeCode(uptr address, AddressInfo *frames, uptr max_frames);
|
||||
bool SymbolizeData(uptr address, AddressInfo *frame);
|
||||
|
||||
// Attempts to demangle the provided C++ mangled name.
|
||||
const char *Demangle(const char *Name);
|
||||
|
||||
// Starts external symbolizer program in a subprocess. Sanitizer communicates
|
||||
// with external symbolizer via pipes.
|
||||
bool InitializeExternalSymbolizer(const char *path_to_symbolizer);
|
||||
|
@ -0,0 +1,40 @@
|
||||
//===-- sanitizer_symbolizer_itanium.cc -----------------------------------===//
|
||||
//
|
||||
// This file is distributed under the University of Illinois Open Source
|
||||
// License. See LICENSE.TXT for details.
|
||||
//
|
||||
//===----------------------------------------------------------------------===//
|
||||
//
|
||||
// This file is shared between the sanitizer run-time libraries.
|
||||
// Itanium C++ ABI-specific implementation of symbolizer parts.
|
||||
//===----------------------------------------------------------------------===//
|
||||
#if defined(__APPLE__) || defined(__linux__)
|
||||
|
||||
#include "sanitizer_symbolizer.h"
|
||||
|
||||
#include <stdlib.h>
|
||||
|
||||
// C++ demangling function, as required by Itanium C++ ABI. This is weak,
|
||||
// because we do not require a C++ ABI library to be linked to a program
|
||||
// using sanitizers; if it's not present, we'll just use the mangled name.
|
||||
namespace __cxxabiv1 {
|
||||
extern "C" char *__cxa_demangle(const char *mangled, char *buffer,
|
||||
size_t *length, int *status)
|
||||
SANITIZER_WEAK_ATTRIBUTE;
|
||||
}
|
||||
|
||||
const char *__sanitizer::Demangle(const char *MangledName) {
|
||||
// FIXME: __cxa_demangle aggressively insists on allocating memory.
|
||||
// There's not much we can do about that, short of providing our
|
||||
// own demangler (libc++abi's implementation could be adapted so that
|
||||
// it does not allocate). For now, we just call it anyway, and we leak
|
||||
// the returned value.
|
||||
if (__cxxabiv1::__cxa_demangle)
|
||||
if (const char *Demangled =
|
||||
__cxxabiv1::__cxa_demangle(MangledName, 0, 0, 0))
|
||||
return Demangled;
|
||||
|
||||
return MangledName;
|
||||
}
|
||||
|
||||
#endif // __APPLE__ || __linux__
|
@ -26,6 +26,10 @@ uptr GetListOfModules(LoadedModule *modules, uptr max_modules) {
|
||||
UNIMPLEMENTED();
|
||||
};
|
||||
|
||||
const char *Demangle(const char *MangledName) {
|
||||
return MangledName;
|
||||
}
|
||||
|
||||
} // namespace __sanitizer
|
||||
|
||||
#endif // _WIN32
|
||||
|
@ -13,6 +13,7 @@
|
||||
#define WIN32_LEAN_AND_MEAN
|
||||
#define NOGDI
|
||||
#include <stdlib.h>
|
||||
#include <io.h>
|
||||
#include <windows.h>
|
||||
|
||||
#include "sanitizer_common.h"
|
||||
@ -73,6 +74,8 @@ void UnmapOrDie(void *addr, uptr size) {
|
||||
}
|
||||
|
||||
void *MmapFixedNoReserve(uptr fixed_addr, uptr size) {
|
||||
// FIXME: is this really "NoReserve"? On Win32 this does not matter much,
|
||||
// but on Win64 it does.
|
||||
void *p = VirtualAlloc((LPVOID)fixed_addr, size,
|
||||
MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE);
|
||||
if (p == 0)
|
||||
@ -81,6 +84,10 @@ void *MmapFixedNoReserve(uptr fixed_addr, uptr size) {
|
||||
return p;
|
||||
}
|
||||
|
||||
void *MmapFixedOrDie(uptr fixed_addr, uptr size) {
|
||||
return MmapFixedNoReserve(fixed_addr, size);
|
||||
}
|
||||
|
||||
void *Mprotect(uptr fixed_addr, uptr size) {
|
||||
return VirtualAlloc((LPVOID)fixed_addr, size,
|
||||
MEM_RESERVE | MEM_COMMIT, PAGE_NOACCESS);
|
||||
@ -127,6 +134,10 @@ void ReExec() {
|
||||
UNIMPLEMENTED();
|
||||
}
|
||||
|
||||
void PrepareForSandboxing() {
|
||||
// Nothing here for now.
|
||||
}
|
||||
|
||||
bool StackSizeIsUnlimited() {
|
||||
UNIMPLEMENTED();
|
||||
}
|
||||
@ -173,7 +184,7 @@ int internal_close(fd_t fd) {
|
||||
}
|
||||
|
||||
int internal_isatty(fd_t fd) {
|
||||
UNIMPLEMENTED();
|
||||
return _isatty(fd);
|
||||
}
|
||||
|
||||
fd_t internal_open(const char *filename, bool write) {
|
||||
|
@ -31,6 +31,9 @@ tsan_files = \
|
||||
tsan_interface_ann.cc \
|
||||
tsan_mman.cc \
|
||||
tsan_rtl_report.cc \
|
||||
tsan_fd.cc \
|
||||
tsan_interface_java.cc \
|
||||
tsan_mutexset.cc \
|
||||
tsan_symbolize_addr2line_linux.cc
|
||||
|
||||
libtsan_la_SOURCES = $(tsan_files)
|
||||
|
@ -87,7 +87,8 @@ am__objects_1 = tsan_clock.lo tsan_interface_atomic.lo tsan_mutex.lo \
|
||||
tsan_rtl.lo tsan_stat.lo tsan_sync.lo tsan_interceptors.lo \
|
||||
tsan_md5.lo tsan_platform_mac.lo tsan_rtl_mutex.lo \
|
||||
tsan_suppressions.lo tsan_interface_ann.lo tsan_mman.lo \
|
||||
tsan_rtl_report.lo tsan_symbolize_addr2line_linux.lo
|
||||
tsan_rtl_report.lo tsan_fd.lo tsan_interface_java.lo \
|
||||
tsan_mutexset.lo tsan_symbolize_addr2line_linux.lo
|
||||
am_libtsan_la_OBJECTS = $(am__objects_1)
|
||||
libtsan_la_OBJECTS = $(am_libtsan_la_OBJECTS)
|
||||
libtsan_la_LINK = $(LIBTOOL) --tag=CXX $(AM_LIBTOOLFLAGS) \
|
||||
@ -273,6 +274,9 @@ tsan_files = \
|
||||
tsan_interface_ann.cc \
|
||||
tsan_mman.cc \
|
||||
tsan_rtl_report.cc \
|
||||
tsan_fd.cc \
|
||||
tsan_interface_java.cc \
|
||||
tsan_mutexset.cc \
|
||||
tsan_symbolize_addr2line_linux.cc
|
||||
|
||||
libtsan_la_SOURCES = $(tsan_files)
|
||||
@ -393,14 +397,17 @@ distclean-compile:
|
||||
-rm -f *.tab.c
|
||||
|
||||
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/tsan_clock.Plo@am__quote@
|
||||
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/tsan_fd.Plo@am__quote@
|
||||
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/tsan_flags.Plo@am__quote@
|
||||
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/tsan_interceptors.Plo@am__quote@
|
||||
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/tsan_interface.Plo@am__quote@
|
||||
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/tsan_interface_ann.Plo@am__quote@
|
||||
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/tsan_interface_atomic.Plo@am__quote@
|
||||
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/tsan_interface_java.Plo@am__quote@
|
||||
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/tsan_md5.Plo@am__quote@
|
||||
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/tsan_mman.Plo@am__quote@
|
||||
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/tsan_mutex.Plo@am__quote@
|
||||
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/tsan_mutexset.Plo@am__quote@
|
||||
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/tsan_platform_linux.Plo@am__quote@
|
||||
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/tsan_platform_mac.Plo@am__quote@
|
||||
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/tsan_report.Plo@am__quote@
|
||||
|
@ -137,6 +137,12 @@ T RoundDown(T p, u64 align) {
|
||||
return (T)((u64)p & ~(align - 1));
|
||||
}
|
||||
|
||||
// Zeroizes high part, returns 'bits' lsb bits.
|
||||
template<typename T>
|
||||
T GetLsb(T v, int bits) {
|
||||
return (T)((u64)v & ((1ull << bits) - 1));
|
||||
}
|
||||
|
||||
struct MD5Hash {
|
||||
u64 hash[2];
|
||||
bool operator==(const MD5Hash &other) const;
|
||||
|
257
libsanitizer/tsan/tsan_fd.cc
Normal file
257
libsanitizer/tsan/tsan_fd.cc
Normal file
@ -0,0 +1,257 @@
|
||||
//===-- tsan_fd.cc --------------------------------------------------------===//
|
||||
//
|
||||
// This file is distributed under the University of Illinois Open Source
|
||||
// License. See LICENSE.TXT for details.
|
||||
//
|
||||
//===----------------------------------------------------------------------===//
|
||||
//
|
||||
// This file is a part of ThreadSanitizer (TSan), a race detector.
|
||||
//
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
#include "tsan_fd.h"
|
||||
#include "tsan_rtl.h"
|
||||
#include <sanitizer_common/sanitizer_atomic.h>
|
||||
|
||||
namespace __tsan {
|
||||
|
||||
const int kTableSizeL1 = 1024;
|
||||
const int kTableSizeL2 = 1024;
|
||||
const int kTableSize = kTableSizeL1 * kTableSizeL2;
|
||||
|
||||
struct FdSync {
|
||||
atomic_uint64_t rc;
|
||||
};
|
||||
|
||||
struct FdDesc {
|
||||
FdSync *sync;
|
||||
int creation_tid;
|
||||
u32 creation_stack;
|
||||
};
|
||||
|
||||
struct FdContext {
|
||||
atomic_uintptr_t tab[kTableSizeL1];
|
||||
// Addresses used for synchronization.
|
||||
FdSync globsync;
|
||||
FdSync filesync;
|
||||
FdSync socksync;
|
||||
u64 connectsync;
|
||||
};
|
||||
|
||||
static FdContext fdctx;
|
||||
|
||||
static FdSync *allocsync() {
|
||||
FdSync *s = (FdSync*)internal_alloc(MBlockFD, sizeof(FdSync));
|
||||
atomic_store(&s->rc, 1, memory_order_relaxed);
|
||||
return s;
|
||||
}
|
||||
|
||||
static FdSync *ref(FdSync *s) {
|
||||
if (s && atomic_load(&s->rc, memory_order_relaxed) != (u64)-1)
|
||||
atomic_fetch_add(&s->rc, 1, memory_order_relaxed);
|
||||
return s;
|
||||
}
|
||||
|
||||
static void unref(ThreadState *thr, uptr pc, FdSync *s) {
|
||||
if (s && atomic_load(&s->rc, memory_order_relaxed) != (u64)-1) {
|
||||
if (atomic_fetch_sub(&s->rc, 1, memory_order_acq_rel) == 1) {
|
||||
CHECK_NE(s, &fdctx.globsync);
|
||||
CHECK_NE(s, &fdctx.filesync);
|
||||
CHECK_NE(s, &fdctx.socksync);
|
||||
SyncVar *v = CTX()->synctab.GetAndRemove(thr, pc, (uptr)s);
|
||||
if (v)
|
||||
DestroyAndFree(v);
|
||||
internal_free(s);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static FdDesc *fddesc(ThreadState *thr, uptr pc, int fd) {
|
||||
CHECK_LT(fd, kTableSize);
|
||||
atomic_uintptr_t *pl1 = &fdctx.tab[fd / kTableSizeL2];
|
||||
uptr l1 = atomic_load(pl1, memory_order_consume);
|
||||
if (l1 == 0) {
|
||||
uptr size = kTableSizeL2 * sizeof(FdDesc);
|
||||
void *p = internal_alloc(MBlockFD, size);
|
||||
internal_memset(p, 0, size);
|
||||
MemoryResetRange(thr, (uptr)&fddesc, (uptr)p, size);
|
||||
if (atomic_compare_exchange_strong(pl1, &l1, (uptr)p, memory_order_acq_rel))
|
||||
l1 = (uptr)p;
|
||||
else
|
||||
internal_free(p);
|
||||
}
|
||||
return &((FdDesc*)l1)[fd % kTableSizeL2]; // NOLINT
|
||||
}
|
||||
|
||||
// pd must be already ref'ed.
|
||||
static void init(ThreadState *thr, uptr pc, int fd, FdSync *s) {
|
||||
FdDesc *d = fddesc(thr, pc, fd);
|
||||
// As a matter of fact, we don't intercept all close calls.
|
||||
// See e.g. libc __res_iclose().
|
||||
if (d->sync) {
|
||||
unref(thr, pc, d->sync);
|
||||
d->sync = 0;
|
||||
}
|
||||
if (flags()->io_sync == 0) {
|
||||
unref(thr, pc, s);
|
||||
} else if (flags()->io_sync == 1) {
|
||||
d->sync = s;
|
||||
} else if (flags()->io_sync == 2) {
|
||||
unref(thr, pc, s);
|
||||
d->sync = &fdctx.globsync;
|
||||
}
|
||||
d->creation_tid = thr->tid;
|
||||
d->creation_stack = CurrentStackId(thr, pc);
|
||||
// To catch races between fd usage and open.
|
||||
MemoryRangeImitateWrite(thr, pc, (uptr)d, 8);
|
||||
}
|
||||
|
||||
void FdInit() {
|
||||
atomic_store(&fdctx.globsync.rc, (u64)-1, memory_order_relaxed);
|
||||
atomic_store(&fdctx.filesync.rc, (u64)-1, memory_order_relaxed);
|
||||
atomic_store(&fdctx.socksync.rc, (u64)-1, memory_order_relaxed);
|
||||
}
|
||||
|
||||
void FdOnFork(ThreadState *thr, uptr pc) {
|
||||
// On fork() we need to reset all fd's, because the child is going
|
||||
// close all them, and that will cause races between previous read/write
|
||||
// and the close.
|
||||
for (int l1 = 0; l1 < kTableSizeL1; l1++) {
|
||||
FdDesc *tab = (FdDesc*)atomic_load(&fdctx.tab[l1], memory_order_relaxed);
|
||||
if (tab == 0)
|
||||
break;
|
||||
for (int l2 = 0; l2 < kTableSizeL2; l2++) {
|
||||
FdDesc *d = &tab[l2];
|
||||
MemoryResetRange(thr, pc, (uptr)d, 8);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool FdLocation(uptr addr, int *fd, int *tid, u32 *stack) {
|
||||
for (int l1 = 0; l1 < kTableSizeL1; l1++) {
|
||||
FdDesc *tab = (FdDesc*)atomic_load(&fdctx.tab[l1], memory_order_relaxed);
|
||||
if (tab == 0)
|
||||
break;
|
||||
if (addr >= (uptr)tab && addr < (uptr)(tab + kTableSizeL2)) {
|
||||
int l2 = (addr - (uptr)tab) / sizeof(FdDesc);
|
||||
FdDesc *d = &tab[l2];
|
||||
*fd = l1 * kTableSizeL1 + l2;
|
||||
*tid = d->creation_tid;
|
||||
*stack = d->creation_stack;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void FdAcquire(ThreadState *thr, uptr pc, int fd) {
|
||||
FdDesc *d = fddesc(thr, pc, fd);
|
||||
FdSync *s = d->sync;
|
||||
DPrintf("#%d: FdAcquire(%d) -> %p\n", thr->tid, fd, s);
|
||||
MemoryRead8Byte(thr, pc, (uptr)d);
|
||||
if (s)
|
||||
Acquire(thr, pc, (uptr)s);
|
||||
}
|
||||
|
||||
void FdRelease(ThreadState *thr, uptr pc, int fd) {
|
||||
FdDesc *d = fddesc(thr, pc, fd);
|
||||
FdSync *s = d->sync;
|
||||
DPrintf("#%d: FdRelease(%d) -> %p\n", thr->tid, fd, s);
|
||||
if (s)
|
||||
Release(thr, pc, (uptr)s);
|
||||
MemoryRead8Byte(thr, pc, (uptr)d);
|
||||
}
|
||||
|
||||
void FdClose(ThreadState *thr, uptr pc, int fd) {
|
||||
DPrintf("#%d: FdClose(%d)\n", thr->tid, fd);
|
||||
FdDesc *d = fddesc(thr, pc, fd);
|
||||
// To catch races between fd usage and close.
|
||||
MemoryWrite8Byte(thr, pc, (uptr)d);
|
||||
// We need to clear it, because if we do not intercept any call out there
|
||||
// that creates fd, we will hit false postives.
|
||||
MemoryResetRange(thr, pc, (uptr)d, 8);
|
||||
unref(thr, pc, d->sync);
|
||||
d->sync = 0;
|
||||
d->creation_tid = 0;
|
||||
d->creation_stack = 0;
|
||||
}
|
||||
|
||||
void FdFileCreate(ThreadState *thr, uptr pc, int fd) {
|
||||
DPrintf("#%d: FdFileCreate(%d)\n", thr->tid, fd);
|
||||
init(thr, pc, fd, &fdctx.filesync);
|
||||
}
|
||||
|
||||
void FdDup(ThreadState *thr, uptr pc, int oldfd, int newfd) {
|
||||
DPrintf("#%d: FdDup(%d, %d)\n", thr->tid, oldfd, newfd);
|
||||
// Ignore the case when user dups not yet connected socket.
|
||||
FdDesc *od = fddesc(thr, pc, oldfd);
|
||||
MemoryRead8Byte(thr, pc, (uptr)od);
|
||||
FdClose(thr, pc, newfd);
|
||||
init(thr, pc, newfd, ref(od->sync));
|
||||
}
|
||||
|
||||
void FdPipeCreate(ThreadState *thr, uptr pc, int rfd, int wfd) {
|
||||
DPrintf("#%d: FdCreatePipe(%d, %d)\n", thr->tid, rfd, wfd);
|
||||
FdSync *s = allocsync();
|
||||
init(thr, pc, rfd, ref(s));
|
||||
init(thr, pc, wfd, ref(s));
|
||||
unref(thr, pc, s);
|
||||
}
|
||||
|
||||
void FdEventCreate(ThreadState *thr, uptr pc, int fd) {
|
||||
DPrintf("#%d: FdEventCreate(%d)\n", thr->tid, fd);
|
||||
init(thr, pc, fd, allocsync());
|
||||
}
|
||||
|
||||
void FdSignalCreate(ThreadState *thr, uptr pc, int fd) {
|
||||
DPrintf("#%d: FdSignalCreate(%d)\n", thr->tid, fd);
|
||||
init(thr, pc, fd, 0);
|
||||
}
|
||||
|
||||
void FdInotifyCreate(ThreadState *thr, uptr pc, int fd) {
|
||||
DPrintf("#%d: FdInotifyCreate(%d)\n", thr->tid, fd);
|
||||
init(thr, pc, fd, 0);
|
||||
}
|
||||
|
||||
void FdPollCreate(ThreadState *thr, uptr pc, int fd) {
|
||||
DPrintf("#%d: FdPollCreate(%d)\n", thr->tid, fd);
|
||||
init(thr, pc, fd, allocsync());
|
||||
}
|
||||
|
||||
void FdSocketCreate(ThreadState *thr, uptr pc, int fd) {
|
||||
DPrintf("#%d: FdSocketCreate(%d)\n", thr->tid, fd);
|
||||
// It can be a UDP socket.
|
||||
init(thr, pc, fd, &fdctx.socksync);
|
||||
}
|
||||
|
||||
void FdSocketAccept(ThreadState *thr, uptr pc, int fd, int newfd) {
|
||||
DPrintf("#%d: FdSocketAccept(%d, %d)\n", thr->tid, fd, newfd);
|
||||
// Synchronize connect->accept.
|
||||
Acquire(thr, pc, (uptr)&fdctx.connectsync);
|
||||
init(thr, pc, newfd, &fdctx.socksync);
|
||||
}
|
||||
|
||||
void FdSocketConnecting(ThreadState *thr, uptr pc, int fd) {
|
||||
DPrintf("#%d: FdSocketConnecting(%d)\n", thr->tid, fd);
|
||||
// Synchronize connect->accept.
|
||||
Release(thr, pc, (uptr)&fdctx.connectsync);
|
||||
}
|
||||
|
||||
void FdSocketConnect(ThreadState *thr, uptr pc, int fd) {
|
||||
DPrintf("#%d: FdSocketConnect(%d)\n", thr->tid, fd);
|
||||
init(thr, pc, fd, &fdctx.socksync);
|
||||
}
|
||||
|
||||
uptr File2addr(char *path) {
|
||||
(void)path;
|
||||
static u64 addr;
|
||||
return (uptr)&addr;
|
||||
}
|
||||
|
||||
uptr Dir2addr(char *path) {
|
||||
(void)path;
|
||||
static u64 addr;
|
||||
return (uptr)&addr;
|
||||
}
|
||||
|
||||
} // namespace __tsan
|
62
libsanitizer/tsan/tsan_fd.h
Normal file
62
libsanitizer/tsan/tsan_fd.h
Normal file
@ -0,0 +1,62 @@
|
||||
//===-- tsan_fd.h -----------------------------------------------*- C++ -*-===//
|
||||
//
|
||||
// This file is distributed under the University of Illinois Open Source
|
||||
// License. See LICENSE.TXT for details.
|
||||
//
|
||||
//===----------------------------------------------------------------------===//
|
||||
//
|
||||
// This file is a part of ThreadSanitizer (TSan), a race detector.
|
||||
//
|
||||
// This file handles synchronization via IO.
|
||||
// People use IO for synchronization along the lines of:
|
||||
//
|
||||
// int X;
|
||||
// int client_socket; // initialized elsewhere
|
||||
// int server_socket; // initialized elsewhere
|
||||
//
|
||||
// Thread 1:
|
||||
// X = 42;
|
||||
// send(client_socket, ...);
|
||||
//
|
||||
// Thread 2:
|
||||
// if (recv(server_socket, ...) > 0)
|
||||
// assert(X == 42);
|
||||
//
|
||||
// This file determines the scope of the file descriptor (pipe, socket,
|
||||
// all local files, etc) and executes acquire and release operations on
|
||||
// the scope as necessary. Some scopes are very fine grained (e.g. pipe
|
||||
// operations synchronize only with operations on the same pipe), while
|
||||
// others are corse-grained (e.g. all operations on local files synchronize
|
||||
// with each other).
|
||||
//===----------------------------------------------------------------------===//
|
||||
#ifndef TSAN_FD_H
|
||||
#define TSAN_FD_H
|
||||
|
||||
#include "tsan_rtl.h"
|
||||
|
||||
namespace __tsan {
|
||||
|
||||
void FdInit();
|
||||
void FdAcquire(ThreadState *thr, uptr pc, int fd);
|
||||
void FdRelease(ThreadState *thr, uptr pc, int fd);
|
||||
void FdClose(ThreadState *thr, uptr pc, int fd);
|
||||
void FdFileCreate(ThreadState *thr, uptr pc, int fd);
|
||||
void FdDup(ThreadState *thr, uptr pc, int oldfd, int newfd);
|
||||
void FdPipeCreate(ThreadState *thr, uptr pc, int rfd, int wfd);
|
||||
void FdEventCreate(ThreadState *thr, uptr pc, int fd);
|
||||
void FdSignalCreate(ThreadState *thr, uptr pc, int fd);
|
||||
void FdInotifyCreate(ThreadState *thr, uptr pc, int fd);
|
||||
void FdPollCreate(ThreadState *thr, uptr pc, int fd);
|
||||
void FdSocketCreate(ThreadState *thr, uptr pc, int fd);
|
||||
void FdSocketAccept(ThreadState *thr, uptr pc, int fd, int newfd);
|
||||
void FdSocketConnecting(ThreadState *thr, uptr pc, int fd);
|
||||
void FdSocketConnect(ThreadState *thr, uptr pc, int fd);
|
||||
bool FdLocation(uptr addr, int *fd, int *tid, u32 *stack);
|
||||
void FdOnFork(ThreadState *thr, uptr pc);
|
||||
|
||||
uptr File2addr(char *path);
|
||||
uptr Dir2addr(char *path);
|
||||
|
||||
} // namespace __tsan
|
||||
|
||||
#endif // TSAN_INTERFACE_H
|
@ -56,6 +56,7 @@ void InitializeFlags(Flags *f, const char *env) {
|
||||
f->running_on_valgrind = false;
|
||||
f->external_symbolizer_path = "";
|
||||
f->history_size = kGoMode ? 1 : 2; // There are a lot of goroutines in Go.
|
||||
f->io_sync = 1;
|
||||
|
||||
// Let a frontend override.
|
||||
OverrideFlags(f);
|
||||
@ -81,6 +82,7 @@ void InitializeFlags(Flags *f, const char *env) {
|
||||
ParseFlag(env, &f->stop_on_start, "stop_on_start");
|
||||
ParseFlag(env, &f->external_symbolizer_path, "external_symbolizer_path");
|
||||
ParseFlag(env, &f->history_size, "history_size");
|
||||
ParseFlag(env, &f->io_sync, "io_sync");
|
||||
|
||||
if (!f->report_bugs) {
|
||||
f->report_thread_leaks = false;
|
||||
@ -93,6 +95,12 @@ void InitializeFlags(Flags *f, const char *env) {
|
||||
" (must be [0..7])\n");
|
||||
Die();
|
||||
}
|
||||
|
||||
if (f->io_sync < 0 || f->io_sync > 2) {
|
||||
Printf("ThreadSanitizer: incorrect value for io_sync"
|
||||
" (must be [0..2])\n");
|
||||
Die();
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace __tsan
|
||||
|
@ -75,6 +75,11 @@ struct Flags {
|
||||
// the amount of memory accesses, up to history_size=7 that amounts to
|
||||
// 4M memory accesses. The default value is 2 (128K memory accesses).
|
||||
int history_size;
|
||||
// Controls level of synchronization implied by IO operations.
|
||||
// 0 - no synchronization
|
||||
// 1 - reasonable level of synchronization (write->read)
|
||||
// 2 - global synchronization of all IO operations
|
||||
int io_sync;
|
||||
};
|
||||
|
||||
Flags *flags();
|
||||
|
@ -7,6 +7,8 @@
|
||||
//
|
||||
// This file is a part of ThreadSanitizer (TSan), a race detector.
|
||||
//
|
||||
// FIXME: move as many interceptors as possible into
|
||||
// sanitizer_common/sanitizer_common_interceptors.h
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
#include "sanitizer_common/sanitizer_atomic.h"
|
||||
@ -18,6 +20,7 @@
|
||||
#include "tsan_platform.h"
|
||||
#include "tsan_rtl.h"
|
||||
#include "tsan_mman.h"
|
||||
#include "tsan_fd.h"
|
||||
|
||||
using namespace __tsan; // NOLINT
|
||||
|
||||
@ -50,6 +53,7 @@ extern "C" void *pthread_self();
|
||||
extern "C" void _exit(int status);
|
||||
extern "C" int __cxa_atexit(void (*func)(void *arg), void *arg, void *dso);
|
||||
extern "C" int *__errno_location();
|
||||
extern "C" int fileno_unlocked(void *stream);
|
||||
const int PTHREAD_MUTEX_RECURSIVE = 1;
|
||||
const int PTHREAD_MUTEX_RECURSIVE_NP = 1;
|
||||
const int kPthreadAttrSize = 56;
|
||||
@ -124,10 +128,8 @@ static SignalContext *SigCtx(ThreadState *thr) {
|
||||
SignalContext *ctx = (SignalContext*)thr->signal_ctx;
|
||||
if (ctx == 0 && thr->is_alive) {
|
||||
ScopedInRtl in_rtl;
|
||||
ctx = (SignalContext*)internal_alloc(
|
||||
MBlockSignal, sizeof(*ctx));
|
||||
MemoryResetRange(thr, 0, (uptr)ctx, sizeof(*ctx));
|
||||
internal_memset(ctx, 0, sizeof(*ctx));
|
||||
ctx = (SignalContext*)MmapOrDie(sizeof(*ctx), "SignalContext");
|
||||
MemoryResetRange(thr, (uptr)&SigCtx, (uptr)ctx, sizeof(*ctx));
|
||||
thr->signal_ctx = ctx;
|
||||
}
|
||||
return ctx;
|
||||
@ -173,8 +175,8 @@ ScopedInterceptor::~ScopedInterceptor() {
|
||||
StatInc(thr, StatInt_##func); \
|
||||
const uptr caller_pc = GET_CALLER_PC(); \
|
||||
ScopedInterceptor si(thr, #func, caller_pc); \
|
||||
/* Subtract one from pc as we need current instruction address */ \
|
||||
const uptr pc = __sanitizer::StackTrace::GetCurrentPc() - 1; \
|
||||
const uptr pc = __sanitizer::StackTrace::GetPreviousInstructionPc( \
|
||||
__sanitizer::StackTrace::GetCurrentPc()); \
|
||||
(void)pc; \
|
||||
/**/
|
||||
|
||||
@ -306,30 +308,6 @@ TSAN_INTERCEPTOR(void, siglongjmp, void *env, int val) {
|
||||
Die();
|
||||
}
|
||||
|
||||
static uptr fd2addr(int fd) {
|
||||
(void)fd;
|
||||
static u64 addr;
|
||||
return (uptr)&addr;
|
||||
}
|
||||
|
||||
static uptr epollfd2addr(int fd) {
|
||||
(void)fd;
|
||||
static u64 addr;
|
||||
return (uptr)&addr;
|
||||
}
|
||||
|
||||
static uptr file2addr(char *path) {
|
||||
(void)path;
|
||||
static u64 addr;
|
||||
return (uptr)&addr;
|
||||
}
|
||||
|
||||
static uptr dir2addr(char *path) {
|
||||
(void)path;
|
||||
static u64 addr;
|
||||
return (uptr)&addr;
|
||||
}
|
||||
|
||||
TSAN_INTERCEPTOR(void*, malloc, uptr size) {
|
||||
void *p = 0;
|
||||
{
|
||||
@ -660,7 +638,7 @@ static void thread_finalize(void *v) {
|
||||
SignalContext *sctx = thr->signal_ctx;
|
||||
if (sctx) {
|
||||
thr->signal_ctx = 0;
|
||||
internal_free(sctx);
|
||||
UnmapOrDie(sctx, sizeof(*sctx));
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -934,11 +912,15 @@ TSAN_INTERCEPTOR(int, pthread_rwlock_unlock, void *m) {
|
||||
return res;
|
||||
}
|
||||
|
||||
// libpthread.so contains several versions of pthread_cond_init symbol.
|
||||
// When we just dlsym() it, we get the wrong (old) version.
|
||||
/*
|
||||
TSAN_INTERCEPTOR(int, pthread_cond_init, void *c, void *a) {
|
||||
SCOPED_TSAN_INTERCEPTOR(pthread_cond_init, c, a);
|
||||
int res = REAL(pthread_cond_init)(c, a);
|
||||
return res;
|
||||
}
|
||||
*/
|
||||
|
||||
TSAN_INTERCEPTOR(int, pthread_cond_destroy, void *c) {
|
||||
SCOPED_TSAN_INTERCEPTOR(pthread_cond_destroy, c);
|
||||
@ -1080,11 +1062,188 @@ TSAN_INTERCEPTOR(int, sem_getvalue, void *s, int *sval) {
|
||||
return res;
|
||||
}
|
||||
|
||||
TSAN_INTERCEPTOR(int, open, const char *name, int flags, int mode) {
|
||||
SCOPED_TSAN_INTERCEPTOR(open, name, flags, mode);
|
||||
int fd = REAL(open)(name, flags, mode);
|
||||
if (fd >= 0)
|
||||
FdFileCreate(thr, pc, fd);
|
||||
return fd;
|
||||
}
|
||||
|
||||
TSAN_INTERCEPTOR(int, open64, const char *name, int flags, int mode) {
|
||||
SCOPED_TSAN_INTERCEPTOR(open64, name, flags, mode);
|
||||
int fd = REAL(open64)(name, flags, mode);
|
||||
if (fd >= 0)
|
||||
FdFileCreate(thr, pc, fd);
|
||||
return fd;
|
||||
}
|
||||
|
||||
TSAN_INTERCEPTOR(int, creat, const char *name, int mode) {
|
||||
SCOPED_TSAN_INTERCEPTOR(creat, name, mode);
|
||||
int fd = REAL(creat)(name, mode);
|
||||
if (fd >= 0)
|
||||
FdFileCreate(thr, pc, fd);
|
||||
return fd;
|
||||
}
|
||||
|
||||
TSAN_INTERCEPTOR(int, creat64, const char *name, int mode) {
|
||||
SCOPED_TSAN_INTERCEPTOR(creat64, name, mode);
|
||||
int fd = REAL(creat64)(name, mode);
|
||||
if (fd >= 0)
|
||||
FdFileCreate(thr, pc, fd);
|
||||
return fd;
|
||||
}
|
||||
|
||||
TSAN_INTERCEPTOR(int, dup, int oldfd) {
|
||||
SCOPED_TSAN_INTERCEPTOR(dup, oldfd);
|
||||
int newfd = REAL(dup)(oldfd);
|
||||
if (oldfd >= 0 && newfd >= 0 && newfd != oldfd)
|
||||
FdDup(thr, pc, oldfd, newfd);
|
||||
return newfd;
|
||||
}
|
||||
|
||||
TSAN_INTERCEPTOR(int, dup2, int oldfd, int newfd) {
|
||||
SCOPED_TSAN_INTERCEPTOR(dup2, oldfd, newfd);
|
||||
int newfd2 = REAL(dup2)(oldfd, newfd);
|
||||
if (oldfd >= 0 && newfd2 >= 0 && newfd2 != oldfd)
|
||||
FdDup(thr, pc, oldfd, newfd2);
|
||||
return newfd2;
|
||||
}
|
||||
|
||||
TSAN_INTERCEPTOR(int, dup3, int oldfd, int newfd, int flags) {
|
||||
SCOPED_TSAN_INTERCEPTOR(dup3, oldfd, newfd, flags);
|
||||
int newfd2 = REAL(dup3)(oldfd, newfd, flags);
|
||||
if (oldfd >= 0 && newfd2 >= 0 && newfd2 != oldfd)
|
||||
FdDup(thr, pc, oldfd, newfd2);
|
||||
return newfd2;
|
||||
}
|
||||
|
||||
TSAN_INTERCEPTOR(int, eventfd, unsigned initval, int flags) {
|
||||
SCOPED_TSAN_INTERCEPTOR(eventfd, initval, flags);
|
||||
int fd = REAL(eventfd)(initval, flags);
|
||||
if (fd >= 0)
|
||||
FdEventCreate(thr, pc, fd);
|
||||
return fd;
|
||||
}
|
||||
|
||||
TSAN_INTERCEPTOR(int, signalfd, int fd, void *mask, int flags) {
|
||||
SCOPED_TSAN_INTERCEPTOR(signalfd, fd, mask, flags);
|
||||
if (fd >= 0)
|
||||
FdClose(thr, pc, fd);
|
||||
fd = REAL(signalfd)(fd, mask, flags);
|
||||
if (fd >= 0)
|
||||
FdSignalCreate(thr, pc, fd);
|
||||
return fd;
|
||||
}
|
||||
|
||||
TSAN_INTERCEPTOR(int, inotify_init, int fake) {
|
||||
SCOPED_TSAN_INTERCEPTOR(inotify_init, fake);
|
||||
int fd = REAL(inotify_init)(fake);
|
||||
if (fd >= 0)
|
||||
FdInotifyCreate(thr, pc, fd);
|
||||
return fd;
|
||||
}
|
||||
|
||||
TSAN_INTERCEPTOR(int, inotify_init1, int flags) {
|
||||
SCOPED_TSAN_INTERCEPTOR(inotify_init1, flags);
|
||||
int fd = REAL(inotify_init1)(flags);
|
||||
if (fd >= 0)
|
||||
FdInotifyCreate(thr, pc, fd);
|
||||
return fd;
|
||||
}
|
||||
|
||||
TSAN_INTERCEPTOR(int, socket, int domain, int type, int protocol) {
|
||||
SCOPED_TSAN_INTERCEPTOR(socket, domain, type, protocol);
|
||||
int fd = REAL(socket)(domain, type, protocol);
|
||||
if (fd >= 0)
|
||||
FdSocketCreate(thr, pc, fd);
|
||||
return fd;
|
||||
}
|
||||
|
||||
TSAN_INTERCEPTOR(int, socketpair, int domain, int type, int protocol, int *fd) {
|
||||
SCOPED_TSAN_INTERCEPTOR(socketpair, domain, type, protocol, fd);
|
||||
int res = REAL(socketpair)(domain, type, protocol, fd);
|
||||
if (res == 0 && fd[0] >= 0 && fd[1] >= 0)
|
||||
FdPipeCreate(thr, pc, fd[0], fd[1]);
|
||||
return res;
|
||||
}
|
||||
|
||||
TSAN_INTERCEPTOR(int, connect, int fd, void *addr, unsigned addrlen) {
|
||||
SCOPED_TSAN_INTERCEPTOR(connect, fd, addr, addrlen);
|
||||
FdSocketConnecting(thr, pc, fd);
|
||||
int res = REAL(connect)(fd, addr, addrlen);
|
||||
if (res == 0 && fd >= 0)
|
||||
FdSocketConnect(thr, pc, fd);
|
||||
return res;
|
||||
}
|
||||
|
||||
TSAN_INTERCEPTOR(int, accept, int fd, void *addr, unsigned *addrlen) {
|
||||
SCOPED_TSAN_INTERCEPTOR(accept, fd, addr, addrlen);
|
||||
int fd2 = REAL(accept)(fd, addr, addrlen);
|
||||
if (fd >= 0 && fd2 >= 0)
|
||||
FdSocketAccept(thr, pc, fd, fd2);
|
||||
return fd2;
|
||||
}
|
||||
|
||||
TSAN_INTERCEPTOR(int, accept4, int fd, void *addr, unsigned *addrlen, int f) {
|
||||
SCOPED_TSAN_INTERCEPTOR(accept4, fd, addr, addrlen, f);
|
||||
int fd2 = REAL(accept4)(fd, addr, addrlen, f);
|
||||
if (fd >= 0 && fd2 >= 0)
|
||||
FdSocketAccept(thr, pc, fd, fd2);
|
||||
return fd2;
|
||||
}
|
||||
|
||||
TSAN_INTERCEPTOR(int, epoll_create, int size) {
|
||||
SCOPED_TSAN_INTERCEPTOR(epoll_create, size);
|
||||
int fd = REAL(epoll_create)(size);
|
||||
if (fd >= 0)
|
||||
FdPollCreate(thr, pc, fd);
|
||||
return fd;
|
||||
}
|
||||
|
||||
TSAN_INTERCEPTOR(int, epoll_create1, int flags) {
|
||||
SCOPED_TSAN_INTERCEPTOR(epoll_create1, flags);
|
||||
int fd = REAL(epoll_create1)(flags);
|
||||
if (fd >= 0)
|
||||
FdPollCreate(thr, pc, fd);
|
||||
return fd;
|
||||
}
|
||||
|
||||
TSAN_INTERCEPTOR(int, close, int fd) {
|
||||
SCOPED_TSAN_INTERCEPTOR(close, fd);
|
||||
if (fd >= 0)
|
||||
FdClose(thr, pc, fd);
|
||||
return REAL(close)(fd);
|
||||
}
|
||||
|
||||
TSAN_INTERCEPTOR(int, __close, int fd) {
|
||||
SCOPED_TSAN_INTERCEPTOR(__close, fd);
|
||||
if (fd >= 0)
|
||||
FdClose(thr, pc, fd);
|
||||
return REAL(__close)(fd);
|
||||
}
|
||||
|
||||
TSAN_INTERCEPTOR(int, pipe, int *pipefd) {
|
||||
SCOPED_TSAN_INTERCEPTOR(pipe, pipefd);
|
||||
int res = REAL(pipe)(pipefd);
|
||||
if (res == 0 && pipefd[0] >= 0 && pipefd[1] >= 0)
|
||||
FdPipeCreate(thr, pc, pipefd[0], pipefd[1]);
|
||||
return res;
|
||||
}
|
||||
|
||||
TSAN_INTERCEPTOR(int, pipe2, int *pipefd, int flags) {
|
||||
SCOPED_TSAN_INTERCEPTOR(pipe2, pipefd, flags);
|
||||
int res = REAL(pipe2)(pipefd, flags);
|
||||
if (res == 0 && pipefd[0] >= 0 && pipefd[1] >= 0)
|
||||
FdPipeCreate(thr, pc, pipefd[0], pipefd[1]);
|
||||
return res;
|
||||
}
|
||||
|
||||
TSAN_INTERCEPTOR(long_t, read, int fd, void *buf, long_t sz) {
|
||||
SCOPED_TSAN_INTERCEPTOR(read, fd, buf, sz);
|
||||
int res = REAL(read)(fd, buf, sz);
|
||||
if (res >= 0) {
|
||||
Acquire(thr, pc, fd2addr(fd));
|
||||
if (res >= 0 && fd >= 0) {
|
||||
FdAcquire(thr, pc, fd);
|
||||
}
|
||||
return res;
|
||||
}
|
||||
@ -1092,8 +1251,8 @@ TSAN_INTERCEPTOR(long_t, read, int fd, void *buf, long_t sz) {
|
||||
TSAN_INTERCEPTOR(long_t, pread, int fd, void *buf, long_t sz, unsigned off) {
|
||||
SCOPED_TSAN_INTERCEPTOR(pread, fd, buf, sz, off);
|
||||
int res = REAL(pread)(fd, buf, sz, off);
|
||||
if (res >= 0) {
|
||||
Acquire(thr, pc, fd2addr(fd));
|
||||
if (res >= 0 && fd >= 0) {
|
||||
FdAcquire(thr, pc, fd);
|
||||
}
|
||||
return res;
|
||||
}
|
||||
@ -1101,8 +1260,8 @@ TSAN_INTERCEPTOR(long_t, pread, int fd, void *buf, long_t sz, unsigned off) {
|
||||
TSAN_INTERCEPTOR(long_t, pread64, int fd, void *buf, long_t sz, u64 off) {
|
||||
SCOPED_TSAN_INTERCEPTOR(pread64, fd, buf, sz, off);
|
||||
int res = REAL(pread64)(fd, buf, sz, off);
|
||||
if (res >= 0) {
|
||||
Acquire(thr, pc, fd2addr(fd));
|
||||
if (res >= 0 && fd >= 0) {
|
||||
FdAcquire(thr, pc, fd);
|
||||
}
|
||||
return res;
|
||||
}
|
||||
@ -1110,8 +1269,8 @@ TSAN_INTERCEPTOR(long_t, pread64, int fd, void *buf, long_t sz, u64 off) {
|
||||
TSAN_INTERCEPTOR(long_t, readv, int fd, void *vec, int cnt) {
|
||||
SCOPED_TSAN_INTERCEPTOR(readv, fd, vec, cnt);
|
||||
int res = REAL(readv)(fd, vec, cnt);
|
||||
if (res >= 0) {
|
||||
Acquire(thr, pc, fd2addr(fd));
|
||||
if (res >= 0 && fd >= 0) {
|
||||
FdAcquire(thr, pc, fd);
|
||||
}
|
||||
return res;
|
||||
}
|
||||
@ -1119,57 +1278,64 @@ TSAN_INTERCEPTOR(long_t, readv, int fd, void *vec, int cnt) {
|
||||
TSAN_INTERCEPTOR(long_t, preadv64, int fd, void *vec, int cnt, u64 off) {
|
||||
SCOPED_TSAN_INTERCEPTOR(preadv64, fd, vec, cnt, off);
|
||||
int res = REAL(preadv64)(fd, vec, cnt, off);
|
||||
if (res >= 0) {
|
||||
Acquire(thr, pc, fd2addr(fd));
|
||||
if (res >= 0 && fd >= 0) {
|
||||
FdAcquire(thr, pc, fd);
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
TSAN_INTERCEPTOR(long_t, write, int fd, void *buf, long_t sz) {
|
||||
SCOPED_TSAN_INTERCEPTOR(write, fd, buf, sz);
|
||||
Release(thr, pc, fd2addr(fd));
|
||||
if (fd >= 0)
|
||||
FdRelease(thr, pc, fd);
|
||||
int res = REAL(write)(fd, buf, sz);
|
||||
return res;
|
||||
}
|
||||
|
||||
TSAN_INTERCEPTOR(long_t, pwrite, int fd, void *buf, long_t sz, unsigned off) {
|
||||
SCOPED_TSAN_INTERCEPTOR(pwrite, fd, buf, sz, off);
|
||||
Release(thr, pc, fd2addr(fd));
|
||||
if (fd >= 0)
|
||||
FdRelease(thr, pc, fd);
|
||||
int res = REAL(pwrite)(fd, buf, sz, off);
|
||||
return res;
|
||||
}
|
||||
|
||||
TSAN_INTERCEPTOR(long_t, pwrite64, int fd, void *buf, long_t sz, u64 off) {
|
||||
SCOPED_TSAN_INTERCEPTOR(pwrite64, fd, buf, sz, off);
|
||||
Release(thr, pc, fd2addr(fd));
|
||||
if (fd >= 0)
|
||||
FdRelease(thr, pc, fd);
|
||||
int res = REAL(pwrite64)(fd, buf, sz, off);
|
||||
return res;
|
||||
}
|
||||
|
||||
TSAN_INTERCEPTOR(long_t, writev, int fd, void *vec, int cnt) {
|
||||
SCOPED_TSAN_INTERCEPTOR(writev, fd, vec, cnt);
|
||||
Release(thr, pc, fd2addr(fd));
|
||||
if (fd >= 0)
|
||||
FdRelease(thr, pc, fd);
|
||||
int res = REAL(writev)(fd, vec, cnt);
|
||||
return res;
|
||||
}
|
||||
|
||||
TSAN_INTERCEPTOR(long_t, pwritev64, int fd, void *vec, int cnt, u64 off) {
|
||||
SCOPED_TSAN_INTERCEPTOR(pwritev64, fd, vec, cnt, off);
|
||||
Release(thr, pc, fd2addr(fd));
|
||||
if (fd >= 0)
|
||||
FdRelease(thr, pc, fd);
|
||||
int res = REAL(pwritev64)(fd, vec, cnt, off);
|
||||
return res;
|
||||
}
|
||||
|
||||
TSAN_INTERCEPTOR(long_t, send, int fd, void *buf, long_t len, int flags) {
|
||||
SCOPED_TSAN_INTERCEPTOR(send, fd, buf, len, flags);
|
||||
Release(thr, pc, fd2addr(fd));
|
||||
if (fd >= 0)
|
||||
FdRelease(thr, pc, fd);
|
||||
int res = REAL(send)(fd, buf, len, flags);
|
||||
return res;
|
||||
}
|
||||
|
||||
TSAN_INTERCEPTOR(long_t, sendmsg, int fd, void *msg, int flags) {
|
||||
SCOPED_TSAN_INTERCEPTOR(sendmsg, fd, msg, flags);
|
||||
Release(thr, pc, fd2addr(fd));
|
||||
if (fd >= 0)
|
||||
FdRelease(thr, pc, fd);
|
||||
int res = REAL(sendmsg)(fd, msg, flags);
|
||||
return res;
|
||||
}
|
||||
@ -1177,8 +1343,8 @@ TSAN_INTERCEPTOR(long_t, sendmsg, int fd, void *msg, int flags) {
|
||||
TSAN_INTERCEPTOR(long_t, recv, int fd, void *buf, long_t len, int flags) {
|
||||
SCOPED_TSAN_INTERCEPTOR(recv, fd, buf, len, flags);
|
||||
int res = REAL(recv)(fd, buf, len, flags);
|
||||
if (res >= 0) {
|
||||
Acquire(thr, pc, fd2addr(fd));
|
||||
if (res >= 0 && fd >= 0) {
|
||||
FdAcquire(thr, pc, fd);
|
||||
}
|
||||
return res;
|
||||
}
|
||||
@ -1186,15 +1352,15 @@ TSAN_INTERCEPTOR(long_t, recv, int fd, void *buf, long_t len, int flags) {
|
||||
TSAN_INTERCEPTOR(long_t, recvmsg, int fd, void *msg, int flags) {
|
||||
SCOPED_TSAN_INTERCEPTOR(recvmsg, fd, msg, flags);
|
||||
int res = REAL(recvmsg)(fd, msg, flags);
|
||||
if (res >= 0) {
|
||||
Acquire(thr, pc, fd2addr(fd));
|
||||
if (res >= 0 && fd >= 0) {
|
||||
FdAcquire(thr, pc, fd);
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
TSAN_INTERCEPTOR(int, unlink, char *path) {
|
||||
SCOPED_TSAN_INTERCEPTOR(unlink, path);
|
||||
Release(thr, pc, file2addr(path));
|
||||
Release(thr, pc, File2addr(path));
|
||||
int res = REAL(unlink)(path);
|
||||
return res;
|
||||
}
|
||||
@ -1202,19 +1368,57 @@ TSAN_INTERCEPTOR(int, unlink, char *path) {
|
||||
TSAN_INTERCEPTOR(void*, fopen, char *path, char *mode) {
|
||||
SCOPED_TSAN_INTERCEPTOR(fopen, path, mode);
|
||||
void *res = REAL(fopen)(path, mode);
|
||||
Acquire(thr, pc, file2addr(path));
|
||||
Acquire(thr, pc, File2addr(path));
|
||||
if (res) {
|
||||
int fd = fileno_unlocked(res);
|
||||
if (fd >= 0)
|
||||
FdFileCreate(thr, pc, fd);
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
TSAN_INTERCEPTOR(void*, freopen, char *path, char *mode, void *stream) {
|
||||
SCOPED_TSAN_INTERCEPTOR(freopen, path, mode, stream);
|
||||
if (stream) {
|
||||
int fd = fileno_unlocked(stream);
|
||||
if (fd >= 0)
|
||||
FdClose(thr, pc, fd);
|
||||
}
|
||||
void *res = REAL(freopen)(path, mode, stream);
|
||||
Acquire(thr, pc, File2addr(path));
|
||||
if (res) {
|
||||
int fd = fileno_unlocked(res);
|
||||
if (fd >= 0)
|
||||
FdFileCreate(thr, pc, fd);
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
TSAN_INTERCEPTOR(int, fclose, void *stream) {
|
||||
{
|
||||
SCOPED_TSAN_INTERCEPTOR(fclose, stream);
|
||||
if (stream) {
|
||||
int fd = fileno_unlocked(stream);
|
||||
if (fd >= 0)
|
||||
FdClose(thr, pc, fd);
|
||||
}
|
||||
}
|
||||
return REAL(fclose)(stream);
|
||||
}
|
||||
|
||||
TSAN_INTERCEPTOR(uptr, fread, void *ptr, uptr size, uptr nmemb, void *f) {
|
||||
SCOPED_TSAN_INTERCEPTOR(fread, ptr, size, nmemb, f);
|
||||
MemoryAccessRange(thr, pc, (uptr)ptr, size * nmemb, true);
|
||||
{
|
||||
SCOPED_TSAN_INTERCEPTOR(fread, ptr, size, nmemb, f);
|
||||
MemoryAccessRange(thr, pc, (uptr)ptr, size * nmemb, true);
|
||||
}
|
||||
return REAL(fread)(ptr, size, nmemb, f);
|
||||
}
|
||||
|
||||
TSAN_INTERCEPTOR(uptr, fwrite, const void *p, uptr size, uptr nmemb, void *f) {
|
||||
SCOPED_TSAN_INTERCEPTOR(fwrite, p, size, nmemb, f);
|
||||
MemoryAccessRange(thr, pc, (uptr)p, size * nmemb, false);
|
||||
{
|
||||
SCOPED_TSAN_INTERCEPTOR(fwrite, p, size, nmemb, f);
|
||||
MemoryAccessRange(thr, pc, (uptr)p, size * nmemb, false);
|
||||
}
|
||||
return REAL(fwrite)(p, size, nmemb, f);
|
||||
}
|
||||
|
||||
@ -1226,7 +1430,7 @@ TSAN_INTERCEPTOR(int, puts, const char *s) {
|
||||
|
||||
TSAN_INTERCEPTOR(int, rmdir, char *path) {
|
||||
SCOPED_TSAN_INTERCEPTOR(rmdir, path);
|
||||
Release(thr, pc, dir2addr(path));
|
||||
Release(thr, pc, Dir2addr(path));
|
||||
int res = REAL(rmdir)(path);
|
||||
return res;
|
||||
}
|
||||
@ -1234,14 +1438,15 @@ TSAN_INTERCEPTOR(int, rmdir, char *path) {
|
||||
TSAN_INTERCEPTOR(void*, opendir, char *path) {
|
||||
SCOPED_TSAN_INTERCEPTOR(opendir, path);
|
||||
void *res = REAL(opendir)(path);
|
||||
Acquire(thr, pc, dir2addr(path));
|
||||
if (res != 0)
|
||||
Acquire(thr, pc, Dir2addr(path));
|
||||
return res;
|
||||
}
|
||||
|
||||
TSAN_INTERCEPTOR(int, epoll_ctl, int epfd, int op, int fd, void *ev) {
|
||||
SCOPED_TSAN_INTERCEPTOR(epoll_ctl, epfd, op, fd, ev);
|
||||
if (op == EPOLL_CTL_ADD) {
|
||||
Release(thr, pc, epollfd2addr(epfd));
|
||||
if (op == EPOLL_CTL_ADD && epfd >= 0) {
|
||||
FdRelease(thr, pc, epfd);
|
||||
}
|
||||
int res = REAL(epoll_ctl)(epfd, op, fd, ev);
|
||||
return res;
|
||||
@ -1250,8 +1455,8 @@ TSAN_INTERCEPTOR(int, epoll_ctl, int epfd, int op, int fd, void *ev) {
|
||||
TSAN_INTERCEPTOR(int, epoll_wait, int epfd, void *ev, int cnt, int timeout) {
|
||||
SCOPED_TSAN_INTERCEPTOR(epoll_wait, epfd, ev, cnt, timeout);
|
||||
int res = BLOCK_REAL(epoll_wait)(epfd, ev, cnt, timeout);
|
||||
if (res > 0) {
|
||||
Acquire(thr, pc, epollfd2addr(epfd));
|
||||
if (res > 0 && epfd >= 0) {
|
||||
FdAcquire(thr, pc, epfd);
|
||||
}
|
||||
return res;
|
||||
}
|
||||
@ -1423,6 +1628,19 @@ TSAN_INTERCEPTOR(int, munlockall, void) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
TSAN_INTERCEPTOR(int, fork, int fake) {
|
||||
SCOPED_TSAN_INTERCEPTOR(fork, fake);
|
||||
// It's intercepted merely to process pending signals.
|
||||
int pid = REAL(fork)(fake);
|
||||
if (pid == 0) {
|
||||
// child
|
||||
FdOnFork(thr, pc);
|
||||
} else if (pid > 0) {
|
||||
// parent
|
||||
}
|
||||
return pid;
|
||||
}
|
||||
|
||||
namespace __tsan {
|
||||
|
||||
void ProcessPendingSignals(ThreadState *thr) {
|
||||
@ -1545,7 +1763,7 @@ void InitializeInterceptors() {
|
||||
TSAN_INTERCEPT(pthread_rwlock_timedwrlock);
|
||||
TSAN_INTERCEPT(pthread_rwlock_unlock);
|
||||
|
||||
TSAN_INTERCEPT(pthread_cond_init);
|
||||
// TSAN_INTERCEPT(pthread_cond_init);
|
||||
TSAN_INTERCEPT(pthread_cond_destroy);
|
||||
TSAN_INTERCEPT(pthread_cond_signal);
|
||||
TSAN_INTERCEPT(pthread_cond_broadcast);
|
||||
@ -1566,6 +1784,28 @@ void InitializeInterceptors() {
|
||||
TSAN_INTERCEPT(sem_post);
|
||||
TSAN_INTERCEPT(sem_getvalue);
|
||||
|
||||
TSAN_INTERCEPT(open);
|
||||
TSAN_INTERCEPT(open64);
|
||||
TSAN_INTERCEPT(creat);
|
||||
TSAN_INTERCEPT(creat64);
|
||||
TSAN_INTERCEPT(dup);
|
||||
TSAN_INTERCEPT(dup2);
|
||||
TSAN_INTERCEPT(dup3);
|
||||
TSAN_INTERCEPT(eventfd);
|
||||
TSAN_INTERCEPT(signalfd);
|
||||
TSAN_INTERCEPT(inotify_init);
|
||||
TSAN_INTERCEPT(inotify_init1);
|
||||
TSAN_INTERCEPT(socket);
|
||||
TSAN_INTERCEPT(socketpair);
|
||||
TSAN_INTERCEPT(connect);
|
||||
TSAN_INTERCEPT(accept);
|
||||
TSAN_INTERCEPT(accept4);
|
||||
TSAN_INTERCEPT(epoll_create);
|
||||
TSAN_INTERCEPT(epoll_create1);
|
||||
TSAN_INTERCEPT(close);
|
||||
TSAN_INTERCEPT(pipe);
|
||||
TSAN_INTERCEPT(pipe2);
|
||||
|
||||
TSAN_INTERCEPT(read);
|
||||
TSAN_INTERCEPT(pread);
|
||||
TSAN_INTERCEPT(pread64);
|
||||
@ -1583,6 +1823,8 @@ void InitializeInterceptors() {
|
||||
|
||||
TSAN_INTERCEPT(unlink);
|
||||
TSAN_INTERCEPT(fopen);
|
||||
TSAN_INTERCEPT(freopen);
|
||||
TSAN_INTERCEPT(fclose);
|
||||
TSAN_INTERCEPT(fread);
|
||||
TSAN_INTERCEPT(fwrite);
|
||||
TSAN_INTERCEPT(puts);
|
||||
@ -1608,6 +1850,8 @@ void InitializeInterceptors() {
|
||||
TSAN_INTERCEPT(mlockall);
|
||||
TSAN_INTERCEPT(munlockall);
|
||||
|
||||
TSAN_INTERCEPT(fork);
|
||||
|
||||
// Need to setup it, because interceptors check that the function is resolved.
|
||||
// But atexit is emitted directly into the module, so can't be resolved.
|
||||
REAL(atexit) = (int(*)(void(*)()))unreachable;
|
||||
@ -1623,6 +1867,8 @@ void InitializeInterceptors() {
|
||||
Printf("ThreadSanitizer: failed to create thread key\n");
|
||||
Die();
|
||||
}
|
||||
|
||||
FdInit();
|
||||
}
|
||||
|
||||
void internal_start_thread(void(*func)(void *arg), void *arg) {
|
||||
|
@ -229,7 +229,7 @@ static T AtomicLoad(ThreadState *thr, uptr pc, const volatile T *a,
|
||||
// Assume the access is atomic.
|
||||
if (!IsAcquireOrder(mo) && sizeof(T) <= sizeof(a))
|
||||
return *a;
|
||||
SyncVar *s = CTX()->synctab.GetAndLock(thr, pc, (uptr)a, false);
|
||||
SyncVar *s = CTX()->synctab.GetOrCreateAndLock(thr, pc, (uptr)a, false);
|
||||
thr->clock.set(thr->tid, thr->fast_state.epoch());
|
||||
thr->clock.acquire(&s->clock);
|
||||
T v = *a;
|
||||
@ -251,7 +251,7 @@ static void AtomicStore(ThreadState *thr, uptr pc, volatile T *a, T v,
|
||||
return;
|
||||
}
|
||||
__sync_synchronize();
|
||||
SyncVar *s = CTX()->synctab.GetAndLock(thr, pc, (uptr)a, true);
|
||||
SyncVar *s = CTX()->synctab.GetOrCreateAndLock(thr, pc, (uptr)a, true);
|
||||
thr->clock.set(thr->tid, thr->fast_state.epoch());
|
||||
thr->clock.ReleaseStore(&s->clock);
|
||||
*a = v;
|
||||
@ -263,7 +263,7 @@ static void AtomicStore(ThreadState *thr, uptr pc, volatile T *a, T v,
|
||||
|
||||
template<typename T, T (*F)(volatile T *v, T op)>
|
||||
static T AtomicRMW(ThreadState *thr, uptr pc, volatile T *a, T v, morder mo) {
|
||||
SyncVar *s = CTX()->synctab.GetAndLock(thr, pc, (uptr)a, true);
|
||||
SyncVar *s = CTX()->synctab.GetOrCreateAndLock(thr, pc, (uptr)a, true);
|
||||
thr->clock.set(thr->tid, thr->fast_state.epoch());
|
||||
if (IsAcqRelOrder(mo))
|
||||
thr->clock.acq_rel(&s->clock);
|
||||
@ -322,7 +322,7 @@ template<typename T>
|
||||
static bool AtomicCAS(ThreadState *thr, uptr pc,
|
||||
volatile T *a, T *c, T v, morder mo, morder fmo) {
|
||||
(void)fmo; // Unused because llvm does not pass it yet.
|
||||
SyncVar *s = CTX()->synctab.GetAndLock(thr, pc, (uptr)a, true);
|
||||
SyncVar *s = CTX()->synctab.GetOrCreateAndLock(thr, pc, (uptr)a, true);
|
||||
thr->clock.set(thr->tid, thr->fast_state.epoch());
|
||||
if (IsAcqRelOrder(mo))
|
||||
thr->clock.acq_rel(&s->clock);
|
||||
|
@ -26,7 +26,7 @@ typedef long __tsan_atomic64; // NOLINT
|
||||
|
||||
#if defined(__SIZEOF_INT128__) \
|
||||
|| (__clang_major__ * 100 + __clang_minor__ >= 302)
|
||||
typedef __int128 __tsan_atomic128;
|
||||
__extension__ typedef __int128 __tsan_atomic128;
|
||||
#define __TSAN_HAS_INT128 1
|
||||
#else
|
||||
typedef char __tsan_atomic128;
|
||||
|
303
libsanitizer/tsan/tsan_interface_java.cc
Normal file
303
libsanitizer/tsan/tsan_interface_java.cc
Normal file
@ -0,0 +1,303 @@
|
||||
//===-- tsan_interface_java.cc --------------------------------------------===//
|
||||
//
|
||||
// This file is distributed under the University of Illinois Open Source
|
||||
// License. See LICENSE.TXT for details.
|
||||
//
|
||||
//===----------------------------------------------------------------------===//
|
||||
//
|
||||
// This file is a part of ThreadSanitizer (TSan), a race detector.
|
||||
//
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
#include "tsan_interface_java.h"
|
||||
#include "tsan_rtl.h"
|
||||
#include "tsan_mutex.h"
|
||||
#include "sanitizer_common/sanitizer_internal_defs.h"
|
||||
#include "sanitizer_common/sanitizer_common.h"
|
||||
#include "sanitizer_common/sanitizer_placement_new.h"
|
||||
|
||||
using namespace __tsan; // NOLINT
|
||||
|
||||
namespace __tsan {
|
||||
|
||||
const uptr kHeapShadow = 0x300000000000ull;
|
||||
const uptr kHeapAlignment = 8;
|
||||
|
||||
struct BlockDesc {
|
||||
bool begin;
|
||||
Mutex mtx;
|
||||
SyncVar *head;
|
||||
|
||||
BlockDesc()
|
||||
: mtx(MutexTypeJavaMBlock, StatMtxJavaMBlock)
|
||||
, head() {
|
||||
CHECK_EQ(begin, false);
|
||||
begin = true;
|
||||
}
|
||||
|
||||
~BlockDesc() {
|
||||
CHECK_EQ(begin, true);
|
||||
begin = false;
|
||||
ThreadState *thr = cur_thread();
|
||||
SyncVar *s = head;
|
||||
while (s) {
|
||||
SyncVar *s1 = s->next;
|
||||
StatInc(thr, StatSyncDestroyed);
|
||||
s->mtx.Lock();
|
||||
s->mtx.Unlock();
|
||||
thr->mset.Remove(s->GetId());
|
||||
DestroyAndFree(s);
|
||||
s = s1;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
struct JavaContext {
|
||||
const uptr heap_begin;
|
||||
const uptr heap_size;
|
||||
BlockDesc *heap_shadow;
|
||||
|
||||
JavaContext(jptr heap_begin, jptr heap_size)
|
||||
: heap_begin(heap_begin)
|
||||
, heap_size(heap_size) {
|
||||
uptr size = heap_size / kHeapAlignment * sizeof(BlockDesc);
|
||||
heap_shadow = (BlockDesc*)MmapFixedNoReserve(kHeapShadow, size);
|
||||
if ((uptr)heap_shadow != kHeapShadow) {
|
||||
Printf("ThreadSanitizer: failed to mmap Java heap shadow\n");
|
||||
Die();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
class ScopedJavaFunc {
|
||||
public:
|
||||
ScopedJavaFunc(ThreadState *thr, uptr pc)
|
||||
: thr_(thr) {
|
||||
Initialize(thr_);
|
||||
FuncEntry(thr, pc);
|
||||
CHECK_EQ(thr_->in_rtl, 0);
|
||||
thr_->in_rtl++;
|
||||
}
|
||||
|
||||
~ScopedJavaFunc() {
|
||||
thr_->in_rtl--;
|
||||
CHECK_EQ(thr_->in_rtl, 0);
|
||||
FuncExit(thr_);
|
||||
// FIXME(dvyukov): process pending signals.
|
||||
}
|
||||
|
||||
private:
|
||||
ThreadState *thr_;
|
||||
};
|
||||
|
||||
static u64 jctx_buf[sizeof(JavaContext) / sizeof(u64) + 1];
|
||||
static JavaContext *jctx;
|
||||
|
||||
static BlockDesc *getblock(uptr addr) {
|
||||
uptr i = (addr - jctx->heap_begin) / kHeapAlignment;
|
||||
return &jctx->heap_shadow[i];
|
||||
}
|
||||
|
||||
static uptr USED getmem(BlockDesc *b) {
|
||||
uptr i = b - jctx->heap_shadow;
|
||||
uptr p = jctx->heap_begin + i * kHeapAlignment;
|
||||
CHECK_GE(p, jctx->heap_begin);
|
||||
CHECK_LT(p, jctx->heap_begin + jctx->heap_size);
|
||||
return p;
|
||||
}
|
||||
|
||||
static BlockDesc *getblockbegin(uptr addr) {
|
||||
for (BlockDesc *b = getblock(addr);; b--) {
|
||||
CHECK_GE(b, jctx->heap_shadow);
|
||||
if (b->begin)
|
||||
return b;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
SyncVar* GetJavaSync(ThreadState *thr, uptr pc, uptr addr,
|
||||
bool write_lock, bool create) {
|
||||
if (jctx == 0 || addr < jctx->heap_begin
|
||||
|| addr >= jctx->heap_begin + jctx->heap_size)
|
||||
return 0;
|
||||
BlockDesc *b = getblockbegin(addr);
|
||||
DPrintf("#%d: GetJavaSync %p->%p\n", thr->tid, addr, b);
|
||||
Lock l(&b->mtx);
|
||||
SyncVar *s = b->head;
|
||||
for (; s; s = s->next) {
|
||||
if (s->addr == addr) {
|
||||
DPrintf("#%d: found existing sync for %p\n", thr->tid, addr);
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (s == 0 && create) {
|
||||
DPrintf("#%d: creating new sync for %p\n", thr->tid, addr);
|
||||
s = CTX()->synctab.Create(thr, pc, addr);
|
||||
s->next = b->head;
|
||||
b->head = s;
|
||||
}
|
||||
if (s) {
|
||||
if (write_lock)
|
||||
s->mtx.Lock();
|
||||
else
|
||||
s->mtx.ReadLock();
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
SyncVar* GetAndRemoveJavaSync(ThreadState *thr, uptr pc, uptr addr) {
|
||||
// We do not destroy Java mutexes other than in __tsan_java_free().
|
||||
return 0;
|
||||
}
|
||||
|
||||
} // namespace __tsan {
|
||||
|
||||
#define SCOPED_JAVA_FUNC(func) \
|
||||
ThreadState *thr = cur_thread(); \
|
||||
const uptr caller_pc = GET_CALLER_PC(); \
|
||||
const uptr pc = (uptr)&func; \
|
||||
(void)pc; \
|
||||
ScopedJavaFunc scoped(thr, caller_pc); \
|
||||
/**/
|
||||
|
||||
void __tsan_java_init(jptr heap_begin, jptr heap_size) {
|
||||
SCOPED_JAVA_FUNC(__tsan_java_init);
|
||||
DPrintf("#%d: java_init(%p, %p)\n", thr->tid, heap_begin, heap_size);
|
||||
CHECK_EQ(jctx, 0);
|
||||
CHECK_GT(heap_begin, 0);
|
||||
CHECK_GT(heap_size, 0);
|
||||
CHECK_EQ(heap_begin % kHeapAlignment, 0);
|
||||
CHECK_EQ(heap_size % kHeapAlignment, 0);
|
||||
CHECK_LT(heap_begin, heap_begin + heap_size);
|
||||
jctx = new(jctx_buf) JavaContext(heap_begin, heap_size);
|
||||
}
|
||||
|
||||
int __tsan_java_fini() {
|
||||
SCOPED_JAVA_FUNC(__tsan_java_fini);
|
||||
DPrintf("#%d: java_fini()\n", thr->tid);
|
||||
CHECK_NE(jctx, 0);
|
||||
// FIXME(dvyukov): this does not call atexit() callbacks.
|
||||
int status = Finalize(thr);
|
||||
DPrintf("#%d: java_fini() = %d\n", thr->tid, status);
|
||||
return status;
|
||||
}
|
||||
|
||||
void __tsan_java_alloc(jptr ptr, jptr size) {
|
||||
SCOPED_JAVA_FUNC(__tsan_java_alloc);
|
||||
DPrintf("#%d: java_alloc(%p, %p)\n", thr->tid, ptr, size);
|
||||
CHECK_NE(jctx, 0);
|
||||
CHECK_NE(size, 0);
|
||||
CHECK_EQ(ptr % kHeapAlignment, 0);
|
||||
CHECK_EQ(size % kHeapAlignment, 0);
|
||||
CHECK_GE(ptr, jctx->heap_begin);
|
||||
CHECK_LE(ptr + size, jctx->heap_begin + jctx->heap_size);
|
||||
|
||||
BlockDesc *b = getblock(ptr);
|
||||
new(b) BlockDesc();
|
||||
}
|
||||
|
||||
void __tsan_java_free(jptr ptr, jptr size) {
|
||||
SCOPED_JAVA_FUNC(__tsan_java_free);
|
||||
DPrintf("#%d: java_free(%p, %p)\n", thr->tid, ptr, size);
|
||||
CHECK_NE(jctx, 0);
|
||||
CHECK_NE(size, 0);
|
||||
CHECK_EQ(ptr % kHeapAlignment, 0);
|
||||
CHECK_EQ(size % kHeapAlignment, 0);
|
||||
CHECK_GE(ptr, jctx->heap_begin);
|
||||
CHECK_LE(ptr + size, jctx->heap_begin + jctx->heap_size);
|
||||
|
||||
BlockDesc *beg = getblock(ptr);
|
||||
BlockDesc *end = getblock(ptr + size);
|
||||
for (BlockDesc *b = beg; b != end; b++) {
|
||||
if (b->begin)
|
||||
b->~BlockDesc();
|
||||
}
|
||||
}
|
||||
|
||||
void __tsan_java_move(jptr src, jptr dst, jptr size) {
|
||||
SCOPED_JAVA_FUNC(__tsan_java_move);
|
||||
DPrintf("#%d: java_move(%p, %p, %p)\n", thr->tid, src, dst, size);
|
||||
CHECK_NE(jctx, 0);
|
||||
CHECK_NE(size, 0);
|
||||
CHECK_EQ(src % kHeapAlignment, 0);
|
||||
CHECK_EQ(dst % kHeapAlignment, 0);
|
||||
CHECK_EQ(size % kHeapAlignment, 0);
|
||||
CHECK_GE(src, jctx->heap_begin);
|
||||
CHECK_LE(src + size, jctx->heap_begin + jctx->heap_size);
|
||||
CHECK_GE(dst, jctx->heap_begin);
|
||||
CHECK_LE(dst + size, jctx->heap_begin + jctx->heap_size);
|
||||
CHECK(dst >= src + size || src >= dst + size);
|
||||
|
||||
// Assuming it's not running concurrently with threads that do
|
||||
// memory accesses and mutex operations (stop-the-world phase).
|
||||
{ // NOLINT
|
||||
BlockDesc *s = getblock(src);
|
||||
BlockDesc *d = getblock(dst);
|
||||
BlockDesc *send = getblock(src + size);
|
||||
for (; s != send; s++, d++) {
|
||||
CHECK_EQ(d->begin, false);
|
||||
if (s->begin) {
|
||||
DPrintf("#%d: moving block %p->%p\n", thr->tid, getmem(s), getmem(d));
|
||||
new(d) BlockDesc;
|
||||
d->head = s->head;
|
||||
for (SyncVar *sync = d->head; sync; sync = sync->next) {
|
||||
uptr newaddr = sync->addr - src + dst;
|
||||
DPrintf("#%d: moving sync %p->%p\n", thr->tid, sync->addr, newaddr);
|
||||
sync->addr = newaddr;
|
||||
}
|
||||
s->head = 0;
|
||||
s->~BlockDesc();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
{ // NOLINT
|
||||
u64 *s = (u64*)MemToShadow(src);
|
||||
u64 *d = (u64*)MemToShadow(dst);
|
||||
u64 *send = (u64*)MemToShadow(src + size);
|
||||
for (; s != send; s++, d++) {
|
||||
*d = *s;
|
||||
*s = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void __tsan_java_mutex_lock(jptr addr) {
|
||||
SCOPED_JAVA_FUNC(__tsan_java_mutex_lock);
|
||||
DPrintf("#%d: java_mutex_lock(%p)\n", thr->tid, addr);
|
||||
CHECK_NE(jctx, 0);
|
||||
CHECK_GE(addr, jctx->heap_begin);
|
||||
CHECK_LT(addr, jctx->heap_begin + jctx->heap_size);
|
||||
|
||||
MutexLock(thr, pc, addr);
|
||||
}
|
||||
|
||||
void __tsan_java_mutex_unlock(jptr addr) {
|
||||
SCOPED_JAVA_FUNC(__tsan_java_mutex_unlock);
|
||||
DPrintf("#%d: java_mutex_unlock(%p)\n", thr->tid, addr);
|
||||
CHECK_NE(jctx, 0);
|
||||
CHECK_GE(addr, jctx->heap_begin);
|
||||
CHECK_LT(addr, jctx->heap_begin + jctx->heap_size);
|
||||
|
||||
MutexUnlock(thr, pc, addr);
|
||||
}
|
||||
|
||||
void __tsan_java_mutex_read_lock(jptr addr) {
|
||||
SCOPED_JAVA_FUNC(__tsan_java_mutex_read_lock);
|
||||
DPrintf("#%d: java_mutex_read_lock(%p)\n", thr->tid, addr);
|
||||
CHECK_NE(jctx, 0);
|
||||
CHECK_GE(addr, jctx->heap_begin);
|
||||
CHECK_LT(addr, jctx->heap_begin + jctx->heap_size);
|
||||
|
||||
MutexReadLock(thr, pc, addr);
|
||||
}
|
||||
|
||||
void __tsan_java_mutex_read_unlock(jptr addr) {
|
||||
SCOPED_JAVA_FUNC(__tsan_java_mutex_read_unlock);
|
||||
DPrintf("#%d: java_mutex_read_unlock(%p)\n", thr->tid, addr);
|
||||
CHECK_NE(jctx, 0);
|
||||
CHECK_GE(addr, jctx->heap_begin);
|
||||
CHECK_LT(addr, jctx->heap_begin + jctx->heap_size);
|
||||
|
||||
MutexReadUnlock(thr, pc, addr);
|
||||
}
|
72
libsanitizer/tsan/tsan_interface_java.h
Normal file
72
libsanitizer/tsan/tsan_interface_java.h
Normal file
@ -0,0 +1,72 @@
|
||||
//===-- tsan_interface_java.h -----------------------------------*- C++ -*-===//
|
||||
//
|
||||
// This file is distributed under the University of Illinois Open Source
|
||||
// License. See LICENSE.TXT for details.
|
||||
//
|
||||
//===----------------------------------------------------------------------===//
|
||||
//
|
||||
// This file is a part of ThreadSanitizer (TSan), a race detector.
|
||||
//
|
||||
// Interface for verification of Java or mixed Java/C++ programs.
|
||||
// The interface is intended to be used from within a JVM and notify TSan
|
||||
// about such events like Java locks and GC memory compaction.
|
||||
//
|
||||
// For plain memory accesses and function entry/exit a JVM is intended to use
|
||||
// C++ interfaces: __tsan_readN/writeN and __tsan_func_enter/exit.
|
||||
//
|
||||
// For volatile memory accesses and atomic operations JVM is intended to use
|
||||
// standard atomics API: __tsan_atomicN_load/store/etc.
|
||||
//
|
||||
// For usage examples see lit_tests/java_*.cc
|
||||
//===----------------------------------------------------------------------===//
|
||||
#ifndef TSAN_INTERFACE_JAVA_H
|
||||
#define TSAN_INTERFACE_JAVA_H
|
||||
|
||||
#ifndef INTERFACE_ATTRIBUTE
|
||||
# define INTERFACE_ATTRIBUTE __attribute__((visibility("default")))
|
||||
#endif
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
typedef unsigned long jptr; // NOLINT
|
||||
|
||||
// Must be called before any other callback from Java.
|
||||
void __tsan_java_init(jptr heap_begin, jptr heap_size) INTERFACE_ATTRIBUTE;
|
||||
// Must be called when the application exits.
|
||||
// Not necessary the last callback (concurrently running threads are OK).
|
||||
// Returns exit status or 0 if tsan does not want to override it.
|
||||
int __tsan_java_fini() INTERFACE_ATTRIBUTE;
|
||||
|
||||
// Callback for memory allocations.
|
||||
// May be omitted for allocations that are not subject to data races
|
||||
// nor contain synchronization objects (e.g. String).
|
||||
void __tsan_java_alloc(jptr ptr, jptr size) INTERFACE_ATTRIBUTE;
|
||||
// Callback for memory free.
|
||||
// Can be aggregated for several objects (preferably).
|
||||
void __tsan_java_free(jptr ptr, jptr size) INTERFACE_ATTRIBUTE;
|
||||
// Callback for memory move by GC.
|
||||
// Can be aggregated for several objects (preferably).
|
||||
// The ranges must not overlap.
|
||||
void __tsan_java_move(jptr src, jptr dst, jptr size) INTERFACE_ATTRIBUTE;
|
||||
|
||||
// Mutex lock.
|
||||
// Addr is any unique address associated with the mutex.
|
||||
// Must not be called on recursive reentry.
|
||||
// Object.wait() is handled as a pair of unlock/lock.
|
||||
void __tsan_java_mutex_lock(jptr addr) INTERFACE_ATTRIBUTE;
|
||||
// Mutex unlock.
|
||||
void __tsan_java_mutex_unlock(jptr addr) INTERFACE_ATTRIBUTE;
|
||||
// Mutex read lock.
|
||||
void __tsan_java_mutex_read_lock(jptr addr) INTERFACE_ATTRIBUTE;
|
||||
// Mutex read unlock.
|
||||
void __tsan_java_mutex_read_unlock(jptr addr) INTERFACE_ATTRIBUTE;
|
||||
|
||||
#ifdef __cplusplus
|
||||
} // extern "C"
|
||||
#endif
|
||||
|
||||
#undef INTERFACE_ATTRIBUTE
|
||||
|
||||
#endif // #ifndef TSAN_INTERFACE_JAVA_H
|
@ -58,8 +58,9 @@ void *user_alloc(ThreadState *thr, uptr pc, uptr sz, uptr align) {
|
||||
void *p = allocator()->Allocate(&thr->alloc_cache, sz, align);
|
||||
if (p == 0)
|
||||
return 0;
|
||||
MBlock *b = (MBlock*)allocator()->GetMetaData(p);
|
||||
MBlock *b = new(allocator()->GetMetaData(p)) MBlock;
|
||||
b->size = sz;
|
||||
b->head = 0;
|
||||
b->alloc_tid = thr->unique_id;
|
||||
b->alloc_stack_id = CurrentStackId(thr, pc);
|
||||
if (CTX() && CTX()->initialized) {
|
||||
@ -90,6 +91,7 @@ void user_free(ThreadState *thr, uptr pc, void *p) {
|
||||
if (CTX() && CTX()->initialized && thr->in_rtl == 1) {
|
||||
MemoryRangeFreed(thr, pc, (uptr)p, b->size);
|
||||
}
|
||||
b->~MBlock();
|
||||
allocator()->Deallocate(&thr->alloc_cache, p);
|
||||
SignalUnsafeCall(thr, pc);
|
||||
}
|
||||
@ -115,9 +117,11 @@ void *user_realloc(ThreadState *thr, uptr pc, void *p, uptr sz) {
|
||||
}
|
||||
|
||||
MBlock *user_mblock(ThreadState *thr, void *p) {
|
||||
// CHECK_GT(thr->in_rtl, 0);
|
||||
CHECK_NE(p, (void*)0);
|
||||
return (MBlock*)allocator()->GetMetaData(p);
|
||||
Allocator *a = allocator();
|
||||
void *b = a->GetBlockBegin(p);
|
||||
CHECK_NE(b, 0);
|
||||
return (MBlock*)a->GetMetaData(b);
|
||||
}
|
||||
|
||||
void invoke_malloc_hook(void *ptr, uptr size) {
|
||||
|
@ -57,6 +57,7 @@ enum MBlockType {
|
||||
MBlockSuppression,
|
||||
MBlockExpectRace,
|
||||
MBlockSignal,
|
||||
MBlockFD,
|
||||
|
||||
// This must be the last.
|
||||
MBlockTypeCount
|
||||
|
@ -23,22 +23,28 @@ namespace __tsan {
|
||||
// then Report mutex can be locked while under Threads mutex.
|
||||
// The leaf mutexes can be locked under any other mutexes.
|
||||
// Recursive locking is not supported.
|
||||
#if TSAN_DEBUG && !TSAN_GO
|
||||
const MutexType MutexTypeLeaf = (MutexType)-1;
|
||||
static MutexType CanLockTab[MutexTypeCount][MutexTypeCount] = {
|
||||
/*0 MutexTypeInvalid*/ {},
|
||||
/*1 MutexTypeTrace*/ {MutexTypeLeaf},
|
||||
/*2 MutexTypeThreads*/ {MutexTypeReport},
|
||||
/*3 MutexTypeReport*/ {},
|
||||
/*4 MutexTypeSyncVar*/ {},
|
||||
/*5 MutexTypeSyncTab*/ {MutexTypeSyncVar},
|
||||
/*6 MutexTypeSlab*/ {MutexTypeLeaf},
|
||||
/*7 MutexTypeAnnotations*/ {},
|
||||
/*8 MutexTypeAtExit*/ {MutexTypeSyncTab},
|
||||
/*0 MutexTypeInvalid*/ {},
|
||||
/*1 MutexTypeTrace*/ {MutexTypeLeaf},
|
||||
/*2 MutexTypeThreads*/ {MutexTypeReport},
|
||||
/*3 MutexTypeReport*/ {MutexTypeSyncTab, MutexTypeMBlock,
|
||||
MutexTypeJavaMBlock},
|
||||
/*4 MutexTypeSyncVar*/ {},
|
||||
/*5 MutexTypeSyncTab*/ {MutexTypeSyncVar},
|
||||
/*6 MutexTypeSlab*/ {MutexTypeLeaf},
|
||||
/*7 MutexTypeAnnotations*/ {},
|
||||
/*8 MutexTypeAtExit*/ {MutexTypeSyncTab},
|
||||
/*9 MutexTypeMBlock*/ {MutexTypeSyncVar},
|
||||
/*10 MutexTypeJavaMBlock*/ {MutexTypeSyncVar},
|
||||
};
|
||||
|
||||
static bool CanLockAdj[MutexTypeCount][MutexTypeCount];
|
||||
#endif
|
||||
|
||||
void InitializeMutex() {
|
||||
#if TSAN_DEBUG && !TSAN_GO
|
||||
// Build the "can lock" adjacency matrix.
|
||||
// If [i][j]==true, then one can lock mutex j while under mutex i.
|
||||
const int N = MutexTypeCount;
|
||||
@ -112,14 +118,18 @@ void InitializeMutex() {
|
||||
Die();
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
DeadlockDetector::DeadlockDetector() {
|
||||
// Rely on zero initialization because some mutexes can be locked before ctor.
|
||||
}
|
||||
|
||||
#if TSAN_DEBUG && !TSAN_GO
|
||||
void DeadlockDetector::Lock(MutexType t) {
|
||||
// Printf("LOCK %d @%zu\n", t, seq_ + 1);
|
||||
CHECK_GT(t, MutexTypeInvalid);
|
||||
CHECK_LT(t, MutexTypeCount);
|
||||
u64 max_seq = 0;
|
||||
u64 max_idx = MutexTypeInvalid;
|
||||
for (int i = 0; i != MutexTypeCount; i++) {
|
||||
@ -148,6 +158,7 @@ void DeadlockDetector::Unlock(MutexType t) {
|
||||
CHECK(locked_[t]);
|
||||
locked_[t] = 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
const uptr kUnlocked = 0;
|
||||
const uptr kWriteLock = 1;
|
||||
|
@ -27,6 +27,8 @@ enum MutexType {
|
||||
MutexTypeSlab,
|
||||
MutexTypeAnnotations,
|
||||
MutexTypeAtExit,
|
||||
MutexTypeMBlock,
|
||||
MutexTypeJavaMBlock,
|
||||
|
||||
// This must be the last.
|
||||
MutexTypeCount
|
||||
|
87
libsanitizer/tsan/tsan_mutexset.cc
Normal file
87
libsanitizer/tsan/tsan_mutexset.cc
Normal file
@ -0,0 +1,87 @@
|
||||
//===-- tsan_mutexset.cc --------------------------------------------------===//
|
||||
//
|
||||
// This file is distributed under the University of Illinois Open Source
|
||||
// License. See LICENSE.TXT for details.
|
||||
//
|
||||
//===----------------------------------------------------------------------===//
|
||||
//
|
||||
// This file is a part of ThreadSanitizer (TSan), a race detector.
|
||||
//
|
||||
//===----------------------------------------------------------------------===//
|
||||
#include "tsan_mutexset.h"
|
||||
#include "tsan_rtl.h"
|
||||
|
||||
namespace __tsan {
|
||||
|
||||
const uptr MutexSet::kMaxSize;
|
||||
|
||||
MutexSet::MutexSet() {
|
||||
size_ = 0;
|
||||
internal_memset(&descs_, 0, sizeof(descs_));
|
||||
}
|
||||
|
||||
void MutexSet::Add(u64 id, bool write, u64 epoch) {
|
||||
// Look up existing mutex with the same id.
|
||||
for (uptr i = 0; i < size_; i++) {
|
||||
if (descs_[i].id == id) {
|
||||
descs_[i].count++;
|
||||
descs_[i].epoch = epoch;
|
||||
return;
|
||||
}
|
||||
}
|
||||
// On overflow, find the oldest mutex and drop it.
|
||||
if (size_ == kMaxSize) {
|
||||
u64 minepoch = (u64)-1;
|
||||
u64 mini = (u64)-1;
|
||||
for (uptr i = 0; i < size_; i++) {
|
||||
if (descs_[i].epoch < minepoch) {
|
||||
minepoch = descs_[i].epoch;
|
||||
mini = i;
|
||||
}
|
||||
}
|
||||
RemovePos(mini);
|
||||
CHECK_EQ(size_, kMaxSize - 1);
|
||||
}
|
||||
// Add new mutex descriptor.
|
||||
descs_[size_].id = id;
|
||||
descs_[size_].write = write;
|
||||
descs_[size_].epoch = epoch;
|
||||
descs_[size_].count = 1;
|
||||
size_++;
|
||||
}
|
||||
|
||||
void MutexSet::Del(u64 id, bool write) {
|
||||
for (uptr i = 0; i < size_; i++) {
|
||||
if (descs_[i].id == id) {
|
||||
if (--descs_[i].count == 0)
|
||||
RemovePos(i);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void MutexSet::Remove(u64 id) {
|
||||
for (uptr i = 0; i < size_; i++) {
|
||||
if (descs_[i].id == id) {
|
||||
RemovePos(i);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void MutexSet::RemovePos(uptr i) {
|
||||
CHECK_LT(i, size_);
|
||||
descs_[i] = descs_[size_ - 1];
|
||||
size_--;
|
||||
}
|
||||
|
||||
uptr MutexSet::Size() const {
|
||||
return size_;
|
||||
}
|
||||
|
||||
MutexSet::Desc MutexSet::Get(uptr i) const {
|
||||
CHECK_LT(i, size_);
|
||||
return descs_[i];
|
||||
}
|
||||
|
||||
} // namespace __tsan
|
63
libsanitizer/tsan/tsan_mutexset.h
Normal file
63
libsanitizer/tsan/tsan_mutexset.h
Normal file
@ -0,0 +1,63 @@
|
||||
//===-- tsan_mutexset.h -----------------------------------------*- C++ -*-===//
|
||||
//
|
||||
// This file is distributed under the University of Illinois Open Source
|
||||
// License. See LICENSE.TXT for details.
|
||||
//
|
||||
//===----------------------------------------------------------------------===//
|
||||
//
|
||||
// This file is a part of ThreadSanitizer (TSan), a race detector.
|
||||
//
|
||||
// MutexSet holds the set of mutexes currently held by a thread.
|
||||
//===----------------------------------------------------------------------===//
|
||||
#ifndef TSAN_MUTEXSET_H
|
||||
#define TSAN_MUTEXSET_H
|
||||
|
||||
#include "tsan_defs.h"
|
||||
|
||||
namespace __tsan {
|
||||
|
||||
class MutexSet {
|
||||
public:
|
||||
// Holds limited number of mutexes.
|
||||
// The oldest mutexes are discarded on overflow.
|
||||
static const uptr kMaxSize = 64;
|
||||
struct Desc {
|
||||
u64 id;
|
||||
u64 epoch;
|
||||
int count;
|
||||
bool write;
|
||||
};
|
||||
|
||||
MutexSet();
|
||||
// The 'id' is obtained from SyncVar::GetId().
|
||||
void Add(u64 id, bool write, u64 epoch);
|
||||
void Del(u64 id, bool write);
|
||||
void Remove(u64 id); // Removes the mutex completely (if it's destroyed).
|
||||
uptr Size() const;
|
||||
Desc Get(uptr i) const;
|
||||
|
||||
private:
|
||||
#ifndef TSAN_GO
|
||||
uptr size_;
|
||||
Desc descs_[kMaxSize];
|
||||
#endif
|
||||
|
||||
void RemovePos(uptr i);
|
||||
};
|
||||
|
||||
// Go does not have mutexes, so do not spend memory and time.
|
||||
// (Go sync.Mutex is actually a semaphore -- can be unlocked
|
||||
// in different goroutine).
|
||||
#ifdef TSAN_GO
|
||||
MutexSet::MutexSet() {}
|
||||
void MutexSet::Add(u64 id, bool write, u64 epoch) {}
|
||||
void MutexSet::Del(u64 id, bool write) {}
|
||||
void MutexSet::Remove(u64 id) {}
|
||||
void MutexSet::RemovePos(uptr i) {}
|
||||
uptr MutexSet::Size() const { return 0; }
|
||||
MutexSet::Desc MutexSet::Get(uptr i) const { return Desc(); }
|
||||
#endif
|
||||
|
||||
} // namespace __tsan
|
||||
|
||||
#endif // TSAN_REPORT_H
|
@ -135,7 +135,6 @@ void FlushShadowMemory();
|
||||
|
||||
const char *InitializePlatform();
|
||||
void FinalizePlatform();
|
||||
void MapThreadTrace(uptr addr, uptr size);
|
||||
uptr ALWAYS_INLINE INLINE GetThreadTrace(int tid) {
|
||||
uptr p = kTraceMemBegin + (uptr)tid * kTraceSize * sizeof(Event);
|
||||
DCHECK_LT(p, kTraceMemBegin + kTraceMemSize);
|
||||
|
@ -69,9 +69,7 @@ uptr GetShadowMemoryConsumption() {
|
||||
}
|
||||
|
||||
void FlushShadowMemory() {
|
||||
madvise((void*)kLinuxShadowBeg,
|
||||
kLinuxShadowEnd - kLinuxShadowBeg,
|
||||
MADV_DONTNEED);
|
||||
FlushUnneededShadowMemory(kLinuxShadowBeg, kLinuxShadowEnd - kLinuxShadowBeg);
|
||||
}
|
||||
|
||||
#ifndef TSAN_GO
|
||||
@ -118,16 +116,6 @@ void InitializeShadowMemory() {
|
||||
}
|
||||
#endif
|
||||
|
||||
void MapThreadTrace(uptr addr, uptr size) {
|
||||
DPrintf("Mapping trace at %p-%p(0x%zx)\n", addr, addr + size, size);
|
||||
CHECK_GE(addr, kTraceMemBegin);
|
||||
CHECK_LE(addr + size, kTraceMemBegin + kTraceMemSize);
|
||||
if (addr != (uptr)MmapFixedNoReserve(addr, size)) {
|
||||
Printf("FATAL: ThreadSanitizer can not mmap thread trace\n");
|
||||
Die();
|
||||
}
|
||||
}
|
||||
|
||||
static uptr g_data_start;
|
||||
static uptr g_data_end;
|
||||
|
||||
@ -180,18 +168,14 @@ static uptr g_tls_size;
|
||||
#else
|
||||
# define INTERNAL_FUNCTION
|
||||
#endif
|
||||
extern "C" void _dl_get_tls_static_info(size_t*, size_t*)
|
||||
__attribute__((weak)) INTERNAL_FUNCTION;
|
||||
|
||||
static int InitTlsSize() {
|
||||
typedef void (*get_tls_func)(size_t*, size_t*) INTERNAL_FUNCTION;
|
||||
get_tls_func get_tls = &_dl_get_tls_static_info;
|
||||
if (get_tls == 0) {
|
||||
void *get_tls_static_info_ptr = dlsym(RTLD_NEXT, "_dl_get_tls_static_info");
|
||||
CHECK_EQ(sizeof(get_tls), sizeof(get_tls_static_info_ptr));
|
||||
internal_memcpy(&get_tls, &get_tls_static_info_ptr,
|
||||
sizeof(get_tls_static_info_ptr));
|
||||
}
|
||||
get_tls_func get_tls;
|
||||
void *get_tls_static_info_ptr = dlsym(RTLD_NEXT, "_dl_get_tls_static_info");
|
||||
CHECK_EQ(sizeof(get_tls), sizeof(get_tls_static_info_ptr));
|
||||
internal_memcpy(&get_tls, &get_tls_static_info_ptr,
|
||||
sizeof(get_tls_static_info_ptr));
|
||||
CHECK_NE(get_tls, 0);
|
||||
size_t tls_size = 0;
|
||||
size_t tls_align = 0;
|
||||
@ -220,29 +204,35 @@ const char *InitializePlatform() {
|
||||
// Disable core dumps, dumping of 16TB usually takes a bit long.
|
||||
setlim(RLIMIT_CORE, 0);
|
||||
}
|
||||
bool reexec = false;
|
||||
// TSan doesn't play well with unlimited stack size (as stack
|
||||
// overlaps with shadow memory). If we detect unlimited stack size,
|
||||
// we re-exec the program with limited stack size as a best effort.
|
||||
if (getlim(RLIMIT_STACK) == (rlim_t)-1) {
|
||||
const uptr kMaxStackSize = 32 * 1024 * 1024;
|
||||
Report("WARNING: Program is run with unlimited stack size, which "
|
||||
"wouldn't work with ThreadSanitizer.\n");
|
||||
Report("Re-execing with stack size limited to %zd bytes.\n", kMaxStackSize);
|
||||
SetStackSizeLimitInBytes(kMaxStackSize);
|
||||
reexec = true;
|
||||
}
|
||||
|
||||
if (getlim(RLIMIT_AS) != (rlim_t)-1) {
|
||||
Report("WARNING: Program is run with limited virtual address space, which "
|
||||
"wouldn't work with ThreadSanitizer.\n");
|
||||
Report("Re-execing with unlimited virtual address space.\n");
|
||||
setlim(RLIMIT_AS, -1);
|
||||
reexec = true;
|
||||
}
|
||||
// Go maps shadow memory lazily and works fine with limited address space.
|
||||
// Unlimited stack is not a problem as well, because the executable
|
||||
// is not compiled with -pie.
|
||||
if (kCppMode) {
|
||||
bool reexec = false;
|
||||
// TSan doesn't play well with unlimited stack size (as stack
|
||||
// overlaps with shadow memory). If we detect unlimited stack size,
|
||||
// we re-exec the program with limited stack size as a best effort.
|
||||
if (getlim(RLIMIT_STACK) == (rlim_t)-1) {
|
||||
const uptr kMaxStackSize = 32 * 1024 * 1024;
|
||||
Report("WARNING: Program is run with unlimited stack size, which "
|
||||
"wouldn't work with ThreadSanitizer.\n");
|
||||
Report("Re-execing with stack size limited to %zd bytes.\n",
|
||||
kMaxStackSize);
|
||||
SetStackSizeLimitInBytes(kMaxStackSize);
|
||||
reexec = true;
|
||||
}
|
||||
|
||||
if (reexec)
|
||||
ReExec();
|
||||
if (getlim(RLIMIT_AS) != (rlim_t)-1) {
|
||||
Report("WARNING: Program is run with limited virtual address space,"
|
||||
" which wouldn't work with ThreadSanitizer.\n");
|
||||
Report("Re-execing with unlimited virtual address space.\n");
|
||||
setlim(RLIMIT_AS, -1);
|
||||
reexec = true;
|
||||
}
|
||||
if (reexec)
|
||||
ReExec();
|
||||
}
|
||||
|
||||
#ifndef TSAN_GO
|
||||
CheckPIE();
|
||||
|
@ -23,12 +23,24 @@ ReportDesc::ReportDesc()
|
||||
, sleep() {
|
||||
}
|
||||
|
||||
ReportMop::ReportMop()
|
||||
: mset(MBlockReportMutex) {
|
||||
}
|
||||
|
||||
ReportDesc::~ReportDesc() {
|
||||
// FIXME(dvyukov): it must be leaking a lot of memory.
|
||||
}
|
||||
|
||||
#ifndef TSAN_GO
|
||||
|
||||
const int kThreadBufSize = 32;
|
||||
const char *thread_name(char *buf, int tid) {
|
||||
if (tid == 0)
|
||||
return "main thread";
|
||||
internal_snprintf(buf, kThreadBufSize, "thread T%d", tid);
|
||||
return buf;
|
||||
}
|
||||
|
||||
static void PrintHeader(ReportType typ) {
|
||||
Printf("WARNING: ThreadSanitizer: ");
|
||||
|
||||
@ -65,52 +77,69 @@ void PrintStack(const ReportStack *ent) {
|
||||
Printf("\n");
|
||||
}
|
||||
|
||||
static void PrintMutexSet(Vector<ReportMopMutex> const& mset) {
|
||||
for (uptr i = 0; i < mset.Size(); i++) {
|
||||
if (i == 0)
|
||||
Printf(" (mutexes:");
|
||||
const ReportMopMutex m = mset[i];
|
||||
Printf(" %s M%llu", m.write ? "write" : "read", m.id);
|
||||
Printf(i == mset.Size() - 1 ? ")" : ",");
|
||||
}
|
||||
}
|
||||
|
||||
static void PrintMop(const ReportMop *mop, bool first) {
|
||||
Printf(" %s of size %d at %p",
|
||||
char thrbuf[kThreadBufSize];
|
||||
Printf(" %s of size %d at %p by %s",
|
||||
(first ? (mop->write ? "Write" : "Read")
|
||||
: (mop->write ? "Previous write" : "Previous read")),
|
||||
mop->size, (void*)mop->addr);
|
||||
if (mop->tid == 0)
|
||||
Printf(" by main thread:\n");
|
||||
else
|
||||
Printf(" by thread %d:\n", mop->tid);
|
||||
mop->size, (void*)mop->addr,
|
||||
thread_name(thrbuf, mop->tid));
|
||||
PrintMutexSet(mop->mset);
|
||||
Printf(":\n");
|
||||
PrintStack(mop->stack);
|
||||
}
|
||||
|
||||
static void PrintLocation(const ReportLocation *loc) {
|
||||
char thrbuf[kThreadBufSize];
|
||||
if (loc->type == ReportLocationGlobal) {
|
||||
Printf(" Location is global '%s' of size %zu at %zx %s:%d (%s+%p)\n\n",
|
||||
loc->name, loc->size, loc->addr, loc->file, loc->line,
|
||||
loc->module, loc->offset);
|
||||
} else if (loc->type == ReportLocationHeap) {
|
||||
Printf(" Location is heap block of size %zu at %p allocated",
|
||||
loc->size, loc->addr);
|
||||
if (loc->tid == 0)
|
||||
Printf(" by main thread:\n");
|
||||
else
|
||||
Printf(" by thread %d:\n", loc->tid);
|
||||
char thrbuf[kThreadBufSize];
|
||||
Printf(" Location is heap block of size %zu at %p allocated by %s:\n",
|
||||
loc->size, loc->addr, thread_name(thrbuf, loc->tid));
|
||||
PrintStack(loc->stack);
|
||||
} else if (loc->type == ReportLocationStack) {
|
||||
Printf(" Location is stack of thread %d:\n\n", loc->tid);
|
||||
Printf(" Location is stack of %s\n\n", thread_name(thrbuf, loc->tid));
|
||||
} else if (loc->type == ReportLocationFD) {
|
||||
Printf(" Location is file descriptor %d created by %s at:\n",
|
||||
loc->fd, thread_name(thrbuf, loc->tid));
|
||||
PrintStack(loc->stack);
|
||||
}
|
||||
}
|
||||
|
||||
static void PrintMutex(const ReportMutex *rm) {
|
||||
if (rm->stack == 0)
|
||||
return;
|
||||
Printf(" Mutex %d created at:\n", rm->id);
|
||||
PrintStack(rm->stack);
|
||||
if (rm->destroyed) {
|
||||
Printf(" Mutex M%llu is already destroyed.\n\n", rm->id);
|
||||
} else {
|
||||
Printf(" Mutex M%llu created at:\n", rm->id);
|
||||
PrintStack(rm->stack);
|
||||
}
|
||||
}
|
||||
|
||||
static void PrintThread(const ReportThread *rt) {
|
||||
if (rt->id == 0) // Little sense in describing the main thread.
|
||||
return;
|
||||
Printf(" Thread %d", rt->id);
|
||||
Printf(" Thread T%d", rt->id);
|
||||
if (rt->name)
|
||||
Printf(" '%s'", rt->name);
|
||||
Printf(" (tid=%zu, %s)", rt->pid, rt->running ? "running" : "finished");
|
||||
char thrbuf[kThreadBufSize];
|
||||
Printf(" (tid=%zu, %s) created by %s",
|
||||
rt->pid, rt->running ? "running" : "finished",
|
||||
thread_name(thrbuf, rt->parent_tid));
|
||||
if (rt->stack)
|
||||
Printf(" created at:");
|
||||
Printf(" at:");
|
||||
Printf("\n");
|
||||
PrintStack(rt->stack);
|
||||
}
|
||||
|
@ -36,20 +36,27 @@ struct ReportStack {
|
||||
int col;
|
||||
};
|
||||
|
||||
struct ReportMopMutex {
|
||||
u64 id;
|
||||
bool write;
|
||||
};
|
||||
|
||||
struct ReportMop {
|
||||
int tid;
|
||||
uptr addr;
|
||||
int size;
|
||||
bool write;
|
||||
int nmutex;
|
||||
int *mutex;
|
||||
Vector<ReportMopMutex> mset;
|
||||
ReportStack *stack;
|
||||
|
||||
ReportMop();
|
||||
};
|
||||
|
||||
enum ReportLocationType {
|
||||
ReportLocationGlobal,
|
||||
ReportLocationHeap,
|
||||
ReportLocationStack
|
||||
ReportLocationStack,
|
||||
ReportLocationFD
|
||||
};
|
||||
|
||||
struct ReportLocation {
|
||||
@ -59,6 +66,7 @@ struct ReportLocation {
|
||||
char *module;
|
||||
uptr offset;
|
||||
int tid;
|
||||
int fd;
|
||||
char *name;
|
||||
char *file;
|
||||
int line;
|
||||
@ -70,11 +78,13 @@ struct ReportThread {
|
||||
uptr pid;
|
||||
bool running;
|
||||
char *name;
|
||||
int parent_tid;
|
||||
ReportStack *stack;
|
||||
};
|
||||
|
||||
struct ReportMutex {
|
||||
int id;
|
||||
u64 id;
|
||||
bool destroyed;
|
||||
ReportStack *stack;
|
||||
};
|
||||
|
||||
|
@ -164,6 +164,16 @@ void MapShadow(uptr addr, uptr size) {
|
||||
MmapFixedNoReserve(MemToShadow(addr), size * kShadowMultiplier);
|
||||
}
|
||||
|
||||
void MapThreadTrace(uptr addr, uptr size) {
|
||||
DPrintf("#0: Mapping trace at %p-%p(0x%zx)\n", addr, addr + size, size);
|
||||
CHECK_GE(addr, kTraceMemBegin);
|
||||
CHECK_LE(addr + size, kTraceMemBegin + kTraceMemSize);
|
||||
if (addr != (uptr)MmapFixedNoReserve(addr, size)) {
|
||||
Printf("FATAL: ThreadSanitizer can not mmap thread trace\n");
|
||||
Die();
|
||||
}
|
||||
}
|
||||
|
||||
void Initialize(ThreadState *thr) {
|
||||
// Thread safe because done before all threads exist.
|
||||
static bool is_initialized = false;
|
||||
@ -289,6 +299,7 @@ void TraceSwitch(ThreadState *thr) {
|
||||
TraceHeader *hdr = &thr->trace.headers[trace];
|
||||
hdr->epoch0 = thr->fast_state.epoch();
|
||||
hdr->stack0.ObtainCurrent(thr, 0);
|
||||
hdr->mset0 = thr->mset;
|
||||
thr->nomalloc--;
|
||||
}
|
||||
|
||||
@ -443,7 +454,7 @@ ALWAYS_INLINE
|
||||
void MemoryAccess(ThreadState *thr, uptr pc, uptr addr,
|
||||
int kAccessSizeLog, bool kAccessIsWrite) {
|
||||
u64 *shadow_mem = (u64*)MemToShadow(addr);
|
||||
DPrintf2("#%d: tsan::OnMemoryAccess: @%p %p size=%d"
|
||||
DPrintf2("#%d: MemoryAccess: @%p %p size=%d"
|
||||
" is_write=%d shadow_mem=%p {%zx, %zx, %zx, %zx}\n",
|
||||
(int)thr->fast_state.tid(), (void*)pc, (void*)addr,
|
||||
(int)(1 << kAccessSizeLog), kAccessIsWrite, shadow_mem,
|
||||
|
@ -34,6 +34,7 @@
|
||||
#include "tsan_vector.h"
|
||||
#include "tsan_report.h"
|
||||
#include "tsan_platform.h"
|
||||
#include "tsan_mutexset.h"
|
||||
|
||||
#if SANITIZER_WORDSIZE != 64
|
||||
# error "ThreadSanitizer is supported only on 64-bit platforms"
|
||||
@ -48,6 +49,10 @@ struct MBlock {
|
||||
u32 alloc_tid;
|
||||
u32 alloc_stack_id;
|
||||
SyncVar *head;
|
||||
|
||||
MBlock()
|
||||
: mtx(MutexTypeMBlock, StatMtxMBlock) {
|
||||
}
|
||||
};
|
||||
|
||||
#ifndef TSAN_GO
|
||||
@ -58,10 +63,22 @@ const uptr kAllocatorSpace = 0x7d0000000000ULL;
|
||||
#endif
|
||||
const uptr kAllocatorSize = 0x10000000000ULL; // 1T.
|
||||
|
||||
struct TsanMapUnmapCallback {
|
||||
void OnMap(uptr p, uptr size) const { }
|
||||
void OnUnmap(uptr p, uptr size) const {
|
||||
// We are about to unmap a chunk of user memory.
|
||||
// Mark the corresponding shadow memory as not needed.
|
||||
uptr shadow_beg = MemToShadow(p);
|
||||
uptr shadow_end = MemToShadow(p + size);
|
||||
CHECK(IsAligned(shadow_end|shadow_beg, GetPageSizeCached()));
|
||||
FlushUnneededShadowMemory(shadow_beg, shadow_end - shadow_beg);
|
||||
}
|
||||
};
|
||||
|
||||
typedef SizeClassAllocator64<kAllocatorSpace, kAllocatorSize, sizeof(MBlock),
|
||||
DefaultSizeClassMap> PrimaryAllocator;
|
||||
typedef SizeClassAllocatorLocalCache<PrimaryAllocator> AllocatorCache;
|
||||
typedef LargeMmapAllocator SecondaryAllocator;
|
||||
typedef LargeMmapAllocator<TsanMapUnmapCallback> SecondaryAllocator;
|
||||
typedef CombinedAllocator<PrimaryAllocator, AllocatorCache,
|
||||
SecondaryAllocator> Allocator;
|
||||
Allocator *allocator();
|
||||
@ -298,6 +315,7 @@ struct ThreadState {
|
||||
uptr *shadow_stack;
|
||||
uptr *shadow_stack_end;
|
||||
#endif
|
||||
MutexSet mset;
|
||||
ThreadClock clock;
|
||||
#ifndef TSAN_GO
|
||||
AllocatorCache alloc_cache;
|
||||
@ -369,6 +387,7 @@ struct ThreadContext {
|
||||
u64 epoch0;
|
||||
u64 epoch1;
|
||||
StackTrace creation_stack;
|
||||
int creation_tid;
|
||||
ThreadDeadInfo *dead_info;
|
||||
ThreadContext *dead_next; // In dead thread list.
|
||||
char *name; // As annotated by user.
|
||||
@ -445,7 +464,8 @@ class ScopedReport {
|
||||
~ScopedReport();
|
||||
|
||||
void AddStack(const StackTrace *stack);
|
||||
void AddMemoryAccess(uptr addr, Shadow s, const StackTrace *stack);
|
||||
void AddMemoryAccess(uptr addr, Shadow s, const StackTrace *stack,
|
||||
const MutexSet *mset);
|
||||
void AddThread(const ThreadContext *tctx);
|
||||
void AddMutex(const SyncVar *s);
|
||||
void AddLocation(uptr addr, uptr size);
|
||||
@ -457,11 +477,13 @@ class ScopedReport {
|
||||
Context *ctx_;
|
||||
ReportDesc *rep_;
|
||||
|
||||
void AddMutex(u64 id);
|
||||
|
||||
ScopedReport(const ScopedReport&);
|
||||
void operator = (const ScopedReport&);
|
||||
};
|
||||
|
||||
void RestoreStack(int tid, const u64 epoch, StackTrace *stk);
|
||||
void RestoreStack(int tid, const u64 epoch, StackTrace *stk, MutexSet *mset);
|
||||
|
||||
void StatAggregate(u64 *dst, u64 *src);
|
||||
void StatOutput(u64 *stat);
|
||||
@ -471,6 +493,7 @@ void ALWAYS_INLINE INLINE StatInc(ThreadState *thr, StatType typ, u64 n = 1) {
|
||||
}
|
||||
|
||||
void MapShadow(uptr addr, uptr size);
|
||||
void MapThreadTrace(uptr addr, uptr size);
|
||||
void InitializeShadowMemory();
|
||||
void InitializeInterceptors();
|
||||
void InitializeDynamicAnnotations();
|
||||
@ -502,6 +525,10 @@ void PrintCurrentStack(ThreadState *thr, uptr pc);
|
||||
void Initialize(ThreadState *thr);
|
||||
int Finalize(ThreadState *thr);
|
||||
|
||||
SyncVar* GetJavaSync(ThreadState *thr, uptr pc, uptr addr,
|
||||
bool write_lock, bool create);
|
||||
SyncVar* GetAndRemoveJavaSync(ThreadState *thr, uptr pc, uptr addr);
|
||||
|
||||
void MemoryAccess(ThreadState *thr, uptr pc, uptr addr,
|
||||
int kAccessSizeLog, bool kAccessIsWrite);
|
||||
void MemoryAccessImpl(ThreadState *thr, uptr addr,
|
||||
@ -575,7 +602,10 @@ uptr TraceParts();
|
||||
|
||||
extern "C" void __tsan_trace_switch();
|
||||
void ALWAYS_INLINE INLINE TraceAddEvent(ThreadState *thr, FastState fs,
|
||||
EventType typ, uptr addr) {
|
||||
EventType typ, u64 addr) {
|
||||
DCHECK_GE((int)typ, 0);
|
||||
DCHECK_LE((int)typ, 7);
|
||||
DCHECK_EQ(GetLsb(addr, 61), addr);
|
||||
StatInc(thr, StatEvents);
|
||||
u64 pos = fs.GetTracePos();
|
||||
if (UNLIKELY((pos % kTracePartSize) == 0)) {
|
||||
|
@ -26,7 +26,7 @@ void MutexCreate(ThreadState *thr, uptr pc, uptr addr,
|
||||
StatInc(thr, StatMutexCreate);
|
||||
if (!linker_init && IsAppMem(addr))
|
||||
MemoryWrite1Byte(thr, pc, addr);
|
||||
SyncVar *s = ctx->synctab.GetAndLock(thr, pc, addr, true);
|
||||
SyncVar *s = ctx->synctab.GetOrCreateAndLock(thr, pc, addr, true);
|
||||
s->is_rw = rw;
|
||||
s->is_recursive = recursive;
|
||||
s->is_linker_init = linker_init;
|
||||
@ -59,11 +59,12 @@ void MutexDestroy(ThreadState *thr, uptr pc, uptr addr) {
|
||||
trace.ObtainCurrent(thr, pc);
|
||||
rep.AddStack(&trace);
|
||||
FastState last(s->last_lock);
|
||||
RestoreStack(last.tid(), last.epoch(), &trace);
|
||||
RestoreStack(last.tid(), last.epoch(), &trace, 0);
|
||||
rep.AddStack(&trace);
|
||||
rep.AddLocation(s->addr, 1);
|
||||
OutputReport(ctx, rep);
|
||||
}
|
||||
thr->mset.Remove(s->GetId());
|
||||
DestroyAndFree(s);
|
||||
}
|
||||
|
||||
@ -72,9 +73,9 @@ void MutexLock(ThreadState *thr, uptr pc, uptr addr) {
|
||||
DPrintf("#%d: MutexLock %zx\n", thr->tid, addr);
|
||||
if (IsAppMem(addr))
|
||||
MemoryRead1Byte(thr, pc, addr);
|
||||
SyncVar *s = CTX()->synctab.GetOrCreateAndLock(thr, pc, addr, true);
|
||||
thr->fast_state.IncrementEpoch();
|
||||
TraceAddEvent(thr, thr->fast_state, EventTypeLock, addr);
|
||||
SyncVar *s = CTX()->synctab.GetAndLock(thr, pc, addr, true);
|
||||
TraceAddEvent(thr, thr->fast_state, EventTypeLock, s->GetId());
|
||||
if (s->owner_tid == SyncVar::kInvalidTid) {
|
||||
CHECK_EQ(s->recursion, 0);
|
||||
s->owner_tid = thr->tid;
|
||||
@ -96,6 +97,7 @@ void MutexLock(ThreadState *thr, uptr pc, uptr addr) {
|
||||
StatInc(thr, StatMutexRecLock);
|
||||
}
|
||||
s->recursion++;
|
||||
thr->mset.Add(s->GetId(), true, thr->fast_state.epoch());
|
||||
s->mtx.Unlock();
|
||||
}
|
||||
|
||||
@ -104,9 +106,9 @@ void MutexUnlock(ThreadState *thr, uptr pc, uptr addr) {
|
||||
DPrintf("#%d: MutexUnlock %zx\n", thr->tid, addr);
|
||||
if (IsAppMem(addr))
|
||||
MemoryRead1Byte(thr, pc, addr);
|
||||
SyncVar *s = CTX()->synctab.GetOrCreateAndLock(thr, pc, addr, true);
|
||||
thr->fast_state.IncrementEpoch();
|
||||
TraceAddEvent(thr, thr->fast_state, EventTypeUnlock, addr);
|
||||
SyncVar *s = CTX()->synctab.GetAndLock(thr, pc, addr, true);
|
||||
TraceAddEvent(thr, thr->fast_state, EventTypeUnlock, s->GetId());
|
||||
if (s->recursion == 0) {
|
||||
if (!s->is_broken) {
|
||||
s->is_broken = true;
|
||||
@ -132,6 +134,7 @@ void MutexUnlock(ThreadState *thr, uptr pc, uptr addr) {
|
||||
StatInc(thr, StatMutexRecUnlock);
|
||||
}
|
||||
}
|
||||
thr->mset.Del(s->GetId(), true);
|
||||
s->mtx.Unlock();
|
||||
}
|
||||
|
||||
@ -141,9 +144,9 @@ void MutexReadLock(ThreadState *thr, uptr pc, uptr addr) {
|
||||
StatInc(thr, StatMutexReadLock);
|
||||
if (IsAppMem(addr))
|
||||
MemoryRead1Byte(thr, pc, addr);
|
||||
SyncVar *s = CTX()->synctab.GetOrCreateAndLock(thr, pc, addr, false);
|
||||
thr->fast_state.IncrementEpoch();
|
||||
TraceAddEvent(thr, thr->fast_state, EventTypeRLock, addr);
|
||||
SyncVar *s = CTX()->synctab.GetAndLock(thr, pc, addr, false);
|
||||
TraceAddEvent(thr, thr->fast_state, EventTypeRLock, s->GetId());
|
||||
if (s->owner_tid != SyncVar::kInvalidTid) {
|
||||
Printf("ThreadSanitizer WARNING: read lock of a write locked mutex\n");
|
||||
PrintCurrentStack(thr, pc);
|
||||
@ -152,6 +155,7 @@ void MutexReadLock(ThreadState *thr, uptr pc, uptr addr) {
|
||||
thr->clock.acquire(&s->clock);
|
||||
s->last_lock = thr->fast_state.raw();
|
||||
StatInc(thr, StatSyncAcquire);
|
||||
thr->mset.Add(s->GetId(), false, thr->fast_state.epoch());
|
||||
s->mtx.ReadUnlock();
|
||||
}
|
||||
|
||||
@ -161,9 +165,9 @@ void MutexReadUnlock(ThreadState *thr, uptr pc, uptr addr) {
|
||||
StatInc(thr, StatMutexReadUnlock);
|
||||
if (IsAppMem(addr))
|
||||
MemoryRead1Byte(thr, pc, addr);
|
||||
SyncVar *s = CTX()->synctab.GetOrCreateAndLock(thr, pc, addr, true);
|
||||
thr->fast_state.IncrementEpoch();
|
||||
TraceAddEvent(thr, thr->fast_state, EventTypeRUnlock, addr);
|
||||
SyncVar *s = CTX()->synctab.GetAndLock(thr, pc, addr, true);
|
||||
TraceAddEvent(thr, thr->fast_state, EventTypeRUnlock, s->GetId());
|
||||
if (s->owner_tid != SyncVar::kInvalidTid) {
|
||||
Printf("ThreadSanitizer WARNING: read unlock of a write "
|
||||
"locked mutex\n");
|
||||
@ -174,6 +178,7 @@ void MutexReadUnlock(ThreadState *thr, uptr pc, uptr addr) {
|
||||
thr->clock.release(&s->read_clock);
|
||||
StatInc(thr, StatSyncRelease);
|
||||
s->mtx.Unlock();
|
||||
thr->mset.Del(s->GetId(), false);
|
||||
}
|
||||
|
||||
void MutexReadOrWriteUnlock(ThreadState *thr, uptr pc, uptr addr) {
|
||||
@ -181,18 +186,22 @@ void MutexReadOrWriteUnlock(ThreadState *thr, uptr pc, uptr addr) {
|
||||
DPrintf("#%d: MutexReadOrWriteUnlock %zx\n", thr->tid, addr);
|
||||
if (IsAppMem(addr))
|
||||
MemoryRead1Byte(thr, pc, addr);
|
||||
SyncVar *s = CTX()->synctab.GetAndLock(thr, pc, addr, true);
|
||||
SyncVar *s = CTX()->synctab.GetOrCreateAndLock(thr, pc, addr, true);
|
||||
bool write = true;
|
||||
if (s->owner_tid == SyncVar::kInvalidTid) {
|
||||
// Seems to be read unlock.
|
||||
write = false;
|
||||
StatInc(thr, StatMutexReadUnlock);
|
||||
thr->fast_state.IncrementEpoch();
|
||||
TraceAddEvent(thr, thr->fast_state, EventTypeRUnlock, addr);
|
||||
TraceAddEvent(thr, thr->fast_state, EventTypeRUnlock, s->GetId());
|
||||
thr->clock.set(thr->tid, thr->fast_state.epoch());
|
||||
thr->fast_synch_epoch = thr->fast_state.epoch();
|
||||
thr->clock.release(&s->read_clock);
|
||||
StatInc(thr, StatSyncRelease);
|
||||
} else if (s->owner_tid == thr->tid) {
|
||||
// Seems to be write unlock.
|
||||
thr->fast_state.IncrementEpoch();
|
||||
TraceAddEvent(thr, thr->fast_state, EventTypeUnlock, s->GetId());
|
||||
CHECK_GT(s->recursion, 0);
|
||||
s->recursion--;
|
||||
if (s->recursion == 0) {
|
||||
@ -202,8 +211,6 @@ void MutexReadOrWriteUnlock(ThreadState *thr, uptr pc, uptr addr) {
|
||||
// The sequence of events is quite tricky and doubled in several places.
|
||||
// First, it's a bug to increment the epoch w/o writing to the trace.
|
||||
// Then, the acquire/release logic can be factored out as well.
|
||||
thr->fast_state.IncrementEpoch();
|
||||
TraceAddEvent(thr, thr->fast_state, EventTypeUnlock, addr);
|
||||
thr->clock.set(thr->tid, thr->fast_state.epoch());
|
||||
thr->fast_synch_epoch = thr->fast_state.epoch();
|
||||
thr->clock.ReleaseStore(&s->clock);
|
||||
@ -216,13 +223,14 @@ void MutexReadOrWriteUnlock(ThreadState *thr, uptr pc, uptr addr) {
|
||||
Printf("ThreadSanitizer WARNING: mutex unlock by another thread\n");
|
||||
PrintCurrentStack(thr, pc);
|
||||
}
|
||||
thr->mset.Del(s->GetId(), write);
|
||||
s->mtx.Unlock();
|
||||
}
|
||||
|
||||
void Acquire(ThreadState *thr, uptr pc, uptr addr) {
|
||||
CHECK_GT(thr->in_rtl, 0);
|
||||
DPrintf("#%d: Acquire %zx\n", thr->tid, addr);
|
||||
SyncVar *s = CTX()->synctab.GetAndLock(thr, pc, addr, false);
|
||||
SyncVar *s = CTX()->synctab.GetOrCreateAndLock(thr, pc, addr, false);
|
||||
thr->clock.set(thr->tid, thr->fast_state.epoch());
|
||||
thr->clock.acquire(&s->clock);
|
||||
StatInc(thr, StatSyncAcquire);
|
||||
@ -246,7 +254,7 @@ void AcquireGlobal(ThreadState *thr, uptr pc) {
|
||||
void Release(ThreadState *thr, uptr pc, uptr addr) {
|
||||
CHECK_GT(thr->in_rtl, 0);
|
||||
DPrintf("#%d: Release %zx\n", thr->tid, addr);
|
||||
SyncVar *s = CTX()->synctab.GetAndLock(thr, pc, addr, true);
|
||||
SyncVar *s = CTX()->synctab.GetOrCreateAndLock(thr, pc, addr, true);
|
||||
thr->clock.set(thr->tid, thr->fast_state.epoch());
|
||||
thr->clock.release(&s->clock);
|
||||
StatInc(thr, StatSyncRelease);
|
||||
@ -256,7 +264,7 @@ void Release(ThreadState *thr, uptr pc, uptr addr) {
|
||||
void ReleaseStore(ThreadState *thr, uptr pc, uptr addr) {
|
||||
CHECK_GT(thr->in_rtl, 0);
|
||||
DPrintf("#%d: ReleaseStore %zx\n", thr->tid, addr);
|
||||
SyncVar *s = CTX()->synctab.GetAndLock(thr, pc, addr, true);
|
||||
SyncVar *s = CTX()->synctab.GetOrCreateAndLock(thr, pc, addr, true);
|
||||
thr->clock.set(thr->tid, thr->fast_state.epoch());
|
||||
thr->clock.ReleaseStore(&s->clock);
|
||||
StatInc(thr, StatSyncRelease);
|
||||
|
@ -12,6 +12,7 @@
|
||||
#include "sanitizer_common/sanitizer_libc.h"
|
||||
#include "sanitizer_common/sanitizer_placement_new.h"
|
||||
#include "sanitizer_common/sanitizer_stackdepot.h"
|
||||
#include "sanitizer_common/sanitizer_common.h"
|
||||
#include "tsan_platform.h"
|
||||
#include "tsan_rtl.h"
|
||||
#include "tsan_suppressions.h"
|
||||
@ -20,9 +21,12 @@
|
||||
#include "tsan_sync.h"
|
||||
#include "tsan_mman.h"
|
||||
#include "tsan_flags.h"
|
||||
#include "tsan_fd.h"
|
||||
|
||||
namespace __tsan {
|
||||
|
||||
using namespace __sanitizer; // NOLINT
|
||||
|
||||
void TsanCheckFailed(const char *file, int line, const char *cond,
|
||||
u64 v1, u64 v2) {
|
||||
ScopedInRtl in_rtl;
|
||||
@ -132,7 +136,7 @@ void ScopedReport::AddStack(const StackTrace *stack) {
|
||||
}
|
||||
|
||||
void ScopedReport::AddMemoryAccess(uptr addr, Shadow s,
|
||||
const StackTrace *stack) {
|
||||
const StackTrace *stack, const MutexSet *mset) {
|
||||
void *mem = internal_alloc(MBlockReportMop, sizeof(ReportMop));
|
||||
ReportMop *mop = new(mem) ReportMop;
|
||||
rep_->mops.PushBack(mop);
|
||||
@ -140,8 +144,27 @@ void ScopedReport::AddMemoryAccess(uptr addr, Shadow s,
|
||||
mop->addr = addr + s.addr0();
|
||||
mop->size = s.size();
|
||||
mop->write = s.is_write();
|
||||
mop->nmutex = 0;
|
||||
mop->stack = SymbolizeStack(*stack);
|
||||
for (uptr i = 0; i < mset->Size(); i++) {
|
||||
MutexSet::Desc d = mset->Get(i);
|
||||
u64 uid = 0;
|
||||
uptr addr = SyncVar::SplitId(d.id, &uid);
|
||||
SyncVar *s = ctx_->synctab.GetIfExistsAndLock(addr, false);
|
||||
// Check that the mutex is still alive.
|
||||
// Another mutex can be created at the same address,
|
||||
// so check uid as well.
|
||||
if (s && s->CheckId(uid)) {
|
||||
ReportMopMutex mtx = {s->uid, d.write};
|
||||
mop->mset.PushBack(mtx);
|
||||
AddMutex(s);
|
||||
} else {
|
||||
ReportMopMutex mtx = {d.id, d.write};
|
||||
mop->mset.PushBack(mtx);
|
||||
AddMutex(d.id);
|
||||
}
|
||||
if (s)
|
||||
s->mtx.ReadUnlock();
|
||||
}
|
||||
}
|
||||
|
||||
void ScopedReport::AddThread(const ThreadContext *tctx) {
|
||||
@ -156,6 +179,7 @@ void ScopedReport::AddThread(const ThreadContext *tctx) {
|
||||
rt->pid = tctx->os_id;
|
||||
rt->running = (tctx->status == ThreadStatusRunning);
|
||||
rt->name = tctx->name ? internal_strdup(tctx->name) : 0;
|
||||
rt->parent_tid = tctx->creation_tid;
|
||||
rt->stack = SymbolizeStack(tctx->creation_stack);
|
||||
}
|
||||
|
||||
@ -173,17 +197,58 @@ static ThreadContext *FindThread(int unique_id) {
|
||||
#endif
|
||||
|
||||
void ScopedReport::AddMutex(const SyncVar *s) {
|
||||
for (uptr i = 0; i < rep_->mutexes.Size(); i++) {
|
||||
if (rep_->mutexes[i]->id == s->uid)
|
||||
return;
|
||||
}
|
||||
void *mem = internal_alloc(MBlockReportMutex, sizeof(ReportMutex));
|
||||
ReportMutex *rm = new(mem) ReportMutex();
|
||||
rep_->mutexes.PushBack(rm);
|
||||
rm->id = 42;
|
||||
rm->id = s->uid;
|
||||
rm->destroyed = false;
|
||||
rm->stack = SymbolizeStack(s->creation_stack);
|
||||
}
|
||||
|
||||
void ScopedReport::AddMutex(u64 id) {
|
||||
for (uptr i = 0; i < rep_->mutexes.Size(); i++) {
|
||||
if (rep_->mutexes[i]->id == id)
|
||||
return;
|
||||
}
|
||||
void *mem = internal_alloc(MBlockReportMutex, sizeof(ReportMutex));
|
||||
ReportMutex *rm = new(mem) ReportMutex();
|
||||
rep_->mutexes.PushBack(rm);
|
||||
rm->id = id;
|
||||
rm->destroyed = true;
|
||||
rm->stack = 0;
|
||||
}
|
||||
|
||||
void ScopedReport::AddLocation(uptr addr, uptr size) {
|
||||
if (addr == 0)
|
||||
return;
|
||||
#ifndef TSAN_GO
|
||||
int fd = -1;
|
||||
int creat_tid = -1;
|
||||
u32 creat_stack = 0;
|
||||
if (FdLocation(addr, &fd, &creat_tid, &creat_stack)
|
||||
|| FdLocation(AlternativeAddress(addr), &fd, &creat_tid, &creat_stack)) {
|
||||
void *mem = internal_alloc(MBlockReportLoc, sizeof(ReportLocation));
|
||||
ReportLocation *loc = new(mem) ReportLocation();
|
||||
rep_->locs.PushBack(loc);
|
||||
loc->type = ReportLocationFD;
|
||||
loc->fd = fd;
|
||||
loc->tid = creat_tid;
|
||||
uptr ssz = 0;
|
||||
const uptr *stack = StackDepotGet(creat_stack, &ssz);
|
||||
if (stack) {
|
||||
StackTrace trace;
|
||||
trace.Init(stack, ssz);
|
||||
loc->stack = SymbolizeStack(trace);
|
||||
}
|
||||
ThreadContext *tctx = FindThread(creat_tid);
|
||||
if (tctx)
|
||||
AddThread(tctx);
|
||||
return;
|
||||
}
|
||||
if (allocator()->PointerIsMine((void*)addr)) {
|
||||
MBlock *b = user_mblock(0, (void*)addr);
|
||||
ThreadContext *tctx = FindThread(b->alloc_tid);
|
||||
@ -246,7 +311,10 @@ const ReportDesc *ScopedReport::GetReport() const {
|
||||
return rep_;
|
||||
}
|
||||
|
||||
void RestoreStack(int tid, const u64 epoch, StackTrace *stk) {
|
||||
void RestoreStack(int tid, const u64 epoch, StackTrace *stk, MutexSet *mset) {
|
||||
// This function restores stack trace and mutex set for the thread/epoch.
|
||||
// It does so by getting stack trace and mutex set at the beginning of
|
||||
// trace part, and then replaying the trace till the given epoch.
|
||||
ThreadContext *tctx = CTX()->threads[tid];
|
||||
if (tctx == 0)
|
||||
return;
|
||||
@ -267,6 +335,7 @@ void RestoreStack(int tid, const u64 epoch, StackTrace *stk) {
|
||||
TraceHeader* hdr = &trace->headers[partidx];
|
||||
if (epoch < hdr->epoch0)
|
||||
return;
|
||||
const u64 epoch0 = RoundDown(epoch, TraceSize());
|
||||
const u64 eend = epoch % TraceSize();
|
||||
const u64 ebegin = RoundDown(eend, kTracePartSize);
|
||||
DPrintf("#%d: RestoreStack epoch=%zu ebegin=%zu eend=%zu partidx=%d\n",
|
||||
@ -276,12 +345,14 @@ void RestoreStack(int tid, const u64 epoch, StackTrace *stk) {
|
||||
stack[i] = hdr->stack0.Get(i);
|
||||
DPrintf2(" #%02lu: pc=%zx\n", i, stack[i]);
|
||||
}
|
||||
if (mset)
|
||||
*mset = hdr->mset0;
|
||||
uptr pos = hdr->stack0.Size();
|
||||
Event *events = (Event*)GetThreadTrace(tid);
|
||||
for (uptr i = ebegin; i <= eend; i++) {
|
||||
Event ev = events[i];
|
||||
EventType typ = (EventType)(ev >> 61);
|
||||
uptr pc = (uptr)(ev & 0xffffffffffffull);
|
||||
uptr pc = (uptr)(ev & ((1ull << 61) - 1));
|
||||
DPrintf2(" %zu typ=%d pc=%zx\n", i, typ, pc);
|
||||
if (typ == EventTypeMop) {
|
||||
stack[pos] = pc;
|
||||
@ -291,6 +362,17 @@ void RestoreStack(int tid, const u64 epoch, StackTrace *stk) {
|
||||
if (pos > 0)
|
||||
pos--;
|
||||
}
|
||||
if (mset) {
|
||||
if (typ == EventTypeLock) {
|
||||
mset->Add(pc, true, epoch0 + i);
|
||||
} else if (typ == EventTypeUnlock) {
|
||||
mset->Del(pc, true);
|
||||
} else if (typ == EventTypeRLock) {
|
||||
mset->Add(pc, false, epoch0 + i);
|
||||
} else if (typ == EventTypeRUnlock) {
|
||||
mset->Del(pc, false);
|
||||
}
|
||||
}
|
||||
for (uptr j = 0; j <= pos; j++)
|
||||
DPrintf2(" #%zu: %zx\n", j, stack[j]);
|
||||
}
|
||||
@ -400,8 +482,11 @@ static bool IsJavaNonsense(const ReportDesc *rep) {
|
||||
if (frame != 0 && frame->func != 0
|
||||
&& (internal_strcmp(frame->func, "memset") == 0
|
||||
|| internal_strcmp(frame->func, "memcpy") == 0
|
||||
|| internal_strcmp(frame->func, "memmove") == 0
|
||||
|| internal_strcmp(frame->func, "strcmp") == 0
|
||||
|| internal_strcmp(frame->func, "strncpy") == 0
|
||||
|| internal_strcmp(frame->func, "strlen") == 0
|
||||
|| internal_strcmp(frame->func, "free") == 0
|
||||
|| internal_strcmp(frame->func, "pthread_mutex_lock") == 0)) {
|
||||
frame = frame->next;
|
||||
if (frame == 0
|
||||
@ -423,6 +508,10 @@ void ReportRace(ThreadState *thr) {
|
||||
return;
|
||||
ScopedInRtl in_rtl;
|
||||
|
||||
if (thr->in_signal_handler)
|
||||
Printf("ThreadSanitizer: printing report from signal handler."
|
||||
" Can crash or hang.\n");
|
||||
|
||||
bool freed = false;
|
||||
{
|
||||
Shadow s(thr->racy_state[1]);
|
||||
@ -454,15 +543,18 @@ void ReportRace(ThreadState *thr) {
|
||||
traces[0].ObtainCurrent(thr, toppc);
|
||||
if (IsFiredSuppression(ctx, rep, traces[0]))
|
||||
return;
|
||||
InternalScopedBuffer<MutexSet> mset2(1);
|
||||
new(mset2.data()) MutexSet();
|
||||
Shadow s2(thr->racy_state[1]);
|
||||
RestoreStack(s2.tid(), s2.epoch(), &traces[1]);
|
||||
RestoreStack(s2.tid(), s2.epoch(), &traces[1], mset2.data());
|
||||
|
||||
if (HandleRacyStacks(thr, traces, addr_min, addr_max))
|
||||
return;
|
||||
|
||||
for (uptr i = 0; i < kMop; i++) {
|
||||
Shadow s(thr->racy_state[i]);
|
||||
rep.AddMemoryAccess(addr, s, &traces[i]);
|
||||
rep.AddMemoryAccess(addr, s, &traces[i],
|
||||
i == 0 ? &thr->mset : mset2.data());
|
||||
}
|
||||
|
||||
if (flags()->suppress_java && IsJavaNonsense(rep.GetReport()))
|
||||
|
@ -154,6 +154,7 @@ int ThreadCreate(ThreadState *thr, uptr pc, uptr uid, bool detached) {
|
||||
thr->clock.release(&tctx->sync);
|
||||
StatInc(thr, StatSyncRelease);
|
||||
tctx->creation_stack.ObtainCurrent(thr, pc);
|
||||
tctx->creation_tid = thr->tid;
|
||||
}
|
||||
return tid;
|
||||
}
|
||||
@ -303,6 +304,7 @@ void ThreadJoin(ThreadState *thr, uptr pc, int tid) {
|
||||
Printf("ThreadSanitizer: join of non-existent thread\n");
|
||||
return;
|
||||
}
|
||||
// FIXME(dvyukov): print message and continue (it's user error).
|
||||
CHECK_EQ(tctx->detached, false);
|
||||
CHECK_EQ(tctx->status, ThreadStatusFinished);
|
||||
thr->clock.acquire(&tctx->sync);
|
||||
|
@ -179,6 +179,28 @@ void StatOutput(u64 *stat) {
|
||||
name[StatInt_sem_timedwait] = " sem_timedwait ";
|
||||
name[StatInt_sem_post] = " sem_post ";
|
||||
name[StatInt_sem_getvalue] = " sem_getvalue ";
|
||||
name[StatInt_open] = " open ";
|
||||
name[StatInt_open64] = " open64 ";
|
||||
name[StatInt_creat] = " creat ";
|
||||
name[StatInt_creat64] = " creat64 ";
|
||||
name[StatInt_dup] = " dup ";
|
||||
name[StatInt_dup2] = " dup2 ";
|
||||
name[StatInt_dup3] = " dup3 ";
|
||||
name[StatInt_eventfd] = " eventfd ";
|
||||
name[StatInt_signalfd] = " signalfd ";
|
||||
name[StatInt_inotify_init] = " inotify_init ";
|
||||
name[StatInt_inotify_init1] = " inotify_init1 ";
|
||||
name[StatInt_socket] = " socket ";
|
||||
name[StatInt_socketpair] = " socketpair ";
|
||||
name[StatInt_connect] = " connect ";
|
||||
name[StatInt_accept] = " accept ";
|
||||
name[StatInt_accept4] = " accept4 ";
|
||||
name[StatInt_epoll_create] = " epoll_create ";
|
||||
name[StatInt_epoll_create1] = " epoll_create1 ";
|
||||
name[StatInt_close] = " close ";
|
||||
name[StatInt___close] = " __close ";
|
||||
name[StatInt_pipe] = " pipe ";
|
||||
name[StatInt_pipe2] = " pipe2 ";
|
||||
name[StatInt_read] = " read ";
|
||||
name[StatInt_pread] = " pread ";
|
||||
name[StatInt_pread64] = " pread64 ";
|
||||
@ -195,6 +217,8 @@ void StatOutput(u64 *stat) {
|
||||
name[StatInt_recvmsg] = " recvmsg ";
|
||||
name[StatInt_unlink] = " unlink ";
|
||||
name[StatInt_fopen] = " fopen ";
|
||||
name[StatInt_freopen] = " freopen ";
|
||||
name[StatInt_fclose] = " fclose ";
|
||||
name[StatInt_fread] = " fread ";
|
||||
name[StatInt_fwrite] = " fwrite ";
|
||||
name[StatInt_puts] = " puts ";
|
||||
@ -208,6 +232,7 @@ void StatOutput(u64 *stat) {
|
||||
name[StatInt_usleep] = " usleep ";
|
||||
name[StatInt_nanosleep] = " nanosleep ";
|
||||
name[StatInt_gettimeofday] = " gettimeofday ";
|
||||
name[StatInt_fork] = " fork ";
|
||||
|
||||
name[StatAnnotation] = "Dynamic annotations ";
|
||||
name[StatAnnotateHappensBefore] = " HappensBefore ";
|
||||
@ -251,6 +276,8 @@ void StatOutput(u64 *stat) {
|
||||
name[StatMtxSlab] = " Slab ";
|
||||
name[StatMtxAtExit] = " Atexit ";
|
||||
name[StatMtxAnnotations] = " Annotations ";
|
||||
name[StatMtxMBlock] = " MBlock ";
|
||||
name[StatMtxJavaMBlock] = " JavaMBlock ";
|
||||
|
||||
Printf("Statistics:\n");
|
||||
for (int i = 0; i < StatCnt; i++)
|
||||
|
@ -174,6 +174,28 @@ enum StatType {
|
||||
StatInt_sem_timedwait,
|
||||
StatInt_sem_post,
|
||||
StatInt_sem_getvalue,
|
||||
StatInt_open,
|
||||
StatInt_open64,
|
||||
StatInt_creat,
|
||||
StatInt_creat64,
|
||||
StatInt_dup,
|
||||
StatInt_dup2,
|
||||
StatInt_dup3,
|
||||
StatInt_eventfd,
|
||||
StatInt_signalfd,
|
||||
StatInt_inotify_init,
|
||||
StatInt_inotify_init1,
|
||||
StatInt_socket,
|
||||
StatInt_socketpair,
|
||||
StatInt_connect,
|
||||
StatInt_accept,
|
||||
StatInt_accept4,
|
||||
StatInt_epoll_create,
|
||||
StatInt_epoll_create1,
|
||||
StatInt_close,
|
||||
StatInt___close,
|
||||
StatInt_pipe,
|
||||
StatInt_pipe2,
|
||||
StatInt_read,
|
||||
StatInt_pread,
|
||||
StatInt_pread64,
|
||||
@ -190,6 +212,8 @@ enum StatType {
|
||||
StatInt_recvmsg,
|
||||
StatInt_unlink,
|
||||
StatInt_fopen,
|
||||
StatInt_freopen,
|
||||
StatInt_fclose,
|
||||
StatInt_fread,
|
||||
StatInt_fwrite,
|
||||
StatInt_puts,
|
||||
@ -207,6 +231,7 @@ enum StatType {
|
||||
StatInt_usleep,
|
||||
StatInt_nanosleep,
|
||||
StatInt_gettimeofday,
|
||||
StatInt_fork,
|
||||
|
||||
// Dynamic annotations.
|
||||
StatAnnotation,
|
||||
@ -253,6 +278,8 @@ enum StatType {
|
||||
StatMtxSlab,
|
||||
StatMtxAnnotations,
|
||||
StatMtxAtExit,
|
||||
StatMtxMBlock,
|
||||
StatMtxJavaMBlock,
|
||||
|
||||
// This must be the last.
|
||||
StatCnt
|
||||
|
@ -102,11 +102,11 @@ static int dl_iterate_phdr_cb(dl_phdr_info *info, size_t size, void *arg) {
|
||||
m->base = (uptr)info->dlpi_addr;
|
||||
m->inp_fd = -1;
|
||||
m->out_fd = -1;
|
||||
DPrintf("Module %s %zx\n", m->name, m->base);
|
||||
DPrintf2("Module %s %zx\n", m->name, m->base);
|
||||
for (int i = 0; i < info->dlpi_phnum; i++) {
|
||||
const Elf64_Phdr *s = &info->dlpi_phdr[i];
|
||||
DPrintf(" Section p_type=%zx p_offset=%zx p_vaddr=%zx p_paddr=%zx"
|
||||
" p_filesz=%zx p_memsz=%zx p_flags=%zx p_align=%zx\n",
|
||||
DPrintf2(" Section p_type=%zx p_offset=%zx p_vaddr=%zx p_paddr=%zx"
|
||||
" p_filesz=%zx p_memsz=%zx p_flags=%zx p_align=%zx\n",
|
||||
(uptr)s->p_type, (uptr)s->p_offset, (uptr)s->p_vaddr,
|
||||
(uptr)s->p_paddr, (uptr)s->p_filesz, (uptr)s->p_memsz,
|
||||
(uptr)s->p_flags, (uptr)s->p_align);
|
||||
@ -119,7 +119,7 @@ static int dl_iterate_phdr_cb(dl_phdr_info *info, size_t size, void *arg) {
|
||||
sec->end = sec->base + s->p_memsz;
|
||||
sec->next = ctx->sections;
|
||||
ctx->sections = sec;
|
||||
DPrintf(" Section %zx-%zx\n", sec->base, sec->end);
|
||||
DPrintf2(" Section %zx-%zx\n", sec->base, sec->end);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
@ -15,9 +15,10 @@
|
||||
|
||||
namespace __tsan {
|
||||
|
||||
SyncVar::SyncVar(uptr addr)
|
||||
SyncVar::SyncVar(uptr addr, u64 uid)
|
||||
: mtx(MutexTypeSyncVar, StatMtxSyncVar)
|
||||
, addr(addr)
|
||||
, uid(uid)
|
||||
, owner_tid(kInvalidTid)
|
||||
, last_lock()
|
||||
, recursion()
|
||||
@ -45,9 +46,38 @@ SyncTab::~SyncTab() {
|
||||
}
|
||||
}
|
||||
|
||||
SyncVar* SyncTab::GetAndLock(ThreadState *thr, uptr pc,
|
||||
uptr addr, bool write_lock) {
|
||||
SyncVar* SyncTab::GetOrCreateAndLock(ThreadState *thr, uptr pc,
|
||||
uptr addr, bool write_lock) {
|
||||
return GetAndLock(thr, pc, addr, write_lock, true);
|
||||
}
|
||||
|
||||
SyncVar* SyncTab::GetIfExistsAndLock(uptr addr, bool write_lock) {
|
||||
return GetAndLock(0, 0, addr, write_lock, false);
|
||||
}
|
||||
|
||||
SyncVar* SyncTab::Create(ThreadState *thr, uptr pc, uptr addr) {
|
||||
StatInc(thr, StatSyncCreated);
|
||||
void *mem = internal_alloc(MBlockSync, sizeof(SyncVar));
|
||||
const u64 uid = atomic_fetch_add(&uid_gen_, 1, memory_order_relaxed);
|
||||
SyncVar *res = new(mem) SyncVar(addr, uid);
|
||||
#ifndef TSAN_GO
|
||||
res->creation_stack.ObtainCurrent(thr, pc);
|
||||
#endif
|
||||
return res;
|
||||
}
|
||||
|
||||
SyncVar* SyncTab::GetAndLock(ThreadState *thr, uptr pc,
|
||||
uptr addr, bool write_lock, bool create) {
|
||||
#ifndef TSAN_GO
|
||||
{ // NOLINT
|
||||
SyncVar *res = GetJavaSync(thr, pc, addr, write_lock, create);
|
||||
if (res)
|
||||
return res;
|
||||
}
|
||||
|
||||
// Here we ask only PrimaryAllocator, because
|
||||
// SecondaryAllocator::PointerIsMine() is slow and we have fallback on
|
||||
// the hashmap anyway.
|
||||
if (PrimaryAllocator::PointerIsMine((void*)addr)) {
|
||||
MBlock *b = user_mblock(thr, (void*)addr);
|
||||
Lock l(&b->mtx);
|
||||
@ -57,10 +87,9 @@ SyncVar* SyncTab::GetAndLock(ThreadState *thr, uptr pc,
|
||||
break;
|
||||
}
|
||||
if (res == 0) {
|
||||
StatInc(thr, StatSyncCreated);
|
||||
void *mem = internal_alloc(MBlockSync, sizeof(SyncVar));
|
||||
res = new(mem) SyncVar(addr);
|
||||
res->creation_stack.ObtainCurrent(thr, pc);
|
||||
if (!create)
|
||||
return 0;
|
||||
res = Create(thr, pc, addr);
|
||||
res->next = b->head;
|
||||
b->head = res;
|
||||
}
|
||||
@ -85,6 +114,8 @@ SyncVar* SyncTab::GetAndLock(ThreadState *thr, uptr pc,
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!create)
|
||||
return 0;
|
||||
{
|
||||
Lock l(&p->mtx);
|
||||
SyncVar *res = p->val;
|
||||
@ -93,12 +124,7 @@ SyncVar* SyncTab::GetAndLock(ThreadState *thr, uptr pc,
|
||||
break;
|
||||
}
|
||||
if (res == 0) {
|
||||
StatInc(thr, StatSyncCreated);
|
||||
void *mem = internal_alloc(MBlockSync, sizeof(SyncVar));
|
||||
res = new(mem) SyncVar(addr);
|
||||
#ifndef TSAN_GO
|
||||
res->creation_stack.ObtainCurrent(thr, pc);
|
||||
#endif
|
||||
res = Create(thr, pc, addr);
|
||||
res->next = p->val;
|
||||
p->val = res;
|
||||
}
|
||||
@ -112,6 +138,11 @@ SyncVar* SyncTab::GetAndLock(ThreadState *thr, uptr pc,
|
||||
|
||||
SyncVar* SyncTab::GetAndRemove(ThreadState *thr, uptr pc, uptr addr) {
|
||||
#ifndef TSAN_GO
|
||||
{ // NOLINT
|
||||
SyncVar *res = GetAndRemoveJavaSync(thr, pc, addr);
|
||||
if (res)
|
||||
return res;
|
||||
}
|
||||
if (PrimaryAllocator::PointerIsMine((void*)addr)) {
|
||||
MBlock *b = user_mblock(thr, (void*)addr);
|
||||
SyncVar *res = 0;
|
||||
|
@ -48,12 +48,13 @@ class StackTrace {
|
||||
};
|
||||
|
||||
struct SyncVar {
|
||||
explicit SyncVar(uptr addr);
|
||||
explicit SyncVar(uptr addr, u64 uid);
|
||||
|
||||
static const int kInvalidTid = -1;
|
||||
|
||||
Mutex mtx;
|
||||
const uptr addr;
|
||||
uptr addr;
|
||||
const u64 uid; // Globally unique id.
|
||||
SyncClock clock;
|
||||
SyncClock read_clock; // Used for rw mutexes only.
|
||||
StackTrace creation_stack;
|
||||
@ -67,6 +68,18 @@ struct SyncVar {
|
||||
SyncVar *next; // In SyncTab hashtable.
|
||||
|
||||
uptr GetMemoryConsumption();
|
||||
u64 GetId() const {
|
||||
// 47 lsb is addr, then 14 bits is low part of uid, then 3 zero bits.
|
||||
return GetLsb((u64)addr | (uid << 47), 61);
|
||||
}
|
||||
bool CheckId(u64 uid) const {
|
||||
CHECK_EQ(uid, GetLsb(uid, 14));
|
||||
return GetLsb(this->uid, 14) == uid;
|
||||
}
|
||||
static uptr SplitId(u64 id, u64 *uid) {
|
||||
*uid = id >> 47;
|
||||
return (uptr)GetLsb(id, 47);
|
||||
}
|
||||
};
|
||||
|
||||
class SyncTab {
|
||||
@ -74,13 +87,15 @@ class SyncTab {
|
||||
SyncTab();
|
||||
~SyncTab();
|
||||
|
||||
// If the SyncVar does not exist yet, it is created.
|
||||
SyncVar* GetAndLock(ThreadState *thr, uptr pc,
|
||||
uptr addr, bool write_lock);
|
||||
SyncVar* GetOrCreateAndLock(ThreadState *thr, uptr pc,
|
||||
uptr addr, bool write_lock);
|
||||
SyncVar* GetIfExistsAndLock(uptr addr, bool write_lock);
|
||||
|
||||
// If the SyncVar does not exist, returns 0.
|
||||
SyncVar* GetAndRemove(ThreadState *thr, uptr pc, uptr addr);
|
||||
|
||||
SyncVar* Create(ThreadState *thr, uptr pc, uptr addr);
|
||||
|
||||
uptr GetMemoryConsumption(uptr *nsync);
|
||||
|
||||
private:
|
||||
@ -94,9 +109,13 @@ class SyncTab {
|
||||
// FIXME: Implement something more sane.
|
||||
static const int kPartCount = 1009;
|
||||
Part tab_[kPartCount];
|
||||
atomic_uint64_t uid_gen_;
|
||||
|
||||
int PartIdx(uptr addr);
|
||||
|
||||
SyncVar* GetAndLock(ThreadState *thr, uptr pc,
|
||||
uptr addr, bool write_lock, bool create);
|
||||
|
||||
SyncTab(const SyncTab&); // Not implemented.
|
||||
void operator = (const SyncTab&); // Not implemented.
|
||||
};
|
||||
|
@ -14,6 +14,7 @@
|
||||
#include "tsan_defs.h"
|
||||
#include "tsan_mutex.h"
|
||||
#include "tsan_sync.h"
|
||||
#include "tsan_mutexset.h"
|
||||
|
||||
namespace __tsan {
|
||||
|
||||
@ -41,6 +42,7 @@ typedef u64 Event;
|
||||
struct TraceHeader {
|
||||
StackTrace stack0; // Start stack for the trace.
|
||||
u64 epoch0; // Start epoch for the trace.
|
||||
MutexSet mset0;
|
||||
#ifndef TSAN_GO
|
||||
uptr stack0buf[kTraceStackSize];
|
||||
#endif
|
||||
|
Loading…
Reference in New Issue
Block a user