diff --git a/renderdoc/hooks/linux_libentry.cpp b/renderdoc/hooks/linux_libentry.cpp index 3ce771f7e..a59b557c8 100644 --- a/renderdoc/hooks/linux_libentry.cpp +++ b/renderdoc/hooks/linux_libentry.cpp @@ -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()); diff --git a/renderdoc/os/linux/linux_process.cpp b/renderdoc/os/linux/linux_process.cpp index e3eeeeca5..3f1dd9958 100644 --- a/renderdoc/os/linux/linux_process.cpp +++ b/renderdoc/os/linux/linux_process.cpp @@ -24,31 +24,355 @@ #include "os/os_specific.h" +#include "api/app/renderdoc_app.h" #include +#include #include +#include +#include +#include + +#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/ 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()