On Fri, Apr 3, 2009 at 7:19 PM, Tommi <to...@chromium.org> wrote: > Yes, that's one way of running into purecall. but, just in case my email > is being misunderstood, now with italics! :) > > "purecall is not called *when* an exception occurs. purecall actually > *throws > the exception - or exits the program"* > > purecall is called when attempting to call a virtual method for which there > is no implementation. purecall is the default virtual method if you will. >
Yes, that's the low level description of purecall, and no one is debating that. But it is also misleading, because, from a high level perspective, when you look at my code, you see that the developer actually did implement the virtual method explicitly, so, still from a high level perspective, it can also happen for a virtual method that does have an implementation if the object has been deleted prior to the call. [All that because when the derived object is deleted, one of the thing it does is to revert its vtable to the base class vtable. That part is not obvious/known to the high level developer] It's not because you implement all your virtual functions correctly that your objects wont purecall. But I'm sure you know that, I just wanted to make sure I'm not misunderstood either ;) Nicolas > When you call _set_purecall_handler, you're giving _purecall a pointer to > your function that purecall will delegate to. There's not an exception that > triggers this. Calling purecall is just a regular function call. > > Here's CRT's implementation of __purecall: > > void __cdecl _purecall() { > _purecall_handler purecall = (_purecall_handler) > _decode_pointer(__pPurecall); > if(purecall != NULL) { > purecall(); > /* shouldn't return, but if it does, we drop back to > default behaviour > */ > } > > _NMSG_WRITE(_RT_PUREVIRT); > /* do not write the abort message */ > _set_abort_behavior(0, _WRITE_ABORT_MSG); > abort(); > } > > and here's the implementation of _set_purecall_handler: > > _purecall_handler _set_purecall_handler(_purecall_handler pNew) { > _purecall_handler pOld = NULL; > pOld = (_purecall_handler) _decode_pointer(__pPurecall); > __pPurecall = (_purecall_handler) _encode_pointer(pNew); > return pOld; > } > > > On Fri, Apr 3, 2009 at 8:42 PM, Nicolas Sylvain <nsylv...@chromium.org>wrote: > >> 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 -~----------~----~----~----~------~----~------~--~---