[llvm-commits] Fwd: Re: [LLVMdev] [PATCH] Fix for bug in JIT exception table allocation

Michael Muller mmuller at enduden.com
Wed Sep 12 10:16:39 PDT 2012


Forwarding this to the correct mailing list (please reply to me directly, I'm
not a subscriber).

---------- Forwarded Message ----------
from: "Michael Muller" <mmuller at enduden.com>
to: Eric Christopher <echristo at apple.com>
cc: LLVM Developers Mail <llvmdev at cs.uiuc.edu>
date: Mon, 10 Sep 2012 15:49:27 -0400
subject: Re: [LLVMdev] [PATCH] Fix for bug in JIT exception table	allocation	(no test yet)


Hi all, I've attached a patch containing my initial bug fix and the unit test
to verify it.  Can someone please review it?

Michael Muller wrote:
> 
> Eric Christopher wrote:
> > 
> > On Aug 21, 2012, at 2:12 PM, Michael Muller <mmuller at enduden.com> wrote:
> > 
> > > 
> > > Hi, I found a bug in the code that generates exception tables, I've attached
> > > what I think is the correct fix.
> > > 
> > > When you run out of space writing to a buffer, the buffer management code
> > > simply stops writing at the end of the buffer.  It is the responsibility of
> > > the caller to verify that it has stayed in bounds and perform a retry with
> > > a larger memory estimate if not.  The function writing code does this, but
> > > the exception table code following it does not.  The end result is that
> > > exception table pointers can get registered pointing to invalid data, causing
> > > seg-faults when an exception is thrown.
> > > 
> > > I haven't implemented a test case that reproduces the problem, but I will do
> > > so.  (I've verified the problem and the fix in the scope of a much larger
> > > system) I'm open to suggestions as to how best to test it, I'm currently
> > > thinking of trying to create a highly contrived situation to force exception
> > > tables to be written at the end of a buffer that won't be long enough.
> > 
> > I'm actually somewhat curious at this point why it doesn't emit the tables before
> > deciding it's done with the function. That'd make it possible to move all of
> > the eh table code earlier in the method and use retryWith... instead of the loop.
> 
> It looks like it might be using a different slab for the function, though I'm
> not quite sure why.  I'll investigate further when I get back to it.  Making
> it part of the function's "transaction" seemed cleaner to me as well.

The memory manager interface defines a separate set of methods for dealing
with exception table memory - presumably this is to allow for architectures
where exception tables can't be in the same pages as the functions.

