Cleaned up renderdoccmd, split out necessary bits to OS-specific files

This commit is contained in:
baldurk
2014-06-08 11:00:41 +01:00
parent 772fd94336
commit 0f080d1f44
8 changed files with 713 additions and 624 deletions
+1 -1
View File
@@ -92,7 +92,7 @@ class CrashHandler : public ICrashHandler
wstring cmdline = L"\"";
cmdline += radpath;
cmdline += L"/renderdoccmd.exe\" crashhandle";
cmdline += L"/renderdoccmd.exe\" --crashhandle";
wchar_t *paramsAlloc = new wchar_t[512];
+1 -1
View File
@@ -249,7 +249,7 @@ uint32_t Process::InjectIntoProcess(uint32_t pid, const wchar_t *logfile, const
string optstr = opts->ToString();
_snwprintf_s(paramsAlloc, 255, 255, L"\"%ls\" -cap32for64 %d \"%ls\" \"%hs\"", renderdocPath, pid, logfile, optstr.c_str());
_snwprintf_s(paramsAlloc, 255, 255, L"\"%ls\" --cap32for64 %d \"%ls\" \"%hs\"", renderdocPath, pid, logfile, optstr.c_str());
BOOL retValue = CreateProcessW(NULL, paramsAlloc, &pSec, &tSec, false, CREATE_SUSPENDED, NULL, NULL, &si, &pi);
+1 -1
View File
@@ -8,7 +8,7 @@ MACROS=-DLINUX \
CFLAGS=-c -Wall -Werror -fPIC $(MACROS) -I../renderdoc/
CPPFLAGS=-std=c++11 -g -Wno-unused -Wno-unknown-pragmas -Wno-reorder
LDFLAGS=-L../renderdoc -lrenderdoc -lGL
OBJECTS=linux_specific.o
OBJECTS=renderdoccmd.o renderdoccmd_linux.o
all: bin/renderdoccmd
+152 -610
View File
@@ -22,489 +22,30 @@
* THE SOFTWARE.
******************************************************************************/
#include <winsock2.h>
#include <Ws2tcpip.h>
#include <windows.h>
#include <tchar.h>
#include <string>
#include <vector>
#include <renderdoc.h>
#include "resource.h"
// breakpad
#include "common/windows/http_upload.h"
#include "client/windows/crash_generation/client_info.h"
#include "client/windows/crash_generation/crash_generation_server.h"
#include "miniz.h"
#include <replay/renderdoc.h>
using std::string;
using std::wstring;
using std::vector;
using google_breakpad::ClientInfo;
using google_breakpad::CrashGenerationServer;
bool exitServer = false;
// defined in *_specific.cpp
void DisplayRendererPreview(ReplayRenderer *renderer);
wstring GetUsername();
static HINSTANCE CrashHandlerInst = 0;
static HWND CrashHandlerWnd = 0;
bool uploadReport = false;
bool uploadDump = false;
bool uploadLog = false;
string reproSteps = "";
wstring dump = L"";
vector<google_breakpad::CustomInfoEntry> customInfo;
wstring logpath = L"";
INT_PTR CALLBACK CrashHandlerProc(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam)
int renderdoccmd(int argc, wchar_t **argv)
{
switch (message)
{
case WM_INITDIALOG:
{
HANDLE hIcon = LoadImage(CrashHandlerInst, MAKEINTRESOURCE(IDI_ICON), IMAGE_ICON, 16, 16, 0);
if(hIcon)
{
SendMessage(hDlg, WM_SETICON, ICON_SMALL, (LPARAM)hIcon);
SendMessage(hDlg, WM_SETICON, ICON_BIG, (LPARAM)hIcon);
}
SetDlgItemTextW(hDlg, IDC_WELCOMETEXT,
L"RenderDoc has encountered an unhandled exception or other similar unrecoverable error.\n\n" \
L"If you had captured but not saved a logfile it should still be available in %TEMP% and will not be deleted," \
L"you can try loading it again.\n\n" \
L"A minidump has been created and the RenderDoc diagnostic log (NOT any capture logfile) is available if you would like " \
L"to send them back to be analysed. The path for both is found below if you would like to inspect their contents and censor as appropriate.\n\n" \
L"Neither contains any significant private information, the minidump has some internal states and local memory at the time of the " \
L"crash & thread stacks, etc. The diagnostic log contains diagnostic messages like warnings and errors.\n\n" \
L"The only other information sent is the version of RenderDoc, C# exception callstack, and any notes you include.\n\n" \
L"Any repro steps or notes would be helpful to include with the report. If you'd like to be contacted about the bug " \
L"e.g. for updates about its status just include your email & name. Thank you!\n\n" \
L"Baldur (renderdoc@crytek.com)");
SetDlgItemTextW(hDlg, IDC_DUMPPATH, dump.c_str());
SetDlgItemTextW(hDlg, IDC_LOGPATH, logpath.c_str());
CheckDlgButton(hDlg, IDC_SENDDUMP, BST_CHECKED);
CheckDlgButton(hDlg, IDC_SENDLOG, BST_CHECKED);
}
case WM_SHOWWINDOW:
{
{
RECT r;
GetClientRect(hDlg, &r);
int xPos = (GetSystemMetrics(SM_CXSCREEN) - r.right)/2;
int yPos = (GetSystemMetrics(SM_CYSCREEN) - r.bottom)/2;
SetWindowPos(hDlg, NULL, xPos, yPos, 0, 0, SWP_NOZORDER | SWP_NOSIZE);
}
return (INT_PTR)TRUE;
}
case WM_COMMAND:
{
int ID = LOWORD(wParam);
if(ID == IDC_DONTSEND)
{
EndDialog(hDlg, 0);
return (INT_PTR)TRUE;
}
else if(ID == IDC_SEND)
{
uploadReport = true;
uploadDump = (IsDlgButtonChecked(hDlg, IDC_SENDDUMP) != 0);
uploadLog = (IsDlgButtonChecked(hDlg, IDC_SENDLOG) != 0);
char notes[4097] = {0};
GetDlgItemTextA(hDlg, IDC_NAME, notes, 4096);
notes[4096] = 0;
reproSteps = "Name: ";
reproSteps += notes;
reproSteps += "\n";
memset(notes, 0, 4096);
GetDlgItemTextA(hDlg, IDC_EMAIL, notes, 4096);
notes[4096] = 0;
reproSteps += "Email: ";
reproSteps += notes;
reproSteps += "\n\n";
memset(notes, 0, 4096);
GetDlgItemTextA(hDlg, IDC_REPRO, notes, 4096);
notes[4096] = 0;
reproSteps += notes;
EndDialog(hDlg, 0);
return (INT_PTR)TRUE;
}
}
break;
case WM_QUIT:
case WM_DESTROY:
case WM_CLOSE:
{
EndDialog(hDlg, 0);
return (INT_PTR)TRUE;
}
break;
}
return (INT_PTR)FALSE;
}
static void _cdecl OnClientCrashed(void* context, const ClientInfo* client_info, const wstring* dump_path)
{
if(dump_path)
{
dump = *dump_path;
google_breakpad::CustomClientInfo custom = client_info->GetCustomInfo();
for(size_t i=0; i < custom.count; i++)
customInfo.push_back(custom.entries[i]);
}
exitServer = true;
}
static void _cdecl OnClientExited(void* context, const ClientInfo* client_info)
{
exitServer = true;
}
LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
if(msg == WM_CLOSE) { DestroyWindow(hwnd); return 0; }
if(msg == WM_DESTROY) { PostQuitMessage(0); return 0; }
return DefWindowProc(hwnd, msg, wParam, lParam);
}
void DisplayRendererPreview(ReplayRenderer *renderer, HINSTANCE hInstance)
{
if(renderer == NULL) return;
HWND wnd = 0;
wnd = CreateWindowEx(WS_EX_CLIENTEDGE, L"renderdoccmd", L"renderdoccmd", WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT, CW_USEDEFAULT, 1280, 720,
NULL, NULL, hInstance, NULL);
if(wnd == NULL)
{
return;
}
ShowWindow(wnd, SW_SHOW);
UpdateWindow(wnd);
rdctype::array<FetchTexture> texs;
ReplayRenderer_GetTextures(renderer, &texs);
ReplayOutput *out = ReplayRenderer_CreateOutput(renderer, wnd);
ReplayRenderer_SetFrameEvent(renderer, 0, 10000000);
OutputConfig c;
c.m_Type = eOutputType_TexDisplay;
ReplayOutput_SetOutputConfig(out, c);
for(int32_t i=0; i < texs.count; i++)
{
wstring name(texs[i].name.elems, texs[i].name.elems+texs[i].name.count);
if(name.find(L"Swap") != wstring::npos)
{
TextureDisplay d;
d.texid = texs[i].ID;
d.mip = 0;
d.overlay = eTexOverlay_None;
d.CustomShader = ResourceId();
d.HDRMul = -1.0f;
d.rangemin = 0.0f;
d.rangemax = 1.0f;
d.scale = 1.0f;
d.offx = 0.0f;
d.offy = 0.0f;
d.sliceFace = 0;
d.rawoutput = false;
d.Red = d.Green = d.Blue = true;
d.Alpha = false;
ReplayOutput_SetTextureDisplay(out, d);
break;
}
}
MSG msg;
ZeroMemory(&msg, sizeof(msg));
while(true)
{
// Check to see if any messages are waiting in the queue
while(PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
{
// Translate the message and dispatch it to WindowProc()
TranslateMessage(&msg);
DispatchMessage(&msg);
}
// If the message is WM_QUIT, exit the while loop
if(msg.message == WM_QUIT)
break;
ReplayRenderer_SetFrameEvent(renderer, 0, 10000000+rand()%1000);
ReplayOutput_SetOutputConfig(out, c);
ReplayOutput_Display(out);
Sleep(40);
}
}
int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPWSTR lpCmdLine, int nShowCmd )
{
LPWSTR *argv;
int argc;
argv = CommandLineToArgvW(GetCommandLine(), &argc);
WNDCLASSEX wc;
wc.cbSize = sizeof(WNDCLASSEX);
wc.style = 0;
wc.lpfnWndProc = WndProc;
wc.cbClsExtra = 0;
wc.cbWndExtra = 0;
wc.hInstance = hInstance;
wc.hIcon = LoadIcon(NULL, MAKEINTRESOURCE(IDI_ICON));
wc.hCursor = LoadCursor(NULL, IDC_ARROW);
wc.hbrBackground = (HBRUSH)(COLOR_WINDOW+1);
wc.lpszMenuName = NULL;
wc.lpszClassName = L"renderdoccmd";
wc.hIconSm = LoadIcon(NULL, MAKEINTRESOURCE(IDI_ICON));
if(!RegisterClassEx(&wc))
{
return 1;
}
CrashGenerationServer *crashServer = NULL;
if(argc == 2 && !_wcsicmp(argv[1], L"crashhandle"))
{
wchar_t tempPath[MAX_PATH] = {0};
GetTempPathW(MAX_PATH-1, tempPath);
Sleep(100);
wstring dumpFolder = tempPath;
dumpFolder += L"RenderDocDumps";
CreateDirectoryW(dumpFolder.c_str(), NULL);
crashServer = new CrashGenerationServer(L"\\\\.\\pipe\\RenderDocBreakpadServer",
NULL, NULL, NULL, OnClientCrashed, NULL,
OnClientExited, NULL, NULL, NULL, true,
&dumpFolder);
if (!crashServer->Start()) {
delete crashServer;
crashServer = NULL;
return 1;
}
CrashHandlerInst = hInstance;
CrashHandlerWnd = CreateWindowEx(WS_EX_CLIENTEDGE, L"renderdoccmd", L"renderdoccmd", WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT, CW_USEDEFAULT, 10, 10,
NULL, NULL, hInstance, NULL);
HANDLE hIcon = LoadImage(CrashHandlerInst, MAKEINTRESOURCE(IDI_ICON), IMAGE_ICON, 16, 16, 0);
if(hIcon)
{
SendMessage(CrashHandlerWnd, WM_SETICON, ICON_SMALL, (LPARAM)hIcon);
SendMessage(CrashHandlerWnd, WM_SETICON, ICON_BIG, (LPARAM)hIcon);
}
ShowWindow(CrashHandlerWnd, SW_HIDE);
HANDLE readyEvent = CreateEventA(NULL, TRUE, FALSE, "RENDERDOC_CRASHHANDLE");
if(readyEvent != NULL)
{
SetEvent(readyEvent);
CloseHandle(readyEvent);
}
MSG msg;
ZeroMemory(&msg, sizeof(msg));
while(!exitServer)
{
// Check to see if any messages are waiting in the queue
while(PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
{
// Translate the message and dispatch it to WindowProc()
TranslateMessage(&msg);
DispatchMessage(&msg);
}
// If the message is WM_QUIT, exit the while loop
if(msg.message == WM_QUIT)
break;
Sleep(100);
}
delete crashServer;
crashServer = NULL;
if(!dump.empty())
{
logpath = L"";
string report = "";
for(size_t i=0; i < customInfo.size(); i++)
{
wstring name = customInfo[i].name;
wstring val = customInfo[i].value;
if(name == L"logpath")
{
logpath = val;
}
else if(name == L"ptime")
{
// breakpad uptime, ignore.
}
else
{
report += string(name.begin(), name.end()) + ": " + string(val.begin(), val.end()) + "\n";
}
}
DialogBox(CrashHandlerInst, MAKEINTRESOURCE(IDD_CRASH_HANDLER), CrashHandlerWnd, (DLGPROC)CrashHandlerProc);
report += "\n\nRepro steps/Notes:\n\n" + reproSteps;
{
FILE *f = NULL;
_wfopen_s(&f, logpath.c_str(), L"r");
if(f)
{
fseek(f, 0, SEEK_END);
long filesize = ftell(f);
fseek(f, 0, SEEK_SET);
if(filesize > 10)
{
char *error_log = new char[filesize+1];
memset(error_log, 0, filesize+1);
fread(error_log, 1, filesize, f);
char *managed_callstack = strstr(error_log, "--- Begin C# Exception Data ---");
if(managed_callstack)
{
report += managed_callstack;
report += "\n\n";
}
delete[] error_log;
}
fclose(f);
}
}
if(uploadReport)
{
mz_zip_archive zip;
ZeroMemory(&zip, sizeof(zip));
wstring destzip = dumpFolder + L"\\report.zip";
DeleteFileW(destzip.c_str());
mz_zip_writer_init_wfile(&zip, destzip.c_str(), 0);
mz_zip_writer_add_mem(&zip, "report.txt", report.c_str(), report.length(), MZ_BEST_COMPRESSION);
if(uploadDump && !dump.empty())
mz_zip_writer_add_wfile(&zip, "minidump.dmp", dump.c_str(), NULL, 0, MZ_BEST_COMPRESSION);
if(uploadLog && !logpath.empty())
mz_zip_writer_add_wfile(&zip, "error.log", logpath.c_str(), NULL, 0, MZ_BEST_COMPRESSION);
mz_zip_writer_finalize_archive(&zip);
mz_zip_writer_end(&zip);
int timeout = 10000;
wstring body = L"";
int code = 0;
std::map<wstring, wstring> params;
google_breakpad::HTTPUpload::SendRequest(L"http://renderdoc.org/bugsubmit", params,
dumpFolder + L"\\report.zip", L"report", &timeout, &body, &code);
DeleteFileW(destzip.c_str());
}
}
if(!dump.empty())
DeleteFileW(dump.c_str());
if(!logpath.empty())
DeleteFileW(logpath.c_str());
return 0;
}
HMODULE renderdoc = LoadLibrary(_T("renderdoc.dll"));
if(!renderdoc)
{
OutputDebugString(_T("Couldn't load library!"));
return 1;
}
CaptureOptions opts;
opts.AllowFullscreen = false;
opts.AllowVSync = false;
opts.DelayForDebugger = 5;
opts.HookIntoChildren = true;
if(argc == 2)
if(argc >= 2)
{
// if we were given an exe, inject into it
if(wcsstr(argv[1], L".exe") != NULL)
// fall through and print usage
if(!_wcsicmp(argv[1], L"--help") || !_wcsicmp(argv[1], L"-h"))
{
uint32_t ident = RENDERDOC_ExecuteAndInject(argv[1], NULL, NULL, NULL, &opts, false);
if(ident == 0)
printf("Failed to create & inject\n");
else
printf("Created & injected as %d\n", ident);
return ident;
}
// if we were given a logfile, load it and continually replay it.
else if(wcsstr(argv[1], L".rdc") != NULL)
@@ -514,166 +55,167 @@ int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPWSTR lpCmdLi
auto status = RENDERDOC_CreateReplayRenderer(argv[1], &progress, &renderer);
if(renderer && status == eReplayCreate_Success)
DisplayRendererPreview(renderer, hInstance);
DisplayRendererPreview(renderer);
delete renderer;
return 0;
}
else if(wcsstr(argv[1], L"-replayhost") != NULL)
// replay a logfile
else if(!_wcsicmp(argv[1], L"--replay") || !_wcsicmp(argv[1], L"-r"))
{
if(argc >= 3)
{
float progress = 0.0f;
ReplayRenderer *renderer = NULL;
auto status = RENDERDOC_CreateReplayRenderer(argv[2], &progress, &renderer);
if(renderer && status == eReplayCreate_Success)
DisplayRendererPreview(renderer);
delete renderer;
return 0;
}
else
{
fprintf(stderr, "Not enough parameters to --replay");
}
}
#ifdef WIN32
// if we were given an executable on windows, inject into it
// can't do this on other platforms as there's no nice extension
// and we don't want to just launch any single parameter in case it's
// -h or -help or some other guess/typo
else if(wcsstr(argv[1], L".exe") != NULL)
{
uint32_t ident = RENDERDOC_ExecuteAndInject(argv[1], NULL, NULL, NULL, &opts, false);
if(ident == 0)
fprintf(stderr, "Failed to create & inject\n");
else
fprintf(stderr, "Created & injected as %d\n", ident);
return ident;
}
#endif
// capture a program with default capture options
else if(!_wcsicmp(argv[1], L"--capture") || !_wcsicmp(argv[1], L"-c"))
{
if(argc >= 3)
{
uint32_t ident = RENDERDOC_ExecuteAndInject(argv[2], NULL, NULL, NULL, &opts, false);
if(ident == 0)
fprintf(stderr, "Failed to create & inject to '%ls'\n", argv[2]);
else
fprintf(stderr, "Created & injected '%ls' as %d\n", argv[2], ident);
return ident;
}
else
{
fprintf(stderr, "Not enough parameters to --capture");
}
}
// inject into a running process with default capture options
else if(!_wcsicmp(argv[1], L"--inject") || !_wcsicmp(argv[1], L"-i"))
{
if(argc >= 3)
{
wchar_t *pid = argv[2];
while(*pid == L'"' || iswspace(*pid)) pid++;
uint32_t pidNum = (uint32_t)_wtoi(pid);
uint32_t ident = RENDERDOC_InjectIntoProcess(pidNum, NULL, &opts, false);
if(ident == 0)
printf("Failed to inject to %u\n", pid);
else
printf("Injected to %u as %d\n", pid, ident);
return ident;
}
else
{
fprintf(stderr, "Not enough parameters to --inject");
}
}
// spawn remote replay host
else if(!_wcsicmp(argv[1], L"--replayhost") || !_wcsicmp(argv[1], L"-rh"))
{
RENDERDOC_SpawnReplayHost(NULL);
return 1;
}
}
else if(argc == 3)
{
if(!_wcsicmp(argv[1], L"-inject"))
// replay a logfile over the network on a remote host
else if(!_wcsicmp(argv[1], L"--remotereplay") || !_wcsicmp(argv[1], L"-rr"))
{
wchar_t *pid = argv[2];
while(*pid == L'"' || iswspace(*pid)) pid++;
DWORD pidNum = (DWORD)_wtoi(pid);
uint32_t ident = RENDERDOC_InjectIntoProcess(pidNum, NULL, &opts, false);
if(ident == 0)
printf("Failed to inject\n");
else
printf("Injected as %d\n", ident);
return ident;
}
else
{
uint32_t ident = RENDERDOC_ExecuteAndInject(argv[1], NULL, NULL, argv[2], &opts, false);
if(ident == 0)
printf("Failed to create & inject\n");
else
printf("Created & injected as %d\n", ident);
return ident;
}
}
else if(argc == 4)
{
if(argc == 4 && wcsstr(argv[1], L"-replay") != NULL)
{
RemoteRenderer *remote = NULL;
auto status = RENDERDOC_CreateRemoteReplayConnection(argv[2], &remote);
if(remote == NULL || status != eReplayCreate_Success)
return 1;
float progress = 0.0f;
ReplayRenderer *renderer = NULL;
status = RemoteRenderer_CreateProxyRenderer(remote, 0, argv[3], &progress, &renderer);
if(renderer && status == eReplayCreate_Success)
DisplayRendererPreview(renderer, hInstance);
RemoteRenderer_Shutdown(remote);
return 0;
}
}
else if(argc == 5)
{
if(!_wcsicmp(argv[1], L"-cap32for64"))
{
wchar_t *pid = argv[2];
while(*pid == L'"' || iswspace(*pid)) pid++;
DWORD pidNum = (DWORD)_wtoi(pid);
wchar_t *log = argv[3];
CaptureOptions cmdopts;
string optstring(&argv[4][0], &argv[4][0] + wcslen(argv[4]));
cmdopts.FromString(optstring);
return RENDERDOC_InjectIntoProcess(pidNum, log, &cmdopts, false);
}
else if(!_wcsicmp(argv[1], L"-remotecontrol"))
{
wchar_t *host = argv[2];
wchar_t *ident = argv[3];
while(*ident == L'"' || iswspace(*ident)) ident++;
bool force = argv[4][0] != '0';
DWORD identNum = (DWORD)_wtoi(ident);
wchar_t username[256] = {0};
DWORD usersize = 255;
GetEnvironmentVariableW(L"renderdoc_username", username, usersize);
RemoteAccess *access = RENDERDOC_CreateRemoteAccessConnection(host, identNum, username, force);
if(access == NULL)
if(argc >= 4)
{
printf("Failed to connect\n");
RemoteRenderer *remote = NULL;
auto status = RENDERDOC_CreateRemoteReplayConnection(argv[2], &remote);
if(remote == NULL || status != eReplayCreate_Success)
return 1;
float progress = 0.0f;
ReplayRenderer *renderer = NULL;
status = RemoteRenderer_CreateProxyRenderer(remote, 0, argv[3], &progress, &renderer);
if(renderer && status == eReplayCreate_Success)
DisplayRendererPreview(renderer);
RemoteRenderer_Shutdown(remote);
return 0;
}
else
{
printf("Target: %ls, API: %ls, Busy: %ls\n", RemoteAccess_GetTarget(access), RemoteAccess_GetAPI(access), RemoteAccess_GetBusyClient(access));
fflush(stdout);
volatile bool run = true;
while(run)
{
RemoteMessage msg;
RemoteAccess_ReceiveMessage(access, &msg);
if(msg.Type == eRemoteMsg_Disconnected)
{
printf("Disconnected\n");
RemoteAccess_Shutdown(access);
access = NULL;
break;
}
else if(msg.Type == eRemoteMsg_Busy)
{
printf("Busy: %ls\n", msg.Busy.ClientName.elems);
RemoteAccess_Shutdown(access);
access = NULL;
break;
}
else if(msg.Type == eRemoteMsg_Noop)
{
}
else if(msg.Type == eRemoteMsg_RegisterAPI)
{
printf("Updated - Target: %ls, API: %ls\n", RemoteAccess_GetTarget(access), RemoteAccess_GetAPI(access));
}
else if(msg.Type == eRemoteMsg_NewCapture)
{
printf("Got capture - %d @ %llu, %d bytes of thumbnail\n", msg.NewCapture.ID, msg.NewCapture.timestamp, msg.NewCapture.thumbnail.count);
}
fflush(stdout);
}
if(access)
{
RemoteAccess_Shutdown(access);
access = NULL;
}
fprintf(stderr, "Not enough parameters to --remotereplay");
}
}
// not documented/useful for manual use on the cmd line, used internally
else if(!_wcsicmp(argv[1], L"--cap32for64"))
{
if(argc >= 5)
{
wchar_t *pid = argv[2];
while(*pid == L'"' || iswspace(*pid)) pid++;
fflush(stdout);
uint32_t pidNum = (uint32_t)_wtoi(pid);
return 0;
wchar_t *log = argv[3];
CaptureOptions cmdopts;
string optstring(&argv[4][0], &argv[4][0] + wcslen(argv[4]));
cmdopts.FromString(optstring);
return RENDERDOC_InjectIntoProcess(pidNum, log, &cmdopts, false);
}
else
{
fprintf(stderr, "Not enough parameters to --cap32for64");
}
}
}
fprintf(stderr, "renderdoccmd usage:\n\n");
fprintf(stderr, " <file.rdc> Launch a preview window that replays this logfile and\n");
fprintf(stderr, " displays the backbuffer.\n");
#ifdef WIN32
fprintf(stderr, " <program.exe> Launch a capture of this program with default options.\n");
#endif
fprintf(stderr, "\n");
fprintf(stderr, " -h, --help Displays this help message.\n");
fprintf(stderr, " -c, --capture PROGRAM Launches capture of the program with default options.\n");
fprintf(stderr, " -i, --inject PID Injects into the specified PID to capture.\n");
fprintf(stderr, " -r, --replay LOGFILE Launch a preview window that replays this logfile and\n");
fprintf(stderr, " displays the backbuffer.\n");
fprintf(stderr, " -rh, --replayhost Starts a replay host server that can be used to remotely\n");
fprintf(stderr, " replay logfiles from another machine.\n");
fprintf(stderr, " -rr, --remotereplay HOST LOGFILE Launch a replay of the logfile and display a preview\n");
fprintf(stderr, " window. Use the remote host to replay all commands.\n");
MessageBoxW(NULL, L"renderdoccmd Usage:\n\n" \
L"renderdoccmd.exe \"full path to exe\" [\"path to capture logfile to save to\"]\n" \
L"renderdoccmd.exe \"full path to logfile to replay\"\n" \
L"renderdoccmd.exe -inject \"Process ID\"\n",
L"renderdoccmd", MB_OK);
return 1;
}
+9 -4
View File
@@ -93,8 +93,9 @@
<WarningLevel>Level3</WarningLevel>
<Optimization>Disabled</Optimization>
<PreprocessorDefinitions>WIN32;RENDERDOC_PLATFORM=win32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<AdditionalIncludeDirectories>..\renderdoc\replay\</AdditionalIncludeDirectories>
<AdditionalIncludeDirectories>..\renderdoc\</AdditionalIncludeDirectories>
<RuntimeLibrary>MultiThreadedDLL</RuntimeLibrary>
<TreatWarningAsError>true</TreatWarningAsError>
</ClCompile>
<Link>
<SubSystem>Windows</SubSystem>
@@ -109,8 +110,9 @@
<WarningLevel>Level3</WarningLevel>
<Optimization>Disabled</Optimization>
<PreprocessorDefinitions>WIN32;RENDERDOC_PLATFORM=win32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<AdditionalIncludeDirectories>..\renderdoc\replay\</AdditionalIncludeDirectories>
<AdditionalIncludeDirectories>..\renderdoc\</AdditionalIncludeDirectories>
<RuntimeLibrary>MultiThreadedDLL</RuntimeLibrary>
<TreatWarningAsError>true</TreatWarningAsError>
</ClCompile>
<Link>
<SubSystem>Windows</SubSystem>
@@ -127,7 +129,8 @@
<FunctionLevelLinking>true</FunctionLevelLinking>
<IntrinsicFunctions>true</IntrinsicFunctions>
<PreprocessorDefinitions>WIN32;RENDERDOC_PLATFORM=win32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<AdditionalIncludeDirectories>..\renderdoc\replay\</AdditionalIncludeDirectories>
<AdditionalIncludeDirectories>..\renderdoc\</AdditionalIncludeDirectories>
<TreatWarningAsError>true</TreatWarningAsError>
</ClCompile>
<Link>
<SubSystem>Windows</SubSystem>
@@ -146,7 +149,8 @@
<FunctionLevelLinking>true</FunctionLevelLinking>
<IntrinsicFunctions>true</IntrinsicFunctions>
<PreprocessorDefinitions>WIN32;RENDERDOC_PLATFORM=win32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<AdditionalIncludeDirectories>..\renderdoc\replay\</AdditionalIncludeDirectories>
<AdditionalIncludeDirectories>..\renderdoc\</AdditionalIncludeDirectories>
<TreatWarningAsError>true</TreatWarningAsError>
</ClCompile>
<Link>
<SubSystem>Windows</SubSystem>
@@ -159,6 +163,7 @@
<ItemGroup>
<ClCompile Include="miniz.c" />
<ClCompile Include="renderdoccmd.cpp" />
<ClCompile Include="renderdoccmd_win32.cpp" />
</ItemGroup>
<ItemGroup>
<ResourceCompile Include="renderdoccmd.rc" />
+2 -1
View File
@@ -3,6 +3,7 @@
<ItemGroup>
<ClCompile Include="renderdoccmd.cpp" />
<ClCompile Include="miniz.c" />
<ClCompile Include="renderdoccmd_win32.cpp" />
</ItemGroup>
<ItemGroup>
<ClInclude Include="resource.h">
@@ -28,4 +29,4 @@
<Filter>Resources</Filter>
</ResourceCompile>
</ItemGroup>
</Project>
</Project>
@@ -22,20 +22,67 @@
* THE SOFTWARE.
******************************************************************************/
#include <stdio.h>
#include <string>
#include <string.h>
#include <locale.h>
#include <iconv.h>
#include <replay/renderdoc.h>
using std::wstring;
wstring GetUsername()
{
}
void DisplayRendererPreview(ReplayRenderer *renderer)
{
if(renderer == NULL) return;
// TODO!
}
// symbol defined in libGL but not librenderdoc.
// Forces link of libGL after renderdoc (otherwise all symbols would
// be resolved and libGL wouldn't link, meaning dlsym(RTLD_NEXT) would fai
extern "C" void glXWaitGL();
int main()
int renderdoccmd(int argc, wchar_t **argv);
int main(int argc, char *argv[])
{
RENDERDOC_SpawnReplayHost(NULL);
std::setlocale(LC_CTYPE, "");
volatile bool never_run = false;
if(never_run) glXWaitGL();
volatile bool never_run = false; if(never_run) glXWaitGL();
return 0;
// do any linux-specific setup here
// process any linux-specific arguments here
wchar_t **wargv = new wchar_t*[argc];
iconv_t ic = iconv_open("WCHAR_T", "UTF-8");
for(int i=0; i < argc; i++)
{
size_t len = strlen(argv[i]);
wargv[i] = new wchar_t[len];
char *inbuf = argv[i];
size_t insize = len+1; // include null terminator
char *outbuf = (char *)wargv[i];
size_t outsize = len*sizeof(wchar_t);
iconv(ic, &inbuf, &insize, &outbuf, &outsize);
}
iconv_close(ic);
int ret = renderdoccmd(argc, wargv);
for(int i=0; i < argc; i++)
delete[] wargv[i];
delete[] wargv;
return ret;
}
+494
View File
@@ -0,0 +1,494 @@
/******************************************************************************
* The MIT License (MIT)
*
* Copyright (c) 2014 Crytek
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
******************************************************************************/
#include <windows.h>
#include <string>
#include <vector>
#include <replay/renderdoc.h>
#include "resource.h"
// breakpad
#include "common/windows/http_upload.h"
#include "client/windows/crash_generation/client_info.h"
#include "client/windows/crash_generation/crash_generation_server.h"
#include "miniz.h"
using std::string;
using std::wstring;
using std::vector;
using google_breakpad::ClientInfo;
using google_breakpad::CrashGenerationServer;
bool exitServer = false;
static HINSTANCE CrashHandlerInst = 0;
static HWND CrashHandlerWnd = 0;
bool uploadReport = false;
bool uploadDump = false;
bool uploadLog = false;
string reproSteps = "";
wstring dump = L"";
vector<google_breakpad::CustomInfoEntry> customInfo;
wstring logpath = L"";
HINSTANCE hInstance = NULL;
INT_PTR CALLBACK CrashHandlerProc(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam)
{
switch (message)
{
case WM_INITDIALOG:
{
HANDLE hIcon = LoadImage(CrashHandlerInst, MAKEINTRESOURCE(IDI_ICON), IMAGE_ICON, 16, 16, 0);
if(hIcon)
{
SendMessage(hDlg, WM_SETICON, ICON_SMALL, (LPARAM)hIcon);
SendMessage(hDlg, WM_SETICON, ICON_BIG, (LPARAM)hIcon);
}
SetDlgItemTextW(hDlg, IDC_WELCOMETEXT,
L"RenderDoc has encountered an unhandled exception or other similar unrecoverable error.\n\n" \
L"If you had captured but not saved a logfile it should still be available in %TEMP% and will not be deleted," \
L"you can try loading it again.\n\n" \
L"A minidump has been created and the RenderDoc diagnostic log (NOT any capture logfile) is available if you would like " \
L"to send them back to be analysed. The path for both is found below if you would like to inspect their contents and censor as appropriate.\n\n" \
L"Neither contains any significant private information, the minidump has some internal states and local memory at the time of the " \
L"crash & thread stacks, etc. The diagnostic log contains diagnostic messages like warnings and errors.\n\n" \
L"The only other information sent is the version of RenderDoc, C# exception callstack, and any notes you include.\n\n" \
L"Any repro steps or notes would be helpful to include with the report. If you'd like to be contacted about the bug " \
L"e.g. for updates about its status just include your email & name. Thank you!\n\n" \
L"Baldur (renderdoc@crytek.com)");
SetDlgItemTextW(hDlg, IDC_DUMPPATH, dump.c_str());
SetDlgItemTextW(hDlg, IDC_LOGPATH, logpath.c_str());
CheckDlgButton(hDlg, IDC_SENDDUMP, BST_CHECKED);
CheckDlgButton(hDlg, IDC_SENDLOG, BST_CHECKED);
}
case WM_SHOWWINDOW:
{
{
RECT r;
GetClientRect(hDlg, &r);
int xPos = (GetSystemMetrics(SM_CXSCREEN) - r.right)/2;
int yPos = (GetSystemMetrics(SM_CYSCREEN) - r.bottom)/2;
SetWindowPos(hDlg, NULL, xPos, yPos, 0, 0, SWP_NOZORDER | SWP_NOSIZE);
}
return (INT_PTR)TRUE;
}
case WM_COMMAND:
{
int ID = LOWORD(wParam);
if(ID == IDC_DONTSEND)
{
EndDialog(hDlg, 0);
return (INT_PTR)TRUE;
}
else if(ID == IDC_SEND)
{
uploadReport = true;
uploadDump = (IsDlgButtonChecked(hDlg, IDC_SENDDUMP) != 0);
uploadLog = (IsDlgButtonChecked(hDlg, IDC_SENDLOG) != 0);
char notes[4097] = {0};
GetDlgItemTextA(hDlg, IDC_NAME, notes, 4096);
notes[4096] = 0;
reproSteps = "Name: ";
reproSteps += notes;
reproSteps += "\n";
memset(notes, 0, 4096);
GetDlgItemTextA(hDlg, IDC_EMAIL, notes, 4096);
notes[4096] = 0;
reproSteps += "Email: ";
reproSteps += notes;
reproSteps += "\n\n";
memset(notes, 0, 4096);
GetDlgItemTextA(hDlg, IDC_REPRO, notes, 4096);
notes[4096] = 0;
reproSteps += notes;
EndDialog(hDlg, 0);
return (INT_PTR)TRUE;
}
}
break;
case WM_QUIT:
case WM_DESTROY:
case WM_CLOSE:
{
EndDialog(hDlg, 0);
return (INT_PTR)TRUE;
}
break;
}
return (INT_PTR)FALSE;
}
static void _cdecl OnClientCrashed(void* context, const ClientInfo* client_info, const wstring* dump_path)
{
if(dump_path)
{
dump = *dump_path;
google_breakpad::CustomClientInfo custom = client_info->GetCustomInfo();
for(size_t i=0; i < custom.count; i++)
customInfo.push_back(custom.entries[i]);
}
exitServer = true;
}
static void _cdecl OnClientExited(void* context, const ClientInfo* client_info)
{
exitServer = true;
}
LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
if(msg == WM_CLOSE) { DestroyWindow(hwnd); return 0; }
if(msg == WM_DESTROY) { PostQuitMessage(0); return 0; }
return DefWindowProc(hwnd, msg, wParam, lParam);
}
wstring GetUsername()
{
wchar_t username[256] = {0};
DWORD usersize = 255;
GetUserNameW(username, &usersize);
return username;
}
void DisplayRendererPreview(ReplayRenderer *renderer)
{
if(renderer == NULL) return;
HWND wnd = 0;
wnd = CreateWindowEx(WS_EX_CLIENTEDGE, L"renderdoccmd", L"renderdoccmd", WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT, CW_USEDEFAULT, 1280, 720,
NULL, NULL, hInstance, NULL);
if(wnd == NULL)
{
return;
}
ShowWindow(wnd, SW_SHOW);
UpdateWindow(wnd);
rdctype::array<FetchTexture> texs;
ReplayRenderer_GetTextures(renderer, &texs);
ReplayOutput *out = ReplayRenderer_CreateOutput(renderer, wnd);
ReplayRenderer_SetFrameEvent(renderer, 0, 10000000);
OutputConfig c;
c.m_Type = eOutputType_TexDisplay;
ReplayOutput_SetOutputConfig(out, c);
for(int32_t i=0; i < texs.count; i++)
{
wstring name(texs[i].name.elems, texs[i].name.elems+texs[i].name.count);
if(name.find(L"Swap") != wstring::npos)
{
TextureDisplay d;
d.texid = texs[i].ID;
d.mip = 0;
d.overlay = eTexOverlay_None;
d.CustomShader = ResourceId();
d.HDRMul = -1.0f;
d.rangemin = 0.0f;
d.rangemax = 1.0f;
d.scale = 1.0f;
d.offx = 0.0f;
d.offy = 0.0f;
d.sliceFace = 0;
d.rawoutput = false;
d.Red = d.Green = d.Blue = true;
d.Alpha = false;
ReplayOutput_SetTextureDisplay(out, d);
break;
}
}
MSG msg;
ZeroMemory(&msg, sizeof(msg));
while(true)
{
// Check to see if any messages are waiting in the queue
while(PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
{
// Translate the message and dispatch it to WindowProc()
TranslateMessage(&msg);
DispatchMessage(&msg);
}
// If the message is WM_QUIT, exit the while loop
if(msg.message == WM_QUIT)
break;
ReplayRenderer_SetFrameEvent(renderer, 0, 10000000+rand()%1000);
ReplayOutput_SetOutputConfig(out, c);
ReplayOutput_Display(out);
Sleep(40);
}
}
int renderdoccmd(int argc, wchar_t **argv);
int WINAPI wWinMain(HINSTANCE hInst, HINSTANCE hPrevInstance, LPWSTR lpCmdLine, int nShowCmd )
{
LPWSTR *argv;
int argc;
argv = CommandLineToArgvW(GetCommandLine(), &argc);
hInstance = hInst;
WNDCLASSEX wc;
wc.cbSize = sizeof(WNDCLASSEX);
wc.style = 0;
wc.lpfnWndProc = WndProc;
wc.cbClsExtra = 0;
wc.cbWndExtra = 0;
wc.hInstance = hInstance;
wc.hIcon = LoadIcon(NULL, MAKEINTRESOURCE(IDI_ICON));
wc.hCursor = LoadCursor(NULL, IDC_ARROW);
wc.hbrBackground = (HBRUSH)(COLOR_WINDOW+1);
wc.lpszMenuName = NULL;
wc.lpszClassName = L"renderdoccmd";
wc.hIconSm = LoadIcon(NULL, MAKEINTRESOURCE(IDI_ICON));
if(!RegisterClassEx(&wc))
{
return 1;
}
CrashGenerationServer *crashServer = NULL;
// special WIN32 option for launching the crash handler
if(argc == 2 && !_wcsicmp(argv[1], L"--crashhandle"))
{
wchar_t tempPath[MAX_PATH] = {0};
GetTempPathW(MAX_PATH-1, tempPath);
Sleep(100);
wstring dumpFolder = tempPath;
dumpFolder += L"RenderDocDumps";
CreateDirectoryW(dumpFolder.c_str(), NULL);
crashServer = new CrashGenerationServer(L"\\\\.\\pipe\\RenderDocBreakpadServer",
NULL, NULL, NULL, OnClientCrashed, NULL,
OnClientExited, NULL, NULL, NULL, true,
&dumpFolder);
if (!crashServer->Start()) {
delete crashServer;
crashServer = NULL;
return 1;
}
CrashHandlerInst = hInstance;
CrashHandlerWnd = CreateWindowEx(WS_EX_CLIENTEDGE, L"renderdoccmd", L"renderdoccmd", WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT, CW_USEDEFAULT, 10, 10,
NULL, NULL, hInstance, NULL);
HANDLE hIcon = LoadImage(CrashHandlerInst, MAKEINTRESOURCE(IDI_ICON), IMAGE_ICON, 16, 16, 0);
if(hIcon)
{
SendMessage(CrashHandlerWnd, WM_SETICON, ICON_SMALL, (LPARAM)hIcon);
SendMessage(CrashHandlerWnd, WM_SETICON, ICON_BIG, (LPARAM)hIcon);
}
ShowWindow(CrashHandlerWnd, SW_HIDE);
HANDLE readyEvent = CreateEventA(NULL, TRUE, FALSE, "RENDERDOC_CRASHHANDLE");
if(readyEvent != NULL)
{
SetEvent(readyEvent);
CloseHandle(readyEvent);
}
MSG msg;
ZeroMemory(&msg, sizeof(msg));
while(!exitServer)
{
// Check to see if any messages are waiting in the queue
while(PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
{
// Translate the message and dispatch it to WindowProc()
TranslateMessage(&msg);
DispatchMessage(&msg);
}
// If the message is WM_QUIT, exit the while loop
if(msg.message == WM_QUIT)
break;
Sleep(100);
}
delete crashServer;
crashServer = NULL;
if(!dump.empty())
{
logpath = L"";
string report = "";
for(size_t i=0; i < customInfo.size(); i++)
{
wstring name = customInfo[i].name;
wstring val = customInfo[i].value;
if(name == L"logpath")
{
logpath = val;
}
else if(name == L"ptime")
{
// breakpad uptime, ignore.
}
else
{
report += string(name.begin(), name.end()) + ": " + string(val.begin(), val.end()) + "\n";
}
}
DialogBox(CrashHandlerInst, MAKEINTRESOURCE(IDD_CRASH_HANDLER), CrashHandlerWnd, (DLGPROC)CrashHandlerProc);
report += "\n\nRepro steps/Notes:\n\n" + reproSteps;
{
FILE *f = NULL;
_wfopen_s(&f, logpath.c_str(), L"r");
if(f)
{
fseek(f, 0, SEEK_END);
long filesize = ftell(f);
fseek(f, 0, SEEK_SET);
if(filesize > 10)
{
char *error_log = new char[filesize+1];
memset(error_log, 0, filesize+1);
fread(error_log, 1, filesize, f);
char *managed_callstack = strstr(error_log, "--- Begin C# Exception Data ---");
if(managed_callstack)
{
report += managed_callstack;
report += "\n\n";
}
delete[] error_log;
}
fclose(f);
}
}
if(uploadReport)
{
mz_zip_archive zip;
ZeroMemory(&zip, sizeof(zip));
wstring destzip = dumpFolder + L"\\report.zip";
DeleteFileW(destzip.c_str());
mz_zip_writer_init_wfile(&zip, destzip.c_str(), 0);
mz_zip_writer_add_mem(&zip, "report.txt", report.c_str(), report.length(), MZ_BEST_COMPRESSION);
if(uploadDump && !dump.empty())
mz_zip_writer_add_wfile(&zip, "minidump.dmp", dump.c_str(), NULL, 0, MZ_BEST_COMPRESSION);
if(uploadLog && !logpath.empty())
mz_zip_writer_add_wfile(&zip, "error.log", logpath.c_str(), NULL, 0, MZ_BEST_COMPRESSION);
mz_zip_writer_finalize_archive(&zip);
mz_zip_writer_end(&zip);
int timeout = 10000;
wstring body = L"";
int code = 0;
std::map<wstring, wstring> params;
google_breakpad::HTTPUpload::SendRequest(L"http://renderdoc.org/bugsubmit", params,
dumpFolder + L"\\report.zip", L"report", &timeout, &body, &code);
DeleteFileW(destzip.c_str());
}
}
if(!dump.empty())
DeleteFileW(dump.c_str());
if(!logpath.empty())
DeleteFileW(logpath.c_str());
return 0;
}
return renderdoccmd(argc, argv);
}