[Pkg-clamav-commits] [SCM] Debian repository for ClamAV branch, debian/unstable, updated. debian/0.95+dfsg-1-6156-g094ec9b
Török Edvin
edwin at clamav.net
Sun Apr 4 01:24:50 UTC 2010
The following commit has been merged in the debian/unstable branch:
commit 99536a178c598983a4e86c02ca5e7c4da18fc9c3
Author: Török Edvin <edwin at clamav.net>
Date: Tue Mar 23 15:54:41 2010 +0200
Insert timeout checks directly into the JITed code.
pthread_cancel is broken on Mac OS X (it only works if the thread
you want to kill calls pthread_testcancel, which is never the situation
when you need async cancelation).
Anyway async cancelation is risky, it may leave bc_ctx in an inconsistent state.
So rather than doing using pthread_cancel (or pthread_kill+siglongjmp)
just insert the timeout checks into the JITed code directly.
These are inserted in each loop, if the loop's tripcount is unknown, or
higher than a threshold. They are also inserted after a certain amount
of APIcalls are made (even in absence of loops).
Note that 'loop' in this sense is not LLVM's notion of a natural loop,
it is simply a BB which is reachable both directly and via a backedge.
For example this doesn't contain natural loops but contains backedges (and a
potential infinite loop):
int foo(int a)
{
int x=4;
if (a == 42)
goto head2;
head:
x++;
head2:
if (a >= 2) {
x += 3;
goto head;
} else if (a >= 0) {
x += 9;
goto head;
}
return x;
}
diff --git a/libclamav/bytecode_priv.h b/libclamav/bytecode_priv.h
index c755b08..eca7660 100644
--- a/libclamav/bytecode_priv.h
+++ b/libclamav/bytecode_priv.h
@@ -133,6 +133,7 @@ struct bc_inflate {
};
struct cli_bc_ctx {
+ uint8_t timeout;/* must be first byte in struct! */
/* id and params of toplevel function called */
const struct cli_bc *bc;
const struct cli_bc_func *func;
diff --git a/libclamav/c++/bytecode2llvm.cpp b/libclamav/c++/bytecode2llvm.cpp
index 238720e..3991a03 100644
--- a/libclamav/c++/bytecode2llvm.cpp
+++ b/libclamav/c++/bytecode2llvm.cpp
@@ -27,9 +27,15 @@
#include "ClamBCModule.h"
#include "llvm/ADT/DenseMap.h"
#include "llvm/ADT/BitVector.h"
+#include "llvm/ADT/PostOrderIterator.h"
#include "llvm/ADT/StringMap.h"
#include "llvm/ADT/StringSwitch.h"
#include "llvm/ADT/Triple.h"
+#include "llvm/ADT/SmallSet.h"
+#include "llvm/ADT/SmallVector.h"
+#include "llvm/Analysis/LoopInfo.h"
+#include "llvm/Analysis/ScalarEvolution.h"
+#include "llvm/Analysis/Verifier.h"
#include "llvm/CallingConv.h"
#include "llvm/DerivedTypes.h"
#include "llvm/Function.h"
@@ -37,6 +43,7 @@
#include "llvm/ExecutionEngine/JIT.h"
#include "llvm/ExecutionEngine/JITEventListener.h"
#include "llvm/LLVMContext.h"
+#include "llvm/Intrinsics.h"
#include "llvm/Module.h"
#include "llvm/PassManager.h"
#include "llvm/Support/Compiler.h"
@@ -58,8 +65,8 @@
#include "llvm/Target/TargetData.h"
#include "llvm/Target/TargetOptions.h"
#include "llvm/Support/TargetFolder.h"
-#include "llvm/Analysis/Verifier.h"
#include "llvm/Transforms/Scalar.h"
+#include "llvm/Transforms/Utils/BasicBlockUtils.h"
#include "llvm/System/ThreadLocal.h"
#include <cstdlib>
#include <csetjmp>
@@ -329,6 +336,187 @@ struct CommonFunctions {
Function *FBSwap64;
};
+// loops with tripcounts higher than this need timeout check
+static const unsigned LoopThreshold = 1000;
+
+// after every N API calls we need timeout check
+static const unsigned ApiThreshold = 100;
+
+class RuntimeLimits : public FunctionPass {
+ typedef SmallVector<std::pair<const BasicBlock*, const BasicBlock*>, 16>
+ BBPairVectorTy;
+ typedef SmallSet<BasicBlock*, 16> BBSetTy;
+ typedef DenseMap<const BasicBlock*, unsigned> BBMapTy;
+ bool loopNeedsTimeoutCheck(ScalarEvolution &SE, const Loop *L, BBMapTy &Map) {
+ // This BB is a loop header, if trip count is small enough
+ // no timeout checks are needed here.
+ const SCEV *S = SE.getMaxBackedgeTakenCount(L);
+ if (isa<SCEVCouldNotCompute>(S))
+ return true;
+ DEBUG(errs() << "Found loop trip count" << *S << "\n");
+ ConstantRange CR = SE.getUnsignedRange(S);
+ uint64_t max = CR.getUnsignedMax().getLimitedValue();
+ DEBUG(errs() << "Found max trip count " << max << "\n");
+ if (max > LoopThreshold)
+ return true;
+ unsigned apicalls = 0;
+ for (Loop::block_iterator J=L->block_begin(),JE=L->block_end();
+ J != JE; ++J) {
+ apicalls += Map[*J];
+ }
+ apicalls *= max;
+ if (apicalls > ApiThreshold) {
+ DEBUG(errs() << "apicall threshold exceeded: " << apicalls << "\n");
+ return true;
+ }
+ Map[L->getHeader()] = apicalls;
+ return false;
+ }
+
+public:
+ static char ID;
+ RuntimeLimits() : FunctionPass(&ID) {}
+
+
+ virtual bool runOnFunction(Function &F) {
+ bool Changed = false;
+ BBSetTy BackedgeTargets;
+ if (!F.isDeclaration()) {
+ // Get the common backedge targets.
+ // Note that we don't rely on LoopInfo here, since
+ // it is possible to construct a CFG that doesn't have natural loops,
+ // yet it does have backedges, and thus can lead to unbounded/high
+ // execution time.
+ BBPairVectorTy V;
+ FindFunctionBackedges(F, V);
+ for (BBPairVectorTy::iterator I=V.begin(),E=V.end();I != E; ++I) {
+ BackedgeTargets.insert(const_cast<BasicBlock*>(I->second));
+ }
+ }
+ BBSetTy needsTimeoutCheck;
+ BBMapTy BBMap;
+ DominatorTree &DT = getAnalysis<DominatorTree>();
+ for (Function::iterator I=F.begin(),E=F.end(); I != E; ++I) {
+ BasicBlock *BB = &*I;
+ unsigned apicalls = 0;
+ for (BasicBlock::const_iterator J=BB->begin(),JE=BB->end();
+ J != JE; ++J) {
+ if (const CallInst *CI = dyn_cast<CallInst>(J)) {
+ Function *F = CI->getCalledFunction();
+ if (!F || F->isDeclaration())
+ apicalls++;
+ }
+ }
+ if (apicalls > ApiThreshold) {
+ DEBUG(errs() << "apicall threshold exceeded: " << apicalls << "\n");
+ needsTimeoutCheck.insert(BB);
+ apicalls = 0;
+ }
+ BBMap[BB] = apicalls;
+ }
+ if (!BackedgeTargets.empty()) {
+ LoopInfo &LI = getAnalysis<LoopInfo>();
+ ScalarEvolution &SE = getAnalysis<ScalarEvolution>();
+
+ // Now check whether any of these backedge targets are part of a loop
+ // with a small constant trip count
+ for (BBSetTy::iterator I=BackedgeTargets.begin(),E=BackedgeTargets.end();
+ I != E; ++I) {
+ const Loop *L = LI.getLoopFor(*I);
+ if (L && L->getHeader() == *I &&
+ !loopNeedsTimeoutCheck(SE, L, BBMap))
+ continue;
+ needsTimeoutCheck.insert(*I);
+ BBMap[*I] = 0;
+ }
+ }
+ // Estimate number of apicalls by walking dominator-tree bottom-up.
+ // BBs that have timeout checks are considered to have 0 APIcalls
+ // (since we already checked for timeout).
+ for (po_iterator<DomTreeNode*> I = po_begin(DT.getRootNode()),
+ E = po_end(DT.getRootNode()); I != E; ++I) {
+ if (needsTimeoutCheck.count(I->getBlock()))
+ continue;
+ unsigned apicalls = BBMap[I->getBlock()];
+ for (DomTreeNode::iterator J=I->begin(),JE=I->end();
+ J != JE; ++J) {
+ apicalls += BBMap[(*J)->getBlock()];
+ }
+ if (apicalls > ApiThreshold) {
+ needsTimeoutCheck.insert(I->getBlock());
+ apicalls = 0;
+ }
+ BBMap[I->getBlock()] = apicalls;
+ }
+ if (needsTimeoutCheck.empty())
+ return false;
+ DEBUG(errs() << "needs timeoutcheck:\n");
+ std::vector<const Type*>args;
+ FunctionType* abrtTy = FunctionType::get(
+ Type::getVoidTy(F.getContext()),args,false);
+ Constant *func_abort =
+ F.getParent()->getOrInsertFunction("abort", abrtTy);
+ BasicBlock *AbrtBB = BasicBlock::Create(F.getContext(), "", &F);
+ CallInst* AbrtC = CallInst::Create(func_abort, "", AbrtBB);
+ AbrtC->setCallingConv(CallingConv::C);
+ AbrtC->setTailCall(true);
+ AbrtC->setDoesNotReturn(true);
+ AbrtC->setDoesNotThrow(true);
+ new UnreachableInst(F.getContext(), AbrtBB);
+ IRBuilder<false> Builder(F.getContext());
+ Function *LSBarrier = Intrinsic::getDeclaration(F.getParent(),
+ Intrinsic::memory_barrier);
+ Value *Flag = F.arg_begin();
+ Value *MBArgs[] = {
+ ConstantInt::getFalse(F.getContext()),
+ ConstantInt::getFalse(F.getContext()),
+ ConstantInt::getTrue(F.getContext()),
+ ConstantInt::getFalse(F.getContext()),
+ ConstantInt::getFalse(F.getContext())
+ };
+ verifyFunction(F);
+ BasicBlock *BB = &F.getEntryBlock();
+ Builder.SetInsertPoint(BB, BB->getTerminator());
+ Flag = Builder.CreatePointerCast(Flag, PointerType::getUnqual(
+ Type::getInt1Ty(F.getContext())));
+ for (BBSetTy::iterator I=needsTimeoutCheck.begin(),
+ E=needsTimeoutCheck.end(); I != E; ++I) {
+ BasicBlock *BB = *I;
+ Builder.SetInsertPoint(BB, BB->getTerminator());
+ // store-load barrier: will be a no-op on x86 but not other arches
+ Builder.CreateCall(LSBarrier, MBArgs, MBArgs+5);
+ // Load Flag that tells us we timed out (first byte in bc_ctx)
+ Value *Cond = Builder.CreateLoad(Flag, true);
+ BasicBlock *newBB = SplitBlock(BB, BB->getTerminator(), this);
+ TerminatorInst *TI = BB->getTerminator();
+ BranchInst::Create(AbrtBB, newBB, Cond, TI);
+ TI->eraseFromParent();
+ BB->dump();
+ // Update dominator info
+ DomTreeNode *N = DT.getNode(AbrtBB);
+ if (!N) {
+ DT.addNewBlock(AbrtBB, BB);
+ } else {
+ BasicBlock *DomBB = DT.findNearestCommonDominator(BB,
+ N->getIDom()->getBlock());
+ DT.changeImmediateDominator(AbrtBB, DomBB);
+ }
+ DEBUG(errs() << *I << "\n");
+ }
+ F.dump();
+ verifyFunction(F);
+ return true;
+ }
+
+ virtual void getAnalysisUsage(AnalysisUsage &AU) const {
+ AU.setPreservesAll();
+ AU.addRequired<LoopInfo>();
+ AU.addRequired<ScalarEvolution>();
+ AU.addRequired<DominatorTree>();
+ }
+};
+char RuntimeLimits::ID;
+
class VISIBILITY_HIDDEN LLVMCodegen {
private:
const struct cli_bc *bc;
@@ -687,6 +875,7 @@ public:
const Type *I32Ty = Type::getInt32Ty(Context);
if (!bc->trusted)
PM.add(createClamBCRTChecks());
+ PM.add(new RuntimeLimits());
for (unsigned j=0;j<bc->num_func;j++) {
PrettyStackTraceString CrashInfo("Generate LLVM IR");
const struct cli_bc_func *func = &bc->funcs[j];
@@ -1373,6 +1562,7 @@ int cli_vm_execute_jit(const struct cli_all_bc *bcs, struct cli_bc_ctx *ctx,
code, ctx, 0,
PTHREAD_MUTEX_INITIALIZER, PTHREAD_COND_INITIALIZER
};
+ ctx->timeout = 0;
gettimeofday(&tv0, NULL);
pthread_mutex_lock(&bcthr.mutex);
ret = pthread_create(&thread, NULL, bytecode_thread, &bcthr);
@@ -1393,12 +1583,15 @@ int cli_vm_execute_jit(const struct cli_all_bc *bcs, struct cli_bc_ctx *ctx,
if (ret == ETIMEDOUT) {
errs() << "Bytecode run timed out, canceling thread\n";
timedout = 1;
+ ctx->timeout = 1;
+#if 0
ret = pthread_cancel(thread);
if (ret) {
errs() << "Bytecode: failed to create new thread!";
errs() << cli_strerror(ret, buf, sizeof(buf));
errs() << "\n";
}
+#endif
}
ret = pthread_join(thread, &threadret);
if (ret) {
--
Debian repository for ClamAV
More information about the Pkg-clamav-commits
mailing list