Skip to content

Commit be37467

Browse files
committed
[msan] Automatically print shadow for failing outlined checks
A commonly used aid for debugging MSan reports is __msan_print_shadow(), which must be manually applied (typically to the variable in the UUM report or nearby). This is in contrast to ASan, which automatically prints out the shadow map when a check fails. This patch changes MSan to print the shadow that failed an outlined check (checks are outlined per function after -msan-instrumentation-with-call-threshold is exceeded). As a side effect, this also enables outlined checks for arbitrary-sized shadows (vs. the current hardcoded handlers for {1,2,4,8}-byte shadows). Note that we do not print out the shadow map of "neighboring" variables because this is technically infeasible; see "Caveat" below. This patch can be easier to use than __msan_print_shadow() because this does not require manual app code annotations. Additionally, due to optimizations, __msan_print_shadow() calls can sometimes spuriously affect whether a variable is initialized. Caveat: the shadow does not necessarily correspond to an individual user variable (MSan instrumentation may combine and/or truncate multiple shadows prior to emitting a check that the mangled shadow is zero; e.g., convertShadowToScalar, handleSSEVectorConvertIntrinsic, materializeInstructionChecks). OTOH it is arguably a strength that this feature emit the shadow that directly matters for the MSan check, but which cannot be obtained using the MSan API.
1 parent 32649e0 commit be37467

File tree

4 files changed

+118
-10
lines changed

4 files changed

+118
-10
lines changed

