Details
Diff Detail
- Repository
- rG LLVM Github Monorepo
Event Timeline
I built Android with this (+ the stack safety change), and after fixing a bug I found it booted and works fine.
Could you investigate how common multi-region lifetimes are? I.e. how much worse is use-after-scope detection in hwasan compared to asan. You can just dump variable and function names and overall numbers into llvm::errs() and compile something big.
I'm sure I've seen allocas getting merged in IR after inlining, but I can not reproduce it now. I see a comment in lib/Transforms/IPO/Inliner.cpp about it being removed. Maybe we do not need to worry about it.
llvm/include/llvm/Transforms/Instrumentation/AddressSanitizerCommon.h | ||
---|---|---|
52 | tagLifetimeEnd is not descriptive enough for namespace llvm. M/b something like ForAllReachableExits? Start and End can be just Instruction *, and RetVec should be const. Instead of erasing End I'd rather return a bool that's true if any callbacks were invoked on any of the RetVec, and caller can remove lifetime.end then. This refactoring is better done in a separate change. | |
llvm/lib/Transforms/Instrumentation/HWAddressSanitizer.cpp | ||
125 | Probably should be on by default. | |
365 | DetectUseAfterScope | |
401–402 | why? | |
479 | Please support this in the new pass manager, too. |
formatting
llvm/include/llvm/Transforms/Instrumentation/AddressSanitizerCommon.h | ||
---|---|---|
52 |
Confirming that you mean I should split this change and pull this helper function out before? | |
llvm/lib/Transforms/Instrumentation/HWAddressSanitizer.cpp | ||
125 | Wouldn't it be prudent to leave it behind a disabled flag until we get some more experience, and then make it opt-out? | |
401–402 | I uploaded a new version of the stack safety change (where I had accidentally introduced this), and then forgot to upload the rebased change. |
compiler-rt/test/hwasan/TestCases/stack-uas.c | ||
---|---|---|
11 | It looks all these test ported version of asan so in D105201 we can see only change you need to make for HWASAN | |
llvm/lib/Transforms/Instrumentation/HWAddressSanitizer.cpp | ||
421 | do we save anything with this if(shouldDetectUseAfterScope)? | |
495 | same here, if it's cached why do we need to avoid forwarding them even if we don't use them later | |
1286–1292 | only if you wish to that later, but it would be nice to refactor HWAddressSanitizer into module and function classes | |
1299–1300 | Range-based looks better to me for (auto &KV : AllocasToInstrument) { auto *AI = KV.first; Value *Tag = getAllocaTag(IRB, StackTag, AI, N++); | |
1350 | can we avoid removing them? it can break other optimizations. | |
1499 | for (auto &KV : AllocasToInstrument) { AllocaInst *AI = KV.first; |
compiler-rt/test/hwasan/TestCases/stack-uas.c | ||
---|---|---|
11 | Actually compiler-rt should not be in this patch. Ideally two additional patches
D105201 changes llvm/lib/Transforms/Instrumentation/, so we need tests in llvm/test/Instrumentation/, similar to AddressSanitizer/lifetime.ll if you move AArch64StackTagging.cpp into a separate refactoring NFC patch, it does not need new tests |
llvm/lib/Transforms/Instrumentation/HWAddressSanitizer.cpp | ||
---|---|---|
1350 | We can't. Tagging before/after lifetime is UB - consider that this memory may be reused for another alloca with different tag value. |
address comment
llvm/include/llvm/Transforms/Instrumentation/AddressSanitizerCommon.h | ||
---|---|---|
52 | Done. The instructions (except Start) cannot be const because they will eventually end up in the IRBuilder constructor (which does not take a const). |
compiler-rt/test/hwasan/TestCases/stack-uas.c | ||
---|---|---|
11 | Ad 2. I will let eugenis comment on that, but to me it seems a lot of the HWAsan feature flags are passed via -mllvm without any intention to make them first-class clang options. Especially given we want to make this the default soon (see the other comment thread between eugenis and me). |
compiler-rt/test/hwasan/TestCases/stack-uas.c | ||
---|---|---|
11 | Let's look at performance and code size impact first, but I'd like that to just be on all the time, and the -mllvm option can be used to opt out if something goes wrong. Note that memtag does not have a frontend flag for use-after-scope, either. |
compiler-rt/test/hwasan/TestCases/stack-uas.c | ||
---|---|---|
11 | So is it okay to leave as is? I'll add an LLVM IR level test as well to this change. |
compiler-rt/test/hwasan/TestCases/stack-uas.c | ||
---|---|---|
11 | Yes. I'd rather not introduce a clang flag unless we know that there will be users who would want it both ways. We can not easily deprecate clang flags. |
compiler-rt/test/hwasan/TestCases/use-after-scope-types.cpp | ||
---|---|---|
24 | i'll sort out the autoformatting diff. |
compiler-rt/test/hwasan/TestCases/stack-uas.c | ||
---|---|---|
11 |
OK
OK | |
compiler-rt/test/hwasan/TestCases/use-after-scope-types.cpp | ||
24 | That's would be great after D106159 and before this one | |
llvm/lib/Transforms/Instrumentation/HWAddressSanitizer.cpp | ||
1306 | Probably not important but now it's starts with N==1 | |
1331 | I guess asan handles multiple starts/ends? | |
1343 | {} unneeded | |
1485 | why we need these empty Trees? |
fix debug info.
llvm/lib/Transforms/Instrumentation/HWAddressSanitizer.cpp | ||
---|---|---|
1306 | Actually this was broken when I changed to the range-based for, sorry about that. Fixed now. |
llvm/lib/Transforms/Instrumentation/HWAddressSanitizer.cpp | ||
---|---|---|
238 | Someone added "#ifdef GNUC" above. To much effort for this simple check. If it needs #ifdef then I'd rather remove pragma GCC poison at all. (in a separate patch) |
llvm/lib/Transforms/Instrumentation/HWAddressSanitizer.cpp | ||
---|---|---|
1485 | They aren't empty, they are constructed from the function. We need the unique_ptrs because sometimes we borrow the LocalDT from the passmanager, and sometimes we construct it ourselves, in which case we need to destruct it again. |
I assume we still wait for IR tests?
llvm/lib/Transforms/Instrumentation/HWAddressSanitizer.cpp | ||
---|---|---|
1484 | Code around mostly uses if (!LocalDT) { style | |
1485 | I think it's very unusuall, you should get if from pass manager with getAnalysis<DominatorTreeWrapperPass>() |
Yes a couple an IR test would be great. It does not need to cover the analysis logic, that is done separately, just the fact that a "safe" alloca is not tagged.
llvm/lib/Transforms/Instrumentation/HWAddressSanitizer.cpp | ||
---|---|---|
1485 | This is a copy of the code from AArch64StackTagging. The idea is to implement on-demand function analysis in the old pass manager so that, in the common case when none of the functions in a module have a sanitize_* attribute, the analysis is not run. We get the analysis from the passmanager if it is available, otherwise we run one locally and then destroy it with unique_ptr. |
This change doesn't preserve the property that Evgenii mentioned in the comment thread about this. In Aarch64StackTagging this is done in the way it was in this change before to avoid running the analysis on the whole module if not all of the functions actually need to be instrumented. In general I think it would be nice to have the AArch64StackTagging and this implementation as close as possible, that makes it easier to reason about and I am planning to refactor this a bit to factor out the common pieces down the line.
I have an IR test ready, but don't want to stomp over your change or have to rebase back and forth while we decide which way we want the DT and PDT analyses :)
Reasons not to optimize:
- AArch64StackTagging is different. AArch64StackTagging is added always for aarch64, so it can compile modules where almost all functions have no attribute. However HWAddressSanitizerLegacyPass one added only for stuff compiler with -fsanitizer=hwaddres, so almost everything will have this attribute.
- For new PM llvm::function does exactly as you like and even better, it will avoid DT for empty AllocasToInstrument, and legacy PM is deprecated and will be removed.
If you still want optimize even legacy PM:
This stuff is Pass specific, but HWAddressSanitizer class is not a pass, it's a tool used by Pass, so it's more reasonable to have this optimization inside of HWAddressSanitizerLegacyPass.
E.g.:
bool runOnFunction(Function &F) override { auto TargetTriple = Triple(F.getParent()->getTargetTriple()); if (shouldUseStackSafetyAnalysis(TargetTriple, DisableOptimization)) { // We cannot call getAnalysis in doInitialization, that would cause a // crash as the required analyses are not initialized yet. HWASan->setSSI( &getAnalysis<StackSafetyGlobalInfoWrapperPass>().getResult()); } std::unique_ptr<DominatorTree> DeleteDT; std::unique_ptr<PostDominatorTree> DeletePDT; return HWASan->sanitizeFunction( F, [&]() -> const DominatorTree & { if (auto *P = getAnalysisIfAvailable<DominatorTreeWrapperPass>()) return P->getDomTree(); if (!DeleteDT) DeleteDT = std::make_unique<DominatorTree>(F); return *DeleteDT; }, [&]() -> const PostDominatorTree & { if (auto *P = getAnalysisIfAvailable<PostDominatorTreeWrapperPass>()) return P->getPostDomTree(); if (!DeletePDT) DeletePDT = std::make_unique<PostDominatorTree>(F); return *DeletePDT; }); } # AU.addRequired<>() -> AU.addUsedIfAvailable<>(); # And you need to keep INITIALIZE_PASS_DEPENDENCY(DominatorTreeWrapperPass) INITIALIZE_PASS_DEPENDENCY(PostDominatorTreeWrapperPass) either way
True. I don't really like this setup, it's not very compatible with LTO, but that's what we have and changing it is low priority.
I'm fine either way.
LGTM if you improve the test
llvm/test/Instrumentation/HWAddressSanitizer/use-after-scope.ll | ||
---|---|---|
9 ↗ | (On Diff #361950) | this tests StandardLifetime && forAllReachableExits also depending on conditions above we may keep or remove lifetime markers. to my taste the following produces good enough result, but I don't ask to switch to auto-generated tests. It's your call. ; NOTE: Assertions have been autogenerated by utils/update_test_checks.py ; RUN: opt -hwasan -hwasan-use-after-scope=1 -hwasan-generate-tags-with-calls -S < %s | FileCheck %s --check-prefixes=SCOPE ; RUN: opt -hwasan -hwasan-use-after-scope=0 -hwasan-generate-tags-with-calls -S < %s | FileCheck %s --check-prefixes=NOSCOPE ; ModuleID = 'use-after-scope.c' source_filename = "use-after-scope.c" target datalayout = "e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-f80:128-n8:16:32:64-S128" target triple = "x86_64-unknown-linux-gnu" define dso_local i32 @main() local_unnamed_addr sanitize_hwaddress { ; SCOPE-LABEL: @main( ; SCOPE-NEXT: [[DOTHWASAN_SHADOW:%.*]] = call i8* asm "", "=r,0"(i8* null) ; SCOPE-NEXT: [[TMP1:%.*]] = alloca { i8, [15 x i8] }, align 16 ; SCOPE-NEXT: [[TMP2:%.*]] = bitcast { i8, [15 x i8] }* [[TMP1]] to i8* ; SCOPE-NEXT: [[TMP3:%.*]] = call i8 @__hwasan_generate_tag() ; SCOPE-NEXT: [[TMP4:%.*]] = zext i8 [[TMP3]] to i64 ; SCOPE-NEXT: [[TMP5:%.*]] = ptrtoint i8* [[TMP2]] to i64 ; SCOPE-NEXT: [[TMP6:%.*]] = shl i64 [[TMP4]], 57 ; SCOPE-NEXT: [[TMP7:%.*]] = or i64 [[TMP5]], [[TMP6]] ; SCOPE-NEXT: [[ALLOCA_0_HWASAN:%.*]] = inttoptr i64 [[TMP7]] to i8* ; SCOPE-NEXT: br label [[TMP8:%.*]] ; SCOPE: 8: ; SCOPE-NEXT: call void @llvm.lifetime.start.p0i8(i64 1, i8* nonnull [[ALLOCA_0_HWASAN]]) ; SCOPE-NEXT: [[TMP9:%.*]] = trunc i64 [[TMP4]] to i8 ; SCOPE-NEXT: call void @__hwasan_tag_memory(i8* [[TMP2]], i8 [[TMP9]], i64 16) ; SCOPE-NEXT: [[TMP10:%.*]] = tail call i32 (...) @cond() ; SCOPE-NEXT: [[TMP11:%.*]] = icmp eq i32 [[TMP10]], 0 ; SCOPE-NEXT: call void @__hwasan_tag_memory(i8* [[TMP2]], i8 0, i64 16) ; SCOPE-NEXT: call void @llvm.lifetime.end.p0i8(i64 1, i8* nonnull [[ALLOCA_0_HWASAN]]) ; SCOPE-NEXT: br i1 [[TMP11]], label [[TMP12:%.*]], label [[TMP8]] ; SCOPE: 12: ; SCOPE-NEXT: call void @use(i8* nonnull [[ALLOCA_0_HWASAN]]) ; SCOPE-NEXT: ret i32 0 ; ; NOSCOPE-LABEL: @main( ; NOSCOPE-NEXT: [[DOTHWASAN_SHADOW:%.*]] = call i8* asm "", "=r,0"(i8* null) ; NOSCOPE-NEXT: [[TMP1:%.*]] = alloca { i8, [15 x i8] }, align 16 ; NOSCOPE-NEXT: [[TMP2:%.*]] = bitcast { i8, [15 x i8] }* [[TMP1]] to i8* ; NOSCOPE-NEXT: [[TMP3:%.*]] = call i8 @__hwasan_generate_tag() ; NOSCOPE-NEXT: [[TMP4:%.*]] = zext i8 [[TMP3]] to i64 ; NOSCOPE-NEXT: [[TMP5:%.*]] = ptrtoint i8* [[TMP2]] to i64 ; NOSCOPE-NEXT: [[TMP6:%.*]] = shl i64 [[TMP4]], 57 ; NOSCOPE-NEXT: [[TMP7:%.*]] = or i64 [[TMP5]], [[TMP6]] ; NOSCOPE-NEXT: [[ALLOCA_0_HWASAN:%.*]] = inttoptr i64 [[TMP7]] to i8* ; NOSCOPE-NEXT: [[TMP8:%.*]] = trunc i64 [[TMP4]] to i8 ; NOSCOPE-NEXT: call void @__hwasan_tag_memory(i8* [[TMP2]], i8 [[TMP8]], i64 16) ; NOSCOPE-NEXT: br label [[TMP9:%.*]] ; NOSCOPE: 9: ; NOSCOPE-NEXT: call void @llvm.lifetime.start.p0i8(i64 1, i8* nonnull [[ALLOCA_0_HWASAN]]) ; NOSCOPE-NEXT: [[TMP10:%.*]] = tail call i32 (...) @cond() ; NOSCOPE-NEXT: [[TMP11:%.*]] = icmp eq i32 [[TMP10]], 0 ; NOSCOPE-NEXT: call void @llvm.lifetime.end.p0i8(i64 1, i8* nonnull [[ALLOCA_0_HWASAN]]) ; NOSCOPE-NEXT: br i1 [[TMP11]], label [[TMP12:%.*]], label [[TMP9]] ; NOSCOPE: 12: ; NOSCOPE-NEXT: call void @use(i8* nonnull [[ALLOCA_0_HWASAN]]) ; NOSCOPE-NEXT: call void @__hwasan_tag_memory(i8* [[TMP2]], i8 0, i64 16) ; NOSCOPE-NEXT: ret i32 0 ; %1 = alloca i8, align 1 br label %2 2: ; preds = %2, %0 ; We should tag the memory after the br (in the loop). call void @llvm.lifetime.start.p0i8(i64 1, i8* nonnull %1) %3 = tail call i32 (...) @cond() #2 %4 = icmp eq i32 %3, 0 ; We should tag the memory before the next br (before the jump back). call void @llvm.lifetime.end.p0i8(i64 1, i8* nonnull %1) br i1 %4, label %5, label %2 5: ; preds = %2 call void @use(i8* nonnull %1) #2 ret i32 0 } declare dso_local i32 @cond(...) local_unnamed_addr declare dso_local void @use(i8*) local_unnamed_addr ; Function Attrs: argmemonly mustprogress nofree nosync nounwind willreturn declare void @llvm.lifetime.start.p0i8(i64 immarg, i8* nocapture) ; Function Attrs: argmemonly mustprogress nofree nosync nounwind willreturn declare void @llvm.lifetime.end.p0i8(i64 immarg, i8* nocapture) |
44 ↗ | (On Diff #361950) | please add new line |
llvm/lib/Transforms/Instrumentation/HWAddressSanitizer.cpp | ||
---|---|---|
1331 | this is based on the MTE stack tagging code. |
Relanding without review as the only change is a minor test only change (adding REQUIRES: stable-runtime as with other tests in the directory).
It looks all these test ported version of asan
it would be nice if in a separate patch you commit them as exact copy (with XFAIL: *)
so in D105201 we can see only change you need to make for HWASAN