Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

GetCPUDescriptorHandleForHeapStart stack corruption

I've stumbled upon a rather unusual problem while programming with DirectX 12.0. No research so far has been insightful.

I am programming using C (not C++). It would appear the official DirectX 12 headers support bindings for both C and C++, however writing C equivalent code to do a said task causes a crash whereas C++ does not. I don't believe the error is mine.

Here's the details: I have here in my D3D12 device-initialisation procedure the following block of code:

/* Get a handle to the memory location in the render target
view heap to identify where the render target views will be
located for the two back buffers */
hRTV = pThis->pRTVHeap->lpVtbl->GetCPUDescriptorHandleForHeapStart(pThis->pRTVHeap);

Where hRTV stands for 'Handle to Render Target View' (D3D12_CPU_DESCRIPTOR_HANDLE) and pRTVHeap stands for 'Pointer to Render Target View Heap' (ID3D12DescriptorHeap).

Here is the C++ equivalent - this works fine:

/* Get a handle to the memory location in the render target
view heap to identify where the render target views will be
located for the two back buffers */
hRTV = this->pRTVHeap->GetCPUDescriptorHandleForHeapStart();

There are no compile-time errors present but at runtime, calling this method (GetCPUDescriptorHandleForHeapStart) in C triggers a stack corruption (%ESP is displaced by 4-bytes).

I examined the disassembly for the the method and made note of the RET (return) instruction:

mov         edi,edi
push        ebp
mov         ebp,esp
mov         ecx,dword ptr [ebp+8]
mov         eax,dword ptr [ecx+2Ch]
cmp         dword ptr [eax],2
jne         5029004A
mov         eax,dword ptr [ebp+0Ch]
mov         ecx,dword ptr [ecx+28h]
mov         dword ptr [eax],ecx
jmp         50290055
push        dword ptr [ebp+0Ch]
call        5029005E
mov         eax,dword ptr [ebp+0Ch]
pop         ebp
ret         8

For those familiar with assembly and (hopefully) the __stdcall calling convention of COM (Component Object Model) objects, the this (or equivalent) pointer, passed on the stack, is the first parameter (and, in this case, should be the only parameter) of the method, a practice that enables COM objects to access their own data.

The following code snippet (also shown above) is what elicits my confusion, and rightfully so when the runtime throws a 'misaligned stack pointer / stack corruption' (%ESP) error:

ret        8

Only one parameter should be passed in this case (this pointer). The size of a pointer (on a 32-bit system - my target architecture is x86) is 4 bytes (32 bits), so why does the callee clean up 8 bytes on the stack?

Am I right to call this a bug? Does Microsoft need to be informed of this issue? Am I wrong?

Thank you for your time and I hope anyone with more knowledge than I do can enlighten me. Please do not suggest the age-old argument of preferring C++ over C.

like image 593
Cara Ames Avatar asked Dec 06 '15 15:12

Cara Ames


2 Answers

SOLUTION

Debug symbols for D3D12.DLL reveal just enough. Naming conventions (e.g. ID3D12DescriptionHeap::GetCPUDescriptorHandleForHeapStart) are a strong indication that the DLL is written in C++. A (hidden) second parameter is indeed passed to the method - a pointer to the output structure D3D12_CPU_DESCRIPTOR_HANDLE (comprising nothing more than just an integer, aliased as a structure. I don't know why they do this). I forgot that C++ differs from C in that C++ can return structures as return values, and that structures cannot be passed as a return via the accumulator (%EAX) register, so it must be passed as a pointer on the stack to the callee.

The problem is bad C bindings (a Microsoft header bug). The following fix is proposed:

Old code:

D3D12_CPU_DESCRIPTOR_HANDLE (
    STDMETHODCALLTYPE *GetCPUDescriptorHandleForHeapStart )( 
    ID3D12DescriptorHeap * This);

Replace with:

void ( STDMETHODCALLTYPE *GetCPUDescriptorHandleForHeapStart )(
    ID3D12DescriptorHeap *This, D3D12_CPU_DESCRIPTOR_HANDLE *pOut);

Thank you.

EDIT

This header bug has been fixed with the release of the 10.0.22000.0 Windows SDK.

like image 161
Cara Ames Avatar answered Oct 20 '22 01:10

Cara Ames


This was finally fixed in Windows SDK 10.0.20348.0. At least the various GetDesc functions were also affected, judging from the header changes.

like image 43
RobDangerous Avatar answered Oct 20 '22 00:10

RobDangerous