compiler-rt/lib/msan/msan.cpp

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -352,11 +352,33 @@ void __sanitizer::BufferedStackTrace::UnwindImpl(
352352

353353
using namespace __msan;
354354

355+
// N.B. Only [shadow, shadow+size) is defined. shadow is *not* a pointer into
356+
// an MSan shadow region.
357+
static void print_shadow_value(void *shadow, u64 size) {
358+
Printf("\n");
359+
Printf("Shadow value (%llu byte%s):", size, size == 1 ? "" : "s");
360+
for (unsigned int i = 0; i < size; i++) {
361+
if (i % 4 == 0)
362+
Printf(" ");
363+
364+
unsigned char x = ((unsigned char *)shadow)[i];
365+
Printf("%x%x", x >> 4, x & 0xf);
366+
}
367+
Printf("\n");
368+
Printf(
369+
"Caveat: the shadow value does not necessarily directly correspond to a "
370+
"single user variable. The correspondence is stronger, but not always "
371+
"perfect, when origin tracking is enabled.\n");
372+
Printf("\n");
373+
}
374+
355375
#define MSAN_MAYBE_WARNING(type, size) \
356376
void __msan_maybe_warning_##size(type s, u32 o) { \
357377
GET_CALLER_PC_BP; \
378+
\
358379
if (UNLIKELY(s)) { \
359380
PrintWarningWithOrigin(pc, bp, o); \
381+
print_shadow_value((void *)(&s), sizeof(s)); \
360382
if (__msan::flags()->halt_on_error) { \
361383
Printf("Exiting\n"); \
362384
Die(); \
@@ -369,6 +391,29 @@ MSAN_MAYBE_WARNING(u16, 2)
369391
MSAN_MAYBE_WARNING(u32, 4)
370392
MSAN_MAYBE_WARNING(u64, 8)
371393

394+
// N.B. Only [shadow, shadow+size) is defined. shadow is *not* a pointer into
395+
// an MSan shadow region.
396+
void __msan_maybe_warning_N(void *shadow, u64 size, u32 o) {
397+
GET_CALLER_PC_BP;
398+
399+
bool allZero = true;
400+
for (unsigned int i = 0; i < size; i++) {
401+
if (((char *)shadow)[i]) {
402+
allZero = false;
403+
break;
404+
}
405+
}
406+
407+
if (UNLIKELY(!allZero)) {
408+
PrintWarningWithOrigin(pc, bp, o);
409+
print_shadow_value(shadow, size);
410+
if (__msan::flags()->halt_on_error) {
411+
Printf("Exiting\n");
412+
Die();
413+
}
414+
}
415+
}
416+
372417
#define MSAN_MAYBE_STORE_ORIGIN(type, size) \
373418
void __msan_maybe_store_origin_##size(type s, void *p, u32 o) { \
374419
if (UNLIKELY(s)) { \

compiler-rt/lib/msan/msan_interface_internal.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,8 @@ SANITIZER_INTERFACE_ATTRIBUTE
6060
void __msan_maybe_warning_4(u32 s, u32 o);
6161
SANITIZER_INTERFACE_ATTRIBUTE
6262
void __msan_maybe_warning_8(u64 s, u32 o);
63+
SANITIZER_INTERFACE_ATTRIBUTE
64+
void __msan_maybe_warning_N(void *shadow, u64 size, u32 o);
6365

6466
SANITIZER_INTERFACE_ATTRIBUTE
6567
void __msan_maybe_store_origin_1(u8 s, void *p, u32 o);
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
// RUN: %clangxx_msan -fsanitize-recover=memory -mllvm -msan-instrumentation-with-call-threshold=0 -g %s -o %t \
2+
// RUN: && not %run %t 2>&1 | FileCheck %s
3+
4+
#include <ctype.h>
5+
#include <stdio.h>
6+
7+
#include <sanitizer/msan_interface.h>
8+
9+
int main(int argc, char *argv[]) {
10+
long double a;
11+
printf("a: %Lf\n", a);
12+
// CHECK: Shadow value (16 bytes): ffffffff ffffffff ffff0000 00000000
13+
14+
unsigned long long b;
15+
printf("b: %llu\n", b);
16+
// CHECK: Shadow value (8 bytes): ffffffff ffffffff
17+
18+
char *p = (char *)(&b);
19+
p[2] = 36;
20+
printf("b: %lld\n", b);
21+
// CHECK: Shadow value (8 bytes): ffff00ff ffffffff
22+
23+
b = b << 8;
24+
printf("b: %lld\n", b);
25+
__msan_print_shadow(&b, sizeof(b));
26+
// CHECK: Shadow value (8 bytes): 00ffff00 ffffffff
27+
28+
unsigned int c;
29+
printf("c: %u\n", c);
30+
// CHECK: Shadow value (4 bytes): ffffffff
31+
32+
// Converted to boolean
33+
if (c) {
34+
// CHECK: Shadow value (1 byte): 01
35+
printf("Hello\n");
36+
}
37+
38+
return 0;
39+
}

llvm/lib/Transforms/Instrumentation/MemorySanitizer.cpp

Lines changed: 32 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -652,6 +652,7 @@ class MemorySanitizer {
652652

653653
// These arrays are indexed by log2(AccessSize).
654654
FunctionCallee MaybeWarningFn[kNumberOfAccessSizes];
655+
FunctionCallee MaybeWarningVarSizeFn;
655656
FunctionCallee MaybeStoreOriginFn[kNumberOfAccessSizes];
656657

657658
/// Run-time helper that generates a new origin value for a stack
@@ -926,7 +927,9 @@ void MemorySanitizer::createUserspaceApi(Module &M,
926927
MaybeWarningFn[AccessSizeIndex] = M.getOrInsertFunction(
927928
FunctionName, TLI.getAttrList(C, {0, 1}, /*Signed=*/false),
928929
IRB.getVoidTy(), IRB.getIntNTy(AccessSize * 8), IRB.getInt32Ty());
929-
930+
MaybeWarningVarSizeFn = M.getOrInsertFunction(
931+
"__msan_maybe_warning_N", TLI.getAttrList(C, {}, /*Signed=*/false),
932+
IRB.getVoidTy(), PtrTy, IRB.getInt64Ty(), IRB.getInt32Ty());
930933
FunctionName = "__msan_maybe_store_origin_" + itostr(AccessSize);
931934
MaybeStoreOriginFn[AccessSizeIndex] = M.getOrInsertFunction(
932935
FunctionName, TLI.getAttrList(C, {0, 2}, /*Signed=*/false),
@@ -1233,7 +1236,6 @@ struct MemorySanitizerVisitor : public InstVisitor<MemorySanitizerVisitor> {
12331236
// Constants likely will be eliminated by follow-up passes.
12341237
if (isa<Constant>(V))
12351238
return false;
1236-
12371239
++SplittableBlocksCount;
12381240
return ClInstrumentationWithCallThreshold >= 0 &&
12391241
SplittableBlocksCount > ClInstrumentationWithCallThreshold;
@@ -1432,18 +1434,38 @@ struct MemorySanitizerVisitor : public InstVisitor<MemorySanitizerVisitor> {
14321434
const DataLayout &DL = F.getDataLayout();
14331435
TypeSize TypeSizeInBits = DL.getTypeSizeInBits(ConvertedShadow->getType());
14341436
unsigned SizeIndex = TypeSizeToSizeIndex(TypeSizeInBits);
1435-
if (instrumentWithCalls(ConvertedShadow) &&
1436-
SizeIndex < kNumberOfAccessSizes && !MS.CompileKernel) {
1437-
FunctionCallee Fn = MS.MaybeWarningFn[SizeIndex];
1437+
if (instrumentWithCalls(ConvertedShadow) && !MS.CompileKernel) {
14381438
// ZExt cannot convert between vector and scalar
14391439
ConvertedShadow = convertShadowToScalar(ConvertedShadow, IRB);
14401440
Value *ConvertedShadow2 =
14411441
IRB.CreateZExt(ConvertedShadow, IRB.getIntNTy(8 * (1 << SizeIndex)));
1442-
CallBase *CB = IRB.CreateCall(
1443-
Fn, {ConvertedShadow2,
1444-
MS.TrackOrigins && Origin ? Origin : (Value *)IRB.getInt32(0)});
1445-
CB->addParamAttr(0, Attribute::ZExt);
1446-
CB->addParamAttr(1, Attribute::ZExt);
1442+
1443+
if (SizeIndex < kNumberOfAccessSizes) {
1444+
FunctionCallee Fn = MS.MaybeWarningFn[SizeIndex];
1445+
CallBase *CB = IRB.CreateCall(
1446+
Fn,
1447+
{ConvertedShadow2,
1448+
MS.TrackOrigins && Origin ? Origin : (Value *)IRB.getInt32(0)});
1449+
CB->addParamAttr(0, Attribute::ZExt);
1450+
CB->addParamAttr(1, Attribute::ZExt);
1451+
} else {
1452+
FunctionCallee Fn = MS.MaybeWarningVarSizeFn;
1453+
1454+
// Note: we can only dump the current shadow value, not an entire
1455+
// neighborhood shadow map (as ASan does). This is because the shadow
1456+
// value does not necessarily correspond to a user variable: MSan code
1457+
// often combines shadows (e.g., convertShadowToScalar,
1458+
// handleSSEVectorConvertIntrinsic, materializeInstructionChecks).
1459+
Value *ShadowAlloca = IRB.CreateAlloca(ConvertedShadow2->getType(), 0u);
1460+
IRB.CreateStore(ConvertedShadow2, ShadowAlloca);
1461+
unsigned ShadowSize = DL.getTypeAllocSize(ConvertedShadow2->getType());
1462+
CallBase *CB = IRB.CreateCall(
1463+
Fn,
1464+
{ShadowAlloca, ConstantInt::get(IRB.getInt64Ty(), ShadowSize),
1465+
MS.TrackOrigins && Origin ? Origin : (Value *)IRB.getInt32(0)});
1466+
CB->addParamAttr(1, Attribute::ZExt);
1467+
CB->addParamAttr(2, Attribute::ZExt);
1468+
}
14471469
} else {
14481470
Value *Cmp = convertToBool(ConvertedShadow, IRB, "_mscmp");
14491471
Instruction *CheckTerm = SplitBlockAndInsertIfThen(

0 commit comments

Comments
 (0)