Skip to content

Commit 8885296

Browse files
committed
[scudo] Add SCUDO_ENABLE_HOOKS to enable hooks at compilation time
Accessing the PLT entries of hooks can lead a certain amount of performance overhead. This is observed on certain tasks which will do a bunch of malloc/free and their throughputs are impacted by the null check of hooks. Also add SCUDO_ENABLE_HOOKS_TESTS to select if we want to run the hook tests. On some platforms they may have different ways to run the wrappers tests (end-to-end tests) and test the hooks along with the wrappers tests may not be feasible. Provide an option to turn it ON/OFF. By default, we only verify the hook behavior in the scudo standalone tests if SCUDO_ENABLE_HOOKS is defined or COMPILER_RT_DEBUG is true. Reviewed By: cferris, fabio-d Differential Revision: https://reviews.llvm.org/D158784
1 parent bc0b569 commit 8885296

File tree

9 files changed

+213
-129
lines changed

9 files changed

+213
-129
lines changed

compiler-rt/lib/scudo/standalone/CMakeLists.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ append_list_if(COMPILER_RT_HAS_WNO_PEDANTIC -Wno-pedantic SCUDO_CFLAGS)
2525
append_list_if(COMPILER_RT_HAS_FNO_LTO_FLAG -fno-lto SCUDO_CFLAGS)
2626

2727
if(COMPILER_RT_DEBUG)
28-
list(APPEND SCUDO_CFLAGS -O0 -DSCUDO_DEBUG=1)
28+
list(APPEND SCUDO_CFLAGS -O0 -DSCUDO_DEBUG=1 -DSCUDO_ENABLE_HOOKS=1)
2929
else()
3030
list(APPEND SCUDO_CFLAGS -O3)
3131
endif()

compiler-rt/lib/scudo/standalone/platform.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,10 @@
7373
#endif
7474
#endif
7575

76+
#ifndef SCUDO_ENABLE_HOOKS
77+
#define SCUDO_ENABLE_HOOKS 0
78+
#endif
79+
7680
#ifndef SCUDO_MIN_ALIGNMENT_LOG
7781
// We force malloc-type functions to be aligned to std::max_align_t, but there
7882
// is no reason why the minimum alignment for all other functions can't be 8

compiler-rt/lib/scudo/standalone/tests/CMakeLists.txt

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,10 @@ set(SCUDO_UNITTEST_CFLAGS
1919
-Wno-mismatched-new-delete)
2020

2121
if(COMPILER_RT_DEBUG)
22-
list(APPEND SCUDO_UNITTEST_CFLAGS -DSCUDO_DEBUG=1)
22+
list(APPEND SCUDO_UNITTEST_CFLAGS -DSCUDO_DEBUG=1 -DSCUDO_ENABLE_HOOKS=1)
23+
if (NOT FUCHSIA)
24+
list(APPEND SCUDO_UNITTEST_CFLAGS -DSCUDO_ENABLE_HOOKS_TESTS=1)
25+
endif()
2326
endif()
2427

2528
if(ANDROID)

compiler-rt/lib/scudo/standalone/tests/wrappers_c_test.cpp

Lines changed: 94 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,23 @@
3737
#define HAVE_VALLOC 1
3838
#endif
3939

