mirror of
https://github.com/baldurk/renderdoc.git
synced 2026-05-05 01:20:42 +00:00
Implement Process::CreateAndInjectIntoProcess on linux
* It's not exactly feature parity but it's a start, and any UI would use this under the hood. * This means at least linux has a semi user-friendly way to launch and capture programs - it's limited but you can just use $ ./bin/renderdoccmd -c /path/to/program "argument string" from the root of the repository. * Logfile and capture options (which can't be set yet, but you could easily hack in something to set them in renderdoccmd.cpp) are passed via environment variables to the child process.
This commit is contained in:
@@ -47,6 +47,24 @@ void library_loaded()
|
||||
else
|
||||
{
|
||||
RenderDoc::Inst().Initialise();
|
||||
|
||||
char *logfile = getenv("RENDERDOC_LOGFILE");
|
||||
char *opts = getenv("RENDERDOC_CAPTUREOPTS");
|
||||
|
||||
if(opts)
|
||||
{
|
||||
string optstr = opts;
|
||||
|
||||
CaptureOptions optstruct;
|
||||
optstruct.FromString(optstr);
|
||||
|
||||
RenderDoc::Inst().SetCaptureOptions(&optstruct);
|
||||
}
|
||||
|
||||
if(logfile)
|
||||
{
|
||||
RenderDoc::Inst().SetLogFile(logfile);
|
||||
}
|
||||
|
||||
RDCLOG("Loading into %s", curfile.c_str());
|
||||
|
||||
|
||||
@@ -24,31 +24,355 @@
|
||||
|
||||
|
||||
#include "os/os_specific.h"
|
||||
#include "api/app/renderdoc_app.h"
|
||||
#include <sys/types.h>
|
||||
#include <sys/wait.h>
|
||||
#include <unistd.h>
|
||||
#include <dlfcn.h>
|
||||
#include <string.h>
|
||||
#include <libgen.h>
|
||||
|
||||
#include "serialise/string_utils.h"
|
||||
|
||||
uint32_t Process::InjectIntoProcess(uint32_t pid, const char *logfile, const CaptureOptions *opts, bool waitForExit)
|
||||
{
|
||||
RDCUNIMPLEMENTED();
|
||||
RDCUNIMPLEMENTED("Injecting into already running processes on linux");
|
||||
return 0;
|
||||
}
|
||||
|
||||
uint32_t Process::CreateAndInjectIntoProcess(const char *app, const char *workingDir, const char *cmdLine,
|
||||
const char *logfile, const CaptureOptions *opts, bool waitForExit)
|
||||
{
|
||||
RDCUNIMPLEMENTED();
|
||||
return 0;
|
||||
if(app == NULL || app[0] == 0)
|
||||
{
|
||||
RDCERR("Invalid empty 'app'");
|
||||
return 0;
|
||||
}
|
||||
|
||||
char **envp = NULL;
|
||||
|
||||
int nenv = 0;
|
||||
for(; environ[nenv]; nenv++);
|
||||
|
||||
const int numEnvAdd = 4;
|
||||
// LD_LIBRARY_PATH
|
||||
// LD_PRELOAD
|
||||
// RENDERDOC_CAPTUREOPTS
|
||||
// RENDERDOC_LOGFILE
|
||||
|
||||
// might find these already existant in the environment
|
||||
bool libpath = false;
|
||||
bool preload = false;
|
||||
|
||||
nenv += 1+numEnvAdd; // account for terminating NULL we need to replicate, and up to N additional environment varibales
|
||||
|
||||
envp = new char*[nenv];
|
||||
|
||||
string localpath;
|
||||
FileIO::GetExecutableFilename(localpath);
|
||||
localpath = dirname(localpath);
|
||||
|
||||
int i=0; int srci=0;
|
||||
for(; i < nenv; srci++)
|
||||
{
|
||||
if(environ[srci] == NULL)
|
||||
{
|
||||
envp[i] = NULL;
|
||||
break;
|
||||
}
|
||||
|
||||
size_t len = strlen(environ[srci])+1;
|
||||
|
||||
if(!strncmp(environ[srci], "LD_LIBRARY_PATH=", sizeof("LD_LIBRARY_PATH=")-1))
|
||||
{
|
||||
libpath = true;
|
||||
envp[i] = new char[len+localpath.length()+1];
|
||||
memcpy(envp[i], environ[srci], len);
|
||||
strcat(envp[i], ":");
|
||||
strcat(envp[i], localpath.c_str());
|
||||
}
|
||||
else if(!strncmp(environ[srci], "LD_PRELOAD=", sizeof("LD_PRELOAD=")-1))
|
||||
{
|
||||
preload = true;
|
||||
envp[i] = new char[len+sizeof("librenderdoc.so")];
|
||||
memcpy(envp[i], environ[srci], len);
|
||||
strcat(envp[i], ":librenderdoc.so");
|
||||
}
|
||||
else if(!strncmp(environ[srci], "RENDERDOC_", sizeof("RENDERDOC_")-1))
|
||||
{
|
||||
// skip this variable entirely
|
||||
continue;
|
||||
}
|
||||
else
|
||||
{
|
||||
// basic copy
|
||||
envp[i] = new char[len];
|
||||
memcpy(envp[i], environ[srci], len);
|
||||
}
|
||||
|
||||
i++;
|
||||
}
|
||||
|
||||
if(!libpath)
|
||||
{
|
||||
string e = StringFormat::Fmt("LD_LIBRARY_PATH=%s", localpath.c_str());
|
||||
envp[i] = new char[e.length()+1];
|
||||
memcpy(envp[i], e.c_str(), e.length()+1);
|
||||
i++;
|
||||
envp[i] = NULL;
|
||||
}
|
||||
|
||||
if(!preload)
|
||||
{
|
||||
string e = StringFormat::Fmt("LD_PRELOAD=%s/librenderdoc.so", localpath.c_str());
|
||||
envp[i] = new char[e.length()+1];
|
||||
memcpy(envp[i], e.c_str(), e.length()+1);
|
||||
i++;
|
||||
envp[i] = NULL;
|
||||
}
|
||||
|
||||
if(opts)
|
||||
{
|
||||
string e = StringFormat::Fmt("RENDERDOC_CAPTUREOPTS=%s", opts->ToString().c_str());
|
||||
envp[i] = new char[e.length()+1];
|
||||
memcpy(envp[i], e.c_str(), e.length()+1);
|
||||
i++;
|
||||
envp[i] = NULL;
|
||||
}
|
||||
|
||||
if(logfile)
|
||||
{
|
||||
string e = StringFormat::Fmt("RENDERDOC_LOGFILE=%s", logfile);
|
||||
envp[i] = new char[e.length()+1];
|
||||
memcpy(envp[i], e.c_str(), e.length()+1);
|
||||
i++;
|
||||
envp[i] = NULL;
|
||||
}
|
||||
|
||||
int argc = 0;
|
||||
char *emptyargv[] = { NULL };
|
||||
char **argv = emptyargv;
|
||||
|
||||
const char *c = cmdLine;
|
||||
|
||||
// parse command line into argv[], similar to how bash would
|
||||
if(cmdLine)
|
||||
{
|
||||
argc = 1;
|
||||
|
||||
// get a rough upper bound on the number of arguments
|
||||
while(*c)
|
||||
{
|
||||
if(*c == ' ' || *c == '\t') argc++;
|
||||
c++;
|
||||
}
|
||||
|
||||
argv = new char*[argc+2];
|
||||
|
||||
c = cmdLine;
|
||||
|
||||
string a;
|
||||
|
||||
argc = 0; // current argument we're fetching
|
||||
|
||||
// argv[0] is the application name, by convention
|
||||
size_t len = strlen(app)+1;
|
||||
argv[argc] = new char[len];
|
||||
strcpy(argv[argc], app);
|
||||
|
||||
argc++;
|
||||
|
||||
bool dquot = false, squot = false; // are we inside ''s or ""s
|
||||
while(*c)
|
||||
{
|
||||
if(!dquot && !squot && (*c == ' ' || *c == '\t'))
|
||||
{
|
||||
if(!a.empty())
|
||||
{
|
||||
// if we've fetched some number of non-space characters
|
||||
argv[argc] = new char[a.length()+1];
|
||||
memcpy(argv[argc], a.c_str(), a.length()+1);
|
||||
argc++;
|
||||
}
|
||||
|
||||
a = "";
|
||||
}
|
||||
else if(!dquot && *c == '"')
|
||||
{
|
||||
dquot = true;
|
||||
}
|
||||
else if(!squot && *c == '\'')
|
||||
{
|
||||
squot = true;
|
||||
}
|
||||
else if(dquot && *c == '"')
|
||||
{
|
||||
dquot = false;
|
||||
}
|
||||
else if(squot && *c == '\'')
|
||||
{
|
||||
squot = false;
|
||||
}
|
||||
else if(squot)
|
||||
{
|
||||
// single quotes don't escape, just copy literally until we leave single quote mode
|
||||
a.push_back(*c);
|
||||
}
|
||||
else if(dquot)
|
||||
{
|
||||
// handle escaping
|
||||
if(*c == '\\')
|
||||
{
|
||||
c++;
|
||||
if(*c)
|
||||
{
|
||||
a.push_back(*c);
|
||||
}
|
||||
else
|
||||
{
|
||||
RDCERR("Malformed command line:\n%s", cmdLine);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
a.push_back(*c);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
a.push_back(*c);
|
||||
}
|
||||
|
||||
c++;
|
||||
}
|
||||
|
||||
if(!a.empty())
|
||||
{
|
||||
// if we've fetched some number of non-space characters
|
||||
argv[argc] = new char[a.length()+1];
|
||||
memcpy(argv[argc], a.c_str(), a.length()+1);
|
||||
argc++;
|
||||
}
|
||||
|
||||
argv[argc] = NULL;
|
||||
|
||||
if(squot || dquot)
|
||||
{
|
||||
RDCERR("Malformed command line\n%s", cmdLine);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
int ret = 0;
|
||||
|
||||
pid_t childPid = fork();
|
||||
if(childPid == 0)
|
||||
{
|
||||
if(workingDir)
|
||||
{
|
||||
chdir(workingDir);
|
||||
}
|
||||
else
|
||||
{
|
||||
string exedir = app;
|
||||
chdir(dirname((char *)exedir.c_str()));
|
||||
}
|
||||
|
||||
execve(app, argv, envp);
|
||||
exit(0);
|
||||
}
|
||||
else if(childPid > 0)
|
||||
{
|
||||
// wait for child to have /proc/<pid> and read out tcp socket
|
||||
usleep(1000);
|
||||
|
||||
string procfile = StringFormat::Fmt("/proc/%d/net/tcp", (int)childPid);
|
||||
|
||||
// try for a little while for the /proc entry to appear
|
||||
for(int retry=0; retry < 10; retry++)
|
||||
{
|
||||
// back-off for each retry
|
||||
usleep(1000 + 500 * retry);
|
||||
|
||||
FILE *f = FileIO::fopen(procfile.c_str(), "r");
|
||||
|
||||
if(f == NULL)
|
||||
{
|
||||
// try again in a bit
|
||||
continue;
|
||||
}
|
||||
|
||||
// read through the proc file to check for an open listen socket
|
||||
while(ret == 0 && !feof(f))
|
||||
{
|
||||
const size_t sz = 512;
|
||||
char line[sz];line[sz-1] = 0;
|
||||
fgets(line, sz-1, f);
|
||||
|
||||
int socketnum = 0, hexip = 0, hexport = 0;
|
||||
int num = sscanf(line, " %d: %x:%x", &socketnum, &hexip, &hexport);
|
||||
|
||||
// find open listen socket on 0.0.0.0:port
|
||||
if(num == 3 && hexip == 0 &&
|
||||
hexport >= RenderDoc_FirstCaptureNetworkPort &&
|
||||
hexport <= RenderDoc_LastCaptureNetworkPort)
|
||||
{
|
||||
ret = hexport;
|
||||
}
|
||||
}
|
||||
|
||||
FileIO::fclose(f);
|
||||
}
|
||||
|
||||
if(waitForExit)
|
||||
{
|
||||
int dummy = 0;
|
||||
waitpid(childPid, &dummy, 0);
|
||||
}
|
||||
}
|
||||
|
||||
char **argv_delete = argv;
|
||||
char **envp_delete = envp;
|
||||
|
||||
if(argv != emptyargv)
|
||||
{
|
||||
while(*argv)
|
||||
{
|
||||
delete[] *argv;
|
||||
argv++;
|
||||
}
|
||||
|
||||
delete[] argv_delete;
|
||||
}
|
||||
|
||||
while(*envp)
|
||||
{
|
||||
delete[] *envp;
|
||||
envp++;
|
||||
}
|
||||
|
||||
delete[] envp_delete;
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
void Process::StartGlobalHook(const char *pathmatch, const char *logfile, const CaptureOptions *opts)
|
||||
{
|
||||
RDCUNIMPLEMENTED();
|
||||
RDCUNIMPLEMENTED("Global hooking of all processes on linux");
|
||||
}
|
||||
|
||||
void *Process::GetFunctionAddress(const char *module, const char *function)
|
||||
{
|
||||
RDCUNIMPLEMENTED();
|
||||
return NULL;
|
||||
void *handle = dlopen(module, RTLD_NOW);
|
||||
|
||||
if(handle == NULL) return NULL;
|
||||
|
||||
void *ret = dlsym(handle, function);
|
||||
|
||||
dlclose(handle);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
uint32_t Process::GetCurrentPID()
|
||||
|
||||
Reference in New Issue
Block a user