The code below shows that it's possible to throw a purecall exception by calling a function from a delete object.
I suspect this is what is happening in our code. Nicolas class Derived; class Base { public: Base(Derived *derived): m_pDerived(derived) {}; ~Base() {}; // Needed, dont know why. virtual void function(void) = 0; void bleh(); Derived * m_pDerived; }; class Derived : public Base { public: Derived() : Base(this) {}; // C4355 virtual void function(void) {}; }; void Base::bleh() { m_pDerived -> function(); } void purecall(void) { __debugbreak(); } #include <windows.h> int _tmain(int argc, _TCHAR* argv[]) { _set_purecall_handler(purecall); Base* base = NULL; { Derived myDerived; myDerived.function(); base = &myDerived; } base->bleh(); } On Fri, Apr 3, 2009 at 2:17 PM, Tommi <to...@chromium.org> wrote: > purecall isn't called when an exception occurs. purecall actually throws > the exception - or exits the program (by default the crt throws up a dialog > and then abort()s). in addition to cpu's email, raymond chen's article is a > good (and short) read :) > http://blogs.msdn.com/oldnewthing/archive/2004/04/28/122037.aspx > > On Fri, Apr 3, 2009 at 3:15 PM, Huan Ren <hu...@google.com> wrote: > >> Based on what I saw in the bug, it looks like an exception happening >> during CALL instruction may lead to PureCall(). >> >> For example, an object obj has been freed and later on someone calls >> obj->func(). Then the assembly code looks like this: >> >> // ecx: pointer to obj which is in memory >> // [ecx]: supposed to be pointer to vtable, it has invalid value since >> obj is freed >> // edx: now has pointer to vtable, which is invalid >> mov edx,dword ptr [ecx] >> >> // deref the vtable and make the call >> call dword ptr [edx+4] >> >> When a (hardware) exception happens during the call instruction, the >> control will be eventually transfered to the routine handling this >> type of exception which I *think* is PureCall(). >> >> Huan >> >> On Fri, Apr 3, 2009 at 11:26 AM, Ricardo Vargas <rvar...@chromium.org> >> wrote: >> > I certainly don't want to imply that it is the case with this particular >> > bug, but I have seen crashes when the cause of the problem is using an >> > object that was previously deleted (and only end up with this exception >> when >> > all the planets are properly aligned). I guess that it depends on the >> actual >> > class hierarchy of the objects in question, but I'd think that "simple" >> > examples end up on a lot of crashes right after the cl that exposes the >> > problem. >> > >> > On Fri, Apr 3, 2009 at 12:52 AM, Dean McNamee <de...@chromium.org> >> wrote: >> >> >> >> You could, however, corrupt the vtable pointer (not the vtable). Say >> >> somehow 32 was added to it, now the table is misaligned, and you might >> >> get a purecall, etc. Not sure that's likely at all though. >> >> >> >> Since the vtable pointer is the first field, it seems ripe for >> >> problems w/ use after free, etc. I kinda doubt that's what's >> >> happening here though. Anyone who is working on one of these can bug >> >> me and I'll look at the crash dump. >> >> >> >> On Fri, Apr 3, 2009 at 7:24 AM, Tommi <to...@chromium.org> wrote: >> >> > On Thu, Apr 2, 2009 at 7:09 PM, cpu <c...@chromium.org> wrote: >> >> >> >> >> >> >> >> >> >> >> >> On Apr 2, 3:53 pm, Nicolas Sylvain <nsylv...@chromium.org> wrote: >> >> >> > Another simple(r) example >> >> >> > :http://msdn.microsoft.com/en-us/library/t296ys27(VS.80).aspx >> >> >> > >> >> >> > <http://msdn.microsoft.com/en-us/library/t296ys27(VS.80).aspx>But, >> as >> >> >> > discussed in bug 8544, we've see many purecall crashes that >> happens >> >> >> > and >> >> >> > we >> >> >> > don't >> >> >> > think it's related to virtual functions. The only thing I can >> think >> >> >> > of >> >> >> > is >> >> >> > that the vtable is corrupted. (overwritten or freed) >> >> >> > >> >> >> > Does it not make sense? >> >> >> >> >> >> I don't think you can overwrite a vtables because they should be in >> >> >> the code section of the executable (the pages marked as >> read-execute), >> >> >> they are known at compile time and it would not make sense to >> >> >> construct them on the fly. >> >> >> >> >> >> But if you know of a case then that would be very interesting. >> >> > >> >> > >> >> > yes they should be protected with read/execute and besides, you'd >> have >> >> > to >> >> > overwrite entries in the vtable with a pointer to __purecall for that >> to >> >> > happen >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> > >> >> >> > Nicolas >> >> >> > >> >> >> > >> >> >> > >> >> >> > On Thu, Apr 2, 2009 at 1:54 PM, cpu <c...@chromium.org> wrote: >> >> >> > >> >> >> > > After reading some speculation in bugs such as >> >> >> > >http://code.google.com/p/chromium/issues/detail?id=8544I felt >> >> >> > > compelled to dispel some myths and misunderstandings about the >> >> >> > > origin >> >> >> > > and meaning of the mythical _purecall_ exception. My hope is >> that >> >> >> > > then >> >> >> > > you can spot the problems in our source code and fix them. Sorry >> >> >> > > for >> >> >> > > the long post. >> >> >> > >> >> >> > > So first of all, what do you see when you get this error? if you >> >> >> > > are >> >> >> > > in a debug build and you are not eating the exceptions via some >> >> >> > > custom >> >> >> > > handler you see this dialog: >> >> >> > >> >> >> > > --------------------------- >> >> >> > > Debug Error! >> >> >> > > R6025 >> >> >> > > - pure virtual function call >> >> >> > > (Press Retry to debug the application) >> >> >> > > --------------------------- >> >> >> > > Abort Retry Ignore >> >> >> > > --------------------------- >> >> >> > >> >> >> > > For chrome/chromium we install a special handler, which forces a >> >> >> > > crash >> >> >> > > dump in which case you'll see in in the debugger analysis >> something >> >> >> > > like this: >> >> >> > >> >> >> > > [chrome_dll_main.cc:100] - `anonymous namespace'::PureCall() >> >> >> > > [purevirt.c:47] - _purecall >> >> >> > >> >> >> > > Before going into too much detail, let me show you a small >> program >> >> >> > > that causes this exception: >> >> >> > >> >> >> > > ================================= >> >> >> > > class Base { >> >> >> > > public: >> >> >> > > virtual ~Base() { >> >> >> > > ThreeFn(); >> >> >> > > } >> >> >> > >> >> >> > > virtual void OneFn() = 0; >> >> >> > > virtual void TwoFn() = 0; >> >> >> > >> >> >> > > void ThreeFn() { >> >> >> > > OneFn(); >> >> >> > > TwoFn(); >> >> >> > > } >> >> >> > > }; >> >> >> > >> >> >> > > class Concrete : public Base { >> >> >> > > public: >> >> >> > > Concrete() : state_(0) { >> >> >> > > } >> >> >> > >> >> >> > > virtual void OneFn() { >> >> >> > > state_ += 1; >> >> >> > > } >> >> >> > > virtual void TwoFn() { >> >> >> > > state_ += 2; >> >> >> > > } >> >> >> > > private: >> >> >> > > int state_; >> >> >> > > }; >> >> >> > >> >> >> > > int _tmain(int argc, _TCHAR* argv[]) { >> >> >> > >> >> >> > > Concrete* obj = new Concrete(); >> >> >> > > obj->OneFn(); >> >> >> > > obj->TwoFn(); >> >> >> > > obj->ThreeFn(); >> >> >> > >> >> >> > > delete obj; >> >> >> > >> >> >> > > return 0; >> >> >> > > } >> >> >> > > ================================= >> >> >> > >> >> >> > > Can you spot the problem? do you know at which line it crashes, >> do >> >> >> > > you >> >> >> > > know why? if so I have wasted your time, apologies. If you are >> >> >> > > unsure >> >> >> > > then read on. >> >> >> > >> >> >> > > This program crashes when trying to call OneFn() with a purecall >> >> >> > > exception on debug build. On release build it exits with no >> error, >> >> >> > > but >> >> >> > > your mileage might vary depending on what optimizations are >> active. >> >> >> > >> >> >> > > The call stack for the crash is: >> >> >> > >> >> >> > > msvcr80d.dll!__purecall() + 0x25 >> >> >> > > <------ >> >> >> > > shows the >> >> >> > > dialog (debug only) >> >> >> > > app.exe!Base::ThreeFn() Line 16 + 0xfc <----- >> error >> >> >> > > here >> >> >> > > app.exe!Base::~Base() Line 10 C++ >> >> >> > > app.exe!Concrete::~Concrete() + 0x2b >> >> >> > > app.exe!Concrete::`scalar deleting destructor'() + 0x2b >> >> >> > > <----- >> >> >> > > delete obj >> >> >> > >> >> >> > > So as you have guessed it has to do with calling virtual >> functions >> >> >> > > from a destructor. >> >> >> > >> >> >> > > What happens is that during construction an object evolves from >> the >> >> >> > > earliest base class to the actual type and during destruction >> the >> >> >> > > object devolves (is that a word?) from the actual object to the >> >> >> > > earliest base class; when we reach ~Base() body the object is no >> >> >> > > longer of type Concrete but of type Base and thus the call >> >> >> > > Base::OneFn >> >> >> > > () is an error because that class does not in fact have any >> >> >> > > implementation. >> >> >> > >> >> >> > > What the compiler does is create two vtables, the vtable of >> >> >> > > Concrete >> >> >> > > looks like this: >> >> >> > >> >> >> > > vtable 1: >> >> >> > > [ 0 ] -> Concrete::OneFn() >> >> >> > > [ 1 ] -> Concrete::TwoFn() >> >> >> > >> >> >> > > vtable 2: >> >> >> > > [ 0 ]-> msvcr80d.dll!__purecall() >> >> >> > > [ 1 ]-> msvcr80d.dll!__purecall() >> >> >> > >> >> >> > > The dtor of Concrete is the default dtor which does nothing >> except >> >> >> > > calling Base::~Base(), but the dtor of base does: >> >> >> > >> >> >> > > this->vtbl_ptr = vtable2 >> >> >> > > call ThreeFn() >> >> >> > >> >> >> > > Now, why doesn't the release build crash? >> >> >> > >> >> >> > > That's because the compiler does not bother with generating the >> >> >> > > second >> >> >> > > vtable, after all is not going to be used and thus also >> eliminates >> >> >> > > the >> >> >> > > related lines such as this->vtbl_ptr = vtable2. Therefore the >> >> >> > > object >> >> >> > > reaches the base dtor with the vtbl_ptr pointing to vtable1 >> which >> >> >> > > makes the call ThreeFn() just work. >> >> >> > >> >> >> > > But that was just luck. If you ever modify the base class, such >> as >> >> >> > > introducing a new virtual function that is not pure, like this: >> >> >> > >> >> >> > > class Base { >> >> >> > > public: >> >> >> > > virtual ~Base() { >> >> >> > > ThreeFn(); >> >> >> > > } >> >> >> > >> >> >> > > virtual void OneFn() = 0; >> >> >> > > virtual void TwoFn() = 0; >> >> >> > >> >> >> > > virtual void FourFn() { <--- new function, not pure >> >> >> > > virtual >> >> >> > > wprintf(L"aw snap"); >> >> >> > > } >> >> >> > >> >> >> > > void ThreeFn() { >> >> >> > > OneFn(); >> >> >> > > TwoFn(); >> >> >> > > } >> >> >> > > }; >> >> >> > >> >> >> > > // Same program below. >> >> >> > > // ....... >> >> >> > > // ======================== >> >> >> > >> >> >> > > Then you are forcing the compiler to generate vtable 2, which >> >> >> > > looks: >> >> >> > >> >> >> > > vtable 2: >> >> >> > > [ 0 ]-> msvcr80d.dll!__purecall() >> >> >> > > [ 1 ]-> msvcr80d.dll!__purecall() >> >> >> > > [ 2 [-> Base::FourFn() >> >> >> > >> >> >> > > And now the purecall crash magically happens (on the same spot) >> on >> >> >> > > release builds, which is quite surprising since the trigger was >> the >> >> >> > > introduction of FourFn() which has _nothing_ to do with the >> crash >> >> >> > > or >> >> >> > > the problem and is many commits after the introduction of the >> >> >> > > problem. >> >> >> > >> >> >> > > So the moral of the story? beware of virtual calls on dtors and >> >> >> > > ctors. >> >> >> > > Note that in practice this is quite tricky because of layers of >> >> >> > > indirection / complexity of the code base. >> >> >> > >> >> >> > > ... so and what about the manbearpig ? Ah, yes no longer a myth: >> >> >> > >> >> >> > >> >> >> > > >> >> >> > > > > >> http://www.thinkgene.com/scientists-successfully-create-human-bear-pi... >> >> >> > >> >> >> > > -cpu >> >> >> >> >> > >> >> > >> >> > > >> >> > >> >> >> >> >> > >> > >> > > >> > >> > > > > > --~--~---------~--~----~------------~-------~--~----~ Chromium Developers mailing list: chromium-dev@googlegroups.com View archives, change email options, or unsubscribe: http://groups.google.com/group/chromium-dev -~----------~----~----~----~------~----~------~--~---