win32平台关于“pure virtual function call”的解释

来源:互联网 发布:网络主播黑名单第二批 编辑:程序博客网 时间:2024/06/10 21:48

本博客(http://blog.csdn.net/livelylittlefish )贴出作者(三二一@小鱼)相关研究、学习内容所做的笔记,欢迎广大朋友指正!

 

Explanation about “pure virtual function call” on Win32 platform

 

Content

0. Introduction

1. an example

2. Disassemble code analysis

3. jump table of this program

4. _purecall function & its disassemble code

5. where is the message from?

5.1 _RT_PUREVIRT macro

5.2 _RT_PUREVIRT_TXT macro

5.3 rterrs array

6. which function does it prompt the message?

6.1 _NMSG_WRITE function

6.2 call stack

7. how does it be when the pure virtual function is implemented explicitly?

7.1 implement it inside of class

7.2 implement it outside of class

8. Summary

 

0. Introduction

 

In this article, we will not explain why does it prompt “pure virtual function call” and how to prompt “pure virtual function call”, but detailed explain the program itself when we call a pure virtual directly/indirectly in a constructor/destructor on win32 platform. This issue on Linux platform will be introduced in another article.

 

At the beginning, a classical example will be shown, in this example, it will prompt a message box with “pure virtual function call”.

 

1. An example

 

 

 

Running result: 

 

 

 

From the example, we can conclude that,

In constructor/destructor,

l   Direct call a pure virtual function, a compiling error will occur, such as

error LNK2001: unresolved external symbol "public: virtual double __thiscall Shape::area(void)const " (?area@Shape@@UBENXZ)

l   Indirect call a pure virtual function, it will prompt “pure virtual function call”

 

 

2. Disassemble code analysis

 

 

Debug this program, we can see the disassemble code of Shape::value() listed below, my comments embedded.

 

    double value() const

    {

004118F0  push        ebp 

004118F1  mov         ebp,esp

004118F3  sub         esp,0CCh

004118F9  push        ebx 

004118FA  push        esi 

004118FB  push        edi 

004118FC  push        ecx 

004118FD  lea         edi,[ebp-0CCh]

00411903  mov         ecx,33h

00411908  mov         eax,0CCCCCCCCh

0041190D  rep stos    dword ptr es:[edi]

0041190F  pop         ecx 

00411910  mov         dword ptr [ebp-8],ecx

    return ValuePerSquareUnit * area();

00411913  mov         eax,dword ptr [this]     //eax = 0x003b5fc0, move this pointer to eax

00411916  mov         edx,dword ptr [eax]      //edx = 0x00417800, move vfptr to edx

00411918  mov         esi,esp

0041191A  mov         ecx,dword ptr [this]    //ecx = 0x003b5fc0, move this pointer to ecx

0041191D  mov         eax,dword ptr [edx]     //eax = 0x004111f4, the address of __purecall, move the first virtual function address to eax

0041191F  call        eax                     //call this virtual function

00411921  cmp         esi,esp

00411923  call        @ILT+500(__RTC_CheckEsp) (4111F9h)

00411928  mov         ecx,dword ptr [this]

0041192B  fmul        qword ptr [ecx+8]

    }

0041192E  pop         edi 

0041192F  pop         esi 

00411930  pop         ebx 

00411931  add         esp,0CCh

00411937  cmp         ebp,esp

00411939  call        @ILT+500(__RTC_CheckEsp) (4111F9h)

0041193E  mov         esp,ebp

00411940  pop         ebp 

00411941  ret             

 

the disassemble codes and my comments can be verified from the following figure, which was captured from debuging. 

 

 

  

3. jump table of this program

 

To find the address of 0x004111f4, it is necessary to find the jump table of this program in the disassemble codes. Then, we find it listed below, which lists all jump items. 

 

00411005  jmp         _setdefaultprecision (413E80h)

0041100A  jmp         _setargv (413F20h)

0041100F  jmp         std::ios_base::good (41283Ah)

00411014  jmp         DebugBreak (414B78h)

00411019  jmp         _RTC_GetErrDesc (413BE0h)

0041101E  jmp         Rectangle::area (411BD0h)

00411023  jmp         __p__fmode (413F94h)

00411028  jmp         __security_check_cookie (412870h)

0041102D  jmp         IsDebuggerPresent (414B6Ch)

00411032  jmp         std::basic_ostream<char,std::char_traits<char> >::sentry::operator bool (412630h)

00411037  jmp         type_info::operator= (412BA0h)

0041103C  jmp         _RTC_Terminate (413F60h)

00411041  jmp         WideCharToMultiByte (414B7Eh)

00411046  jmp         _RTC_AllocaHelper (412940h)

0041104B  jmp         _RTC_GetErrorFuncW (413CA0h)

00411050  jmp         _RTC_NumErrors (413BD0h)

00411055  jmp         std::basic_ios<char,std::char_traits<char> >::rdbuf (412810h)

0041105A  jmp         __setusermatherr (413F04h)

0041105F  jmp         Sleep (414B48h)

00411064  jmp         type_info::_type_info_dtor_internal_method (414B12h)

00411069  jmp         Circle::`scalar deleting destructor' (411DC0h)

0041106E  jmp         Rectangle::Rectangle (4119A0h)

00411073  jmp         std::basic_ios<char,std::char_traits<char> >::setstate (4127ECh)

00411078  jmp         GetModuleFileNameW (414BD2h)

0041107D  jmp         __security_init_cookie (414120h)

00411082  jmp         Shape::getPerSquareUnit (411960h)

00411087  jmp         Circle::`scalar deleting destructor' (411DC0h)

0041108C  jmp         SetUnhandledExceptionFilter (414B66h)

00411091  jmp         _cexit (41428Ch)

00411096  jmp         Shape::`scalar deleting destructor' (411AF0h)

0041109B  jmp         _CrtDbgReportW (414504h)

004110A0  jmp         VirtualQuery (414BD8h)

004110A5  jmp         atexit (4140E0h)

004110AA  jmp         MultiByteToWideChar (414B84h)

004110AF  jmp         FatalAppExitA (414BBAh)

004110B4  jmp         std::endl (4127E6h)

004110B9  jmp         _RTC_SetErrorType (413C00h)

004110BE  jmp         _except_handler4 (414520h)

004110C3  jmp         _lock (414B30h)

004110C8  jmp         std::basic_streambuf<char,std::char_traits<char> >::_Unlock (412852h)

004110CD  jmp         GetProcAddress (414B90h)

004110D2  jmp         std::char_traits<char>::length (412828h)

004110D7  jmp         _RTC_CheckStackVars (4128C0h)

004110DC  jmp         operator delete (412858h)

004110E1  jmp         std::char_traits<char>::eq_int_type (4127FEh)

004110E6  jmp         type_info::_type_info_dtor_internal_method (414B12h)

004110EB  jmp         std::uncaught_exception (412846h)

004110F0  jmp         __report_gsfailure (4130E0h)

004110F5  jmp         terminate (414B0Ch)

004110FA  jmp         _exit (414280h)

004110FF  jmp         GetCurrentThreadId (414BA8h)

00411104  jmp         _initterm (41450Ah)

00411109  jmp         std::basic_ios<char,std::char_traits<char> >::tie (412834h)

0041110E  jmp         std::ios_base::width (4127F2h)

00411113  jmp         GetCurrentProcess (414B5Ah)

00411118  jmp         Circle::~Circle (411E30h)

0041111D  jmp         std::basic_streambuf<char,std::char_traits<char> >::sputc (41280Ah)

00411122  jmp         std::basic_ostream<char,std::char_traits<char> >::operator<< (4127E0h)

00411127  jmp         _encode_pointer (414100h)

0041112C  jmp         std::ios_base::width (412822h)

00411131  jmp         _RTC_UninitUse (413A80h)

00411136  jmp         _RTC_Shutdown (412AD0h)

0041113B  jmp         type_info::`vector deleting destructor' (412B10h)

00411140  jmp         _FindPESection (414320h)

00411145  jmp         Rectangle::`scalar deleting destructor' (411C20h)

0041114A  jmp         _configthreadlocale (413E78h)

0041114F  jmp         _RTC_InitBase (412A90h)

00411154  jmp         _RTC_StackFailure (413700h)

00411159  jmp         LoadLibraryA (414B96h)

0041115E  jmp         RaiseException (414B72h)

00411163  jmp         _crt_debugger_hook (414550h)

00411168  jmp         _ValidateImageBase (4142A0h)

0041116D  jmp         Shape::value (4118F0h)

00411172  jmp         InterlockedCompareExchange (414B4Eh)

00411177  jmp         Rectangle::~Rectangle (411C90h)

0041117C  jmp         Shape::Shape (411A30h)

00411181  jmp         std::basic_streambuf<char,std::char_traits<char> >::_Lock (41284Ch)

00411186  jmp         std::char_traits<char>::eof (412804h)

0041118B  jmp         std::basic_ostream<char,std::char_traits<char> >::sentry::~sentry (412560h)

00411190  jmp         Shape::~Shape (411B60h)

00411195  jmp         GetProcessHeap (414BCCh)

0041119A  jmp         _RTC_SetErrorFuncW (413C60h)

0041119F  jmp         _onexit (413FA0h)

004111A4  jmp         NtCurrentTeb (412FF0h)

004111A9  jmp         HeapFree (414BC0h)

004111AE  jmp         std::operator<<<std::char_traits<char> > (411E90h)

004111B3  jmp         _RTC_SetErrorFunc (413C30h)

004111B8  jmp         _invoke_watson_if_error (413ED0h)

004111BD  jmp         std::basic_ostream<char,std::char_traits<char> >::operator<< (4127DAh)

004111C2  jmp         std::basic_ostream<char,std::char_traits<char> >::_Sentry_base::~_Sentry_base (412730h)

004111C7  jmp         TerminateProcess (414B54h)

004111CC  jmp         std::basic_ostream<char,std::char_traits<char> >::flush (41282Eh)

004111D1  jmp         mainCRTStartup (412CF0h)

004111D6  jmp         QueryPerformanceCounter (414B9Ch)

004111DB  jmp         __p__commode (413F8Eh)

004111E0  jmp         _unlock (414B24h)

004111E5  jmp         GetCurrentProcessId (414BAEh)

004111EA  jmp         _RTC_CheckStackVars2 (412980h)

004111EF  jmp         __set_app_type (414106h)

004111F4  jmp         _purecall (412BB4h)

004111F9  jmp         _RTC_CheckEsp (412890h)

004111FE  jmp         main (4115B0h)

00411203  jmp         Rectangle::`scalar deleting destructor' (411C20h)

00411208  jmp         _RTC_Initialize (413F30h)

0041120D  jmp         _controlfp_s (414B18h)

00411212  jmp         GetSystemTimeAsFileTime (414BB4h)

00411217  jmp         _decode_pointer (414B36h)

0041121C  jmp         _invoke_watson (414B1Eh)

00411221  jmp         _RTC_GetSrcLine (414560h)

00411226  jmp         _CRT_RTC_INITW (413CA6h)

0041122B  jmp         GetTickCount (414BA2h)

00411230  jmp         std::basic_streambuf<char,std::char_traits<char> >::sputn (4127F8h)

00411235  jmp         _IsNonwritableInCurrentImage (4143B0h)

0041123A  jmp         __CxxFrameHandler3 (41286Ah)

0041123F  jmp         HeapAlloc (414BC6h)

00411244  jmp         _amsg_exit (41410Ch)

00411249  jmp         operator new (412864h)

0041124E  jmp         _XcptFilter (414286h)

00411253  jmp         _CrtSetCheckCount (414298h)

00411258  jmp         InterlockedExchange (414B42h)

0041125D  jmp         UnhandledExceptionFilter (414B60h)

00411262  jmp         std::basic_ostream<char,std::char_traits<char> >::sentry::sentry (412400h)

00411267  jmp         type_info::type_info (412AF0h)

0041126C  jmp         printf (41285Eh)

00411271  jmp         Circle::Circle (411CF0h)

00411276  jmp         _except_handler4_common (414B3Ch)

0041127B  jmp         _matherr (413F10h)

00411280  jmp         std::basic_ios<char,std::char_traits<char> >::fill (412816h)

00411285  jmp         __getmainargs (414112h)

0041128A  jmp         __ArrayUnwind (413D90h)

0041128F  jmp         Circle::area (411D70h)

00411294  jmp         lstrlenA (414B8Ah)

00411299  jmp         _RTC_Failure (413300h)

0041129E  jmp         std::ios_base::flags (41281Ch)

004112A3  jmp         _RTC_AllocaFailure (413870h)

004112A8  jmp         Shape::`scalar deleting destructor' (411AF0h)

004112AD  jmp         DebuggerKnownHandle (413230h)

004112B2  jmp         exit (414292h)

004112B7  jmp         std::basic_ostream<char,std::char_traits<char> >::_Sentry_base::_Sentry_base (412670h)

004112BC  jmp         __dllonexit (414B2Ah)

004112C1  jmp         FreeLibrary (414BDEh)

004112C6  jmp         `eh vector destructor iterator' (413CB0h)

004112CB  jmp         _initterm_e (414510h)

004112D0  jmp         std::basic_ostream<char,std::char_traits<char> >::_Osfx (412840h)

004112D5  jmp         _RTC_GetErrorFunc (413C90h)

 

Where,

004111F4  jmp         _purecall (412BB4h)

 

It indicates the program jump to address 0x412BB4. 

 

4. _purecall function & its disassemble code

 

The code from 0x00412BB4 listed below, where, we can see that it is indirect addressing (间接寻址). It will jump to the content of 0x0041B418.

 

_purecall:

00412BB4  jmp         dword ptr [__imp___purecall (41B418h)]

00412BBA  int         3   

00412BBB  int         3   

00412BBC  int         3   

00412BBD  int         3   

00412BBE  int         3   

00412BBF  int         3  

 

From the following figure, we can see that, the content of 0x0041B418 is 0x102527f0, which is the start address of _purecall.

 

 

 

We go on step, then, it will jump to 0x102527f0, the start address of _purecall. From the following figure, we can see it clearly.

 

 

 

The disassemble code of _purecall is as follows.

 

void __cdecl _purecall(

        void

        )

{

102527F0  push        ebp 

102527F1  mov         ebp,esp

102527F3  push        ecx 

    _purecall_handler purecall = (_purecall_handler) _decode_pointer(__pPurecall);

102527F4  mov         eax,dword ptr [___pPurecall (10313144h)]

102527F9  push        eax 

102527FA  call        _decode_pointer (10204900h)

102527FF  add         esp,4

10252802  mov         dword ptr [purecall],eax

    if(purecall != NULL)

10252805  cmp         dword ptr [purecall],0

10252809  je          _purecall+1Eh (1025280Eh)

    {

        purecall();

1025280B  call        dword ptr [purecall]

 

        /*  shouldn't return, but if it does, we drop back to

            default behaviour

        */

    }

 

    _NMSG_WRITE(_RT_PUREVIRT);

1025280E  push        19h 

10252810  call        _NMSG_WRITE (10202AA0h)

10252815  add         esp,4

    /* do not write the abort message */

    _set_abort_behavior(0, _WRITE_ABORT_MSG);

10252818  push        1   

1025281A  push        0   

1025281C  call        _set_abort_behavior (10218780h)

10252821  add         esp,8

    abort();

10252824  call        abort (10218640h)

}

10252829  mov         esp,ebp

1025282B  pop         ebp 

1025282C  ret             

 

But who initialze __pPurecall? And what things does _decode_pointer do? This is the important thing, which will explained in the next article.

Its source code is as follows.

 

5. where is the message from?

 

5.1 _RT_PUREVIRT macro

 

//file: src/rterr.h

 

#define _RT_PUREVIRT   25    /* pure virtual function call attempted (C++ error) */

 

5.2 _RT_PUREVIRT_TXT macro

 

//file: src/cmsgs.h

 

#define EOL "/r/n"

#define _RT_PUREVIRT_TXT   "R6025" EOL "- pure virtual function call" EOL

 

5.3 rterrs array

 

//file: src/crt0msg.c

 

/* struct used to lookup and access runtime error messages */

struct rterrmsgs {

        int rterrno;        /* error number */

        char *rterrtxt;     /* text of error message */

};

 

/* runtime error messages */

static struct rterrmsgs rterrs[] = {

        /* 2 */

        { _RT_FLOAT, _RT_FLOAT_TXT },

        /* 8 */

        { _RT_SPACEARG, _RT_SPACEARG_TXT },

        /* 9 */

        { _RT_SPACEENV, _RT_SPACEENV_TXT },

        /* 10 */

        { _RT_ABORT, _RT_ABORT_TXT },

        /* 16 */

        { _RT_THREAD, _RT_THREAD_TXT },

        /* 17 */

        { _RT_LOCK, _RT_LOCK_TXT },

        /* 18 */

        { _RT_HEAP, _RT_HEAP_TXT },

        /* 19 */

        { _RT_OPENCON, _RT_OPENCON_TXT },

        /* 22 */

        /* { _RT_NONCONT, _RT_NONCONT_TXT }, */

        /* 23 */

        /* { _RT_INVALDISP, _RT_INVALDISP_TXT }, */

        /* 24 */

        { _RT_ONEXIT, _RT_ONEXIT_TXT },

        /* 25 */

        { _RT_PUREVIRT, _RT_PUREVIRT_TXT },

        /* 26 */

        { _RT_STDIOINIT, _RT_STDIOINIT_TXT },

        /* 27 */

        { _RT_LOWIOINIT, _RT_LOWIOINIT_TXT },

        /* 28 */

        { _RT_HEAPINIT, _RT_HEAPINIT_TXT },

        ///* 29 */

        //{ _RT_BADCLRVERSION, _RT_BADCLRVERSION_TXT },

        /* 30 */

        { _RT_CRT_NOTINIT, _RT_CRT_NOTINIT_TXT },

        /* 31 */

        { _RT_CRT_INIT_CONFLICT, _RT_CRT_INIT_CONFLICT_TXT},

        /* 32 */

        { _RT_LOCALE, _RT_LOCALE_TXT},

        /* 33 */

        { _RT_CRT_INIT_MANAGED_CONFLICT, _RT_CRT_INIT_MANAGED_CONFLICT_TXT},

        /* 34 */

        { _RT_CHECKMANIFEST, _RT_CHECKMANIFEST_TXT},

        ///* 35 - not for _NMSG_WRITE, text passed directly to FatalAppExit */

        //{ _RT_COOKIE_INIT, _RT_COOKIE_INIT_TXT},

        /* 120 */

        { _RT_DOMAIN, _RT_DOMAIN_TXT },

        /* 121 */

        { _RT_SING, _RT_SING_TXT },

        /* 122 */

        { _RT_TLOSS, _RT_TLOSS_TXT },

        /* 252 */

        { _RT_CRNL, _RT_CRNL_TXT },

        /* 255 */

        { _RT_BANNER, _RT_BANNER_TXT }

};

 

/* number of elements in rterrs[] */

#define _RTERRCNT   ( sizeof(rterrs) / sizeof(struct rterrmsgs) )

  

This can be verified from the following figure captured from debuging.

 

 

 

 

6. which function does it prompt the message?

 

6.1 _NMSG_WRITE function

 

//file: src/crt0msg.c

 

/***

*__NMSG_WRITE(message) - write a given message to handle 2 (stderr)

*

*Purpose:

*       This routine writes the message associated with rterrnum

*       to stderr.

*

*Entry:

*       int rterrnum - runtime error number

*

*Exit:

*       no return value

*

*Exceptions:

*       none

*

*******************************************************************************/

 

void __cdecl _NMSG_WRITE (

        int rterrnum

        )

{

        int tblindx;

        DWORD bytes_written;            /* bytes written */

 

        for ( tblindx = 0 ; tblindx < _RTERRCNT ; tblindx++ )

            if ( rterrnum == rterrs[tblindx].rterrno )    //in rterrs array, find the mapped message

                break;

 

        if ( tblindx < _RTERRCNT )

        {

#ifdef _DEBUG

            /*

             * Report error.

             *

             * If _CRT_ERROR has _CRTDBG_REPORT_WNDW on, and user chooses

             * "Retry", call the debugger.

             *

             * Otherwise, continue execution.

             *

             */

 

            if (rterrnum != _RT_CRNL && rterrnum != _RT_BANNER && rterrnum != _RT_CRT_NOTINIT)

            {

                if (1 == _CrtDbgReport(_CRT_ERROR, NULL, 0, NULL, rterrs[tblindx].rterrtxt))

                    _CrtDbgBreak();

            }

#endif  /* _DEBUG */

            if ( (_set_error_mode(_REPORT_ERRMODE) == _OUT_TO_STDERR) ||

                 ((_set_error_mode(_REPORT_ERRMODE) == _OUT_TO_DEFAULT) &&

                  (__app_type == _CONSOLE_APP)) )

            {

                HANDLE hStdErr = GetStdHandle(STD_ERROR_HANDLE);

                if (hStdErr && hStdErr!=INVALID_HANDLE_VALUE)

                {

                    WriteFile( hStdErr,

                                  rterrs[tblindx].rterrtxt,

                                  (unsigned long)strlen(rterrs[tblindx].rterrtxt),

                                  &bytes_written,

                                  NULL );

                }

            }

            else if (rterrnum != _RT_CRNL)

            {

                #define MSGTEXTPREFIX "Runtime Error!/n/nProgram: "

                static char outmsg[sizeof(MSGTEXTPREFIX) + _MAX_PATH + 2 + 500];

                    // runtime error msg + progname + 2 newline + runtime error text.

                char * progname = &outmsg[sizeof(MSGTEXTPREFIX)-1];

                size_t progname_size = _countof(outmsg) - (progname - outmsg);

                char * pch = progname;

 

                _ERRCHECK(strcpy_s(outmsg, _countof(outmsg), MSGTEXTPREFIX));

 

                progname[MAX_PATH] = '/0';

                if (!GetModuleFileName(NULL, progname, MAX_PATH))

                    _ERRCHECK(strcpy_s(progname, progname_size, "<program name unknown>"));

 

                #define MAXLINELEN 60

                if (strlen(pch) + 1 > MAXLINELEN)

                {

                    pch += strlen(progname) + 1 - MAXLINELEN;

                    _ERRCHECK(strncpy_s(pch, progname_size - (pch - progname), "...", 3));

                }

 

                _ERRCHECK(strcat_s(outmsg, _countof(outmsg), "/n/n"));

                _ERRCHECK(strcat_s(outmsg, _countof(outmsg), rterrs[tblindx].rterrtxt));

 

                __crtMessageBoxA(outmsg,

                        "Microsoft Visual C++ Runtime Library",

                        MB_OK|MB_ICONHAND|MB_SETFOREGROUND|MB_TASKMODAL);

            }

        }

}

 

6.2 call stack

 

Step into some key functions, we can see the callstack as follows.

 

 

 

 

 

7. how does it be when the pure virtual function is implemented explicitly?

 

7.1 implement it inside of class

 

Modifiy the class like:

 

class Shape

{

...

public:

    virtual double area() const = 0

    {

        std::cout << "pure virtual area() called" << std::endl;

        return 0;

}

...

};

 

There is no compiler error, it will prompt “pure virtual function call”.

 

7.2 implement it outside of class

 

Modifiy the code like:

 

class Shape

{

...

public:

    virtual double area() const = 0;

...

};

 

double Shape::area() const

{

    std::cout << "pure virtual area() called" << std::endl;

    return 0;

}

 

There is no compiler error, it will prompt “pure virtual function call”, too.

 

 

8. Summary

 

 

In this article, detailed explain the program itself when we call a pure virtual function directly/indirectly in a constructor/destructor on win32 platform, through a classical example. Some msvc crt source code is listed and _purecall function and its disassemble code is analyzed.

 

Besides, we introduced the jump table of this program, and where the prompt message is from, which is defined in a array (rterrs) and some macros such as _RT_PUREVIRT and _RT_PUREVIRT_TXT. At the last, we give a two implementations of the pure virtual function to verify whether it works or not, from the result, we find that even though there is implementation of pure virtual function, the compiler will ignore the implemented codes explicitly, _purecall still will be called, and prompt “pure virtual function call”.

 

 

 

原创粉丝点击