diff --git a/renderdoc/os/linux/linux_callstack.cpp b/renderdoc/os/linux/linux_callstack.cpp index f3e758600..be59851f7 100644 --- a/renderdoc/os/linux/linux_callstack.cpp +++ b/renderdoc/os/linux/linux_callstack.cpp @@ -22,15 +22,299 @@ * THE SOFTWARE. ******************************************************************************/ - #include "os/os_specific.h" +#include +#include +#include + +#include +#include + +void *renderdocBase = NULL; +void *renderdocEnd = NULL; + +class LinuxCallstack : public Callstack::Stackwalk +{ + public: + LinuxCallstack() + { + RDCEraseEl(addrs); + numLevels = 0; + Collect(); + } + LinuxCallstack(uint64_t *calls, size_t num) + { + numLevels = (int)RDCMIN(ARRAY_COUNT(addrs), num); + for(int i=0; i < numLevels; i++) + addrs[i] = calls[i]; + } + ~LinuxCallstack() {} + + size_t NumLevels() const { return size_t(numLevels); } + const uint64_t *GetAddrs() const { return addrs; } + private: + LinuxCallstack(const Callstack::Stackwalk &other); + + void Collect() + { + void *addrs_ptr[ARRAY_COUNT(addrs)]; + + numLevels = backtrace(addrs_ptr, ARRAY_COUNT(addrs)); + + int offs = 0; + // if we want to trim levels of the stack, we can do that here + // by incrementing offs and decrementing numLevels + while(numLevels > 0 && addrs_ptr[offs] >= renderdocBase && addrs_ptr[offs] < renderdocEnd) + { + offs++; + numLevels--; + } + + for(int i=0; i < numLevels; i++) + addrs[i] = (uint64_t)addrs_ptr[i+offs]; + } + + uint64_t addrs[128]; + int numLevels; +}; + namespace Callstack { - void Init() {} - Stackwalk *Collect() { RDCUNIMPLEMENTED(); return NULL; } - Stackwalk *Load(uint64_t *calls, size_t numLevels) { RDCUNIMPLEMENTED(); return NULL; } - StackResolver *MakeResolver(char *moduleDB, size_t DBSize, string pdbSearchPaths, volatile bool *killSignal) { RDCUNIMPLEMENTED(); return NULL; } + void Init() + { + // look for our own line + FILE *f = FileIO::fopen("/proc/self/maps", "r"); - bool GetLoadedModules(char *&buf, size_t &size) { RDCUNIMPLEMENTED(); return false; } + while(!feof(f)) + { + char line[512] = {0}; + if(fgets(line, 511, f)) + { + if(strstr(line, "librenderdoc") && strstr(line, "r-xp")) + { + sscanf(line, "%p-%p", &renderdocBase, &renderdocEnd); + break; + } + } + } + + FileIO::fclose(f); + } + + Stackwalk *Collect() + { + return new LinuxCallstack(); + } + + Stackwalk *Load(uint64_t *calls, size_t numLevels) + { + return new LinuxCallstack(calls, numLevels); + } + + bool GetLoadedModules(char *&buf, size_t &size) + { + // we just dump the whole file rather than pre-parsing, that way we can improve + // parsing without needing to recapture + FILE *f = FileIO::fopen("/proc/self/maps", "r"); + + if(buf) + { + buf[0] = 'L'; + buf[1] = 'N'; + buf[2] = 'U'; + buf[3] = 'X'; + buf[4] = 'C'; + buf[5] = 'A'; + buf[6] = 'L'; + buf[7] = 'L'; + } + + size += 8; + + char dummy[512]; + + while(!feof(f)) + { + char *readbuf = buf ? buf + size : dummy; + size += FileIO::fread(readbuf, 1, 512, f); + } + + FileIO::fclose(f); + + return true; + } + + struct LookupModule + { + uint64_t base; + uint64_t end; + char path[2048]; + }; + + class LinuxResolver : public Callstack::StackResolver + { + public: + LinuxResolver(vector modules) + { + m_Modules = modules; + } + + Callstack::AddressDetails GetAddr(uint64_t addr) + { + EnsureCached(addr); + + return m_Cache[addr]; + } + + private: + void EnsureCached(uint64_t addr) + { + auto it = m_Cache.insert(std::pair(addr, Callstack::AddressDetails())); + if(!it.second) return; + + Callstack::AddressDetails &ret = it.first->second; + + ret.filename = "Unknown"; + ret.line = 0; + ret.function = StringFormat::Fmt("0x%08llx", addr); + + for(size_t i=0; i < m_Modules.size(); i++) + { + if(addr >= m_Modules[i].base && addr < m_Modules[i].end) + { + uint64_t relative = addr - m_Modules[i].base; + string cmd = StringFormat::Fmt("addr2line -j.text -fCe \"%s\" 0x%llx", m_Modules[i].path, relative); + + FILE *f = ::popen(cmd.c_str(), "r"); + + char result[2048] = {0}; + fread(result, 1, 2047, f); + + fclose(f); + + char *line2 = strchr(result, '\n'); + if(line2) { *line2 = 0; line2++; } + + ret.function = result; + + if(line2) + { + char *last = line2 + strlen(line2) - 1; + uint32_t mul = 1; ret.line = 0; + while(*last >= '0' && *last <= '9') + { + ret.line += mul * (uint32_t(*last)-uint32_t('0')); + *last = 0; + last--; + mul *= 10; + } + if(*last == ':') *last = 0; + + ret.filename = line2; + } + + break; + } + } + } + + std::vector m_Modules; + std::map m_Cache; + }; + + StackResolver *MakeResolver(char *moduleDB, size_t DBSize, string pdbSearchPaths, volatile bool *killSignal) + { + // we look in the original locations for the files, we don't prompt if we can't + // find the file, or the file doesn't have symbols (and we don't validate that + // the file is the right version). A good option for doing this would be + // http://github.com/mlabbe/nativefiledialog + + bool valid = true; + if(memcmp(moduleDB, "LNUXCALL", 8)) + { + RDCWARN("Can't load callstack resolve for this log. Possibly from another platform?"); + valid = false; + } + + char *search = moduleDB+8; + + vector modules; + + while(valid && search && size_t(search-moduleDB) < DBSize) + { + if(killSignal && *killSignal) break; + + // find .text segments + { + long unsigned int base = 0, end = 0; + + int inode = 0; + int offs = 0; + // base-end perms offset devid inode offs + int num = sscanf(search, "%lx-%lx r-xp %*x %*x:%*x %d %n", &base, &end, &inode, &offs); + + // we don't care about inode actually, we ust use it to verify that + // we read all 3 params (and so perms == r-xp) + if(num == 3 && offs > 0) + { + LookupModule mod = {0}; + + mod.base = (uint64_t)base; + mod.end = (uint64_t)end; + + search += offs; + while(size_t(search-moduleDB) < DBSize && (*search == ' ' || *search == '\t')) search++; + + if(size_t(search-moduleDB) < DBSize && *search != '[' && *search != 0 && *search != '\n') + { + size_t n = ARRAY_COUNT(mod.path)-1; + mod.path[n] = 0; + for(size_t i=0; i < n; i++) + { + if(search[i] == 0 || search[i] == '\n') { mod.path[i] = 0; break; } + if(search + i >= moduleDB + DBSize) { mod.path[i] = 0; break; } + mod.path[i] = search[i]; + } + + // addr2line wants offsets relative to text section, have to find offset of .text section in this + // mmapped section + + int textoffs = 0; + string cmd = StringFormat::Fmt("readelf -WS \"%s\"", mod.path); + + FILE *f = ::popen(cmd.c_str(), "r"); + + char result[4096] = {0}; + fread(result, 1, 4095, f); + + fclose(f); + + // find .text line + char *find = strstr(result, ".text"); + + if(find) + { + find += 5; + + while(isalpha(*find) || isspace(*find)) find++; + + // virtual address is listed first, we want the offset + sscanf(find, "%*x %x", &textoffs); + + mod.base += textoffs; + + modules.push_back(mod); + } + } + } + } + + if(search >= (char *)(moduleDB+DBSize)) break; + + search = strchr(search, '\n'); if(search) search++; + } + + return new LinuxResolver(modules); + } }; diff --git a/renderdoc/os/win32/win32_callstack.cpp b/renderdoc/os/win32/win32_callstack.cpp index a66cc0c69..f04af3d47 100644 --- a/renderdoc/os/win32/win32_callstack.cpp +++ b/renderdoc/os/win32/win32_callstack.cpp @@ -537,24 +537,31 @@ Win32CallstackResolver::Win32CallstackResolver(char *moduleDB, size_t DBSize, st split(widepdbsearch, pdbRememberedPaths, L';'); - EnumModChunk *chunk = (EnumModChunk *)moduleDB; - WCHAR *modName = (WCHAR *)(moduleDB+sizeof(EnumModChunk)); - pdblocateProcess = NULL; pdblocatePipe = NULL; OpenPdblocateHandle(); + + if(memcmp(moduleDB, "WN32CALL", 8)) + { + RDCWARN("Can't load callstack resolve for this log. Possibly from another platform?"); + return; + } + + char *chunks = moduleDB + 8; + char *end = chunks + DBSize - 8; + + EnumModChunk *chunk = (EnumModChunk *)(chunks); + WCHAR *modName = (WCHAR *)(chunks+sizeof(EnumModChunk)); if(pdblocatePipe == NULL) return; // loop over all our modules - for(char *end = moduleDB + DBSize; - moduleDB < end; - moduleDB += sizeof(EnumModChunk)+(chunk->imageNameLen)*sizeof(WCHAR) ) + for(; chunks < end; chunks += sizeof(EnumModChunk)+(chunk->imageNameLen)*sizeof(WCHAR) ) { - chunk = (EnumModChunk *)moduleDB; - modName = (WCHAR *)(moduleDB+sizeof(EnumModChunk)); + chunk = (EnumModChunk *)chunks; + modName = (WCHAR *)(chunks+sizeof(EnumModChunk)); if(killSignal && *killSignal) break; @@ -770,6 +777,22 @@ namespace Callstack EnumBuf e; e.bufPtr = buf; e.size = 0; + + if(buf) + { + buf[0] = 'W'; + buf[1] = 'N'; + buf[2] = '3'; + buf[3] = '2'; + buf[4] = 'C'; + buf[5] = 'A'; + buf[6] = 'L'; + buf[7] = 'L'; + + e.bufPtr += 8; + } + + e.size += 8; bool inited = InitDbgHelp();