Add an option to verify Map() writes don't overrun. Closes #42

* Has a couple of limitations - won't check deferred context or
  NO_OVERWRITE Map()s except in a captured frame. This could in theory be
  implemented but it'd be complex and I don't want to complicate/break
  the normal path.
* When an overrun is detected, a messagebox pops up to block the thread,
  and if you hit yes, it will debugbreak.
This commit is contained in:
baldurk
2014-10-05 23:42:25 +01:00
parent a23f597c99
commit 56e7f0c754
10 changed files with 128 additions and 24 deletions
+6 -3
View File
@@ -67,7 +67,7 @@ struct CaptureOptions
CaptureCallstacks(false),
CaptureCallstacksOnlyDraws(false),
DelayForDebugger(0),
CacheStateObjects(false),
VerifyMapWrites(true),
HookIntoChildren(false),
RefAllResources(false),
SaveAllInitials(false),
@@ -101,8 +101,9 @@ struct CaptureOptions
// creating or injecting into a process, before continuing to allow it to run.
uint32_t DelayForDebugger;
// Deprecated, ignored.
bool32 CacheStateObjects;
// Verify any writes to mapped buffers, to check that they don't overwrite the
// bounds of the pointer returned.
bool32 VerifyMapWrites;
// Hooks any system API events that create child processes, and injects
// renderdoc into them recursively with the same options.
@@ -141,6 +142,7 @@ struct CaptureOptions
>> CaptureCallstacks
>> CaptureCallstacksOnlyDraws
>> DelayForDebugger
>> VerifyMapWrites
>> HookIntoChildren
>> RefAllResources
>> SaveAllInitials
@@ -157,6 +159,7 @@ struct CaptureOptions
<< CaptureCallstacks << " "
<< CaptureCallstacksOnlyDraws << " "
<< DelayForDebugger << " "
<< VerifyMapWrites << " "
<< HookIntoChildren << " "
<< RefAllResources << " "
<< SaveAllInitials << " "
+4 -1
View File
@@ -913,8 +913,11 @@ void WrappedID3D11DeviceContext::RefreshDrawcallIDs(DrawcallTreeNode &node)
// assign new drawcall IDs
for(size_t i=0; i < node.children.size(); i++)
{
m_CurEventID++;
node.children[i].draw.eventID = m_CurEventID;
node.children[i].draw.drawcallID = m_CurDrawcallID;
// markers don't increment drawcall ID
if((node.children[i].draw.flags & (eDraw_SetMarker|eDraw_PushMarker)) == 0)
m_CurDrawcallID++;
+2
View File
@@ -64,6 +64,8 @@ struct MapIntercept
D3D11_MAP MapType;
UINT MapFlags;
bool verifyWrite;
void CopyFromD3D();
void CopyToD3D(size_t RangeStart = 0, size_t RangeEnd = 0);
};
+65 -12
View File
@@ -6188,6 +6188,7 @@ bool WrappedID3D11DeviceContext::Serialise_Map(ID3D11Resource *pResource, UINT S
}
intercept = MapIntercept();
intercept.verifyWrite = (RenderDoc::Inst().GetCaptureOptions().VerifyMapWrites != 0);
intercept.SetD3D(mappedResource);
intercept.InitWrappedResource(resMap, Subresource, appMem);
intercept.MapType = MapType;
@@ -6205,11 +6206,44 @@ bool WrappedID3D11DeviceContext::Serialise_Map(ID3D11Resource *pResource, UINT S
mapLength = record->Length;
intercept = MapIntercept();
intercept.verifyWrite = (RenderDoc::Inst().GetCaptureOptions().VerifyMapWrites != 0);
intercept.SetD3D(mappedResource);
intercept.InitWrappedResource(pResource, Subresource, record->GetDataPtr());
intercept.MapType = MapType;
intercept.MapFlags = MapFlags;
if(intercept.verifyWrite)
{
int ctxMapID = 0;
ResourceId Resource = GetIDForResource(pResource);
if(GetType() == D3D11_DEVICE_CONTEXT_DEFERRED)
{
if(m_MapResourceRecordAllocs[Resource] == 0)
m_MapResourceRecordAllocs[Resource] = record->GetContextID();
ctxMapID = m_MapResourceRecordAllocs[Resource];
RDCASSERT(ctxMapID != 0);
}
byte *appMem = record->GetShadowPtr(ctxMapID, 0);
if(appMem == NULL)
{
record->AllocShadowStorage(ctxMapID, mapLength);
appMem = record->GetShadowPtr(ctxMapID, 0);
}
memcpy(appMem, record->GetDataPtr(), mapLength);
intercept.InitWrappedResource(pResource, Subresource, appMem);
}
else
{
intercept.InitWrappedResource(pResource, Subresource, record->GetDataPtr());
}
*pMappedResource = intercept.app;
m_OpenMaps[MappedResource(GetIDForResource(pResource), Subresource)] = intercept;
@@ -6310,9 +6344,10 @@ HRESULT WrappedID3D11DeviceContext::Map(ID3D11Resource *pResource, UINT Subresou
if(record->NumSubResources > (int)Subresource)
record = (D3D11ResourceRecord *)record->SubResources[Subresource];
record->UpdateCount++;
if(RenderDoc::Inst().GetCaptureOptions().VerifyMapWrites == 0)
record->UpdateCount++;
if(record->UpdateCount > 60)
if(record->UpdateCount > 60 && RenderDoc::Inst().GetCaptureOptions().VerifyMapWrites == 0)
{
m_HighTrafficResources.insert(pResource);
m_pDevice->GetResourceManager()->MarkDirtyResource(Id);
@@ -6356,6 +6391,8 @@ bool WrappedID3D11DeviceContext::Serialise_Unmap(ID3D11Resource *pResource, UINT
MapIntercept intercept;
int ctxMapID = 0;
if(m_State >= WRITING)
{
auto it = m_OpenMaps.find(mapIdx);
@@ -6365,6 +6402,27 @@ bool WrappedID3D11DeviceContext::Serialise_Unmap(ID3D11Resource *pResource, UINT
intercept = it->second;
m_OpenMaps.erase(it);
if(GetType() == D3D11_DEVICE_CONTEXT_DEFERRED && (m_State == WRITING_CAPFRAME || intercept.verifyWrite))
{
ctxMapID = m_MapResourceRecordAllocs[mapIdx.resource];
RDCASSERT(ctxMapID != 0);
}
if(intercept.verifyWrite && record)
{
if(!record->VerifyShadowStorage(ctxMapID))
{
int res = MessageBoxA(NULL,
"Breakpoint now to see callstack,\nor click 'Yes' to debugbreak.",
"Map() overwrite detected!", MB_YESNO|MB_ICONERROR);
if(res == IDYES)
{
OS_DEBUG_BREAK();
}
}
}
}
if(m_State < WRITING || m_State == WRITING_CAPFRAME)
@@ -6378,15 +6436,6 @@ bool WrappedID3D11DeviceContext::Serialise_Unmap(ID3D11Resource *pResource, UINT
size_t diffStart = 0;
size_t diffEnd = len;
int ctxMapID = 0;
if(GetType() == D3D11_DEVICE_CONTEXT_DEFERRED && m_State == WRITING_CAPFRAME)
{
ctxMapID = m_MapResourceRecordAllocs[mapIdx.resource];
RDCASSERT(ctxMapID != 0);
}
if(m_State == WRITING_CAPFRAME && len > 512 && intercept.MapType != D3D11_MAP_WRITE_DISCARD)
{
bool found = FindDiffRange(appWritePtr, record->GetShadowPtr(ctxMapID, 1), len, diffStart, diffEnd);
@@ -6526,6 +6575,10 @@ bool WrappedID3D11DeviceContext::Serialise_Unmap(ID3D11Resource *pResource, UINT
if(m_State < WRITING)
SAFE_DELETE_ARRAY(buf);
}
else if(intercept.verifyWrite)
{
memcpy(record->GetDataPtr(), intercept.app.pData, len);
}
}
return true;
+2
View File
@@ -2379,6 +2379,8 @@ void WrappedID3D11Device::StartFrameCapture(void *wnd)
GetResourceManager()->MarkResourceFrameReferenced(m_ResourceID, eFrameRef_Write);
m_pImmediateContext->FreeCaptureData();
m_pImmediateContext->AttemptCapture();
m_pImmediateContext->BeginCaptureFrame();
+7
View File
@@ -27,6 +27,13 @@
#include "driver/d3d11/d3d11_context.h"
#include "driver/d3d11/d3d11_resources.h"
byte D3D11ResourceRecord::markerValue[32] = {
0xaa, 0xbb, 0xcc, 0xdd,
0x88, 0x77, 0x66, 0x55,
0x01, 0x23, 0x45, 0x67,
0x98, 0x76, 0x54, 0x32,
};
ID3D11DeviceChild *D3D11ResourceManager::UnwrapResource(ID3D11DeviceChild *res)
{
if(res == NULL) return res;
+21 -2
View File
@@ -43,6 +43,8 @@ struct D3D11ResourceRecord : public ResourceRecord
{
enum { NullResource = NULL };
static byte markerValue[32];
D3D11ResourceRecord(ResourceId id)
: ResourceRecord(id, true)
{
@@ -60,11 +62,27 @@ struct D3D11ResourceRecord : public ResourceRecord
{
if(ShadowPtr[ctx][0] == NULL)
{
ShadowPtr[ctx][0] = Serialiser::AllocAlignedBuffer(size);
ShadowPtr[ctx][1] = Serialiser::AllocAlignedBuffer(size);
ShadowPtr[ctx][0] = Serialiser::AllocAlignedBuffer(size + sizeof(markerValue));
ShadowPtr[ctx][1] = Serialiser::AllocAlignedBuffer(size + sizeof(markerValue));
memcpy(ShadowPtr[ctx][0] + size, markerValue, sizeof(markerValue));
memcpy(ShadowPtr[ctx][1] + size, markerValue, sizeof(markerValue));
ShadowSize[ctx] = size;
}
}
bool VerifyShadowStorage(int ctx)
{
if(ShadowPtr[ctx][0] && memcmp(ShadowPtr[ctx][0] + ShadowSize[ctx], markerValue, sizeof(markerValue)))
return false;
if(ShadowPtr[ctx][1] && memcmp(ShadowPtr[ctx][1] + ShadowSize[ctx], markerValue, sizeof(markerValue)))
return false;
return true;
}
void FreeShadowStorage()
{
for(int i=0; i < 32; i++)
@@ -109,6 +127,7 @@ struct D3D11ResourceRecord : public ResourceRecord
private:
byte *ShadowPtr[32][2];
size_t ShadowSize[32];
bool contexts[32];
};
+2 -2
View File
@@ -73,7 +73,7 @@ namespace renderdoc
public bool CaptureCallstacks;
public bool CaptureCallstacksOnlyDraws;
public UInt32 DelayForDebugger;
public bool CacheStateObjects;
public bool VerifyMapWrites;
public bool HookIntoChildren;
public bool RefAllResources;
public bool SaveAllInitials;
@@ -90,7 +90,7 @@ namespace renderdoc
defs.CaptureCallstacks = false;
defs.CaptureCallstacksOnlyDraws = false;
defs.DelayForDebugger = 0;
defs.CacheStateObjects = false;
defs.VerifyMapWrites = true;
defs.HookIntoChildren = false;
defs.RefAllResources = false;
defs.SaveAllInitials = false;
+17 -4
View File
@@ -56,6 +56,7 @@
this.SaveAllInitials = new System.Windows.Forms.CheckBox();
this.RefAllResources = new System.Windows.Forms.CheckBox();
this.CaptureAllCmdLists = new System.Windows.Forms.CheckBox();
this.VerifyMapWrites = new System.Windows.Forms.CheckBox();
this.AutoStart = new System.Windows.Forms.CheckBox();
this.panel2 = new System.Windows.Forms.Panel();
this.load = new System.Windows.Forms.Button();
@@ -181,7 +182,7 @@
this.queueFrameCap.Location = new System.Drawing.Point(3, 3);
this.queueFrameCap.Name = "queueFrameCap";
this.queueFrameCap.Size = new System.Drawing.Size(151, 24);
this.queueFrameCap.TabIndex = 19;
this.queueFrameCap.TabIndex = 20;
this.queueFrameCap.Text = "Queue Capture of Frame";
this.queueFrameCap.UseVisualStyleBackColor = true;
//
@@ -201,7 +202,7 @@
0});
this.queuedCapFrame.Name = "queuedCapFrame";
this.queuedCapFrame.Size = new System.Drawing.Size(120, 20);
this.queuedCapFrame.TabIndex = 20;
this.queuedCapFrame.TabIndex = 21;
this.queuedCapFrame.ThousandsSeparator = true;
this.queuedCapFrame.Value = new decimal(new int[] {
2,
@@ -278,6 +279,7 @@
this.capOptsFlow.Controls.Add(this.SaveAllInitials);
this.capOptsFlow.Controls.Add(this.RefAllResources);
this.capOptsFlow.Controls.Add(this.CaptureAllCmdLists);
this.capOptsFlow.Controls.Add(this.VerifyMapWrites);
this.capOptsFlow.Controls.Add(this.AutoStart);
this.capOptsFlow.Location = new System.Drawing.Point(3, 16);
this.capOptsFlow.MaximumSize = new System.Drawing.Size(640, 0);
@@ -412,12 +414,22 @@
"as an overhead but ensures if you hold onto a list it will be captured.");
this.CaptureAllCmdLists.UseVisualStyleBackColor = true;
//
// VerifyMapWrites
//
this.VerifyMapWrites.Location = new System.Drawing.Point(275, 55);
this.VerifyMapWrites.Name = "VerifyMapWrites";
this.VerifyMapWrites.Size = new System.Drawing.Size(130, 20);
this.VerifyMapWrites.TabIndex = 18;
this.VerifyMapWrites.Text = "Verify Map() Writes";
this.toolTip.SetToolTip(this.AutoStart, "When enabled, causes Map() pointers to be validated to check for buffer overruns.");
this.VerifyMapWrites.UseVisualStyleBackColor = true;
//
// AutoStart
//
this.AutoStart.Location = new System.Drawing.Point(275, 55);
this.AutoStart.Location = new System.Drawing.Point(411, 55);
this.AutoStart.Name = "AutoStart";
this.AutoStart.Size = new System.Drawing.Size(130, 20);
this.AutoStart.TabIndex = 18;
this.AutoStart.TabIndex = 19;
this.AutoStart.Text = "Auto start";
this.toolTip.SetToolTip(this.AutoStart, "If these capture settings are saved & run, auto start the capture instantly on lo" +
"ad");
@@ -733,6 +745,7 @@
private System.Windows.Forms.CheckBox SaveAllInitials;
private System.Windows.Forms.CheckBox RefAllResources;
private System.Windows.Forms.CheckBox CaptureCallstacksOnlyDraws;
private System.Windows.Forms.CheckBox VerifyMapWrites;
private System.Windows.Forms.CheckBox AutoStart;
private System.Windows.Forms.ToolTip toolTip;
private System.Windows.Forms.FlowLayoutPanel capOptsFlow;
@@ -79,6 +79,7 @@ namespace renderdocui.Windows.Dialogs
RefAllResources.Checked = settings.Options.RefAllResources;
SaveAllInitials.Checked = settings.Options.SaveAllInitials;
DelayForDebugger.Value = settings.Options.DelayForDebugger;
VerifyMapWrites.Checked = settings.Options.VerifyMapWrites;
AutoStart.Checked = settings.AutoStart;
if (settings.AutoStart)
@@ -117,6 +118,7 @@ namespace renderdocui.Windows.Dialogs
ret.Options.SaveAllInitials = SaveAllInitials.Checked;
ret.Options.CaptureAllCmdLists = CaptureAllCmdLists.Checked;
ret.Options.DelayForDebugger = (uint)DelayForDebugger.Value;
ret.Options.VerifyMapWrites = VerifyMapWrites.Checked;
return ret;
}