Rubato and Chord

Reiley's technical blog

May, 2013

  • Rubato and Chord

    Vector Deleting Destructor


    Today one guy in my team asked a question regarding the behavior of delete[] operator in C++ - how does the program know it needs to call CBar::~CBar instead of CFoo::~CFoo?

    Note that the vector deleting destructor is a feature in Microsoft C++ Compiler, not required by the C++ standard.

    #define _CRTDBG_MAP_ALLOC
    #include <malloc.h>
    #include <crtdbg.h>
    class CFoo
      virtual ~CFoo() = 0;
    class CBar
      virtual ~CBar()
    void __cdecl main()
      CBar* p = new CBar[1];
      delete[] (CFoo*)p; // what does this mean?

    Well the short answer would be - how does virtual destructor work and how does the program know the actual number of elements in the array?

    If you've spent time on understanding the object layout in C++ and COM, you would know that virtual functions are backed by vtable, and it will not be too difficult to understand BSTR.

    So we are ready for the answer - delete[] is a combination of BSTR and virtual function.

    CBar::`vector deleting destructor':
    000007F7CD2E1140  mov         dword ptr [rsp+10h],edx  
    000007F7CD2E1144  mov         qword ptr [rsp+8],rcx  
    000007F7CD2E1149  sub         rsp,28h  
    000007F7CD2E114D  mov         eax,dword ptr [rsp+38h]  
    000007F7CD2E1151 and eax,2
    000007F7CD2E1154 test eax,eax
    000007F7CD2E1156 je CBar::`vector deleting destructor'+5Eh (07F7CD2E119Eh)
    000007F7CD2E1158 lea r9,[CBar::~CBar (07F7CD2E1120h)] ; the address of CBar::~CBar 000007F7CD2E115F mov rax,qword ptr [this] ; this pointer 000007F7CD2E1164 mov r8d,dword ptr [rax-8] ; array size *((size_t*)this - 1) 000007F7CD2E1168 mov edx,8 ; sizeof(CBar) 000007F7CD2E116D mov rcx,qword ptr [this] ; this pointer 000007F7CD2E1172 call `vector destructor iterator' (07F7CD2E1220h)

    As you can see here, the vector deleting destructor was emitted as a virtual function in vtable, it takes a flag parameter, if flag & 0x2 equals to true, the actual function vector destructor iterator would get called.

    The size of array was stored using the BSTR approach, passed in via the r8d register.

    The callstack from Visual Studio Debugger also tells us the same thing:

    crtdbg.exe!`vector destructor iterator'(void * __t, unsigned __int64 __s, int __n, void (void *) * __f)	C++
    crtdbg.exe!CBar::`vector deleting destructor'(unsigned int)	C++
    crtdbg.exe!main() Line 22	C++
    crtdbg.exe!__tmainCRTStartup() Line 536	C
    crtdbg.exe!mainCRTStartup() Line 377	C
    kernel32.dll!BaseThreadInitThunk()	Unknown
    ntdll.dll!RtlUserThreadStart()	Unknown

    Regarding the actual meaning of the flag I mentioned, I'll leave it as a homework for the readers (hint: you may try out the DGML tool).


  • Rubato and Chord

    Undocumented Environment Variables


    Although we have less Easter Eggs, there are still a huge number of undocumented behaviors.

    Recently I'm writing a CLR profiler using ICorProfilerCallback for fun, the CLR profiler was modeled as an in-proc COM server, and the activition was done through environment variables:

    • SET COR_PROFILER_PATH="C:\FOO\BAR\MyProfiler.dll"

    Immediately I realized there must be a lot more environment variables, and it was a perfect time to use WinDBG: 

    cdb.exe -hd -g -G -xi ld -xe cpr -c "bu KERNELBASE!GetEnvironmentVariableW \"du @rcx; gc\"; g" "%WINDIR%\Microsoft.NET\Framework64\v4.0.30319\csc.exe"
    0:000> cdb: Reading initial command 'bu KERNELBASE!GetEnvironmentVariableW "du @rcx; gc"; g'
    000007fe`581406f0  "SHIM_DEBUG_LEVEL"
    000007fe`58140428  "SHIM_FILE_LOG"
    000007fe`581406f0  "SHIM_DEBUG_LEVEL"
    SHIMVIEW: ShimInfo(Complete)
    (ba0.db8): Break instruction exception - code 80000003 (first chance)
    000007fe`5c4eada0 cc              int     3
    0:000> g
    Microsoft (R) Visual C# Compiler version 4.0.30319.17929
    for Microsoft (R) .NET Framework 4.5
    Copyright (C) Microsoft Corporation. All rights reserved.
    warning CS2008: No source files specified
    000000a3`60e9c230  "COMPlus_Version"
    000000a3`60e9c230  "COMPlus_InstallRoot"
    000000a3`60e9b790  "COMPlus_InstallRoot"
    000000a3`60e9c140  "COMPlus_DefaultVersion"
    000000a3`60e9bad0  "COMPlus_InstallRoot"
    000000a3`60e9b880  "COMPlus_InstallRoot"
    000000a3`60e9b8a0  "COMPlus_3gbEatMem"
    000000a3`60e9b400  "COMPLUS_CLRLoadLogDir"
    000000a3`60e9b1d0  "COMPlus_InstallRoot"
    000000a3`60e9b180  "COMPlus_NicPath"
    000000a3`60e9b180  "COMPlus_RegistryRoot"
    000000a3`60e9b180  "COMPlus_AssemblyPath"
    000000a3`60e9b180  "COMPlus_AssemblyPath2"
    000000a3`60e9ab80  "COMPLUS_InstallRoot"
    000000a3`60e9ab40  "COMPLUS_DefaultVersion"
    000000a3`60e9ab40  "COMPLUS_Version"
    000000a3`60e9af60  "COMPLUS_ApplicationMigrationRunt"
    000000a3`60e9afa0  "imeActivationConfigPath"
    000000a3`60e9afb0  "COMPLUS_OnlyUseLatestCLR"
    000007fe`51a06580  "APPX_PROCESS"
    000000a3`60e9af60  "COMPLUS_BuildFlavor"
    000007f6`2e5c16a8  "LIB"
    error CS1562: Outputs without source must have the /out option specified

    As we can see, the red parts are the environment variables used by Application Compatibility team for debugging a shim.

    The blue parts are the environment variables used by COMPLUS, which was the original name for .NET.

    Notice that there is no guarantee of completeness using my WinDBG approach, since GetEnvironmentVariableW is not the only way to retrieve environment variables.

    I'll leave a homework for the readers - find the environment variables consumed by the Visual Studio IDE (hint: you need to modify the command line to make it work under 32bit).

Page 1 of 1 (2 items)