From 01e2e00b5fbe903986592766d13d4303919f0d46 Mon Sep 17 00:00:00 2001 From: baldurk Date: Thu, 23 Nov 2017 12:11:50 +0000 Subject: [PATCH] Add error checking and handling while loading captures * Previously once we started loading a capture we'd blindly continue until we loaded it (and then it's assumed to be successful), or we crash. * Now errors can be reported during serialisation and bubbled up to abort the file load process. The next steps are to add error checking in each function serialise before doing any replay calls to the API with potentially corrupt data, and on top of that catching API-only errors when the serialisation is (seemingly) fine, and propagating those in a reasonable way. * We also harden the serialisation a bit so that if it reads an obviously invalid byte length for a buffer or array count, it won't continue. It's still not perfect as the sizes could still be large and invalid but within range, but it should catch the worst cases. --- qrenderdoc/Code/ReplayManager.cpp | 6 ++ qrenderdoc/Windows/MainWindow.cpp | 19 +++- renderdoc/api/replay/renderdoc_replay.h | 10 ++ renderdoc/api/replay/renderdoc_tostr.inl | 12 ++- renderdoc/api/replay/replay_enums.h | 11 ++ renderdoc/core/image_viewer.cpp | 5 +- renderdoc/core/remote_server.cpp | 24 +++-- renderdoc/core/replay_proxy.h | 5 +- renderdoc/core/target_control.cpp | 2 + renderdoc/driver/d3d11/d3d11_device.cpp | 14 ++- renderdoc/driver/d3d11/d3d11_device.h | 2 +- renderdoc/driver/d3d11/d3d11_replay.cpp | 11 +- renderdoc/driver/d3d11/d3d11_replay.h | 2 +- renderdoc/driver/d3d12/d3d12_device.cpp | 14 ++- renderdoc/driver/d3d12/d3d12_device.h | 2 +- renderdoc/driver/d3d12/d3d12_replay.cpp | 11 +- renderdoc/driver/d3d12/d3d12_replay.h | 2 +- renderdoc/driver/gl/gl_driver.cpp | 14 ++- renderdoc/driver/gl/gl_driver.h | 2 +- renderdoc/driver/gl/gl_replay.cpp | 9 +- renderdoc/driver/gl/gl_replay.h | 2 +- renderdoc/driver/gl/gl_replay_egl.cpp | 2 +- renderdoc/driver/gl/gl_replay_linux.cpp | 2 +- renderdoc/driver/gl/gl_replay_win32.cpp | 2 +- renderdoc/driver/vulkan/vk_core.cpp | 14 ++- renderdoc/driver/vulkan/vk_core.h | 2 +- renderdoc/driver/vulkan/vk_replay.cpp | 11 +- renderdoc/driver/vulkan/vk_replay.h | 2 +- renderdoc/replay/capture_file.cpp | 8 +- renderdoc/replay/replay_controller.cpp | 8 +- renderdoc/replay/replay_driver.h | 2 +- renderdoc/serialise/rdcfile.cpp | 123 +++++++++++------------ renderdoc/serialise/rdcfile.h | 6 ++ renderdoc/serialise/serialiser.cpp | 2 +- renderdoc/serialise/serialiser.h | 74 ++++++++++++++ renderdoc/serialise/streamio.cpp | 4 + 36 files changed, 312 insertions(+), 129 deletions(-) diff --git a/qrenderdoc/Code/ReplayManager.cpp b/qrenderdoc/Code/ReplayManager.cpp index 67bdd3bd1..a105e3848 100644 --- a/qrenderdoc/Code/ReplayManager.cpp +++ b/qrenderdoc/Code/ReplayManager.cpp @@ -423,7 +423,13 @@ void ReplayManager::run(int proxyRenderer, const QString &capturefile, float *pr } if(m_Renderer == NULL) + { + if(m_CaptureFile) + m_CaptureFile->Shutdown(); + m_CaptureFile = NULL; + return; + } qInfo() << "QRenderDoc - renderer created for" << capturefile; diff --git a/qrenderdoc/Windows/MainWindow.cpp b/qrenderdoc/Windows/MainWindow.cpp index dc9686509..8ba80e9f6 100644 --- a/qrenderdoc/Windows/MainWindow.cpp +++ b/qrenderdoc/Windows/MainWindow.cpp @@ -423,10 +423,18 @@ void MainWindow::LoadCapture(const QString &filename, bool temporary, bool local { ICaptureFile *file = RENDERDOC_OpenCaptureFile(); - if(file->OpenFile(filename.toUtf8().data(), "rdc") != ReplayStatus::Succeeded) + ReplayStatus status = file->OpenFile(filename.toUtf8().data(), "rdc"); + + if(status != ReplayStatus::Succeeded) { - RDDialog::critical(NULL, tr("Error opening capture"), - tr("Couldn't open file '%1'").arg(filename)); + QString text = tr("Couldn't open file '%1'\n").arg(filename); + QString message = file->ErrorString(); + if(message.isEmpty()) + text += tr("%1").arg(ToQStr(status)); + else + text += tr("%1: %2").arg(ToQStr(status)).arg(message); + + RDDialog::critical(NULL, tr("Error opening capture"), text); file->Shutdown(); return; @@ -559,6 +567,8 @@ void MainWindow::LoadCapture(const QString &filename, bool temporary, bool local } } + statusText->setText(tr("Loading %1...").arg(origFilename)); + m_Ctx.LoadCapture(fileToLoad, origFilename, temporary, local); } @@ -566,8 +576,6 @@ void MainWindow::LoadCapture(const QString &filename, bool temporary, bool local { m_Ctx.Config().LastCaptureFilePath = QFileInfo(filename).absolutePath(); } - - statusText->setText(tr("Loading %1...").arg(origFilename)); } } @@ -976,6 +984,7 @@ void MainWindow::setProgress(float val) if(val < 0.0f || val >= 1.0f) { statusProgress->setVisible(false); + statusText->setText(QString()); } else { diff --git a/renderdoc/api/replay/renderdoc_replay.h b/renderdoc/api/replay/renderdoc_replay.h index ae96461fa..3a1ca5f25 100644 --- a/renderdoc/api/replay/renderdoc_replay.h +++ b/renderdoc/api/replay/renderdoc_replay.h @@ -1406,6 +1406,16 @@ representation back to native RDC. )"); virtual ReplayStatus Convert(const char *filename, const char *filetype, float *progress) = 0; + DOCUMENT(R"(Returns the human-readable error string for the last error received. + +The error string is not reset by calling this function so it's safe to call multiple times. However +any other function call may reset the error string to empty. + +:return: The error string, if one exists, or an empty string. +:rtype: str +)"); + virtual rdcstr ErrorString() = 0; + DOCUMENT(R"(Returns the list of capture file formats. :return: The list of capture file formats available. diff --git a/renderdoc/api/replay/renderdoc_tostr.inl b/renderdoc/api/replay/renderdoc_tostr.inl index 2d8415aeb..5bf51f0a0 100644 --- a/renderdoc/api/replay/renderdoc_tostr.inl +++ b/renderdoc/api/replay/renderdoc_tostr.inl @@ -39,10 +39,16 @@ std::string DoStringise(const ReplayStatus &el) STRINGISE_ENUM_CLASS_NAMED(FileIOFailed, "File I/O failed"); STRINGISE_ENUM_CLASS_NAMED(FileIncompatibleVersion, "File of incompatible version"); STRINGISE_ENUM_CLASS_NAMED(FileCorrupted, "File corrupted"); - STRINGISE_ENUM_CLASS_NAMED(APIUnsupported, "API unsupported"); + STRINGISE_ENUM_CLASS_NAMED(APIUnsupported, "API is not unsupported"); STRINGISE_ENUM_CLASS_NAMED(APIInitFailed, "API initialisation failed"); - STRINGISE_ENUM_CLASS_NAMED(APIIncompatibleVersion, "API incompatible version"); - STRINGISE_ENUM_CLASS_NAMED(APIHardwareUnsupported, "API hardware unsupported"); + STRINGISE_ENUM_CLASS_NAMED(APIIncompatibleVersion, + "Captured API data has an incompatible version"); + STRINGISE_ENUM_CLASS_NAMED( + APIHardwareUnsupported, + "Current hardware unsupported or incompatible with captured hardware"); + STRINGISE_ENUM_CLASS_NAMED(APIDataCorrupted, + "Replaying the capture encountered invalid/corrupted data"); + STRINGISE_ENUM_CLASS_NAMED(APIReplayFailed, "Replaying the capture failed at the API level"); } END_ENUM_STRINGISE(); } diff --git a/renderdoc/api/replay/replay_enums.h b/renderdoc/api/replay/replay_enums.h index 2377e30da..26640ebd3 100644 --- a/renderdoc/api/replay/replay_enums.h +++ b/renderdoc/api/replay/replay_enums.h @@ -2833,6 +2833,15 @@ a remote server. .. data:: APIHardwareUnsupported The API is not supported on the currently available hardware. + +.. data:: APIDataCorrupted + + While loading the capture for replay, the driver encountered corrupted or invalid serialised data. + +.. data:: APIReplayFailed + + The API failed to replay the capture, with some runtime error that couldn't be determined until + the replay began. )"); enum class ReplayStatus : uint32_t { @@ -2853,6 +2862,8 @@ enum class ReplayStatus : uint32_t APIInitFailed, APIIncompatibleVersion, APIHardwareUnsupported, + APIDataCorrupted, + APIReplayFailed, }; DECLARE_REFLECTION_ENUM(ReplayStatus); diff --git a/renderdoc/core/image_viewer.cpp b/renderdoc/core/image_viewer.cpp index 822035d1d..6b381c0b3 100644 --- a/renderdoc/core/image_viewer.cpp +++ b/renderdoc/core/image_viewer.cpp @@ -151,7 +151,10 @@ public: FrameRecord GetFrameRecord() { return m_FrameRecord; } const D3D11Pipe::State &GetD3D11PipelineState() { return m_PipelineState; } // other operations are dropped/ignored, to avoid confusion - void ReadLogInitialisation(RDCFile *rdc, bool storeStructuredBuffers) {} + ReplayStatus ReadLogInitialisation(RDCFile *rdc, bool storeStructuredBuffers) + { + return ReplayStatus::Succeeded; + } const SDFile &GetStructuredFile() { return m_File; } void RenderMesh(uint32_t eventID, const vector &secondaryDraws, const MeshDisplay &cfg) { diff --git a/renderdoc/core/remote_server.cpp b/renderdoc/core/remote_server.cpp index a113dddc9..85c7a8bc4 100644 --- a/renderdoc/core/remote_server.cpp +++ b/renderdoc/core/remote_server.cpp @@ -205,6 +205,7 @@ static void ActiveRemoteClientThread(ClientThread *threadData) ReadSerialiser reader(new StreamReader(client, Ownership::Nothing), Ownership::Stream); writer.SetStreamingMode(true); + reader.SetStreamingMode(true); while(client) { @@ -429,15 +430,25 @@ static void ActiveRemoteClientThread(ClientThread *threadData) } else { - driver->ReadLogInitialisation(rdc, false); + status = driver->ReadLogInitialisation(rdc, false); - RenderDoc::Inst().SetProgressPtr(NULL); + if(status != ReplayStatus::Succeeded) + { + RDCERR("Failed to initialise remote driver."); - kill = true; - Threading::JoinThread(ticker); - Threading::CloseThread(ticker); + driver->Shutdown(); + driver = NULL; + } + else + { + RenderDoc::Inst().SetProgressPtr(NULL); - proxy = new ReplayProxy(reader, writer, driver); + kill = true; + Threading::JoinThread(ticker); + Threading::CloseThread(ticker); + + proxy = new ReplayProxy(reader, writer, driver); + } } } else @@ -966,6 +977,7 @@ public: writer(new StreamWriter(sock, Ownership::Nothing), Ownership::Stream) { writer.SetStreamingMode(true); + reader.SetStreamingMode(true); std::map m = RenderDoc::Inst().GetReplayDrivers(); diff --git a/renderdoc/core/replay_proxy.h b/renderdoc/core/replay_proxy.h index 35c053182..24ea69383 100644 --- a/renderdoc/core/replay_proxy.h +++ b/renderdoc/core/replay_proxy.h @@ -120,7 +120,10 @@ public: bool IsRemoteProxy() { return !m_RemoteServer; } void Shutdown() { delete this; } - void ReadLogInitialisation(RDCFile *rdc, bool storeStructuredBuffers) {} + ReplayStatus ReadLogInitialisation(RDCFile *rdc, bool storeStructuredBuffers) + { + return ReplayStatus::Succeeded; + } vector GetSupportedWindowSystems() { if(m_Proxy) diff --git a/renderdoc/core/target_control.cpp b/renderdoc/core/target_control.cpp index 25fe81872..3c0006183 100644 --- a/renderdoc/core/target_control.cpp +++ b/renderdoc/core/target_control.cpp @@ -76,6 +76,7 @@ void RenderDoc::TargetControlClientThread(Network::Socket *client) ReadSerialiser reader(new StreamReader(client, Ownership::Nothing), Ownership::Stream); writer.SetStreamingMode(true); + reader.SetStreamingMode(true); std::string api = ""; RDCDriver driver; @@ -404,6 +405,7 @@ public: std::vector payload; writer.SetStreamingMode(true); + reader.SetStreamingMode(true); m_PID = 0; diff --git a/renderdoc/driver/d3d11/d3d11_device.cpp b/renderdoc/driver/d3d11/d3d11_device.cpp index c663d5399..01d78e2a4 100644 --- a/renderdoc/driver/d3d11/d3d11_device.cpp +++ b/renderdoc/driver/d3d11/d3d11_device.cpp @@ -912,17 +912,17 @@ void WrappedID3D11Device::Serialise_CaptureScope(SerialiserType &ser) } } -void WrappedID3D11Device::ReadLogInitialisation(RDCFile *rdc, bool storeStructuredBuffers) +ReplayStatus WrappedID3D11Device::ReadLogInitialisation(RDCFile *rdc, bool storeStructuredBuffers) { int sectionIdx = rdc->SectionIndex(SectionType::FrameCapture); if(sectionIdx < 0) - return; + return ReplayStatus::FileCorrupted; StreamReader *reader = rdc->ReadSection(sectionIdx); if(reader->IsErrored()) - return; + return ReplayStatus::FileIOFailed; ReadSerialiser ser(reader, Ownership::Stream); @@ -961,10 +961,16 @@ void WrappedID3D11Device::ReadLogInitialisation(RDCFile *rdc, bool storeStructur chunkIdx++; + if(reader->IsErrored()) + return ReplayStatus::APIDataCorrupted; + ProcessChunk(ser, context); ser.EndChunk(); + if(reader->IsErrored()) + return ReplayStatus::APIDataCorrupted; + uint64_t offsetEnd = reader->GetOffset(); RenderDoc::Inst().SetProgress(FileInitialRead, float(offsetEnd) / float(reader->GetSize())); @@ -1027,6 +1033,8 @@ void WrappedID3D11Device::ReadLogInitialisation(RDCFile *rdc, bool storeStructur RDCDEBUG("Allocating %llu persistant bytes of memory for the log.", m_FrameRecord.frameInfo.persistentSize); + + return ReplayStatus::Succeeded; } void WrappedID3D11Device::ReplayLog(uint32_t startEventID, uint32_t endEventID, diff --git a/renderdoc/driver/d3d11/d3d11_device.h b/renderdoc/driver/d3d11/d3d11_device.h index 63d20c304..7375b0910 100644 --- a/renderdoc/driver/d3d11/d3d11_device.h +++ b/renderdoc/driver/d3d11/d3d11_device.h @@ -514,7 +514,7 @@ public: m_SectionVersion = sectionVersion; m_State = CaptureState::StructuredExport; } - void ReadLogInitialisation(RDCFile *rdc, bool storeStructuredBuffers); + ReplayStatus ReadLogInitialisation(RDCFile *rdc, bool storeStructuredBuffers); void ProcessChunk(ReadSerialiser &ser, D3D11Chunk context); void ReplayLog(uint32_t startEventID, uint32_t endEventID, ReplayLogType replayType); diff --git a/renderdoc/driver/d3d11/d3d11_replay.cpp b/renderdoc/driver/d3d11/d3d11_replay.cpp index f0559eaa8..2e1577a10 100644 --- a/renderdoc/driver/d3d11/d3d11_replay.cpp +++ b/renderdoc/driver/d3d11/d3d11_replay.cpp @@ -1218,9 +1218,9 @@ void D3D11Replay::SavePipelineState() } } -void D3D11Replay::ReadLogInitialisation(RDCFile *rdc, bool storeStructuredBuffers) +ReplayStatus D3D11Replay::ReadLogInitialisation(RDCFile *rdc, bool storeStructuredBuffers) { - m_pDevice->ReadLogInitialisation(rdc, storeStructuredBuffers); + return m_pDevice->ReadLogInitialisation(rdc, storeStructuredBuffers); } void D3D11Replay::ReplayLog(uint32_t endEventID, ReplayLogType replayType) @@ -1896,7 +1896,7 @@ ReplayStatus D3D11_CreateReplayDevice(RDCFile *rdc, IReplayDriver **driver) if(!D3D11InitParams::IsSupportedVersion(ver)) { RDCERR("Incompatible D3D11 serialise version %llu", ver); - return ReplayStatus::APIUnsupported; + return ReplayStatus::APIIncompatibleVersion; } StreamReader *reader = rdc->ReadSection(sectionIdx); @@ -2051,9 +2051,10 @@ void D3D11_ProcessStructured(RDCFile *rdc, SDFile &output) return; device.SetStructuredExport(rdc->GetSectionProperties(sectionIdx).version); - device.ReadLogInitialisation(rdc, true); + ReplayStatus status = device.ReadLogInitialisation(rdc, true); - device.GetStructuredFile().swap(output); + if(status == ReplayStatus::Succeeded) + device.GetStructuredFile().swap(output); } static StructuredProcessRegistration D3D11ProcessRegistration(RDC_D3D11, &D3D11_ProcessStructured); \ No newline at end of file diff --git a/renderdoc/driver/d3d11/d3d11_replay.h b/renderdoc/driver/d3d11/d3d11_replay.h index c70fb7684..27da86e48 100644 --- a/renderdoc/driver/d3d11/d3d11_replay.h +++ b/renderdoc/driver/d3d11/d3d11_replay.h @@ -77,7 +77,7 @@ public: void FreeTargetResource(ResourceId id); void FreeCustomShader(ResourceId id); - void ReadLogInitialisation(RDCFile *rdc, bool storeStructuredBuffers); + ReplayStatus ReadLogInitialisation(RDCFile *rdc, bool storeStructuredBuffers); void ReplayLog(uint32_t endEventID, ReplayLogType replayType); const SDFile &GetStructuredFile(); diff --git a/renderdoc/driver/d3d12/d3d12_device.cpp b/renderdoc/driver/d3d12/d3d12_device.cpp index d8bfabbcf..d6b1c9402 100644 --- a/renderdoc/driver/d3d12/d3d12_device.cpp +++ b/renderdoc/driver/d3d12/d3d12_device.cpp @@ -2367,17 +2367,17 @@ void WrappedID3D12Device::AddResourceCurChunk(ResourceId id) AddResourceCurChunk(GetReplay()->GetResourceDesc(id)); } -void WrappedID3D12Device::ReadLogInitialisation(RDCFile *rdc, bool storeStructuredBuffers) +ReplayStatus WrappedID3D12Device::ReadLogInitialisation(RDCFile *rdc, bool storeStructuredBuffers) { int sectionIdx = rdc->SectionIndex(SectionType::FrameCapture); if(sectionIdx < 0) - return; + return ReplayStatus::FileCorrupted; StreamReader *reader = rdc->ReadSection(sectionIdx); if(reader->IsErrored()) - return; + return ReplayStatus::FileIOFailed; ReadSerialiser ser(reader, Ownership::Stream); @@ -2416,10 +2416,16 @@ void WrappedID3D12Device::ReadLogInitialisation(RDCFile *rdc, bool storeStructur chunkIdx++; + if(reader->IsErrored()) + return ReplayStatus::APIDataCorrupted; + ProcessChunk(ser, context); ser.EndChunk(); + if(reader->IsErrored()) + return ReplayStatus::APIDataCorrupted; + uint64_t offsetEnd = reader->GetOffset(); RenderDoc::Inst().SetProgress(FileInitialRead, float(offsetEnd) / float(reader->GetSize())); @@ -2498,6 +2504,8 @@ void WrappedID3D12Device::ReadLogInitialisation(RDCFile *rdc, bool storeStructur RDCDEBUG("Allocating %llu persistant bytes of memory for the log.", m_FrameRecord.frameInfo.persistentSize); + + return ReplayStatus::Succeeded; } void WrappedID3D12Device::ReplayLog(uint32_t startEventID, uint32_t endEventID, diff --git a/renderdoc/driver/d3d12/d3d12_device.h b/renderdoc/driver/d3d12/d3d12_device.h index 5b3dbf73c..0e422a328 100644 --- a/renderdoc/driver/d3d12/d3d12_device.h +++ b/renderdoc/driver/d3d12/d3d12_device.h @@ -474,7 +474,7 @@ public: bool Serialise_DynamicDescriptorCopies(SerialiserType &ser, const std::vector &DescriptorCopies); - void ReadLogInitialisation(RDCFile *rdc, bool storeStructuredBuffers); + ReplayStatus ReadLogInitialisation(RDCFile *rdc, bool storeStructuredBuffers); void ReplayLog(uint32_t startEventID, uint32_t endEventID, ReplayLogType replayType); void SetStructuredExport(uint64_t sectionVersion) diff --git a/renderdoc/driver/d3d12/d3d12_replay.cpp b/renderdoc/driver/d3d12/d3d12_replay.cpp index 21ae33122..875a97d1f 100644 --- a/renderdoc/driver/d3d12/d3d12_replay.cpp +++ b/renderdoc/driver/d3d12/d3d12_replay.cpp @@ -59,9 +59,9 @@ void D3D12Replay::Shutdown() D3D12Replay::PostDeviceShutdownCounters(); } -void D3D12Replay::ReadLogInitialisation(RDCFile *rdc, bool storeStructuredBuffers) +ReplayStatus D3D12Replay::ReadLogInitialisation(RDCFile *rdc, bool storeStructuredBuffers) { - m_pDevice->ReadLogInitialisation(rdc, storeStructuredBuffers); + return m_pDevice->ReadLogInitialisation(rdc, storeStructuredBuffers); } APIProperties D3D12Replay::GetAPIProperties() @@ -1752,7 +1752,7 @@ ReplayStatus D3D12_CreateReplayDevice(RDCFile *rdc, IReplayDriver **driver) if(!D3D12InitParams::IsSupportedVersion(ver)) { RDCERR("Incompatible D3D11 serialise version %llu", ver); - return ReplayStatus::APIUnsupported; + return ReplayStatus::APIIncompatibleVersion; } StreamReader *reader = rdc->ReadSection(sectionIdx); @@ -1816,9 +1816,10 @@ void D3D12_ProcessStructured(RDCFile *rdc, SDFile &output) return; device.SetStructuredExport(rdc->GetSectionProperties(sectionIdx).version); - device.ReadLogInitialisation(rdc, true); + ReplayStatus status = device.ReadLogInitialisation(rdc, true); - device.GetStructuredFile().swap(output); + if(status == ReplayStatus::Succeeded) + device.GetStructuredFile().swap(output); } static StructuredProcessRegistration D3D12ProcessRegistration(RDC_D3D12, &D3D12_ProcessStructured); diff --git a/renderdoc/driver/d3d12/d3d12_replay.h b/renderdoc/driver/d3d12/d3d12_replay.h index bc7afd342..7336aaa37 100644 --- a/renderdoc/driver/d3d12/d3d12_replay.h +++ b/renderdoc/driver/d3d12/d3d12_replay.h @@ -75,7 +75,7 @@ public: void FreeTargetResource(ResourceId id); void FreeCustomShader(ResourceId id); - void ReadLogInitialisation(RDCFile *rdc, bool readStructuredBuffers); + ReplayStatus ReadLogInitialisation(RDCFile *rdc, bool readStructuredBuffers); void ReplayLog(uint32_t endEventID, ReplayLogType replayType); const SDFile &GetStructuredFile(); diff --git a/renderdoc/driver/gl/gl_driver.cpp b/renderdoc/driver/gl/gl_driver.cpp index a9d856e56..28914a62d 100644 --- a/renderdoc/driver/gl/gl_driver.cpp +++ b/renderdoc/driver/gl/gl_driver.cpp @@ -3054,17 +3054,17 @@ void WrappedOpenGL::AddResourceInitChunk(GLResource res) } } -void WrappedOpenGL::ReadLogInitialisation(RDCFile *rdc, bool storeStructuredBuffers) +ReplayStatus WrappedOpenGL::ReadLogInitialisation(RDCFile *rdc, bool storeStructuredBuffers) { int sectionIdx = rdc->SectionIndex(SectionType::FrameCapture); if(sectionIdx < 0) - return; + return ReplayStatus::FileCorrupted; StreamReader *reader = rdc->ReadSection(sectionIdx); if(reader->IsErrored()) - return; + return ReplayStatus::FileIOFailed; ReadSerialiser ser(reader, Ownership::Stream); @@ -3103,10 +3103,16 @@ void WrappedOpenGL::ReadLogInitialisation(RDCFile *rdc, bool storeStructuredBuff chunkIdx++; + if(reader->IsErrored()) + return ReplayStatus::APIDataCorrupted; + ProcessChunk(ser, context); ser.EndChunk(); + if(reader->IsErrored()) + return ReplayStatus::APIDataCorrupted; + uint64_t offsetEnd = reader->GetOffset(); RenderDoc::Inst().SetProgress(FileInitialRead, float(offsetEnd) / float(reader->GetSize())); @@ -3161,6 +3167,8 @@ void WrappedOpenGL::ReadLogInitialisation(RDCFile *rdc, bool storeStructuredBuff RDCDEBUG("Allocating %llu persistant bytes of memory for the log.", m_FrameRecord.frameInfo.persistentSize); + + return ReplayStatus::Succeeded; } void WrappedOpenGL::ProcessChunk(ReadSerialiser &ser, GLChunk chunk) diff --git a/renderdoc/driver/gl/gl_driver.h b/renderdoc/driver/gl/gl_driver.h index f9dc91a30..f95a8bfc4 100644 --- a/renderdoc/driver/gl/gl_driver.h +++ b/renderdoc/driver/gl/gl_driver.h @@ -559,7 +559,7 @@ public: // replay interface void Initialise(GLInitParams ¶ms, uint64_t sectionVersion); void ReplayLog(uint32_t startEventID, uint32_t endEventID, ReplayLogType replayType); - void ReadLogInitialisation(RDCFile *rdc, bool storeStructuredBuffers); + ReplayStatus ReadLogInitialisation(RDCFile *rdc, bool storeStructuredBuffers); GLuint GetFakeBBFBO() { return m_FakeBB_FBO; } GLuint GetFakeVAO() { return m_FakeVAO; } diff --git a/renderdoc/driver/gl/gl_replay.cpp b/renderdoc/driver/gl/gl_replay.cpp index f7f11b7e4..6cc35981a 100644 --- a/renderdoc/driver/gl/gl_replay.cpp +++ b/renderdoc/driver/gl/gl_replay.cpp @@ -73,10 +73,10 @@ void GLReplay::Shutdown() GLReplay::PostContextShutdownCounters(); } -void GLReplay::ReadLogInitialisation(RDCFile *rdc, bool storeStructuredBuffers) +ReplayStatus GLReplay::ReadLogInitialisation(RDCFile *rdc, bool storeStructuredBuffers) { MakeCurrentReplayContext(&m_ReplayCtx); - m_pDriver->ReadLogInitialisation(rdc, storeStructuredBuffers); + return m_pDriver->ReadLogInitialisation(rdc, storeStructuredBuffers); } void GLReplay::ReplayLog(uint32_t endEventID, ReplayLogType replayType) @@ -3341,9 +3341,10 @@ void GL_ProcessStructured(RDCFile *rdc, SDFile &output) return; device.SetStructuredExport(rdc->GetSectionProperties(sectionIdx).version); - device.ReadLogInitialisation(rdc, true); + ReplayStatus status = device.ReadLogInitialisation(rdc, true); - device.GetStructuredFile().swap(output); + if(status == ReplayStatus::Succeeded) + device.GetStructuredFile().swap(output); } static StructuredProcessRegistration GLProcessRegistration(RDC_OpenGL, &GL_ProcessStructured); diff --git a/renderdoc/driver/gl/gl_replay.h b/renderdoc/driver/gl/gl_replay.h index bbf2d1962..95c9c90bc 100644 --- a/renderdoc/driver/gl/gl_replay.h +++ b/renderdoc/driver/gl/gl_replay.h @@ -121,7 +121,7 @@ public: const VKPipe::State &GetVulkanPipelineState() { return m_VKState; } void FreeTargetResource(ResourceId id); - void ReadLogInitialisation(RDCFile *rdc, bool storeStructuredBuffers); + ReplayStatus ReadLogInitialisation(RDCFile *rdc, bool storeStructuredBuffers); void ReplayLog(uint32_t endEventID, ReplayLogType replayType); const SDFile &GetStructuredFile(); diff --git a/renderdoc/driver/gl/gl_replay_egl.cpp b/renderdoc/driver/gl/gl_replay_egl.cpp index e9b948163..7b2567f4d 100644 --- a/renderdoc/driver/gl/gl_replay_egl.cpp +++ b/renderdoc/driver/gl/gl_replay_egl.cpp @@ -73,7 +73,7 @@ ReplayStatus GLES_CreateReplayDevice(RDCFile *rdc, IReplayDriver **driver) if(!GLInitParams::IsSupportedVersion(ver)) { RDCERR("Incompatible D3D11 serialise version %llu", ver); - return ReplayStatus::APIUnsupported; + return ReplayStatus::APIIncompatibleVersion; } StreamReader *reader = rdc->ReadSection(sectionIdx); diff --git a/renderdoc/driver/gl/gl_replay_linux.cpp b/renderdoc/driver/gl/gl_replay_linux.cpp index f534bc07a..3eaa54741 100644 --- a/renderdoc/driver/gl/gl_replay_linux.cpp +++ b/renderdoc/driver/gl/gl_replay_linux.cpp @@ -108,7 +108,7 @@ ReplayStatus GL_CreateReplayDevice(RDCFile *rdc, IReplayDriver **driver) if(!GLInitParams::IsSupportedVersion(ver)) { RDCERR("Incompatible D3D11 serialise version %llu", ver); - return ReplayStatus::APIUnsupported; + return ReplayStatus::APIIncompatibleVersion; } StreamReader *reader = rdc->ReadSection(sectionIdx); diff --git a/renderdoc/driver/gl/gl_replay_win32.cpp b/renderdoc/driver/gl/gl_replay_win32.cpp index fab7ca6bd..a67a31644 100644 --- a/renderdoc/driver/gl/gl_replay_win32.cpp +++ b/renderdoc/driver/gl/gl_replay_win32.cpp @@ -70,7 +70,7 @@ ReplayStatus GL_CreateReplayDevice(RDCFile *rdc, IReplayDriver **driver) if(!GLInitParams::IsSupportedVersion(ver)) { RDCERR("Incompatible D3D11 serialise version %llu", ver); - return ReplayStatus::APIUnsupported; + return ReplayStatus::APIIncompatibleVersion; } StreamReader *reader = rdc->ReadSection(sectionIdx); diff --git a/renderdoc/driver/vulkan/vk_core.cpp b/renderdoc/driver/vulkan/vk_core.cpp index 378a1830c..cf33c747e 100644 --- a/renderdoc/driver/vulkan/vk_core.cpp +++ b/renderdoc/driver/vulkan/vk_core.cpp @@ -1420,17 +1420,17 @@ void WrappedVulkan::AddResourceCurChunk(ResourceId id) AddResourceCurChunk(GetReplay()->GetResourceDesc(id)); } -void WrappedVulkan::ReadLogInitialisation(RDCFile *rdc, bool storeStructuredBuffers) +ReplayStatus WrappedVulkan::ReadLogInitialisation(RDCFile *rdc, bool storeStructuredBuffers) { int sectionIdx = rdc->SectionIndex(SectionType::FrameCapture); if(sectionIdx < 0) - return; + return ReplayStatus::FileCorrupted; StreamReader *reader = rdc->ReadSection(sectionIdx); if(reader->IsErrored()) - return; + return ReplayStatus::FileIOFailed; ReadSerialiser ser(reader, Ownership::Stream); @@ -1469,10 +1469,16 @@ void WrappedVulkan::ReadLogInitialisation(RDCFile *rdc, bool storeStructuredBuff chunkIdx++; + if(reader->IsErrored()) + return ReplayStatus::APIDataCorrupted; + ProcessChunk(ser, context); ser.EndChunk(); + if(reader->IsErrored()) + return ReplayStatus::APIDataCorrupted; + uint64_t offsetEnd = reader->GetOffset(); RenderDoc::Inst().SetProgress(FileInitialRead, float(offsetEnd) / float(reader->GetSize())); @@ -1533,6 +1539,8 @@ void WrappedVulkan::ReadLogInitialisation(RDCFile *rdc, bool storeStructuredBuff RDCASSERT(m_Device != VK_NULL_HANDLE && m_Queue != VK_NULL_HANDLE && m_InternalCmds.cmdpool != VK_NULL_HANDLE); } + + return ReplayStatus::Succeeded; } void WrappedVulkan::ContextReplayLog(CaptureState readType, uint32_t startEventID, diff --git a/renderdoc/driver/vulkan/vk_core.h b/renderdoc/driver/vulkan/vk_core.h index a473a84a3..8bd2bc845 100644 --- a/renderdoc/driver/vulkan/vk_core.h +++ b/renderdoc/driver/vulkan/vk_core.h @@ -763,7 +763,7 @@ public: } void Shutdown(); void ReplayLog(uint32_t startEventID, uint32_t endEventID, ReplayLogType replayType); - void ReadLogInitialisation(RDCFile *rdc, bool storeStructuredBuffers); + ReplayStatus ReadLogInitialisation(RDCFile *rdc, bool storeStructuredBuffers); SDFile &GetStructuredFile() { return *m_StructuredFile; } FrameRecord &GetFrameRecord() { return m_FrameRecord; } diff --git a/renderdoc/driver/vulkan/vk_replay.cpp b/renderdoc/driver/vulkan/vk_replay.cpp index b8e42ec02..49f898d8f 100644 --- a/renderdoc/driver/vulkan/vk_replay.cpp +++ b/renderdoc/driver/vulkan/vk_replay.cpp @@ -659,9 +659,9 @@ APIProperties VulkanReplay::GetAPIProperties() return ret; } -void VulkanReplay::ReadLogInitialisation(RDCFile *rdc, bool storeStructuredBuffers) +ReplayStatus VulkanReplay::ReadLogInitialisation(RDCFile *rdc, bool storeStructuredBuffers) { - m_pDriver->ReadLogInitialisation(rdc, storeStructuredBuffers); + return m_pDriver->ReadLogInitialisation(rdc, storeStructuredBuffers); } void VulkanReplay::ReplayLog(uint32_t endEventID, ReplayLogType replayType) @@ -5360,7 +5360,7 @@ ReplayStatus Vulkan_CreateReplayDevice(RDCFile *rdc, IReplayDriver **driver) if(!VkInitParams::IsSupportedVersion(ver)) { RDCERR("Incompatible Vulkan serialise version %llu", ver); - return ReplayStatus::APIUnsupported; + return ReplayStatus::APIIncompatibleVersion; } StreamReader *reader = rdc->ReadSection(sectionIdx); @@ -5428,9 +5428,10 @@ void Vulkan_ProcessStructured(RDCFile *rdc, SDFile &output) return; vulkan.SetStructuredExport(rdc->GetSectionProperties(sectionIdx).version); - vulkan.ReadLogInitialisation(rdc, true); + ReplayStatus status = vulkan.ReadLogInitialisation(rdc, true); - vulkan.GetStructuredFile().swap(output); + if(status == ReplayStatus::Succeeded) + vulkan.GetStructuredFile().swap(output); } static StructuredProcessRegistration VulkanProcessRegistration(RDC_Vulkan, &Vulkan_ProcessStructured); \ No newline at end of file diff --git a/renderdoc/driver/vulkan/vk_replay.h b/renderdoc/driver/vulkan/vk_replay.h index e40ca341a..1e694015d 100644 --- a/renderdoc/driver/vulkan/vk_replay.h +++ b/renderdoc/driver/vulkan/vk_replay.h @@ -163,7 +163,7 @@ public: const VKPipe::State &GetVulkanPipelineState() { return m_VulkanPipelineState; } void FreeTargetResource(ResourceId id); - void ReadLogInitialisation(RDCFile *rdc, bool storeStructuredBuffers); + ReplayStatus ReadLogInitialisation(RDCFile *rdc, bool storeStructuredBuffers); void ReplayLog(uint32_t endEventID, ReplayLogType replayType); const SDFile &GetStructuredFile(); diff --git a/renderdoc/replay/capture_file.cpp b/renderdoc/replay/capture_file.cpp index 52392dbdb..76860ac28 100644 --- a/renderdoc/replay/capture_file.cpp +++ b/renderdoc/replay/capture_file.cpp @@ -113,7 +113,7 @@ public: ReplayStatus OpenFile(const char *filename, const char *filetype); ReplayStatus OpenBuffer(const bytebuf &buffer, const char *filetype); bool CopyFileTo(const char *filename); - + rdcstr ErrorString() { return m_ErrorString; } void Shutdown() { delete this; } ReplaySupport LocalReplaySupport() { return m_Support; } const char *DriverName() { return m_DriverName.c_str(); } @@ -183,7 +183,7 @@ private: SDFile m_StructuredData; - std::string m_DriverName, m_Ident; + std::string m_DriverName, m_Ident, m_ErrorString; ReplaySupport m_Support = ReplaySupport::Unsupported; }; @@ -214,6 +214,7 @@ ReplayStatus CaptureFile::OpenFile(const char *filename, const char *filetype) if(ret != ReplayStatus::Succeeded) { + m_ErrorString = StringFormat::Fmt("Importer '%s' failed to import file.", filetype); delete m_RDC; return ret; } @@ -249,6 +250,7 @@ ReplayStatus CaptureFile::OpenBuffer(const bytebuf &buffer, const char *filetype if(ret != ReplayStatus::Succeeded) { + m_ErrorString = StringFormat::Fmt("Importer '%s' failed to import file.", filetype); delete m_RDC; return ret; } @@ -278,6 +280,8 @@ ReplayStatus CaptureFile::Init() if(!m_RDC) return ReplayStatus::InternalError; + m_ErrorString = m_RDC->ErrorString(); + switch(m_RDC->ErrorCode()) { case ContainerError::FileNotFound: return ReplayStatus::FileNotFound; break; diff --git a/renderdoc/replay/replay_controller.cpp b/renderdoc/replay/replay_controller.cpp index 6c94dfeee..5f25af31e 100644 --- a/renderdoc/replay/replay_controller.cpp +++ b/renderdoc/replay/replay_controller.cpp @@ -1586,7 +1586,10 @@ ReplayStatus ReplayController::PostCreateInit(IReplayDriver *device, RDCFile *rd { m_pDevice = device; - m_pDevice->ReadLogInitialisation(rdc, false); + ReplayStatus status = m_pDevice->ReadLogInitialisation(rdc, false); + + if(status != ReplayStatus::Succeeded) + return status; FetchPipelineState(); @@ -1612,6 +1615,9 @@ ReplayStatus ReplayController::PostCreateInit(IReplayDriver *device, RDCFile *rd m_FrameRecord = m_pDevice->GetFrameRecord(); + if(m_FrameRecord.drawcallList.empty()) + return ReplayStatus::APIReplayFailed; + DrawcallDescription *previous = NULL; SetupDrawcallPointers(&m_Drawcalls, m_FrameRecord.drawcallList, NULL, previous); diff --git a/renderdoc/replay/replay_driver.h b/renderdoc/replay/replay_driver.h index 8300a6b73..81fc7ec91 100644 --- a/renderdoc/replay/replay_driver.h +++ b/renderdoc/replay/replay_driver.h @@ -117,7 +117,7 @@ public: virtual FrameRecord GetFrameRecord() = 0; - virtual void ReadLogInitialisation(RDCFile *rdc, bool storeStructuredBuffers) = 0; + virtual ReplayStatus ReadLogInitialisation(RDCFile *rdc, bool storeStructuredBuffers) = 0; virtual void ReplayLog(uint32_t endEventID, ReplayLogType replayType) = 0; virtual const SDFile &GetStructuredFile() = 0; diff --git a/renderdoc/serialise/rdcfile.cpp b/renderdoc/serialise/rdcfile.cpp index e2e89c20c..c663ea583 100644 --- a/renderdoc/serialise/rdcfile.cpp +++ b/renderdoc/serialise/rdcfile.cpp @@ -223,11 +223,17 @@ struct BinarySectionHeader }; }; -#define RETURNCORRUPT(...) \ - { \ - RDCERR(__VA_ARGS__); \ - m_Error = ContainerError::Corrupt; \ - return; \ +#define SETERROR(error, ...) \ + { \ + m_ErrorString = StringFormat::Fmt(__VA_ARGS__); \ + RDCERR("%s", m_ErrorString.c_str()); \ + m_Error = error; \ + } + +#define RETURNERROR(error, ...) \ + { \ + SETERROR(error, __VA_ARGS__); \ + return; \ } RDCFile::~RDCFile() @@ -245,8 +251,7 @@ void RDCFile::Open(const char *path) // empty path. if(path == NULL || path[0] == 0) { - m_Error = ContainerError::FileNotFound; - return; + RETURNERROR(ContainerError::FileNotFound, "Invalid file path specified"); } RDCLOG("Opening RDCFile %s", path); @@ -260,9 +265,8 @@ void RDCFile::Open(const char *path) if(!m_File) { - RDCERR("Can't open capture file '%s' for read - errno %d", path, errno); - m_Error = ContainerError::FileNotFound; - return; + RETURNERROR(ContainerError::FileNotFound, "Can't open capture file '%s' for read - errno %d", + path, errno); } // try to identify if this is an image @@ -318,31 +322,30 @@ void RDCFile::Init(StreamReader &reader) if(reader.IsErrored()) { - RDCERR("I/O error reading magic number"); - m_Error = ContainerError::FileIO; - return; + RETURNERROR(ContainerError::FileIO, "I/O error reading magic number"); } if(header.magic != MAGIC_HEADER) { - RDCWARN("Invalid capture file. Expected magic %08x, got %08x.", MAGIC_HEADER, - (uint32_t)header.magic); - - m_Error = ContainerError::Corrupt; - return; + RETURNERROR(ContainerError::Corrupt, "Invalid capture file. Expected magic %08x, got %08x.", + MAGIC_HEADER, (uint32_t)header.magic); } m_SerVer = header.version; if(m_SerVer != SERIALISE_VERSION) { - RDCERR( - "Capture file from wrong version. This program (v%s) is logfile version %llu, file is " - "logfile version %llu capture on %s.", - MAJOR_MINOR_VERSION_STRING, SERIALISE_VERSION, header.version, header.progVersion); + if(header.version < V1_0_VERSION) + { + RDCEraseEl(header.progVersion); + memcpy(header.progVersion, "v0.x", sizeof("v0.x")); + } - m_Error = ContainerError::UnsupportedVersion; - return; + RETURNERROR( + ContainerError::UnsupportedVersion, + "Capture file from wrong version. This program (v%s) uses logfile version %u, this file is " + "logfile version %u captured on %s.", + MAJOR_MINOR_VERSION_STRING, SERIALISE_VERSION, header.version, header.progVersion); } BinaryThumbnail thumb; @@ -350,15 +353,13 @@ void RDCFile::Init(StreamReader &reader) if(reader.IsErrored()) { - RDCERR("I/O error reading thumbnail header"); - m_Error = ContainerError::FileIO; - return; + RETURNERROR(ContainerError::FileIO, "I/O error reading thumbnail header"); } // check the thumbnail size is sensible if(thumb.length > 10 * 1024 * 1024) { - RETURNCORRUPT("Thumbnail byte length invalid: %u", thumb.length); + RETURNERROR(ContainerError::Corrupt, "Thumbnail byte length invalid: %u", thumb.length); } byte *thumbData = new byte[thumb.length]; @@ -366,10 +367,8 @@ void RDCFile::Init(StreamReader &reader) if(reader.IsErrored()) { - RDCERR("I/O error reading thumbnail data"); delete[] thumbData; - m_Error = ContainerError::FileIO; - return; + RETURNERROR(ContainerError::FileIO, "I/O error reading thumbnail data"); } CaptureMetaData meta; @@ -377,16 +376,15 @@ void RDCFile::Init(StreamReader &reader) if(reader.IsErrored()) { - RDCERR("I/O error reading capture metadata"); delete[] thumbData; - m_Error = ContainerError::FileIO; - return; + RETURNERROR(ContainerError::FileIO, "I/O error reading capture metadata"); } if(meta.driverNameLength == 0) { delete[] thumbData; - RETURNCORRUPT("Driver name length is invalid, must be at least 1 to contain NULL terminator"); + RETURNERROR(ContainerError::Corrupt, + "Driver name length is invalid, must be at least 1 to contain NULL terminator"); } char *driverName = new char[meta.driverNameLength]; @@ -394,11 +392,9 @@ void RDCFile::Init(StreamReader &reader) if(reader.IsErrored()) { - RDCERR("I/O error reading driver name"); delete[] thumbData; delete[] driverName; - m_Error = ContainerError::FileIO; - return; + RETURNERROR(ContainerError::FileIO, "I/O error reading driver name"); } driverName[meta.driverNameLength - 1] = '\0'; @@ -421,9 +417,7 @@ void RDCFile::Init(StreamReader &reader) if(reader.GetOffset() > header.headerLength) { - RDCERR("I/O error seeking to end of header"); - m_Error = ContainerError::FileIO; - return; + RETURNERROR(ContainerError::FileIO, "I/O error seeking to end of header"); } reader.SkipBytes(header.headerLength - (uint32_t)reader.GetOffset()); @@ -447,10 +441,10 @@ void RDCFile::Init(StreamReader &reader) char c = 0; reader.Read(c); if(reader.IsErrored()) - RETURNCORRUPT("Invalid ASCII data section '%hhx'", c); + RETURNERROR(ContainerError::Corrupt, "Invalid ASCII data section '%hhx'", c); if(reader.AtEnd()) - RETURNCORRUPT("Invalid truncated ASCII data section"); + RETURNERROR(ContainerError::Corrupt, "Invalid truncated ASCII data section"); uint64_t length = 0; @@ -468,7 +462,7 @@ void RDCFile::Init(StreamReader &reader) } if(reader.IsErrored() || reader.AtEnd()) - RETURNCORRUPT("Invalid truncated ASCII data section"); + RETURNERROR(ContainerError::Corrupt, "Invalid truncated ASCII data section"); uint32_t type = 0; @@ -486,7 +480,7 @@ void RDCFile::Init(StreamReader &reader) } if(reader.IsErrored() || reader.AtEnd()) - RETURNCORRUPT("Invalid truncated ASCII data section"); + RETURNERROR(ContainerError::Corrupt, "Invalid truncated ASCII data section"); uint64_t version = 0; @@ -504,7 +498,7 @@ void RDCFile::Init(StreamReader &reader) } if(reader.IsErrored() || reader.AtEnd()) - RETURNCORRUPT("Invalid truncated ASCII data section"); + RETURNERROR(ContainerError::Corrupt, "Invalid truncated ASCII data section"); std::string name; @@ -521,7 +515,7 @@ void RDCFile::Init(StreamReader &reader) } if(reader.IsErrored() || reader.AtEnd()) - RETURNCORRUPT("Invalid truncated ASCII data section"); + RETURNERROR(ContainerError::Corrupt, "Invalid truncated ASCII data section"); SectionProperties props; props.flags = SectionFlags::ASCIIStored; @@ -539,7 +533,8 @@ void RDCFile::Init(StreamReader &reader) reader.SkipBytes(loc.diskLength); if(reader.IsErrored()) - RETURNCORRUPT("Error seeking past ASCII section '%s' data", name.c_str()); + RETURNERROR(ContainerError::Corrupt, "Error seeking past ASCII section '%s' data", + name.c_str()); m_Sections.push_back(props); m_SectionLocations.push_back(loc); @@ -550,7 +545,7 @@ void RDCFile::Init(StreamReader &reader) reader.Read(reading, offsetof(BinarySectionHeader, name) - 1); if(reader.IsErrored()) - RETURNCORRUPT("Error reading binary section header"); + RETURNERROR(ContainerError::Corrupt, "Error reading binary section header"); SectionProperties props; props.flags = sectionHeader.sectionFlags; @@ -561,7 +556,8 @@ void RDCFile::Init(StreamReader &reader) if(sectionHeader.sectionNameLength == 0 || sectionHeader.sectionNameLength > 2 * 1024) { - RETURNCORRUPT("Invalid section name length %u", sectionHeader.sectionNameLength); + RETURNERROR(ContainerError::Corrupt, "Invalid section name length %u", + sectionHeader.sectionNameLength); } props.name.resize(sectionHeader.sectionNameLength - 1); @@ -569,12 +565,12 @@ void RDCFile::Init(StreamReader &reader) reader.Read(&props.name[0], sectionHeader.sectionNameLength - 1); if(reader.IsErrored()) - RETURNCORRUPT("Error reading binary section header"); + RETURNERROR(ContainerError::Corrupt, "Error reading binary section header"); reader.SkipBytes(1); if(reader.IsErrored()) - RETURNCORRUPT("Error reading binary section header"); + RETURNERROR(ContainerError::Corrupt, "Error reading binary section header"); SectionLocation loc; loc.headerOffset = headerOffset; @@ -587,17 +583,18 @@ void RDCFile::Init(StreamReader &reader) reader.SkipBytes(loc.diskLength); if(reader.IsErrored()) - RETURNCORRUPT("Error seeking past binary section '%s' data", props.name.c_str()); + RETURNERROR(ContainerError::Corrupt, "Error seeking past binary section '%s' data", + props.name.c_str()); } else { - RETURNCORRUPT("Unrecognised section type '%hhx'", sectionHeader.isASCII); + RETURNERROR(ContainerError::Corrupt, "Unrecognised section type '%hhx'", sectionHeader.isASCII); } } if(SectionIndex(SectionType::FrameCapture) == -1) { - RETURNCORRUPT("Capture file doesn't have a frame capture"); + RETURNERROR(ContainerError::Corrupt, "Capture file doesn't have a frame capture"); } } @@ -649,9 +646,8 @@ void RDCFile::Create(const char *filename) if(!m_File) { - RDCERR("Can't open capture file '%s' for write, errno %d", filename, errno); - m_Error = ContainerError::FileIO; - return; + RETURNERROR(ContainerError::FileIO, "Can't open capture file '%s' for write, errno %d", + filename, errno); } RDCDEBUG("Opened capture file for write"); @@ -686,9 +682,7 @@ void RDCFile::Create(const char *filename) if(writer.IsErrored()) { - RDCERR("Error writing file header"); - m_Error = ContainerError::FileIO; - return; + RETURNERROR(ContainerError::FileIO, "Error writing file header"); } } @@ -1033,8 +1027,7 @@ StreamWriter *RDCFile::WriteSection(const SectionProperties &props) if(numWritten != offsetof(BinarySectionHeader, name) + name.size() + 1) { - RDCERR("Error seeking to end of file, errno %d", errno); - m_Error = ContainerError::FileIO; + SETERROR(ContainerError::FileIO, "Error seeking to end of file, errno %d", errno); return new StreamWriter(StreamWriter::InvalidStream); } @@ -1097,9 +1090,7 @@ StreamWriter *RDCFile::WriteSection(const SectionProperties &props) if(bytesWritten != 2 * sizeof(uint64_t)) { - RDCERR("Error applying fixup to section header, errno %d", errno); - m_Error = ContainerError::FileIO; - return; + RETURNERROR(ContainerError::FileIO, "Error applying fixup to section header, errno %d", errno); } }); diff --git a/renderdoc/serialise/rdcfile.h b/renderdoc/serialise/rdcfile.h index c66bb927d..b74f2b0e5 100644 --- a/renderdoc/serialise/rdcfile.h +++ b/renderdoc/serialise/rdcfile.h @@ -54,6 +54,10 @@ public: // API that interprets the stream that can be bumped. static const uint32_t SERIALISE_VERSION = 0x00000100; + // this must never be changed - files before this were in the v0.x series and didn't have embedded + // version numbers + static const uint32_t V1_0_VERSION = 0x00000100; + ~RDCFile(); // opens an existing file for read and/or modification. Error if file doesn't exist @@ -70,6 +74,7 @@ public: void Create(const char *filename); ContainerError ErrorCode() const { return m_Error; } + std::string ErrorString() const { return m_ErrorString; } RDCDriver GetDriver() const { return m_Driver; } const std::string &GetDriverName() const { return m_DriverName; } uint64_t GetMachineIdent() const { return m_MachineIdent; } @@ -102,6 +107,7 @@ private: RDCThumb m_Thumb; ContainerError m_Error = ContainerError::NoError; + std::string m_ErrorString; struct SectionLocation { diff --git a/renderdoc/serialise/serialiser.cpp b/renderdoc/serialise/serialiser.cpp index 59063c7a1..947aacad0 100644 --- a/renderdoc/serialise/serialiser.cpp +++ b/renderdoc/serialise/serialiser.cpp @@ -198,7 +198,7 @@ void Serialiser::EndChunk() // only skip remaining bytes if we have a valid length - if we have a length of 0 we wrote this // chunk in 'streaming mode' (see SetStreamingMode and the Writing EndChunk() impl) so there's // nothing to skip. - if(m_ChunkMetadata.length > 0) + if(m_ChunkMetadata.length > 0 && !m_Read->IsErrored()) { // this will be a no-op if the last chunk length was accurate. If it was a // conservative estimate of the length then we'll skip some padding bytes diff --git a/renderdoc/serialise/serialiser.h b/renderdoc/serialise/serialiser.h index f8c3fcd2d..123f3df60 100644 --- a/renderdoc/serialise/serialiser.h +++ b/renderdoc/serialise/serialiser.h @@ -146,6 +146,14 @@ public: uint32_t BeginChunk(uint32_t chunkID, uint32_t byteLength); void EndChunk(); + std::string GetCurChunkName() + { + if(m_ChunkLookup) + return m_ChunkLookup(m_ChunkMetadata.chunkID); + + return StringFormat::Fmt("", m_ChunkMetadata.chunkID); + } + ///////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////// @@ -213,7 +221,10 @@ public: } if(IsReading()) + { + VerifyArraySize(count); byteSize = count; + } if(ExportStructure()) { @@ -343,6 +354,11 @@ public: m_InternalElement = false; } + if(IsReading()) + { + VerifyArraySize(count); + } + if(ExportStructure()) { if(m_StructureStack.empty()) @@ -411,6 +427,11 @@ public: m_InternalElement = false; } + if(IsReading()) + { + VerifyArraySize(count); + } + if(ExportStructure()) { if(m_StructureStack.empty()) @@ -651,7 +672,10 @@ public: } if(IsReading()) + { + VerifyArraySize(count); arrayCount = count; + } if(ExportStructure()) { @@ -750,6 +774,11 @@ public: m_InternalElement = false; } + if(IsReading()) + { + VerifyArraySize(size); + } + if(ExportStructure()) { if(m_StructureStack.empty()) @@ -815,6 +844,11 @@ public: m_InternalElement = false; } + if(IsReading()) + { + VerifyArraySize(size); + } + if(ExportStructure()) { if(m_StructureStack.empty()) @@ -948,6 +982,11 @@ public: m_InternalElement = false; } + if(IsReading()) + { + VerifyArraySize(size); + } + if(ExportStructure()) { if(m_StructureStack.empty()) @@ -1516,6 +1555,32 @@ private: } }; + void VerifyArraySize(uint64_t &count) + { + uint64_t size = m_Read->GetSize(); + + // for streaming, just take 4GB as a 'semi reasonable' upper limit for array sizes + if(m_DataStreaming) + size = 0xFFFFFFFFU; + + if(count > size) + { + RDCERR("Reading invalid array or byte buffer - %llu larger than total stream size %llu.", + count, size); + + // if we owned the previous stream, delete it + if(m_Ownership == Ownership::Stream) + delete m_Read; + + // replace our stream with an invalid one so all subsequent reads fail + m_Read = new StreamReader(StreamReader::InvalidStream); + m_Ownership = Ownership::Stream; + + // set the count to 0 + count = 0; + } + } + void *m_pUserData = NULL; StreamWriter *m_Write = NULL; @@ -1907,3 +1972,12 @@ struct ScopedDeserialiseArray // a member that is a pointer and could be NULL, so needs a hidden 'present' // flag serialised out #define SERIALISE_MEMBER_OPT(obj) ser.SerialiseNullable(#obj, el.obj) + +// simple utility function for inside serialise functions, to check if the serialiser has hit an +// error and then bail, without trying to replay anything. +#define SERIALISE_CHECK_READ_ERRORS() \ + if(ser.IsReading() && ser.IsErrored()) \ + { \ + RDCERR("Serialisation failed in '%s'.", ser.GetCurChunkName().c_str()); \ + return false; \ + } diff --git a/renderdoc/serialise/streamio.cpp b/renderdoc/serialise/streamio.cpp index 7d4db6e1b..b95132647 100644 --- a/renderdoc/serialise/streamio.cpp +++ b/renderdoc/serialise/streamio.cpp @@ -68,6 +68,8 @@ StreamReader::StreamReader(StreamInvalidType) m_BufferHead = m_BufferBase = NULL; m_Ownership = Ownership::Nothing; + + m_HasError = true; } StreamReader::StreamReader(Network::Socket *sock, Ownership own) @@ -336,6 +338,8 @@ StreamWriter::StreamWriter(StreamInvalidType) m_Ownership = Ownership::Nothing; m_InMemory = false; + + m_HasError = true; } StreamWriter::StreamWriter(Network::Socket *sock, Ownership own)