Implement callstack gathering and resolution for linux.

* At the moment it only works if the files from the capture are in the
  exact same absolute paths (so generally on the same machine or same
  distro will likely satisfy this, if the library versions are unchanged).
* Also since linux doesn't have a built-in way of verifying that symbols
  match via GUIDs there's no checking yet. We could md5 or otherwise hash
  the files to make sure they match.
* If there is no match at all we don't prompt for better symbols currently.
* I've added a little platform stamp onto the start of the module database
  per-platform just so linux/windows won't try to load each other's symbols
  as otherwise the logs are cross-platform. I didn't bump the serialise
  version as this is a rarely used (and transient, typically) feature, so
  not worth adding backwards compatiblity code. Symbols from an old windows
  log won't resolve but that's not a problem. Symbols from a new log won't
  load and will probably crash in an old version, but I'm not worried about
  that.
This commit is contained in:
baldurk
2015-01-03 19:30:06 +00:00
parent 69bbc5e779
commit dc8ed153ac
2 changed files with 321 additions and 14 deletions
+290 -6
View File
@@ -22,15 +22,299 @@
* THE SOFTWARE.
******************************************************************************/
#include "os/os_specific.h"
#include <execinfo.h>
#include <stdio.h>
#include <string.h>
#include <vector>
#include <map>
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<LookupModule> 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<uint64_t, Callstack::AddressDetails>(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<LookupModule> m_Modules;
std::map<uint64_t, Callstack::AddressDetails> 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<LookupModule> 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);
}
};
+31 -8
View File
@@ -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();