> 
> > 
> > -eric
> > 
> 
> 
> =============================================================================
> michaelMuller = mmuller at enduden.com | http://www.mindhog.net/~mmuller
> -----------------------------------------------------------------------------
> The people can always be brought to the bidding of the leaders. That is easy.
> All you have to do is tell them they are being attacked and denounce the
> pacifists for lack of patriotism and exposing the country to danger. It works
> the same way in any country. - Hermann Goering (Hitler's right-hand man)
> =============================================================================
> _______________________________________________
> LLVM Developers mailing list
> LLVMdev at cs.uiuc.edu         http://llvm.cs.uiuc.edu
> http://lists.cs.uiuc.edu/mailman/listinfo/llvmdev
> 


=============================================================================
michaelMuller = mmuller at enduden.com | http://www.mindhog.net/~mmuller
-----------------------------------------------------------------------------
you and I are only different in our minds, the universe makes no such
distinction
=============================================================================
-------------- next part --------------

Hi all, I've attached a patch containing my initial bug fix and the unit test
to verify it.  Can someone please review it?

Michael Muller wrote:
> 
> Eric Christopher wrote:
> > 
> > On Aug 21, 2012, at 2:12 PM, Michael Muller <mmuller at enduden.com> wrote:
> > 
> > > 
> > > Hi, I found a bug in the code that generates exception tables, I've attached
> > > what I think is the correct fix.
> > > 
> > > When you run out of space writing to a buffer, the buffer management code
> > > simply stops writing at the end of the buffer.  It is the responsibility of
> > > the caller to verify that it has stayed in bounds and perform a retry with
> > > a larger memory estimate if not.  The function writing code does this, but
> > > the exception table code following it does not.  The end result is that
> > > exception table pointers can get registered pointing to invalid data, causing
> > > seg-faults when an exception is thrown.
> > > 
> > > I haven't implemented a test case that reproduces the problem, but I will do
> > > so.  (I've verified the problem and the fix in the scope of a much larger
> > > system) I'm open to suggestions as to how best to test it, I'm currently
> > > thinking of trying to create a highly contrived situation to force exception
> > > tables to be written at the end of a buffer that won't be long enough.
> > 
> > I'm actually somewhat curious at this point why it doesn't emit the tables before
> > deciding it's done with the function. That'd make it possible to move all of
> > the eh table code earlier in the method and use retryWith... instead of the loop.
> 
> It looks like it might be using a different slab for the function, though I'm
> not quite sure why.  I'll investigate further when I get back to it.  Making
> it part of the function's "transaction" seemed cleaner to me as well.

The memory manager interface defines a separate set of methods for dealing
with exception table memory - presumably this is to allow for architectures
where exception tables can't be in the same pages as the functions.

> 
> > 
> > -eric
> > 
> 
> 
> =============================================================================
> michaelMuller = mmuller at enduden.com | http://www.mindhog.net/~mmuller
> -----------------------------------------------------------------------------
> The people can always be brought to the bidding of the leaders. That is easy.
> All you have to do is tell them they are being attacked and denounce the
> pacifists for lack of patriotism and exposing the country to danger. It works
> the same way in any country. - Hermann Goering (Hitler's right-hand man)
> =============================================================================
> _______________________________________________
> LLVM Developers mailing list
> LLVMdev at cs.uiuc.edu         http://llvm.cs.uiuc.edu
> http://lists.cs.uiuc.edu/mailman/listinfo/llvmdev
> 


=============================================================================
michaelMuller = mmuller at enduden.com | http://www.mindhog.net/~mmuller
-----------------------------------------------------------------------------
you and I are only different in our minds, the universe makes no such
distinction
=============================================================================
-------------- next part --------------
Index: unittests/ExecutionEngine/JIT/JITTest.cpp
===================================================================
--- unittests/ExecutionEngine/JIT/JITTest.cpp	(revision 163535)
+++ unittests/ExecutionEngine/JIT/JITTest.cpp	(working copy)
@@ -203,14 +203,21 @@
 
 class JITTest : public testing::Test {
  protected:
+  virtual RecordingJITMemoryManager* CreateMemoryManager() {
+    return new RecordingJITMemoryManager;
+  }
+
   virtual void SetUp() {
     M = new Module("<main>", Context);
-    RJMM = new RecordingJITMemoryManager;
+    RJMM = CreateMemoryManager();
     RJMM->setPoisonMemory(true);
     std::string Error;
+    TargetOptions Options;
+    Options.JITExceptionHandling = true;
     TheJIT.reset(EngineBuilder(M).setEngineKind(EngineKind::JIT)
                  .setJITMemoryManager(RJMM)
-                 .setErrorStr(&Error).create());
+                 .setErrorStr(&Error)
+                 .setTargetOptions(Options).create());
     ASSERT_TRUE(TheJIT.get() != NULL) << Error;
   }
 
@@ -292,6 +299,46 @@
   EXPECT_EQ(3, *GPtr);
 }
 
+// Regression test for a bug.  The JITEmitter wasn't checking to verify that
+// it hadn't run out of space while generating the DWARF exception information
+// for an emitted function.
+
+class ExceptionMemoryManagerMock : public RecordingJITMemoryManager {
+ public:
+  virtual uint8_t* startExceptionTable(const Function* F,
+                                       uintptr_t &ActualSize) {
+    // force an insufficient size the first time through.
+    bool ChangeActualSize = false;
+    if (ActualSize == 0)
+      ChangeActualSize = true;;
+    uint8_t* result =
+      RecordingJITMemoryManager::startExceptionTable(F, ActualSize);
+    if (ChangeActualSize)
+      ActualSize = 1;
+    return result;
+  }
+};
+
+class JITExceptionMemoryTest : public JITTest {
+ protected:
+  virtual RecordingJITMemoryManager* CreateMemoryManager() {
+    return new ExceptionMemoryManagerMock;
+  }
+};
+
+TEST_F(JITExceptionMemoryTest, ExceptionTableOverflow) {
+  Function *F = Function::Create(TypeBuilder<void(void), false>::get(Context),
+                                 Function::ExternalLinkage,
+                                 "func1", M);
+  BasicBlock *Block = BasicBlock::Create(Context, "block", F);
+  IRBuilder<> Builder(Block);
+  Builder.CreateRetVoid();
+  TheJIT->getPointerToFunction(F);
+  ASSERT_TRUE(RJMM->startExceptionTableCalls.size() == 2);
+  ASSERT_TRUE(RJMM->deallocateExceptionTableCalls.size() == 1);
+  ASSERT_TRUE(RJMM->endExceptionTableCalls.size() == 1);
+}
+
 int PlusOne(int arg) {
   return arg + 1;
 }
Index: lib/ExecutionEngine/JIT/JITEmitter.cpp
===================================================================
--- lib/ExecutionEngine/JIT/JITEmitter.cpp	(revision 163478)
+++ lib/ExecutionEngine/JIT/JITEmitter.cpp	(working copy)
@@ -974,14 +974,24 @@
     SavedBufferBegin = BufferBegin;
     SavedBufferEnd = BufferEnd;
     SavedCurBufferPtr = CurBufferPtr;
+    uint8_t *FrameRegister;
 
-    BufferBegin = CurBufferPtr = MemMgr->startExceptionTable(F.getFunction(),
-                                                             ActualSize);
-    BufferEnd = BufferBegin+ActualSize;
-    EmittedFunctions[F.getFunction()].ExceptionTable = BufferBegin;
-    uint8_t *EhStart;
-    uint8_t *FrameRegister = DE->EmitDwarfTable(F, *this, FnStart, FnEnd,
-                                                EhStart);
+    while (true) {
+      BufferBegin = CurBufferPtr = MemMgr->startExceptionTable(F.getFunction(),
+                                                              ActualSize);
+      BufferEnd = BufferBegin+ActualSize;
+      EmittedFunctions[F.getFunction()].ExceptionTable = BufferBegin;
+      uint8_t *EhStart;
+      FrameRegister = DE->EmitDwarfTable(F, *this, FnStart, FnEnd, EhStart);
+
+      // If we've run out of memory, try again with twice as much space.
+      if (CurBufferPtr == BufferEnd) {
+        ActualSize = (CurBufferPtr-BufferBegin)*2;
+        MemMgr->deallocateExceptionTable(BufferBegin);
+      } else {
+        break;
+      }
+    }
     MemMgr->endExceptionTable(F.getFunction(), BufferBegin, CurBufferPtr,
                               FrameRegister);
     BufferBegin = SavedBufferBegin;
-------------- next part --------------
_______________________________________________
LLVM Developers mailing list
LLVMdev at cs.uiuc.edu         http://llvm.cs.uiuc.edu
http://lists.cs.uiuc.edu/mailman/listinfo/llvmdev
-------------- next part --------------
Index: unittests/ExecutionEngine/JIT/JITTest.cpp
===================================================================
--- unittests/ExecutionEngine/JIT/JITTest.cpp	(revision 163535)
+++ unittests/ExecutionEngine/JIT/JITTest.cpp	(working copy)
@@ -203,14 +203,21 @@
 
 class JITTest : public testing::Test {
  protected:
+  virtual RecordingJITMemoryManager* CreateMemoryManager() {
+    return new RecordingJITMemoryManager;
+  }
+
   virtual void SetUp() {
     M = new Module("<main>", Context);
-    RJMM = new RecordingJITMemoryManager;
+    RJMM = CreateMemoryManager();
     RJMM->setPoisonMemory(true);
     std::string Error;
+    TargetOptions Options;
+    Options.JITExceptionHandling = true;
     TheJIT.reset(EngineBuilder(M).setEngineKind(EngineKind::JIT)
                  .setJITMemoryManager(RJMM)
-                 .setErrorStr(&Error).create());
+                 .setErrorStr(&Error)
+                 .setTargetOptions(Options).create());
     ASSERT_TRUE(TheJIT.get() != NULL) << Error;
   }
 
@@ -292,6 +299,46 @@
   EXPECT_EQ(3, *GPtr);
 }
 