40+
extern "C" {
41+
void malloc_enable(void);
42+
void malloc_disable(void);
43+
int malloc_iterate(uintptr_t base, size_t size,
44+
void (*callback)(uintptr_t base, size_t size, void *arg),
45+
void *arg);
46+
void *valloc(size_t size);
47+
void *pvalloc(size_t size);
48+
49+
#ifndef SCUDO_ENABLE_HOOKS_TESTS
50+
#define SCUDO_ENABLE_HOOKS_TESTS 0
51+
#endif
52+
53+
#if (SCUDO_ENABLE_HOOKS_TESTS == 1) && (SCUDO_ENABLE_HOOKS == 0)
54+
#error "Hooks tests should have hooks enabled as well!"
55+
#endif
56+
4057
struct AllocContext {
4158
void *Ptr;
4259
size_t Size;
@@ -47,15 +64,7 @@ struct DeallocContext {
4764
static AllocContext AC;
4865
static DeallocContext DC;
4966

50-
extern "C" {
51-
void malloc_enable(void);
52-
void malloc_disable(void);
53-
int malloc_iterate(uintptr_t base, size_t size,
54-
void (*callback)(uintptr_t base, size_t size, void *arg),
55-
void *arg);
56-
void *valloc(size_t size);
57-
void *pvalloc(size_t size);
58-
67+
#if (SCUDO_ENABLE_HOOKS_TESTS == 1)
5968
__attribute__((visibility("default"))) void __scudo_allocate_hook(void *Ptr,
6069
size_t Size) {
6170
AC.Ptr = Ptr;
@@ -64,8 +73,35 @@ __attribute__((visibility("default"))) void __scudo_allocate_hook(void *Ptr,
6473
__attribute__((visibility("default"))) void __scudo_deallocate_hook(void *Ptr) {
6574
DC.Ptr = Ptr;
6675
}
76+
#endif // (SCUDO_ENABLE_HOOKS_TESTS == 1)
6777
}
6878

79+
class ScudoWrappersCTest : public Test {
80+
protected:
81+
void SetUp() override {
82+
if (SCUDO_ENABLE_HOOKS && !SCUDO_ENABLE_HOOKS_TESTS)
83+
printf("Hooks are enabled but hooks tests are disabled.\n");
84+
}
85+
86+
void invalidateAllocHookPtrAs(UNUSED void *Ptr) {
87+
if (SCUDO_ENABLE_HOOKS_TESTS)
88+
AC.Ptr = Ptr;
89+
}
90+
void verifyAllocHookPtr(UNUSED void *Ptr) {
91+
if (SCUDO_ENABLE_HOOKS_TESTS)
92+
EXPECT_EQ(Ptr, AC.Ptr);
93+
}
94+
void verifyAllocHookSize(UNUSED size_t Size) {
95+
if (SCUDO_ENABLE_HOOKS_TESTS)
96+
EXPECT_EQ(Size, AC.Size);
97+
}
98+
void verifyDeallocHookPtr(UNUSED void *Ptr) {
99+
if (SCUDO_ENABLE_HOOKS_TESTS)
100+
EXPECT_EQ(Ptr, DC.Ptr);
101+
}
102+
};
103+
using ScudoWrappersCDeathTest = ScudoWrappersCTest;
104+
69105
// Note that every C allocation function in the test binary will be fulfilled
70106
// by Scudo (this includes the gtest APIs, etc.), which is a test by itself.
71107
// But this might also lead to unexpected side-effects, since the allocation and
@@ -78,13 +114,13 @@ __attribute__((visibility("default"))) void __scudo_deallocate_hook(void *Ptr) {
78114

79115
static const size_t Size = 100U;
80116

81-
TEST(ScudoWrappersCDeathTest, Malloc) {
117+
TEST_F(ScudoWrappersCDeathTest, Malloc) {
82118
void *P = malloc(Size);
83119
EXPECT_NE(P, nullptr);
84120
EXPECT_LE(Size, malloc_usable_size(P));
85121
EXPECT_EQ(reinterpret_cast<uintptr_t>(P) % FIRST_32_SECOND_64(8U, 16U), 0U);
86-
EXPECT_EQ(P, AC.Ptr);
87-
EXPECT_EQ(Size, AC.Size);
122+
verifyAllocHookPtr(P);
123+
verifyAllocHookSize(Size);
88124

89125
// An update to this warning in Clang now triggers in this line, but it's ok
90126
// because the check is expecting a bad pointer and should fail.
@@ -99,7 +135,7 @@ TEST(ScudoWrappersCDeathTest, Malloc) {
99135
#endif
100136

101137
free(P);
102-
EXPECT_EQ(P, DC.Ptr);
138+
verifyDeallocHookPtr(P);
103139
EXPECT_DEATH(free(P), "");
104140

105141
P = malloc(0U);
@@ -111,16 +147,16 @@ TEST(ScudoWrappersCDeathTest, Malloc) {
111147
EXPECT_EQ(errno, ENOMEM);
112148
}
113149

114-
TEST(ScudoWrappersCTest, Calloc) {
150+
TEST_F(ScudoWrappersCTest, Calloc) {
115151
void *P = calloc(1U, Size);
116152
EXPECT_NE(P, nullptr);
117153
EXPECT_LE(Size, malloc_usable_size(P));
118-
EXPECT_EQ(P, AC.Ptr);
119-
EXPECT_EQ(Size, AC.Size);
154+
verifyAllocHookPtr(P);
155+
verifyAllocHookSize(Size);
120156
for (size_t I = 0; I < Size; I++)
121157
EXPECT_EQ((reinterpret_cast<uint8_t *>(P))[I], 0U);
122158
free(P);
123-
EXPECT_EQ(P, DC.Ptr);
159+
verifyDeallocHookPtr(P);
124160

125161
P = calloc(1U, 0U);
126162
EXPECT_NE(P, nullptr);
@@ -141,7 +177,7 @@ TEST(ScudoWrappersCTest, Calloc) {
141177
EXPECT_EQ(errno, ENOMEM);
142178
}
143179

144-
TEST(ScudoWrappersCTest, SmallAlign) {
180+
TEST_F(ScudoWrappersCTest, SmallAlign) {
145181
// Allocating pointers by the powers of 2 from 1 to 0x10000
146182
// Using powers of 2 due to memalign using powers of 2 and test more sizes
147183
constexpr size_t MaxSize = 0x10000;
@@ -162,7 +198,7 @@ TEST(ScudoWrappersCTest, SmallAlign) {
162198
free(ptr);
163199
}
164200

165-
TEST(ScudoWrappersCTest, Memalign) {
201+
TEST_F(ScudoWrappersCTest, Memalign) {
166202
void *P;
167203
for (size_t I = FIRST_32_SECOND_64(2U, 3U); I <= 18U; I++) {
168204
const size_t Alignment = 1U << I;
@@ -171,20 +207,20 @@ TEST(ScudoWrappersCTest, Memalign) {
171207
EXPECT_NE(P, nullptr);
172208
EXPECT_LE(Size, malloc_usable_size(P));
173209
EXPECT_EQ(reinterpret_cast<uintptr_t>(P) % Alignment, 0U);
174-
EXPECT_EQ(P, AC.Ptr);
175-
EXPECT_EQ(Size, AC.Size);
210+
verifyAllocHookPtr(P);
211+
verifyAllocHookSize(Size);
176212
free(P);
177-
EXPECT_EQ(P, DC.Ptr);
213+
verifyDeallocHookPtr(P);
178214

179215
P = nullptr;
180216
EXPECT_EQ(posix_memalign(&P, Alignment, Size), 0);
181217
EXPECT_NE(P, nullptr);
182218
EXPECT_LE(Size, malloc_usable_size(P));
183219
EXPECT_EQ(reinterpret_cast<uintptr_t>(P) % Alignment, 0U);
184-
EXPECT_EQ(P, AC.Ptr);
185-
EXPECT_EQ(Size, AC.Size);
220+
verifyAllocHookPtr(P);
221+
verifyAllocHookSize(Size);
186222
free(P);
187-
EXPECT_EQ(P, DC.Ptr);
223+
verifyDeallocHookPtr(P);
188224
}
189225

190226
EXPECT_EQ(memalign(4096U, SIZE_MAX), nullptr);
@@ -196,78 +232,78 @@ TEST(ScudoWrappersCTest, Memalign) {
196232
for (size_t Alignment = 0U; Alignment <= 128U; Alignment++) {
197233
P = memalign(Alignment, 1024U);
198234
EXPECT_NE(P, nullptr);
199-
EXPECT_EQ(P, AC.Ptr);
200-
EXPECT_EQ(Size, AC.Size);
235+
verifyAllocHookPtr(P);
236+
verifyAllocHookSize(Size);
201237
free(P);
202-
EXPECT_EQ(P, DC.Ptr);
238+
verifyDeallocHookPtr(P);
203239
}
204240
}
205241
}
206242

207-
TEST(ScudoWrappersCTest, AlignedAlloc) {
243+
TEST_F(ScudoWrappersCTest, AlignedAlloc) {
208244
const size_t Alignment = 4096U;
209245
void *P = aligned_alloc(Alignment, Alignment * 4U);
210246
EXPECT_NE(P, nullptr);
211247
EXPECT_LE(Alignment * 4U, malloc_usable_size(P));
212248
EXPECT_EQ(reinterpret_cast<uintptr_t>(P) % Alignment, 0U);
213-
EXPECT_EQ(P, AC.Ptr);
214-
EXPECT_EQ(Alignment * 4U, AC.Size);
249+
verifyAllocHookPtr(P);
250+
verifyAllocHookSize(Alignment * 4U);
215251
free(P);
216-
EXPECT_EQ(P, DC.Ptr);
252+
verifyDeallocHookPtr(P);
217253

218254
errno = 0;
219255
P = aligned_alloc(Alignment, Size);
220256
EXPECT_EQ(P, nullptr);
221257
EXPECT_EQ(errno, EINVAL);
222258
}
223259

224-
TEST(ScudoWrappersCDeathTest, Realloc) {
260+
TEST_F(ScudoWrappersCDeathTest, Realloc) {
225261
// realloc(nullptr, N) is malloc(N)
226262
void *P = realloc(nullptr, Size);
227263
EXPECT_NE(P, nullptr);
228-
EXPECT_EQ(P, AC.Ptr);
229-
EXPECT_EQ(Size, AC.Size);
264+
verifyAllocHookPtr(P);
265+
verifyAllocHookSize(Size);
230266
free(P);
231-
EXPECT_EQ(P, DC.Ptr);
267+
verifyDeallocHookPtr(P);
232268

233269
P = malloc(Size);
234270
EXPECT_NE(P, nullptr);
235271
// realloc(P, 0U) is free(P) and returns nullptr
236272
EXPECT_EQ(realloc(P, 0U), nullptr);
237-
EXPECT_EQ(P, DC.Ptr);
273+
verifyDeallocHookPtr(P);
238274

239275
P = malloc(Size);
240276
EXPECT_NE(P, nullptr);
241277
EXPECT_LE(Size, malloc_usable_size(P));
242278
memset(P, 0x42, Size);
243279

244-
AC.Ptr = reinterpret_cast<void *>(0xdeadbeef);
280+
invalidateAllocHookPtrAs(reinterpret_cast<void *>(0xdeadbeef));
245281
void *OldP = P;
246282
P = realloc(P, Size * 2U);
247283
EXPECT_NE(P, nullptr);
248284
EXPECT_LE(Size * 2U, malloc_usable_size(P));
249285
for (size_t I = 0; I < Size; I++)
250286
EXPECT_EQ(0x42, (reinterpret_cast<uint8_t *>(P))[I]);
251287
if (OldP == P) {
252-
EXPECT_EQ(AC.Ptr, reinterpret_cast<void *>(0xdeadbeef));
288+
verifyAllocHookPtr(reinterpret_cast<void *>(0xdeadbeef));
253289
} else {
254-
EXPECT_EQ(P, AC.Ptr);
255-
EXPECT_EQ(Size * 2U, AC.Size);
256-
EXPECT_EQ(OldP, DC.Ptr);
290+
verifyAllocHookPtr(P);
291+
verifyAllocHookSize(Size * 2U);
292+
verifyDeallocHookPtr(OldP);
257293
}
258294

259-
AC.Ptr = reinterpret_cast<void *>(0xdeadbeef);
295+
invalidateAllocHookPtrAs(reinterpret_cast<void *>(0xdeadbeef));
260296
OldP = P;
261297
P = realloc(P, Size / 2U);
262298
EXPECT_NE(P, nullptr);
263299
EXPECT_LE(Size / 2U, malloc_usable_size(P));
264300
for (size_t I = 0; I < Size / 2U; I++)
265301
EXPECT_EQ(0x42, (reinterpret_cast<uint8_t *>(P))[I]);
266302
if (OldP == P) {
267-
EXPECT_EQ(AC.Ptr, reinterpret_cast<void *>(0xdeadbeef));
303+
verifyAllocHookPtr(reinterpret_cast<void *>(0xdeadbeef));
268304
} else {
269-
EXPECT_EQ(P, AC.Ptr);
270-
EXPECT_EQ(Size / 2U, AC.Size);
305+
verifyAllocHookPtr(P);
306+
verifyAllocHookSize(Size / 2U);
271307
}
272308
free(P);
273309

@@ -302,7 +338,7 @@ TEST(ScudoWrappersCDeathTest, Realloc) {
302338
}
303339

304340
#if !SCUDO_FUCHSIA
305-
TEST(ScudoWrappersCTest, MallOpt) {
341+
TEST_F(ScudoWrappersCTest, MallOpt) {
306342
errno = 0;
307343
EXPECT_EQ(mallopt(-1000, 1), 0);
308344
// mallopt doesn't set errno.
@@ -323,19 +359,19 @@ TEST(ScudoWrappersCTest, MallOpt) {
323359
}
324360
#endif
325361

326-
TEST(ScudoWrappersCTest, OtherAlloc) {
362+
TEST_F(ScudoWrappersCTest, OtherAlloc) {
327363
#if HAVE_PVALLOC
328364
const size_t PageSize = static_cast<size_t>(sysconf(_SC_PAGESIZE));
329365

330366
void *P = pvalloc(Size);
331367
EXPECT_NE(P, nullptr);
332368
EXPECT_EQ(reinterpret_cast<uintptr_t>(P) & (PageSize - 1), 0U);
333369
EXPECT_LE(PageSize, malloc_usable_size(P));
334-
EXPECT_EQ(P, AC.Ptr);
370+
verifyAllocHookPtr(P);
335371
// Size will be rounded up to PageSize.
336-
EXPECT_EQ(PageSize, AC.Size);
372+
verifyAllocHookSize(PageSize);
337373
free(P);
338-
EXPECT_EQ(P, DC.Ptr);
374+
verifyDeallocHookPtr(P);
339375

340376
EXPECT_EQ(pvalloc(SIZE_MAX), nullptr);
341377

@@ -351,7 +387,7 @@ TEST(ScudoWrappersCTest, OtherAlloc) {
351387
}
352388

353389
#if !SCUDO_FUCHSIA
354-
TEST(ScudoWrappersCTest, MallInfo) {
390+
TEST_F(ScudoWrappersCTest, MallInfo) {
355391
// mallinfo is deprecated.
356392
#pragma clang diagnostic push
357393
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
@@ -372,7 +408,7 @@ TEST(ScudoWrappersCTest, MallInfo) {
372408
#endif
373409

374410
#if __GLIBC_PREREQ(2, 33)
375-
TEST(ScudoWrappersCTest, MallInfo2) {
411+
TEST_F(ScudoWrappersCTest, MallInfo2) {
376412
const size_t BypassQuarantineSize = 1024U;
377413
struct mallinfo2 MI = mallinfo2();
378414
size_t Allocated = MI.uordblks;
@@ -404,7 +440,7 @@ static void callback(uintptr_t Base, UNUSED size_t Size, UNUSED void *Arg) {
404440
// To achieve this, we allocate a chunk for which the backing block will be
405441
// aligned on a page, then run the malloc_iterate on both the pages that the
406442
// block is a boundary for. It must only be seen once by the callback function.
407-
TEST(ScudoWrappersCTest, MallocIterateBoundary) {
443+
TEST_F(ScudoWrappersCTest, MallocIterateBoundary) {
408444
const size_t PageSize = static_cast<size_t>(sysconf(_SC_PAGESIZE));
409445
#if SCUDO_ANDROID
410446
// Android uses a 16 byte alignment for both 32 bit and 64 bit.
@@ -456,7 +492,7 @@ TEST(ScudoWrappersCTest, MallocIterateBoundary) {
456492

457493
// Fuchsia doesn't have alarm, fork or malloc_info.
458494
#if !SCUDO_FUCHSIA
459-
TEST(ScudoWrappersCDeathTest, MallocDisableDeadlock) {
495+
TEST_F(ScudoWrappersCDeathTest, MallocDisableDeadlock) {
460496
// We expect heap operations within a disable/enable scope to deadlock.
461497
EXPECT_DEATH(
462498
{
@@ -471,7 +507,7 @@ TEST(ScudoWrappersCDeathTest, MallocDisableDeadlock) {
471507
"");
472508
}
473509

474-
TEST(ScudoWrappersCTest, MallocInfo) {
510+
TEST_F(ScudoWrappersCTest, MallocInfo) {
475511
// Use volatile so that the allocations don't get optimized away.
476512
void *volatile P1 = malloc(1234);
477513
void *volatile P2 = malloc(4321);
@@ -491,7 +527,7 @@ TEST(ScudoWrappersCTest, MallocInfo) {
491527
free(P2);
492528
}
493529

494-
TEST(ScudoWrappersCDeathTest, Fork) {
530+
TEST_F(ScudoWrappersCDeathTest, Fork) {
495531
void *P;
496532
pid_t Pid = fork();
497533
EXPECT_GE(Pid, 0) << strerror(errno);
@@ -543,7 +579,7 @@ static void *enableMalloc(UNUSED void *Unused) {
543579
return nullptr;
544580
}
545581

546-
TEST(ScudoWrappersCTest, DisableForkEnable) {
582+
TEST_F(ScudoWrappersCTest, DisableForkEnable) {
547583
pthread_t ThreadId;
548584
Ready = false;
549585
EXPECT_EQ(pthread_create(&ThreadId, nullptr, &enableMalloc, nullptr), 0);

0 commit comments

Comments
 (0)