Skip to content

Commit

Permalink
[esan|wset] Iterate all memory to compute the total working set
Browse files Browse the repository at this point in the history
Summary:
Adds iteration of all application memory in an efficient manner using
shadow faults.  Shadow memory starts out inaccessible and we mark it
writable one page at a time on each fault when the instrumentation touches
it.  This allows iteration over just the mapped shadow memory, saving
significant time.

Adds a process-end iteration and pretty-printing of the final result.

Adds a new test and updates the existing tests.

Reviewers: aizatsky, filcab

Subscribers: vitalybuka, zhaoqin, kcc, eugenis, llvm-commits, kubabrecka

Differential Revision: http://reviews.llvm.org/D20578

llvm-svn: 271277
  • Loading branch information
derekbruening committed May 31, 2016
1 parent 659afd5 commit 8ef3f0f
Show file tree
Hide file tree
Showing 6 changed files with 125 additions and 13 deletions.
12 changes: 10 additions & 2 deletions compiler-rt/lib/esan/esan.cpp
Expand Up @@ -149,8 +149,16 @@ static void initializeShadow() {
VPrintf(1, "Shadow #%d: [%zx-%zx) (%zuGB)\n", i, ShadowStart, ShadowEnd,
(ShadowEnd - ShadowStart) >> 30);

uptr Map = (uptr)MmapFixedNoReserve(ShadowStart, ShadowEnd - ShadowStart,
"shadow");
uptr Map;
if (WhichTool == ESAN_WorkingSet) {
// We want to identify all shadow pages that are touched so we start
// out inaccessible.
Map = (uptr)MmapFixedNoAccess(ShadowStart, ShadowEnd- ShadowStart,
"shadow");
} else {
Map = (uptr)MmapFixedNoReserve(ShadowStart, ShadowEnd - ShadowStart,
"shadow");
}
if (Map != ShadowStart) {
Printf("FATAL: EfficiencySanitizer failed to map its shadow memory.\n");
Die();
Expand Down
78 changes: 73 additions & 5 deletions compiler-rt/lib/esan/working_set.cpp
Expand Up @@ -16,6 +16,7 @@
#include "esan.h"
#include "esan_flags.h"
#include "esan_shadow.h"
#include "sanitizer_common/sanitizer_procmaps.h"

// We shadow every cache line of app memory with one shadow byte.
// - The highest bit of each shadow byte indicates whether the corresponding
Expand All @@ -30,6 +31,9 @@ typedef unsigned char byte;

namespace __esan {

// Our shadow memory assumes that the line size is 64.
static const u32 CacheLineSize = 64;

// See the shadow byte layout description above.
static const u32 TotalWorkingSetBitIdx = 7;
static const u32 CurWorkingSetBitIdx = 0;
Expand Down Expand Up @@ -75,16 +79,80 @@ void processRangeAccessWorkingSet(uptr PC, uptr Addr, SIZE_T Size,
}
}

// This routine will word-align ShadowStart and ShadowEnd prior to scanning.
static u32 countAndClearShadowValues(u32 BitIdx, uptr ShadowStart,
uptr ShadowEnd) {
u32 WorkingSetSize = 0;
u32 ByteValue = 0x1 << BitIdx;
u32 WordValue = ByteValue | ByteValue << 8 | ByteValue << 16 |
ByteValue << 24;
// Get word aligned start.
ShadowStart = RoundDownTo(ShadowStart, sizeof(u32));
for (u32 *Ptr = (u32 *)ShadowStart; Ptr < (u32 *)ShadowEnd; ++Ptr) {
if ((*Ptr & WordValue) != 0) {
byte *BytePtr = (byte *)Ptr;
for (u32 j = 0; j < sizeof(u32); ++j) {
if (BytePtr[j] & ByteValue) {
++WorkingSetSize;
// TODO: Accumulate to the lower-frequency bit to the left.
}
}
// Clear this bit from every shadow byte.
*Ptr &= ~WordValue;
}
}
return WorkingSetSize;
}

// Scan shadow memory to calculate the number of cache lines being accessed,
// i.e., the number of non-zero bits indexed by BitIdx in each shadow byte.
// We also clear the lowest bits (most recent working set snapshot).
static u32 computeWorkingSizeAndReset(u32 BitIdx) {
u32 WorkingSetSize = 0;
MemoryMappingLayout MemIter(true/*cache*/);
uptr Start, End, Prot;
while (MemIter.Next(&Start, &End, nullptr/*offs*/, nullptr/*file*/,
0/*file size*/, &Prot)) {
VPrintf(4, "%s: considering %p-%p app=%d shadow=%d prot=%u\n",
__FUNCTION__, Start, End, Prot, isAppMem(Start),
isShadowMem(Start));
if (isShadowMem(Start) && (Prot & MemoryMappingLayout::kProtectionWrite)) {
VPrintf(3, "%s: walking %p-%p\n", __FUNCTION__, Start, End);
WorkingSetSize += countAndClearShadowValues(BitIdx, Start, End);
}
}
return WorkingSetSize;
}

void initializeWorkingSet() {
// The shadow mapping assumes 64 so this cannot be changed.
CHECK(getFlags()->cache_line_size == 64);
CHECK(getFlags()->cache_line_size == CacheLineSize);
registerMemoryFaultHandler();
}

static u32 getSizeForPrinting(u32 NumOfCachelines, const char *&Unit) {
// We need a constant to avoid software divide support:
static const u32 KilobyteCachelines = (0x1 << 10) / CacheLineSize;
static const u32 MegabyteCachelines = KilobyteCachelines << 10;

if (NumOfCachelines > 10 * MegabyteCachelines) {
Unit = "MB";
return NumOfCachelines / MegabyteCachelines;
} else if (NumOfCachelines > 10 * KilobyteCachelines) {
Unit = "KB";
return NumOfCachelines / KilobyteCachelines;
} else {
Unit = "Bytes";
return NumOfCachelines * CacheLineSize;
}
}

int finalizeWorkingSet() {
// FIXME NYI: we need to add memory scanning to report the total lines
// touched, and later add sampling to get intermediate values.
Report("%s is not finished: nothing yet to report\n", SanitizerToolName);
// Get the working set size for the entire execution.
u32 NumOfCachelines = computeWorkingSizeAndReset(TotalWorkingSetBitIdx);
const char *Unit;
u32 Size = getSizeForPrinting(NumOfCachelines, Unit);
Report(" %s: the total working set size: %u %s (%u cache lines)\n",
SanitizerToolName, Size, Unit, NumOfCachelines);
return 0;
}

Expand Down
14 changes: 10 additions & 4 deletions compiler-rt/lib/esan/working_set_posix.cpp
Expand Up @@ -68,10 +68,16 @@ static void reinstateDefaultHandler(int SigNum) {
// app to handle it just as the app would do without our tool in place.
static void handleMemoryFault(int SigNum, void *Info, void *Ctx) {
if (SigNum == SIGSEGV) {

// TODO: Add shadow memory fault detection and handling.

if (AppSigAct.sigaction) {
// We rely on si_addr being filled in (thus we do not support old kernels).
siginfo_t *SigInfo = (siginfo_t *)Info;
uptr Addr = (uptr)SigInfo->si_addr;
if (isShadowMem(Addr)) {
VPrintf(3, "Shadow fault @%p\n", Addr);
uptr PageSize = GetPageSizeCached();
int Res = internal_mprotect((void *)RoundDownTo(Addr, PageSize),
PageSize, PROT_READ|PROT_WRITE);
CHECK(Res == 0);
} else if (AppSigAct.sigaction) {
// FIXME: For simplicity we ignore app options including its signal stack
// (we just use ours) and all the delivery flags.
AppSigAct.sigaction(SigNum, Info, Ctx);
Expand Down
3 changes: 1 addition & 2 deletions compiler-rt/test/esan/TestCases/workingset-memset.cpp
Expand Up @@ -16,6 +16,5 @@ int main(int argc, char **argv) {
memset((char *)p + 63*i, i, 63*i);
munmap(p, size);
return 0;
// FIXME: once the memory scan and size report is in place add it here.
// CHECK: {{.*}}EfficiencySanitizer is not finished: nothing yet to report
// CHECK: {{.*}} EfficiencySanitizer: the total working set size: 77 KB (12{{[0-9]+}} cache lines)
}
Expand Up @@ -57,3 +57,4 @@ int main(int argc, char **argv) {
// CHECK-NEXT: Past longjmp for signal
// CHECK-NEXT: Handling SIGSEGV for sigaction
// CHECK-NEXT: Past longjmp for sigaction
// CHECK: {{.*}} EfficiencySanitizer: the total working set size: {{[0-9][0-9][0-9]}} Bytes ({{[0-9][0-9]}} cache lines)
30 changes: 30 additions & 0 deletions compiler-rt/test/esan/TestCases/workingset-simple.cpp
@@ -0,0 +1,30 @@
// RUN: %clang_esan_wset -O0 %s -o %t 2>&1
// RUN: %run %t 2>&1 | FileCheck %s

#include <stdlib.h>
#include <string.h>
#include <sys/mman.h>
#include <assert.h>

const int size = 0x1 << 25; // 523288 cache lines
const int line_size = 64;

int main(int argc, char **argv) {
char *bufA = (char *)malloc(sizeof(char) * line_size);
char bufB[64];
char *bufC = (char *)mmap(0, size, PROT_READ | PROT_WRITE,
MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
bufA[0] = 0;
// This additional access to the same line should not increase the line
// count: but it's difficult to make a non-flaky test that measures the
// lines down to the ones digit so right now we're not really testing that.
// If we add a heap-only mode we may be able to be more precise.
bufA[1] = 0;
bufB[33] = 1;
for (int i = 0; i < size; i += line_size)
bufC[i] = 0;
free(bufA);
munmap(bufC, 0x4000);
// CHECK: {{.*}} EfficiencySanitizer: the total working set size: 32 MB (524{{[0-9][0-9][0-9]}} cache lines)
return 0;
}

0 comments on commit 8ef3f0f

Please sign in to comment.