+// Regression test for a bug.  The JITEmitter wasn't checking to verify that
+// it hadn't run out of space while generating the DWARF exception information
+// for an emitted function.
+
+class ExceptionMemoryManagerMock : public RecordingJITMemoryManager {
+ public:
+  virtual uint8_t* startExceptionTable(const Function* F,
+                                       uintptr_t &ActualSize) {
+    // force an insufficient size the first time through.
+    bool ChangeActualSize = false;
+    if (ActualSize == 0)
+      ChangeActualSize = true;;
+    uint8_t* result =
+      RecordingJITMemoryManager::startExceptionTable(F, ActualSize);
+    if (ChangeActualSize)
+      ActualSize = 1;
+    return result;
+  }
+};
+
+class JITExceptionMemoryTest : public JITTest {
+ protected:
+  virtual RecordingJITMemoryManager* CreateMemoryManager() {
+    return new ExceptionMemoryManagerMock;
+  }
+};
+
+TEST_F(JITExceptionMemoryTest, ExceptionTableOverflow) {
+  Function *F = Function::Create(TypeBuilder<void(void), false>::get(Context),
+                                 Function::ExternalLinkage,
+                                 "func1", M);
+  BasicBlock *Block = BasicBlock::Create(Context, "block", F);
+  IRBuilder<> Builder(Block);
+  Builder.CreateRetVoid();
+  TheJIT->getPointerToFunction(F);
+  ASSERT_TRUE(RJMM->startExceptionTableCalls.size() == 2);
+  ASSERT_TRUE(RJMM->deallocateExceptionTableCalls.size() == 1);
+  ASSERT_TRUE(RJMM->endExceptionTableCalls.size() == 1);
+}
+
 int PlusOne(int arg) {
   return arg + 1;
 }
Index: lib/ExecutionEngine/JIT/JITEmitter.cpp
===================================================================
--- lib/ExecutionEngine/JIT/JITEmitter.cpp	(revision 163478)
+++ lib/ExecutionEngine/JIT/JITEmitter.cpp	(working copy)
@@ -974,14 +974,24 @@
     SavedBufferBegin = BufferBegin;
     SavedBufferEnd = BufferEnd;
     SavedCurBufferPtr = CurBufferPtr;
+    uint8_t *FrameRegister;
 
-    BufferBegin = CurBufferPtr = MemMgr->startExceptionTable(F.getFunction(),
-                                                             ActualSize);
-    BufferEnd = BufferBegin+ActualSize;
-    EmittedFunctions[F.getFunction()].ExceptionTable = BufferBegin;
-    uint8_t *EhStart;
-    uint8_t *FrameRegister = DE->EmitDwarfTable(F, *this, FnStart, FnEnd,
-                                                EhStart);
+    while (true) {
+      BufferBegin = CurBufferPtr = MemMgr->startExceptionTable(F.getFunction(),
+                                                              ActualSize);
+      BufferEnd = BufferBegin+ActualSize;
+      EmittedFunctions[F.getFunction()].ExceptionTable = BufferBegin;
+      uint8_t *EhStart;
+      FrameRegister = DE->EmitDwarfTable(F, *this, FnStart, FnEnd, EhStart);
+
+      // If we've run out of memory, try again with twice as much space.
+      if (CurBufferPtr == BufferEnd) {
+        ActualSize = (CurBufferPtr-BufferBegin)*2;
+        MemMgr->deallocateExceptionTable(BufferBegin);
+      } else {
+        break;
+      }
+    }
     MemMgr->endExceptionTable(F.getFunction(), BufferBegin, CurBufferPtr,
                               FrameRegister);
     BufferBegin = SavedBufferBegin;


More information about the llvm-commits mailing list