Bug 1964113 - Remove LUL from the baseprofiler r=mstange,profiler-reviewers
Apparently native stackwalking on linux was removed in Bug 1658232. And after that patch we never had LUL in the baseprofiler. So this patch removes the copy of the LUL code that we don't use. Differential Revision: https://phabricator.services.mozilla.com/D247642
This commit is contained in:
committed by
canaltinova@gmail.com
parent
6285e9bd4e
commit
3c32b37bd4
@@ -401,27 +401,6 @@ SamplerThread::SamplerThread(PSLockRef aLock, uint32_t aActivityGeneration,
|
||||
mActivityGeneration(aActivityGeneration),
|
||||
mIntervalMicroseconds(
|
||||
std::max(1, int(floor(aIntervalMilliseconds * 1000 + 0.5)))) {
|
||||
#if defined(USE_LUL_STACKWALK)
|
||||
lul::LUL* lul = CorePS::Lul(aLock);
|
||||
if (!lul && ProfilerFeature::HasStackWalkEnabled(aFeatures)) {
|
||||
CorePS::SetLul(aLock, MakeUnique<lul::LUL>(logging_sink_for_LUL));
|
||||
// Read all the unwind info currently available.
|
||||
lul = CorePS::Lul(aLock);
|
||||
read_procmaps(lul);
|
||||
|
||||
// Switch into unwind mode. After this point, we can't add or remove any
|
||||
// unwind info to/from this LUL instance. The only thing we can do with
|
||||
// it is Unwind() calls.
|
||||
lul->EnableUnwinding();
|
||||
|
||||
// Has a test been requested?
|
||||
if (getenv("MOZ_PROFILER_LUL_TEST")) {
|
||||
int nTests = 0, nTestsPassed = 0;
|
||||
RunLulUnitTests(&nTests, &nTestsPassed, lul);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
// Start the sampling thread. It repeatedly sends a SIGPROF signal. Sending
|
||||
// the signal ourselves instead of relying on itimer provides much better
|
||||
// accuracy.
|
||||
|
||||
@@ -104,45 +104,6 @@
|
||||
# define USE_FRAME_POINTER_STACK_WALK
|
||||
#endif
|
||||
|
||||
// No stack-walking in baseprofiler on linux, android, bsd.
|
||||
// APIs now make it easier to capture backtraces from the Base Profiler, which
|
||||
// is currently not supported on these platform, and would lead to a MOZ_CRASH
|
||||
// in REGISTERS_SYNC_POPULATE(). `#if 0` added in bug 1658232, follow-up bugs
|
||||
// should be referenced in meta bug 1557568.
|
||||
#if 0
|
||||
// Android builds use the ARM Exception Handling ABI to unwind.
|
||||
# if defined(GP_PLAT_arm_linux) || defined(GP_PLAT_arm_android)
|
||||
# define HAVE_NATIVE_UNWIND
|
||||
# define USE_EHABI_STACKWALK
|
||||
# include "EHABIStackWalk.h"
|
||||
# endif
|
||||
|
||||
// Linux/BSD builds use LUL, which uses DWARF info to unwind stacks.
|
||||
# if defined(GP_PLAT_amd64_linux) || defined(GP_PLAT_x86_linux) || \
|
||||
defined(GP_PLAT_amd64_android) || defined(GP_PLAT_x86_android) || \
|
||||
defined(GP_PLAT_mips64_linux) || defined(GP_PLAT_arm64_linux) || \
|
||||
defined(GP_PLAT_arm64_android) || defined(GP_PLAT_amd64_freebsd) || \
|
||||
defined(GP_PLAT_arm64_freebsd)
|
||||
# define HAVE_NATIVE_UNWIND
|
||||
# define USE_LUL_STACKWALK
|
||||
# include "lul/LulMain.h"
|
||||
# include "lul/platform-linux-lul.h"
|
||||
|
||||
// On linux we use LUL for periodic samples and synchronous samples, but we use
|
||||
// FramePointerStackWalk for backtrace samples when MOZ_PROFILING is enabled.
|
||||
// (See the comment at the top of the file for a definition of
|
||||
// periodic/synchronous/backtrace.).
|
||||
//
|
||||
// FramePointerStackWalk can produce incomplete stacks when the current entry is
|
||||
// in a shared library without framepointers, however LUL can take a long time
|
||||
// to initialize, which is undesirable for consumers of
|
||||
// profiler_suspend_and_sample_thread like the Background Hang Reporter.
|
||||
# if defined(MOZ_PROFILING)
|
||||
# define USE_FRAME_POINTER_STACK_WALK
|
||||
# endif
|
||||
# endif
|
||||
#endif
|
||||
|
||||
// We can only stackwalk without expensive initialization on platforms which
|
||||
// support FramePointerStackWalk or MozStackWalk. LUL Stackwalking requires
|
||||
// initializing LUL, and EHABIStackWalk requires initializing EHABI, both of
|
||||
@@ -327,14 +288,7 @@ typedef const PSAutoLock& PSLockRef;
|
||||
// TLSRegisteredThread::RacyRegisteredThread().
|
||||
class CorePS {
|
||||
private:
|
||||
CorePS()
|
||||
: mProcessStartTime(TimeStamp::ProcessCreation())
|
||||
#ifdef USE_LUL_STACKWALK
|
||||
,
|
||||
mLul(nullptr)
|
||||
#endif
|
||||
{
|
||||
}
|
||||
CorePS() : mProcessStartTime(TimeStamp::ProcessCreation()) {}
|
||||
|
||||
~CorePS() {}
|
||||
|
||||
@@ -376,12 +330,6 @@ class CorePS {
|
||||
// - CorePS::mRegisteredPages itself (its elements' children are
|
||||
// measured above)
|
||||
// - CorePS::mInterposeObserver
|
||||
|
||||
#if defined(USE_LUL_STACKWALK)
|
||||
if (sInstance->mLul) {
|
||||
aLulSize += sInstance->mLul->SizeOfIncludingThis(aMallocSizeOf);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
// No PSLockRef is needed for this field because it's immutable.
|
||||
@@ -471,17 +419,6 @@ class CorePS {
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef USE_LUL_STACKWALK
|
||||
static lul::LUL* Lul(PSLockRef) {
|
||||
MOZ_ASSERT(sInstance);
|
||||
return sInstance->mLul.get();
|
||||
}
|
||||
static void SetLul(PSLockRef, UniquePtr<lul::LUL> aLul) {
|
||||
MOZ_ASSERT(sInstance);
|
||||
sInstance->mLul = std::move(aLul);
|
||||
}
|
||||
#endif
|
||||
|
||||
PS_GET_AND_SET(const std::string&, ProcessName)
|
||||
PS_GET_AND_SET(const std::string&, ETLDplus1)
|
||||
|
||||
@@ -503,11 +440,6 @@ class CorePS {
|
||||
// Non-owning pointers to all active counters
|
||||
Vector<BaseProfilerCount*> mCounters;
|
||||
|
||||
#ifdef USE_LUL_STACKWALK
|
||||
// LUL's state. Null prior to the first activation, non-null thereafter.
|
||||
UniquePtr<lul::LUL> mLul;
|
||||
#endif
|
||||
|
||||
// Process name, provided by child process initialization code.
|
||||
std::string mProcessName;
|
||||
// Private name, provided by child process initialization code (eTLD+1 in
|
||||
@@ -1479,175 +1411,6 @@ static void DoEHABIBacktrace(PSLockRef aLock,
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef USE_LUL_STACKWALK
|
||||
|
||||
// See the comment at the callsite for why this function is necessary.
|
||||
# if defined(MOZ_HAVE_ASAN_IGNORE)
|
||||
MOZ_ASAN_IGNORE static void ASAN_memcpy(void* aDst, const void* aSrc,
|
||||
size_t aLen) {
|
||||
// The obvious thing to do here is call memcpy(). However, although
|
||||
// ASAN_memcpy() is not instrumented by ASAN, memcpy() still is, and the
|
||||
// false positive still manifests! So we must implement memcpy() ourselves
|
||||
// within this function.
|
||||
char* dst = static_cast<char*>(aDst);
|
||||
const char* src = static_cast<const char*>(aSrc);
|
||||
|
||||
for (size_t i = 0; i < aLen; i++) {
|
||||
dst[i] = src[i];
|
||||
}
|
||||
}
|
||||
# endif
|
||||
|
||||
static void DoLULBacktrace(PSLockRef aLock,
|
||||
const RegisteredThread& aRegisteredThread,
|
||||
const Registers& aRegs, NativeStack& aNativeStack) {
|
||||
// WARNING: this function runs within the profiler's "critical section".
|
||||
// WARNING: this function might be called while the profiler is inactive, and
|
||||
// cannot rely on ActivePS.
|
||||
|
||||
const mcontext_t* mc = &aRegs.mContext->uc_mcontext;
|
||||
|
||||
lul::UnwindRegs startRegs;
|
||||
memset(&startRegs, 0, sizeof(startRegs));
|
||||
|
||||
# if defined(GP_PLAT_amd64_linux) || defined(GP_PLAT_amd64_android)
|
||||
startRegs.xip = lul::TaggedUWord(mc->gregs[REG_RIP]);
|
||||
startRegs.xsp = lul::TaggedUWord(mc->gregs[REG_RSP]);
|
||||
startRegs.xbp = lul::TaggedUWord(mc->gregs[REG_RBP]);
|
||||
# elif defined(GP_PLAT_amd64_freebsd)
|
||||
startRegs.xip = lul::TaggedUWord(mc->mc_rip);
|
||||
startRegs.xsp = lul::TaggedUWord(mc->mc_rsp);
|
||||
startRegs.xbp = lul::TaggedUWord(mc->mc_rbp);
|
||||
# elif defined(GP_PLAT_arm_linux) || defined(GP_PLAT_arm_android)
|
||||
startRegs.r15 = lul::TaggedUWord(mc->arm_pc);
|
||||
startRegs.r14 = lul::TaggedUWord(mc->arm_lr);
|
||||
startRegs.r13 = lul::TaggedUWord(mc->arm_sp);
|
||||
startRegs.r12 = lul::TaggedUWord(mc->arm_ip);
|
||||
startRegs.r11 = lul::TaggedUWord(mc->arm_fp);
|
||||
startRegs.r7 = lul::TaggedUWord(mc->arm_r7);
|
||||
# elif defined(GP_PLAT_arm64_linux) || defined(GP_PLAT_arm64_android)
|
||||
startRegs.pc = lul::TaggedUWord(mc->pc);
|
||||
startRegs.x29 = lul::TaggedUWord(mc->regs[29]);
|
||||
startRegs.x30 = lul::TaggedUWord(mc->regs[30]);
|
||||
startRegs.sp = lul::TaggedUWord(mc->sp);
|
||||
# elif defined(GP_PLAT_arm64_freebsd)
|
||||
startRegs.pc = lul::TaggedUWord(mc->mc_gpregs.gp_elr);
|
||||
startRegs.x29 = lul::TaggedUWord(mc->mc_gpregs.gp_x[29]);
|
||||
startRegs.x30 = lul::TaggedUWord(mc->mc_gpregs.gp_lr);
|
||||
startRegs.sp = lul::TaggedUWord(mc->mc_gpregs.gp_sp);
|
||||
# elif defined(GP_PLAT_x86_linux) || defined(GP_PLAT_x86_android)
|
||||
startRegs.xip = lul::TaggedUWord(mc->gregs[REG_EIP]);
|
||||
startRegs.xsp = lul::TaggedUWord(mc->gregs[REG_ESP]);
|
||||
startRegs.xbp = lul::TaggedUWord(mc->gregs[REG_EBP]);
|
||||
# elif defined(GP_PLAT_mips64_linux)
|
||||
startRegs.pc = lul::TaggedUWord(mc->pc);
|
||||
startRegs.sp = lul::TaggedUWord(mc->gregs[29]);
|
||||
startRegs.fp = lul::TaggedUWord(mc->gregs[30]);
|
||||
# else
|
||||
# error "Unknown plat"
|
||||
# endif
|
||||
|
||||
// Copy up to N_STACK_BYTES from rsp-REDZONE upwards, but not going past the
|
||||
// stack's registered top point. Do some basic sanity checks too. This
|
||||
// assumes that the TaggedUWord holding the stack pointer value is valid, but
|
||||
// it should be, since it was constructed that way in the code just above.
|
||||
|
||||
// We could construct |stackImg| so that LUL reads directly from the stack in
|
||||
// question, rather than from a copy of it. That would reduce overhead and
|
||||
// space use a bit. However, it gives a problem with dynamic analysis tools
|
||||
// (ASan, TSan, Valgrind) which is that such tools will report invalid or
|
||||
// racing memory accesses, and such accesses will be reported deep inside LUL.
|
||||
// By taking a copy here, we can either sanitise the copy (for Valgrind) or
|
||||
// copy it using an unchecked memcpy (for ASan, TSan). That way we don't have
|
||||
// to try and suppress errors inside LUL.
|
||||
//
|
||||
// N_STACK_BYTES is set to 160KB. This is big enough to hold all stacks
|
||||
// observed in some minutes of testing, whilst keeping the size of this
|
||||
// function (DoNativeBacktrace)'s frame reasonable. Most stacks observed in
|
||||
// practice are small, 4KB or less, and so the copy costs are insignificant
|
||||
// compared to other profiler overhead.
|
||||
//
|
||||
// |stackImg| is allocated on this (the sampling thread's) stack. That
|
||||
// implies that the frame for this function is at least N_STACK_BYTES large.
|
||||
// In general it would be considered unacceptable to have such a large frame
|
||||
// on a stack, but it only exists for the unwinder thread, and so is not
|
||||
// expected to be a problem. Allocating it on the heap is troublesome because
|
||||
// this function runs whilst the sampled thread is suspended, so any heap
|
||||
// allocation risks deadlock. Allocating it as a global variable is not
|
||||
// thread safe, which would be a problem if we ever allow multiple sampler
|
||||
// threads. Hence allocating it on the stack seems to be the least-worst
|
||||
// option.
|
||||
|
||||
lul::StackImage stackImg;
|
||||
|
||||
{
|
||||
# if defined(GP_PLAT_amd64_linux) || defined(GP_PLAT_amd64_android) || \
|
||||
defined(GP_PLAT_amd64_freebsd)
|
||||
uintptr_t rEDZONE_SIZE = 128;
|
||||
uintptr_t start = startRegs.xsp.Value() - rEDZONE_SIZE;
|
||||
# elif defined(GP_PLAT_arm_linux) || defined(GP_PLAT_arm_android)
|
||||
uintptr_t rEDZONE_SIZE = 0;
|
||||
uintptr_t start = startRegs.r13.Value() - rEDZONE_SIZE;
|
||||
# elif defined(GP_PLAT_arm64_linux) || defined(GP_PLAT_arm64_android) || \
|
||||
defined(GP_PLAT_arm64_freebsd)
|
||||
uintptr_t rEDZONE_SIZE = 0;
|
||||
uintptr_t start = startRegs.sp.Value() - rEDZONE_SIZE;
|
||||
# elif defined(GP_PLAT_x86_linux) || defined(GP_PLAT_x86_android)
|
||||
uintptr_t rEDZONE_SIZE = 0;
|
||||
uintptr_t start = startRegs.xsp.Value() - rEDZONE_SIZE;
|
||||
# elif defined(GP_PLAT_mips64_linux)
|
||||
uintptr_t rEDZONE_SIZE = 0;
|
||||
uintptr_t start = startRegs.sp.Value() - rEDZONE_SIZE;
|
||||
# else
|
||||
# error "Unknown plat"
|
||||
# endif
|
||||
uintptr_t end = reinterpret_cast<uintptr_t>(aRegisteredThread.StackTop());
|
||||
uintptr_t ws = sizeof(void*);
|
||||
start &= ~(ws - 1);
|
||||
end &= ~(ws - 1);
|
||||
uintptr_t nToCopy = 0;
|
||||
if (start < end) {
|
||||
nToCopy = end - start;
|
||||
if (nToCopy > lul::N_STACK_BYTES) nToCopy = lul::N_STACK_BYTES;
|
||||
}
|
||||
MOZ_ASSERT(nToCopy <= lul::N_STACK_BYTES);
|
||||
stackImg.mLen = nToCopy;
|
||||
stackImg.mStartAvma = start;
|
||||
if (nToCopy > 0) {
|
||||
// If this is a vanilla memcpy(), ASAN makes the following complaint:
|
||||
//
|
||||
// ERROR: AddressSanitizer: stack-buffer-underflow ...
|
||||
// ...
|
||||
// HINT: this may be a false positive if your program uses some custom
|
||||
// stack unwind mechanism or swapcontext
|
||||
//
|
||||
// This code is very much a custom stack unwind mechanism! So we use an
|
||||
// alternative memcpy() implementation that is ignored by ASAN.
|
||||
# if defined(MOZ_HAVE_ASAN_IGNORE)
|
||||
ASAN_memcpy(&stackImg.mContents[0], (void*)start, nToCopy);
|
||||
# else
|
||||
memcpy(&stackImg.mContents[0], (void*)start, nToCopy);
|
||||
# endif
|
||||
(void)VALGRIND_MAKE_MEM_DEFINED(&stackImg.mContents[0], nToCopy);
|
||||
}
|
||||
}
|
||||
|
||||
size_t framePointerFramesAcquired = 0;
|
||||
lul::LUL* lul = CorePS::Lul(aLock);
|
||||
lul->Unwind(reinterpret_cast<uintptr_t*>(aNativeStack.mPCs),
|
||||
reinterpret_cast<uintptr_t*>(aNativeStack.mSPs),
|
||||
&aNativeStack.mCount, &framePointerFramesAcquired,
|
||||
MAX_NATIVE_FRAMES, &startRegs, &stackImg);
|
||||
|
||||
// Update stats in the LUL stats object. Unfortunately this requires
|
||||
// three global memory operations.
|
||||
lul->mStats.mContext += 1;
|
||||
lul->mStats.mCFI += aNativeStack.mCount - 1 - framePointerFramesAcquired;
|
||||
lul->mStats.mFP += framePointerFramesAcquired;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
#ifdef HAVE_NATIVE_UNWIND
|
||||
static void DoNativeBacktrace(PSLockRef aLock,
|
||||
const RegisteredThread& aRegisteredThread,
|
||||
@@ -1658,9 +1421,7 @@ static void DoNativeBacktrace(PSLockRef aLock,
|
||||
// profiler_suspend_and_sample_thread() for details). The only part of the
|
||||
// ordering that matters is that LUL must precede FRAME_POINTER, because on
|
||||
// Linux they can both be present.
|
||||
# if defined(USE_LUL_STACKWALK)
|
||||
DoLULBacktrace(aLock, aRegisteredThread, aRegs, aNativeStack);
|
||||
# elif defined(USE_EHABI_STACKWALK)
|
||||
# if defined(USE_EHABI_STACKWALK)
|
||||
DoEHABIBacktrace(aLock, aRegisteredThread, aRegs, aNativeStack);
|
||||
# elif defined(USE_FRAME_POINTER_STACK_WALK)
|
||||
DoFramePointerBacktrace(aLock, aRegisteredThread, aRegs, aNativeStack);
|
||||
|
||||
@@ -1,80 +0,0 @@
|
||||
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#include <sys/mman.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/stat.h>
|
||||
#include <fcntl.h>
|
||||
|
||||
#include "mozilla/Assertions.h"
|
||||
#include "mozilla/Sprintf.h"
|
||||
|
||||
#include "BaseProfiler.h"
|
||||
#include "PlatformMacros.h"
|
||||
#include "AutoObjectMapper.h"
|
||||
|
||||
// A helper function for creating failure error messages in
|
||||
// AutoObjectMapper*::Map.
|
||||
static void failedToMessage(void (*aLog)(const char*), const char* aHowFailed,
|
||||
std::string aFileName) {
|
||||
char buf[300];
|
||||
SprintfLiteral(buf, "AutoObjectMapper::Map: Failed to %s \'%s\'", aHowFailed,
|
||||
aFileName.c_str());
|
||||
buf[sizeof(buf) - 1] = 0;
|
||||
aLog(buf);
|
||||
}
|
||||
|
||||
AutoObjectMapperPOSIX::AutoObjectMapperPOSIX(void (*aLog)(const char*))
|
||||
: mImage(nullptr), mSize(0), mLog(aLog), mIsMapped(false) {}
|
||||
|
||||
AutoObjectMapperPOSIX::~AutoObjectMapperPOSIX() {
|
||||
if (!mIsMapped) {
|
||||
// There's nothing to do.
|
||||
MOZ_ASSERT(!mImage);
|
||||
MOZ_ASSERT(mSize == 0);
|
||||
return;
|
||||
}
|
||||
MOZ_ASSERT(mSize > 0);
|
||||
// The following assertion doesn't necessarily have to be true,
|
||||
// but we assume (reasonably enough) that no mmap facility would
|
||||
// be crazy enough to map anything at page zero.
|
||||
MOZ_ASSERT(mImage);
|
||||
munmap(mImage, mSize);
|
||||
}
|
||||
|
||||
bool AutoObjectMapperPOSIX::Map(/*OUT*/ void** start, /*OUT*/ size_t* length,
|
||||
std::string fileName) {
|
||||
MOZ_ASSERT(!mIsMapped);
|
||||
|
||||
int fd = open(fileName.c_str(), O_RDONLY);
|
||||
if (fd == -1) {
|
||||
failedToMessage(mLog, "open", fileName);
|
||||
return false;
|
||||
}
|
||||
|
||||
struct stat st;
|
||||
int err = fstat(fd, &st);
|
||||
size_t sz = (err == 0) ? st.st_size : 0;
|
||||
if (err != 0 || sz == 0) {
|
||||
failedToMessage(mLog, "fstat", fileName);
|
||||
close(fd);
|
||||
return false;
|
||||
}
|
||||
|
||||
void* image = mmap(nullptr, sz, PROT_READ, MAP_SHARED, fd, 0);
|
||||
if (image == MAP_FAILED) {
|
||||
failedToMessage(mLog, "mmap", fileName);
|
||||
close(fd);
|
||||
return false;
|
||||
}
|
||||
|
||||
close(fd);
|
||||
mIsMapped = true;
|
||||
mImage = *start = image;
|
||||
mSize = *length = sz;
|
||||
return true;
|
||||
}
|
||||
@@ -1,64 +0,0 @@
|
||||
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#ifndef AutoObjectMapper_h
|
||||
#define AutoObjectMapper_h
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "mozilla/Attributes.h"
|
||||
#include "PlatformMacros.h"
|
||||
|
||||
// A (nearly-) RAII class that maps an object in and then unmaps it on
|
||||
// destruction. This base class version uses the "normal" POSIX
|
||||
// functions: open, fstat, close, mmap, munmap.
|
||||
|
||||
class MOZ_STACK_CLASS AutoObjectMapperPOSIX {
|
||||
public:
|
||||
// The constructor does not attempt to map the file, because that
|
||||
// might fail. Instead, once the object has been constructed,
|
||||
// call Map() to attempt the mapping. There is no corresponding
|
||||
// Unmap() since the unmapping is done in the destructor. Failure
|
||||
// messages are sent to |aLog|.
|
||||
explicit AutoObjectMapperPOSIX(void (*aLog)(const char*));
|
||||
|
||||
// Unmap the file on destruction of this object.
|
||||
~AutoObjectMapperPOSIX();
|
||||
|
||||
// Map |fileName| into the address space and return the mapping
|
||||
// extents. If the file is zero sized this will fail. The file is
|
||||
// mapped read-only and private. Returns true iff the mapping
|
||||
// succeeded, in which case *start and *length hold its extent.
|
||||
// Once a call to Map succeeds, all subsequent calls to it will
|
||||
// fail.
|
||||
bool Map(/*OUT*/ void** start, /*OUT*/ size_t* length, std::string fileName);
|
||||
|
||||
protected:
|
||||
// If we are currently holding a mapped object, these record the
|
||||
// mapped address range.
|
||||
void* mImage;
|
||||
size_t mSize;
|
||||
|
||||
// A logging sink, for complaining about mapping failures.
|
||||
void (*mLog)(const char*);
|
||||
|
||||
private:
|
||||
// Are we currently holding a mapped object? This is private to
|
||||
// the base class. Derived classes need to have their own way to
|
||||
// track whether they are holding a mapped object.
|
||||
bool mIsMapped;
|
||||
|
||||
// Disable copying and assignment.
|
||||
AutoObjectMapperPOSIX(const AutoObjectMapperPOSIX&);
|
||||
AutoObjectMapperPOSIX& operator=(const AutoObjectMapperPOSIX&);
|
||||
// Disable heap allocation of this class.
|
||||
void* operator new(size_t);
|
||||
void* operator new[](size_t);
|
||||
void operator delete(void*);
|
||||
void operator delete[](void*);
|
||||
};
|
||||
|
||||
#endif // AutoObjectMapper_h
|
||||
@@ -1,102 +0,0 @@
|
||||
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
|
||||
|
||||
// Copyright (c) 2011, 2013 Google Inc.
|
||||
// All rights reserved.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are
|
||||
// met:
|
||||
//
|
||||
// * Redistributions of source code must retain the above copyright
|
||||
// notice, this list of conditions and the following disclaimer.
|
||||
// * Redistributions in binary form must reproduce the above
|
||||
// copyright notice, this list of conditions and the following disclaimer
|
||||
// in the documentation and/or other materials provided with the
|
||||
// distribution.
|
||||
// * Neither the name of Google Inc. nor the names of its
|
||||
// contributors may be used to endorse or promote products derived from
|
||||
// this software without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
// Original author: Jim Blandy <jimb@mozilla.com> <jimb@red-bean.com>
|
||||
|
||||
// This file is derived from the following files in
|
||||
// toolkit/crashreporter/google-breakpad:
|
||||
// src/common/module.cc
|
||||
// src/common/unique_string.cc
|
||||
|
||||
// There's no internal-only interface for LulCommon. Hence include
|
||||
// the external interface directly.
|
||||
#include "LulCommonExt.h"
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <string>
|
||||
#include <map>
|
||||
|
||||
#include "BaseProfiler.h"
|
||||
|
||||
namespace lul {
|
||||
|
||||
using std::string;
|
||||
|
||||
////////////////////////////////////////////////////////////////
|
||||
// Module
|
||||
//
|
||||
Module::Module(const string& name, const string& os, const string& architecture,
|
||||
const string& id)
|
||||
: name_(name), os_(os), architecture_(architecture), id_(id) {}
|
||||
|
||||
Module::~Module() {}
|
||||
|
||||
////////////////////////////////////////////////////////////////
|
||||
// UniqueString
|
||||
//
|
||||
class UniqueString {
|
||||
public:
|
||||
explicit UniqueString(string str) { str_ = strdup(str.c_str()); }
|
||||
~UniqueString() { free(reinterpret_cast<void*>(const_cast<char*>(str_))); }
|
||||
const char* str_;
|
||||
};
|
||||
|
||||
const char* FromUniqueString(const UniqueString* ustr) { return ustr->str_; }
|
||||
|
||||
bool IsEmptyUniqueString(const UniqueString* ustr) {
|
||||
return (ustr->str_)[0] == '\0';
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////
|
||||
// UniqueStringUniverse
|
||||
//
|
||||
UniqueStringUniverse::~UniqueStringUniverse() {
|
||||
for (std::map<string, UniqueString*>::iterator it = map_.begin();
|
||||
it != map_.end(); it++) {
|
||||
delete it->second;
|
||||
}
|
||||
}
|
||||
|
||||
const UniqueString* UniqueStringUniverse::ToUniqueString(string str) {
|
||||
std::map<string, UniqueString*>::iterator it = map_.find(str);
|
||||
if (it == map_.end()) {
|
||||
UniqueString* ustr = new UniqueString(str);
|
||||
map_[str] = ustr;
|
||||
return ustr;
|
||||
} else {
|
||||
return it->second;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace lul
|
||||
@@ -1,509 +0,0 @@
|
||||
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
|
||||
|
||||
// Copyright (c) 2006, 2010, 2012, 2013 Google Inc.
|
||||
// All rights reserved.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are
|
||||
// met:
|
||||
//
|
||||
// * Redistributions of source code must retain the above copyright
|
||||
// notice, this list of conditions and the following disclaimer.
|
||||
// * Redistributions in binary form must reproduce the above
|
||||
// copyright notice, this list of conditions and the following disclaimer
|
||||
// in the documentation and/or other materials provided with the
|
||||
// distribution.
|
||||
// * Neither the name of Google Inc. nor the names of its
|
||||
// contributors may be used to endorse or promote products derived from
|
||||
// this software without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
// Original author: Jim Blandy <jimb@mozilla.com> <jimb@red-bean.com>
|
||||
|
||||
// module.h: Define google_breakpad::Module. A Module holds debugging
|
||||
// information, and can write that information out as a Breakpad
|
||||
// symbol file.
|
||||
|
||||
// (C) Copyright Greg Colvin and Beman Dawes 1998, 1999.
|
||||
// Copyright (c) 2001, 2002 Peter Dimov
|
||||
//
|
||||
// Permission to copy, use, modify, sell and distribute this software
|
||||
// is granted provided this copyright notice appears in all copies.
|
||||
// This software is provided "as is" without express or implied
|
||||
// warranty, and with no claim as to its suitability for any purpose.
|
||||
//
|
||||
// See http://www.boost.org/libs/smart_ptr/scoped_ptr.htm for documentation.
|
||||
//
|
||||
|
||||
// This file is derived from the following files in
|
||||
// toolkit/crashreporter/google-breakpad:
|
||||
// src/common/unique_string.h
|
||||
// src/common/scoped_ptr.h
|
||||
// src/common/module.h
|
||||
|
||||
// External interface for the "Common" component of LUL.
|
||||
|
||||
#ifndef LulCommonExt_h
|
||||
#define LulCommonExt_h
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#include <string>
|
||||
#include <map>
|
||||
#include <vector>
|
||||
#include <cstddef> // for std::ptrdiff_t
|
||||
|
||||
#include "mozilla/Assertions.h"
|
||||
|
||||
namespace lul {
|
||||
|
||||
using std::map;
|
||||
using std::string;
|
||||
|
||||
////////////////////////////////////////////////////////////////
|
||||
// UniqueString
|
||||
//
|
||||
|
||||
// Abstract type
|
||||
class UniqueString;
|
||||
|
||||
// Get the contained C string (debugging only)
|
||||
const char* FromUniqueString(const UniqueString*);
|
||||
|
||||
// Is the given string empty (that is, "") ?
|
||||
bool IsEmptyUniqueString(const UniqueString*);
|
||||
|
||||
////////////////////////////////////////////////////////////////
|
||||
// UniqueStringUniverse
|
||||
//
|
||||
|
||||
// All UniqueStrings live in some specific UniqueStringUniverse.
|
||||
class UniqueStringUniverse {
|
||||
public:
|
||||
UniqueStringUniverse() {}
|
||||
~UniqueStringUniverse();
|
||||
// Convert a |string| to a UniqueString, that lives in this universe.
|
||||
const UniqueString* ToUniqueString(string str);
|
||||
|
||||
private:
|
||||
map<string, UniqueString*> map_;
|
||||
};
|
||||
|
||||
////////////////////////////////////////////////////////////////
|
||||
// GUID
|
||||
//
|
||||
|
||||
typedef struct {
|
||||
uint32_t data1;
|
||||
uint16_t data2;
|
||||
uint16_t data3;
|
||||
uint8_t data4[8];
|
||||
} MDGUID; // GUID
|
||||
|
||||
typedef MDGUID GUID;
|
||||
|
||||
////////////////////////////////////////////////////////////////
|
||||
// scoped_ptr
|
||||
//
|
||||
|
||||
// scoped_ptr mimics a built-in pointer except that it guarantees deletion
|
||||
// of the object pointed to, either on destruction of the scoped_ptr or via
|
||||
// an explicit reset(). scoped_ptr is a simple solution for simple needs;
|
||||
// use shared_ptr or std::auto_ptr if your needs are more complex.
|
||||
|
||||
// *** NOTE ***
|
||||
// If your scoped_ptr is a class member of class FOO pointing to a
|
||||
// forward declared type BAR (as shown below), then you MUST use a non-inlined
|
||||
// version of the destructor. The destructor of a scoped_ptr (called from
|
||||
// FOO's destructor) must have a complete definition of BAR in order to
|
||||
// destroy it. Example:
|
||||
//
|
||||
// -- foo.h --
|
||||
// class BAR;
|
||||
//
|
||||
// class FOO {
|
||||
// public:
|
||||
// FOO();
|
||||
// ~FOO(); // Required for sources that instantiate class FOO to compile!
|
||||
//
|
||||
// private:
|
||||
// scoped_ptr<BAR> bar_;
|
||||
// };
|
||||
//
|
||||
// -- foo.cc --
|
||||
// #include "foo.h"
|
||||
// FOO::~FOO() {} // Empty, but must be non-inlined to FOO's class definition.
|
||||
|
||||
// scoped_ptr_malloc added by Google
|
||||
// When one of these goes out of scope, instead of doing a delete or
|
||||
// delete[], it calls free(). scoped_ptr_malloc<char> is likely to see
|
||||
// much more use than any other specializations.
|
||||
|
||||
// release() added by Google
|
||||
// Use this to conditionally transfer ownership of a heap-allocated object
|
||||
// to the caller, usually on method success.
|
||||
|
||||
template <typename T>
|
||||
class scoped_ptr {
|
||||
private:
|
||||
T* ptr;
|
||||
|
||||
scoped_ptr(scoped_ptr const&);
|
||||
scoped_ptr& operator=(scoped_ptr const&);
|
||||
|
||||
public:
|
||||
typedef T element_type;
|
||||
|
||||
explicit scoped_ptr(T* p = 0) : ptr(p) {}
|
||||
|
||||
~scoped_ptr() { delete ptr; }
|
||||
|
||||
void reset(T* p = 0) {
|
||||
if (ptr != p) {
|
||||
delete ptr;
|
||||
ptr = p;
|
||||
}
|
||||
}
|
||||
|
||||
T& operator*() const {
|
||||
MOZ_ASSERT(ptr != 0);
|
||||
return *ptr;
|
||||
}
|
||||
|
||||
T* operator->() const {
|
||||
MOZ_ASSERT(ptr != 0);
|
||||
return ptr;
|
||||
}
|
||||
|
||||
bool operator==(T* p) const { return ptr == p; }
|
||||
|
||||
bool operator!=(T* p) const { return ptr != p; }
|
||||
|
||||
T* get() const { return ptr; }
|
||||
|
||||
void swap(scoped_ptr& b) {
|
||||
T* tmp = b.ptr;
|
||||
b.ptr = ptr;
|
||||
ptr = tmp;
|
||||
}
|
||||
|
||||
T* release() {
|
||||
T* tmp = ptr;
|
||||
ptr = 0;
|
||||
return tmp;
|
||||
}
|
||||
|
||||
private:
|
||||
// no reason to use these: each scoped_ptr should have its own object
|
||||
template <typename U>
|
||||
bool operator==(scoped_ptr<U> const& p) const;
|
||||
template <typename U>
|
||||
bool operator!=(scoped_ptr<U> const& p) const;
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
inline void swap(scoped_ptr<T>& a, scoped_ptr<T>& b) {
|
||||
a.swap(b);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
inline bool operator==(T* p, const scoped_ptr<T>& b) {
|
||||
return p == b.get();
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
inline bool operator!=(T* p, const scoped_ptr<T>& b) {
|
||||
return p != b.get();
|
||||
}
|
||||
|
||||
// scoped_array extends scoped_ptr to arrays. Deletion of the array pointed to
|
||||
// is guaranteed, either on destruction of the scoped_array or via an explicit
|
||||
// reset(). Use shared_array or std::vector if your needs are more complex.
|
||||
|
||||
template <typename T>
|
||||
class scoped_array {
|
||||
private:
|
||||
T* ptr;
|
||||
|
||||
scoped_array(scoped_array const&);
|
||||
scoped_array& operator=(scoped_array const&);
|
||||
|
||||
public:
|
||||
typedef T element_type;
|
||||
|
||||
explicit scoped_array(T* p = 0) : ptr(p) {}
|
||||
|
||||
~scoped_array() { delete[] ptr; }
|
||||
|
||||
void reset(T* p = 0) {
|
||||
if (ptr != p) {
|
||||
delete[] ptr;
|
||||
ptr = p;
|
||||
}
|
||||
}
|
||||
|
||||
T& operator[](std::ptrdiff_t i) const {
|
||||
MOZ_ASSERT(ptr != 0);
|
||||
MOZ_ASSERT(i >= 0);
|
||||
return ptr[i];
|
||||
}
|
||||
|
||||
bool operator==(T* p) const { return ptr == p; }
|
||||
|
||||
bool operator!=(T* p) const { return ptr != p; }
|
||||
|
||||
T* get() const { return ptr; }
|
||||
|
||||
void swap(scoped_array& b) {
|
||||
T* tmp = b.ptr;
|
||||
b.ptr = ptr;
|
||||
ptr = tmp;
|
||||
}
|
||||
|
||||
T* release() {
|
||||
T* tmp = ptr;
|
||||
ptr = 0;
|
||||
return tmp;
|
||||
}
|
||||
|
||||
private:
|
||||
// no reason to use these: each scoped_array should have its own object
|
||||
template <typename U>
|
||||
bool operator==(scoped_array<U> const& p) const;
|
||||
template <typename U>
|
||||
bool operator!=(scoped_array<U> const& p) const;
|
||||
};
|
||||
|
||||
template <class T>
|
||||
inline void swap(scoped_array<T>& a, scoped_array<T>& b) {
|
||||
a.swap(b);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
inline bool operator==(T* p, const scoped_array<T>& b) {
|
||||
return p == b.get();
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
inline bool operator!=(T* p, const scoped_array<T>& b) {
|
||||
return p != b.get();
|
||||
}
|
||||
|
||||
// This class wraps the c library function free() in a class that can be
|
||||
// passed as a template argument to scoped_ptr_malloc below.
|
||||
class ScopedPtrMallocFree {
|
||||
public:
|
||||
inline void operator()(void* x) const { free(x); }
|
||||
};
|
||||
|
||||
// scoped_ptr_malloc<> is similar to scoped_ptr<>, but it accepts a
|
||||
// second template argument, the functor used to free the object.
|
||||
|
||||
template <typename T, typename FreeProc = ScopedPtrMallocFree>
|
||||
class scoped_ptr_malloc {
|
||||
private:
|
||||
T* ptr;
|
||||
|
||||
scoped_ptr_malloc(scoped_ptr_malloc const&);
|
||||
scoped_ptr_malloc& operator=(scoped_ptr_malloc const&);
|
||||
|
||||
public:
|
||||
typedef T element_type;
|
||||
|
||||
explicit scoped_ptr_malloc(T* p = 0) : ptr(p) {}
|
||||
|
||||
~scoped_ptr_malloc() { free_((void*)ptr); }
|
||||
|
||||
void reset(T* p = 0) {
|
||||
if (ptr != p) {
|
||||
free_((void*)ptr);
|
||||
ptr = p;
|
||||
}
|
||||
}
|
||||
|
||||
T& operator*() const {
|
||||
MOZ_ASSERT(ptr != 0);
|
||||
return *ptr;
|
||||
}
|
||||
|
||||
T* operator->() const {
|
||||
MOZ_ASSERT(ptr != 0);
|
||||
return ptr;
|
||||
}
|
||||
|
||||
bool operator==(T* p) const { return ptr == p; }
|
||||
|
||||
bool operator!=(T* p) const { return ptr != p; }
|
||||
|
||||
T* get() const { return ptr; }
|
||||
|
||||
void swap(scoped_ptr_malloc& b) {
|
||||
T* tmp = b.ptr;
|
||||
b.ptr = ptr;
|
||||
ptr = tmp;
|
||||
}
|
||||
|
||||
T* release() {
|
||||
T* tmp = ptr;
|
||||
ptr = 0;
|
||||
return tmp;
|
||||
}
|
||||
|
||||
private:
|
||||
// no reason to use these: each scoped_ptr_malloc should have its own object
|
||||
template <typename U, typename GP>
|
||||
bool operator==(scoped_ptr_malloc<U, GP> const& p) const;
|
||||
template <typename U, typename GP>
|
||||
bool operator!=(scoped_ptr_malloc<U, GP> const& p) const;
|
||||
|
||||
static FreeProc const free_;
|
||||
};
|
||||
|
||||
template <typename T, typename FP>
|
||||
MOZ_RUNINIT FP const scoped_ptr_malloc<T, FP>::free_ = FP();
|
||||
|
||||
template <typename T, typename FP>
|
||||
inline void swap(scoped_ptr_malloc<T, FP>& a, scoped_ptr_malloc<T, FP>& b) {
|
||||
a.swap(b);
|
||||
}
|
||||
|
||||
template <typename T, typename FP>
|
||||
inline bool operator==(T* p, const scoped_ptr_malloc<T, FP>& b) {
|
||||
return p == b.get();
|
||||
}
|
||||
|
||||
template <typename T, typename FP>
|
||||
inline bool operator!=(T* p, const scoped_ptr_malloc<T, FP>& b) {
|
||||
return p != b.get();
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////
|
||||
// Module
|
||||
//
|
||||
|
||||
// A Module represents the contents of a module, and supports methods
|
||||
// for adding information produced by parsing STABS or DWARF data
|
||||
// --- possibly both from the same file --- and then writing out the
|
||||
// unified contents as a Breakpad-format symbol file.
|
||||
class Module {
|
||||
public:
|
||||
// The type of addresses and sizes in a symbol table.
|
||||
typedef uint64_t Address;
|
||||
|
||||
// Representation of an expression. This can either be a postfix
|
||||
// expression, in which case it is stored as a string, or a simple
|
||||
// expression of the form (identifier + imm) or *(identifier + imm).
|
||||
// It can also be invalid (denoting "no value").
|
||||
enum ExprHow { kExprInvalid = 1, kExprPostfix, kExprSimple, kExprSimpleMem };
|
||||
|
||||
struct Expr {
|
||||
// Construct a simple-form expression
|
||||
Expr(const UniqueString* ident, long offset, bool deref) {
|
||||
if (IsEmptyUniqueString(ident)) {
|
||||
Expr();
|
||||
} else {
|
||||
postfix_ = "";
|
||||
ident_ = ident;
|
||||
offset_ = offset;
|
||||
how_ = deref ? kExprSimpleMem : kExprSimple;
|
||||
}
|
||||
}
|
||||
|
||||
// Construct an invalid expression
|
||||
Expr() {
|
||||
postfix_ = "";
|
||||
ident_ = nullptr;
|
||||
offset_ = 0;
|
||||
how_ = kExprInvalid;
|
||||
}
|
||||
|
||||
// Return the postfix expression string, either directly,
|
||||
// if this is a postfix expression, or by synthesising it
|
||||
// for a simple expression.
|
||||
std::string getExprPostfix() const {
|
||||
switch (how_) {
|
||||
case kExprPostfix:
|
||||
return postfix_;
|
||||
case kExprSimple:
|
||||
case kExprSimpleMem: {
|
||||
char buf[40];
|
||||
sprintf(buf, " %ld %c%s", labs(offset_), offset_ < 0 ? '-' : '+',
|
||||
how_ == kExprSimple ? "" : " ^");
|
||||
return std::string(FromUniqueString(ident_)) + std::string(buf);
|
||||
}
|
||||
case kExprInvalid:
|
||||
default:
|
||||
MOZ_ASSERT(0 && "getExprPostfix: invalid Module::Expr type");
|
||||
return "Expr::genExprPostfix: kExprInvalid";
|
||||
}
|
||||
}
|
||||
|
||||
// The identifier that gives the starting value for simple expressions.
|
||||
const UniqueString* ident_;
|
||||
// The offset to add for simple expressions.
|
||||
long offset_;
|
||||
// The Postfix expression string to evaluate for non-simple expressions.
|
||||
std::string postfix_;
|
||||
// The operation expressed by this expression.
|
||||
ExprHow how_;
|
||||
};
|
||||
|
||||
// A map from register names to expressions that recover
|
||||
// their values. This can represent a complete set of rules to
|
||||
// follow at some address, or a set of changes to be applied to an
|
||||
// extant set of rules.
|
||||
// NOTE! there are two completely different types called RuleMap. This
|
||||
// is one of them.
|
||||
typedef std::map<const UniqueString*, Expr> RuleMap;
|
||||
|
||||
// A map from addresses to RuleMaps, representing changes that take
|
||||
// effect at given addresses.
|
||||
typedef std::map<Address, RuleMap> RuleChangeMap;
|
||||
|
||||
// A range of 'STACK CFI' stack walking information. An instance of
|
||||
// this structure corresponds to a 'STACK CFI INIT' record and the
|
||||
// subsequent 'STACK CFI' records that fall within its range.
|
||||
struct StackFrameEntry {
|
||||
// The starting address and number of bytes of machine code this
|
||||
// entry covers.
|
||||
Address address, size;
|
||||
|
||||
// The initial register recovery rules, in force at the starting
|
||||
// address.
|
||||
RuleMap initial_rules;
|
||||
|
||||
// A map from addresses to rule changes. To find the rules in
|
||||
// force at a given address, start with initial_rules, and then
|
||||
// apply the changes given in this map for all addresses up to and
|
||||
// including the address you're interested in.
|
||||
RuleChangeMap rule_changes;
|
||||
};
|
||||
|
||||
// Create a new module with the given name, operating system,
|
||||
// architecture, and ID string.
|
||||
Module(const std::string& name, const std::string& os,
|
||||
const std::string& architecture, const std::string& id);
|
||||
~Module();
|
||||
|
||||
private:
|
||||
// Module header entries.
|
||||
std::string name_, os_, architecture_, id_;
|
||||
};
|
||||
|
||||
} // namespace lul
|
||||
|
||||
#endif // LulCommonExt_h
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -1,193 +0,0 @@
|
||||
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
|
||||
|
||||
// Copyright (c) 2008, 2010 Google Inc. All Rights Reserved.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are
|
||||
// met:
|
||||
//
|
||||
// * Redistributions of source code must retain the above copyright
|
||||
// notice, this list of conditions and the following disclaimer.
|
||||
// * Redistributions in binary form must reproduce the above
|
||||
// copyright notice, this list of conditions and the following disclaimer
|
||||
// in the documentation and/or other materials provided with the
|
||||
// distribution.
|
||||
// * Neither the name of Google Inc. nor the names of its
|
||||
// contributors may be used to endorse or promote products derived from
|
||||
// this software without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
// CFI reader author: Jim Blandy <jimb@mozilla.com> <jimb@red-bean.com>
|
||||
|
||||
// This file is derived from the following file in
|
||||
// toolkit/crashreporter/google-breakpad:
|
||||
// src/common/dwarf/dwarf2enums.h
|
||||
|
||||
#ifndef LulDwarfInt_h
|
||||
#define LulDwarfInt_h
|
||||
|
||||
#include "LulCommonExt.h"
|
||||
#include "LulDwarfExt.h"
|
||||
|
||||
namespace lul {
|
||||
|
||||
// These enums do not follow the google3 style only because they are
|
||||
// known universally (specs, other implementations) by the names in
|
||||
// exactly this capitalization.
|
||||
// Tag names and codes.
|
||||
|
||||
// Call Frame Info instructions.
|
||||
enum DwarfCFI {
|
||||
DW_CFA_advance_loc = 0x40,
|
||||
DW_CFA_offset = 0x80,
|
||||
DW_CFA_restore = 0xc0,
|
||||
DW_CFA_nop = 0x00,
|
||||
DW_CFA_set_loc = 0x01,
|
||||
DW_CFA_advance_loc1 = 0x02,
|
||||
DW_CFA_advance_loc2 = 0x03,
|
||||
DW_CFA_advance_loc4 = 0x04,
|
||||
DW_CFA_offset_extended = 0x05,
|
||||
DW_CFA_restore_extended = 0x06,
|
||||
DW_CFA_undefined = 0x07,
|
||||
DW_CFA_same_value = 0x08,
|
||||
DW_CFA_register = 0x09,
|
||||
DW_CFA_remember_state = 0x0a,
|
||||
DW_CFA_restore_state = 0x0b,
|
||||
DW_CFA_def_cfa = 0x0c,
|
||||
DW_CFA_def_cfa_register = 0x0d,
|
||||
DW_CFA_def_cfa_offset = 0x0e,
|
||||
DW_CFA_def_cfa_expression = 0x0f,
|
||||
DW_CFA_expression = 0x10,
|
||||
DW_CFA_offset_extended_sf = 0x11,
|
||||
DW_CFA_def_cfa_sf = 0x12,
|
||||
DW_CFA_def_cfa_offset_sf = 0x13,
|
||||
DW_CFA_val_offset = 0x14,
|
||||
DW_CFA_val_offset_sf = 0x15,
|
||||
DW_CFA_val_expression = 0x16,
|
||||
|
||||
// Opcodes in this range are reserved for user extensions.
|
||||
DW_CFA_lo_user = 0x1c,
|
||||
DW_CFA_hi_user = 0x3f,
|
||||
|
||||
// SGI/MIPS specific.
|
||||
DW_CFA_MIPS_advance_loc8 = 0x1d,
|
||||
|
||||
// GNU extensions.
|
||||
DW_CFA_GNU_window_save = 0x2d,
|
||||
DW_CFA_GNU_args_size = 0x2e,
|
||||
DW_CFA_GNU_negative_offset_extended = 0x2f
|
||||
};
|
||||
|
||||
// Exception handling 'z' augmentation letters.
|
||||
enum DwarfZAugmentationCodes {
|
||||
// If the CFI augmentation string begins with 'z', then the CIE and FDE
|
||||
// have an augmentation data area just before the instructions, whose
|
||||
// contents are determined by the subsequent augmentation letters.
|
||||
DW_Z_augmentation_start = 'z',
|
||||
|
||||
// If this letter is present in a 'z' augmentation string, the CIE
|
||||
// augmentation data includes a pointer encoding, and the FDE
|
||||
// augmentation data includes a language-specific data area pointer,
|
||||
// represented using that encoding.
|
||||
DW_Z_has_LSDA = 'L',
|
||||
|
||||
// If this letter is present in a 'z' augmentation string, the CIE
|
||||
// augmentation data includes a pointer encoding, followed by a pointer
|
||||
// to a personality routine, represented using that encoding.
|
||||
DW_Z_has_personality_routine = 'P',
|
||||
|
||||
// If this letter is present in a 'z' augmentation string, the CIE
|
||||
// augmentation data includes a pointer encoding describing how the FDE's
|
||||
// initial location, address range, and DW_CFA_set_loc operands are
|
||||
// encoded.
|
||||
DW_Z_has_FDE_address_encoding = 'R',
|
||||
|
||||
// If this letter is present in a 'z' augmentation string, then code
|
||||
// addresses covered by FDEs that cite this CIE are signal delivery
|
||||
// trampolines. Return addresses of frames in trampolines should not be
|
||||
// adjusted as described in section 6.4.4 of the DWARF 3 spec.
|
||||
DW_Z_is_signal_trampoline = 'S'
|
||||
};
|
||||
|
||||
// Expression opcodes
|
||||
enum DwarfExpressionOpcodes {
|
||||
DW_OP_addr = 0x03,
|
||||
DW_OP_deref = 0x06,
|
||||
DW_OP_const1s = 0x09,
|
||||
DW_OP_const2u = 0x0a,
|
||||
DW_OP_const2s = 0x0b,
|
||||
DW_OP_const4u = 0x0c,
|
||||
DW_OP_const4s = 0x0d,
|
||||
DW_OP_const8u = 0x0e,
|
||||
DW_OP_const8s = 0x0f,
|
||||
DW_OP_constu = 0x10,
|
||||
DW_OP_consts = 0x11,
|
||||
DW_OP_dup = 0x12,
|
||||
DW_OP_drop = 0x13,
|
||||
DW_OP_over = 0x14,
|
||||
DW_OP_pick = 0x15,
|
||||
DW_OP_swap = 0x16,
|
||||
DW_OP_rot = 0x17,
|
||||
DW_OP_xderef = 0x18,
|
||||
DW_OP_abs = 0x19,
|
||||
DW_OP_and = 0x1a,
|
||||
DW_OP_div = 0x1b,
|
||||
DW_OP_minus = 0x1c,
|
||||
DW_OP_mod = 0x1d,
|
||||
DW_OP_mul = 0x1e,
|
||||
DW_OP_neg = 0x1f,
|
||||
DW_OP_not = 0x20,
|
||||
DW_OP_or = 0x21,
|
||||
DW_OP_plus = 0x22,
|
||||
DW_OP_plus_uconst = 0x23,
|
||||
DW_OP_shl = 0x24,
|
||||
DW_OP_shr = 0x25,
|
||||
DW_OP_shra = 0x26,
|
||||
DW_OP_xor = 0x27,
|
||||
DW_OP_skip = 0x2f,
|
||||
DW_OP_bra = 0x28,
|
||||
DW_OP_eq = 0x29,
|
||||
DW_OP_ge = 0x2a,
|
||||
DW_OP_gt = 0x2b,
|
||||
DW_OP_le = 0x2c,
|
||||
DW_OP_lt = 0x2d,
|
||||
DW_OP_ne = 0x2e,
|
||||
DW_OP_lit0 = 0x30,
|
||||
DW_OP_lit31 = 0x4f,
|
||||
DW_OP_reg0 = 0x50,
|
||||
DW_OP_reg31 = 0x6f,
|
||||
DW_OP_breg0 = 0x70,
|
||||
DW_OP_breg31 = 0x8f,
|
||||
DW_OP_regx = 0x90,
|
||||
DW_OP_fbreg = 0x91,
|
||||
DW_OP_bregx = 0x92,
|
||||
DW_OP_piece = 0x93,
|
||||
DW_OP_deref_size = 0x94,
|
||||
DW_OP_xderef_size = 0x95,
|
||||
DW_OP_nop = 0x96,
|
||||
DW_OP_push_object_address = 0x97,
|
||||
DW_OP_call2 = 0x98,
|
||||
DW_OP_call4 = 0x99,
|
||||
DW_OP_call_ref = 0x9a,
|
||||
DW_OP_form_tls_address = 0x9b,
|
||||
DW_OP_call_frame_cfa = 0x9c,
|
||||
DW_OP_bit_piece = 0x9d,
|
||||
DW_OP_lo_user = 0xe0,
|
||||
DW_OP_hi_user = 0xff
|
||||
};
|
||||
|
||||
} // namespace lul
|
||||
|
||||
#endif // LulDwarfInt_h
|
||||
@@ -1,553 +0,0 @@
|
||||
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#include "LulDwarfSummariser.h"
|
||||
|
||||
#include "mozilla/Assertions.h"
|
||||
#include "mozilla/Sprintf.h"
|
||||
|
||||
#include "LulDwarfExt.h"
|
||||
|
||||
// Set this to 1 for verbose logging
|
||||
#define DEBUG_SUMMARISER 0
|
||||
|
||||
namespace lul {
|
||||
|
||||
// Do |s64|'s lowest 32 bits sign extend back to |s64| itself?
|
||||
static inline bool fitsIn32Bits(int64 s64) {
|
||||
return s64 == ((s64 & 0xffffffff) ^ 0x80000000) - 0x80000000;
|
||||
}
|
||||
|
||||
// Check a LExpr prefix expression, starting at pfxInstrs[start] up to
|
||||
// the next PX_End instruction, to ensure that:
|
||||
// * It only mentions registers that are tracked on this target
|
||||
// * The start point is sane
|
||||
// If the expression is ok, return NULL. Else return a pointer
|
||||
// a const char* holding a bit of text describing the problem.
|
||||
static const char* checkPfxExpr(const vector<PfxInstr>* pfxInstrs,
|
||||
int64_t start) {
|
||||
size_t nInstrs = pfxInstrs->size();
|
||||
if (start < 0 || start >= (ssize_t)nInstrs) {
|
||||
return "bogus start point";
|
||||
}
|
||||
size_t i;
|
||||
for (i = start; i < nInstrs; i++) {
|
||||
PfxInstr pxi = (*pfxInstrs)[i];
|
||||
if (pxi.mOpcode == PX_End) break;
|
||||
if (pxi.mOpcode == PX_DwReg &&
|
||||
!registerIsTracked((DW_REG_NUMBER)pxi.mOperand)) {
|
||||
return "uses untracked reg";
|
||||
}
|
||||
}
|
||||
return nullptr; // success
|
||||
}
|
||||
|
||||
Summariser::Summariser(SecMap* aSecMap, uintptr_t aTextBias,
|
||||
void (*aLog)(const char*))
|
||||
: mSecMap(aSecMap), mTextBias(aTextBias), mLog(aLog) {
|
||||
mCurrAddr = 0;
|
||||
mMax1Addr = 0; // Gives an empty range.
|
||||
|
||||
// Initialise the running RuleSet to "haven't got a clue" status.
|
||||
new (&mCurrRules) RuleSet();
|
||||
}
|
||||
|
||||
void Summariser::Entry(uintptr_t aAddress, uintptr_t aLength) {
|
||||
aAddress += mTextBias;
|
||||
if (DEBUG_SUMMARISER) {
|
||||
char buf[100];
|
||||
SprintfLiteral(buf, "LUL Entry(%llx, %llu)\n",
|
||||
(unsigned long long int)aAddress,
|
||||
(unsigned long long int)aLength);
|
||||
mLog(buf);
|
||||
}
|
||||
// This throws away any previous summary, that is, assumes
|
||||
// that the previous summary, if any, has been properly finished
|
||||
// by a call to End().
|
||||
mCurrAddr = aAddress;
|
||||
mMax1Addr = aAddress + aLength;
|
||||
new (&mCurrRules) RuleSet();
|
||||
}
|
||||
|
||||
void Summariser::Rule(uintptr_t aAddress, int aNewReg, LExprHow how,
|
||||
int16_t oldReg, int64_t offset) {
|
||||
aAddress += mTextBias;
|
||||
if (DEBUG_SUMMARISER) {
|
||||
char buf[100];
|
||||
if (how == NODEREF || how == DEREF) {
|
||||
bool deref = how == DEREF;
|
||||
SprintfLiteral(buf, "LUL 0x%llx old-r%d = %sr%d + %lld%s\n",
|
||||
(unsigned long long int)aAddress, aNewReg,
|
||||
deref ? "*(" : "", (int)oldReg, (long long int)offset,
|
||||
deref ? ")" : "");
|
||||
} else if (how == PFXEXPR) {
|
||||
SprintfLiteral(buf, "LUL 0x%llx old-r%d = pfx-expr-at %lld\n",
|
||||
(unsigned long long int)aAddress, aNewReg,
|
||||
(long long int)offset);
|
||||
} else {
|
||||
SprintfLiteral(buf, "LUL 0x%llx old-r%d = (invalid LExpr!)\n",
|
||||
(unsigned long long int)aAddress, aNewReg);
|
||||
}
|
||||
mLog(buf);
|
||||
}
|
||||
|
||||
if (mCurrAddr < aAddress) {
|
||||
// Flush the existing summary first.
|
||||
mCurrRules.mAddr = mCurrAddr;
|
||||
mCurrRules.mLen = aAddress - mCurrAddr;
|
||||
mSecMap->AddRuleSet(&mCurrRules);
|
||||
if (DEBUG_SUMMARISER) {
|
||||
mLog("LUL ");
|
||||
mCurrRules.Print(mLog);
|
||||
mLog("\n");
|
||||
}
|
||||
mCurrAddr = aAddress;
|
||||
}
|
||||
|
||||
// If for some reason summarisation fails, either or both of these
|
||||
// become non-null and point at constant text describing the
|
||||
// problem. Using two rather than just one avoids complications of
|
||||
// having to concatenate two strings to produce a complete error message.
|
||||
const char* reason1 = nullptr;
|
||||
const char* reason2 = nullptr;
|
||||
|
||||
// |offset| needs to be a 32 bit value that sign extends to 64 bits
|
||||
// on a 64 bit target. We will need to incorporate |offset| into
|
||||
// any LExpr made here. So we may as well check it right now.
|
||||
if (!fitsIn32Bits(offset)) {
|
||||
reason1 = "offset not in signed 32-bit range";
|
||||
goto cant_summarise;
|
||||
}
|
||||
|
||||
// FIXME: factor out common parts of the arch-dependent summarisers.
|
||||
|
||||
#if defined(GP_ARCH_arm)
|
||||
|
||||
// ----------------- arm ----------------- //
|
||||
|
||||
// Now, can we add the rule to our summary? This depends on whether
|
||||
// the registers and the overall expression are representable. This
|
||||
// is the heart of the summarisation process.
|
||||
switch (aNewReg) {
|
||||
case DW_REG_CFA:
|
||||
// This is a rule that defines the CFA. The only forms we
|
||||
// choose to represent are: r7/11/12/13 + offset. The offset
|
||||
// must fit into 32 bits since 'uintptr_t' is 32 bit on ARM,
|
||||
// hence there is no need to check it for overflow.
|
||||
if (how != NODEREF) {
|
||||
reason1 = "rule for DW_REG_CFA: invalid |how|";
|
||||
goto cant_summarise;
|
||||
}
|
||||
switch (oldReg) {
|
||||
case DW_REG_ARM_R7:
|
||||
case DW_REG_ARM_R11:
|
||||
case DW_REG_ARM_R12:
|
||||
case DW_REG_ARM_R13:
|
||||
break;
|
||||
default:
|
||||
reason1 = "rule for DW_REG_CFA: invalid |oldReg|";
|
||||
goto cant_summarise;
|
||||
}
|
||||
mCurrRules.mCfaExpr = LExpr(how, oldReg, offset);
|
||||
break;
|
||||
|
||||
case DW_REG_ARM_R7:
|
||||
case DW_REG_ARM_R11:
|
||||
case DW_REG_ARM_R12:
|
||||
case DW_REG_ARM_R13:
|
||||
case DW_REG_ARM_R14:
|
||||
case DW_REG_ARM_R15: {
|
||||
// This is a new rule for R7, R11, R12, R13 (SP), R14 (LR) or
|
||||
// R15 (the return address).
|
||||
switch (how) {
|
||||
case NODEREF:
|
||||
case DEREF:
|
||||
// Check the old register is one we're tracking.
|
||||
if (!registerIsTracked((DW_REG_NUMBER)oldReg) &&
|
||||
oldReg != DW_REG_CFA) {
|
||||
reason1 = "rule for R7/11/12/13/14/15: uses untracked reg";
|
||||
goto cant_summarise;
|
||||
}
|
||||
break;
|
||||
case PFXEXPR: {
|
||||
// Check that the prefix expression only mentions tracked registers.
|
||||
const vector<PfxInstr>* pfxInstrs = mSecMap->GetPfxInstrs();
|
||||
reason2 = checkPfxExpr(pfxInstrs, offset);
|
||||
if (reason2) {
|
||||
reason1 = "rule for R7/11/12/13/14/15: ";
|
||||
goto cant_summarise;
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
goto cant_summarise;
|
||||
}
|
||||
LExpr expr = LExpr(how, oldReg, offset);
|
||||
switch (aNewReg) {
|
||||
case DW_REG_ARM_R7:
|
||||
mCurrRules.mR7expr = expr;
|
||||
break;
|
||||
case DW_REG_ARM_R11:
|
||||
mCurrRules.mR11expr = expr;
|
||||
break;
|
||||
case DW_REG_ARM_R12:
|
||||
mCurrRules.mR12expr = expr;
|
||||
break;
|
||||
case DW_REG_ARM_R13:
|
||||
mCurrRules.mR13expr = expr;
|
||||
break;
|
||||
case DW_REG_ARM_R14:
|
||||
mCurrRules.mR14expr = expr;
|
||||
break;
|
||||
case DW_REG_ARM_R15:
|
||||
mCurrRules.mR15expr = expr;
|
||||
break;
|
||||
default:
|
||||
MOZ_ASSERT(0);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
// Leave |reason1| and |reason2| unset here. This program point
|
||||
// is reached so often that it causes a flood of "Can't
|
||||
// summarise" messages. In any case, we don't really care about
|
||||
// the fact that this summary would produce a new value for a
|
||||
// register that we're not tracking. We do on the other hand
|
||||
// care if the summary's expression *uses* a register that we're
|
||||
// not tracking. But in that case one of the above failures
|
||||
// should tell us which.
|
||||
goto cant_summarise;
|
||||
}
|
||||
|
||||
// Mark callee-saved registers (r4 .. r11) as unchanged, if there is
|
||||
// no other information about them. FIXME: do this just once, at
|
||||
// the point where the ruleset is committed.
|
||||
if (mCurrRules.mR7expr.mHow == UNKNOWN) {
|
||||
mCurrRules.mR7expr = LExpr(NODEREF, DW_REG_ARM_R7, 0);
|
||||
}
|
||||
if (mCurrRules.mR11expr.mHow == UNKNOWN) {
|
||||
mCurrRules.mR11expr = LExpr(NODEREF, DW_REG_ARM_R11, 0);
|
||||
}
|
||||
if (mCurrRules.mR12expr.mHow == UNKNOWN) {
|
||||
mCurrRules.mR12expr = LExpr(NODEREF, DW_REG_ARM_R12, 0);
|
||||
}
|
||||
|
||||
// The old r13 (SP) value before the call is always the same as the
|
||||
// CFA.
|
||||
mCurrRules.mR13expr = LExpr(NODEREF, DW_REG_CFA, 0);
|
||||
|
||||
// If there's no information about R15 (the return address), say
|
||||
// it's a copy of R14 (the link register).
|
||||
if (mCurrRules.mR15expr.mHow == UNKNOWN) {
|
||||
mCurrRules.mR15expr = LExpr(NODEREF, DW_REG_ARM_R14, 0);
|
||||
}
|
||||
|
||||
#elif defined(GP_ARCH_arm64)
|
||||
|
||||
// ----------------- arm64 ----------------- //
|
||||
|
||||
switch (aNewReg) {
|
||||
case DW_REG_CFA:
|
||||
if (how != NODEREF) {
|
||||
reason1 = "rule for DW_REG_CFA: invalid |how|";
|
||||
goto cant_summarise;
|
||||
}
|
||||
switch (oldReg) {
|
||||
case DW_REG_AARCH64_X29:
|
||||
case DW_REG_AARCH64_SP:
|
||||
break;
|
||||
default:
|
||||
reason1 = "rule for DW_REG_CFA: invalid |oldReg|";
|
||||
goto cant_summarise;
|
||||
}
|
||||
mCurrRules.mCfaExpr = LExpr(how, oldReg, offset);
|
||||
break;
|
||||
|
||||
case DW_REG_AARCH64_X29:
|
||||
case DW_REG_AARCH64_X30:
|
||||
case DW_REG_AARCH64_SP: {
|
||||
switch (how) {
|
||||
case NODEREF:
|
||||
case DEREF:
|
||||
// Check the old register is one we're tracking.
|
||||
if (!registerIsTracked((DW_REG_NUMBER)oldReg) &&
|
||||
oldReg != DW_REG_CFA) {
|
||||
reason1 = "rule for X29/X30/SP: uses untracked reg";
|
||||
goto cant_summarise;
|
||||
}
|
||||
break;
|
||||
case PFXEXPR: {
|
||||
// Check that the prefix expression only mentions tracked registers.
|
||||
const vector<PfxInstr>* pfxInstrs = mSecMap->GetPfxInstrs();
|
||||
reason2 = checkPfxExpr(pfxInstrs, offset);
|
||||
if (reason2) {
|
||||
reason1 = "rule for X29/X30/SP: ";
|
||||
goto cant_summarise;
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
goto cant_summarise;
|
||||
}
|
||||
LExpr expr = LExpr(how, oldReg, offset);
|
||||
switch (aNewReg) {
|
||||
case DW_REG_AARCH64_X29:
|
||||
mCurrRules.mX29expr = expr;
|
||||
break;
|
||||
case DW_REG_AARCH64_X30:
|
||||
mCurrRules.mX30expr = expr;
|
||||
break;
|
||||
case DW_REG_AARCH64_SP:
|
||||
mCurrRules.mSPexpr = expr;
|
||||
break;
|
||||
default:
|
||||
MOZ_ASSERT(0);
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
// Leave |reason1| and |reason2| unset here, for the reasons explained
|
||||
// in the analogous point
|
||||
goto cant_summarise;
|
||||
}
|
||||
|
||||
if (mCurrRules.mX29expr.mHow == UNKNOWN) {
|
||||
mCurrRules.mX29expr = LExpr(NODEREF, DW_REG_AARCH64_X29, 0);
|
||||
}
|
||||
if (mCurrRules.mX30expr.mHow == UNKNOWN) {
|
||||
mCurrRules.mX30expr = LExpr(NODEREF, DW_REG_AARCH64_X30, 0);
|
||||
}
|
||||
// On aarch64, it seems the old SP value before the call is always the
|
||||
// same as the CFA. Therefore, in the absence of any other way to
|
||||
// recover the SP, specify that the CFA should be copied.
|
||||
if (mCurrRules.mSPexpr.mHow == UNKNOWN) {
|
||||
mCurrRules.mSPexpr = LExpr(NODEREF, DW_REG_CFA, 0);
|
||||
}
|
||||
#elif defined(GP_ARCH_amd64) || defined(GP_ARCH_x86)
|
||||
|
||||
// ---------------- x64/x86 ---------------- //
|
||||
|
||||
// Now, can we add the rule to our summary? This depends on whether
|
||||
// the registers and the overall expression are representable. This
|
||||
// is the heart of the summarisation process.
|
||||
switch (aNewReg) {
|
||||
case DW_REG_CFA: {
|
||||
// This is a rule that defines the CFA. The only forms we choose to
|
||||
// represent are: = SP+offset, = FP+offset, or =prefix-expr.
|
||||
switch (how) {
|
||||
case NODEREF:
|
||||
if (oldReg != DW_REG_INTEL_XSP && oldReg != DW_REG_INTEL_XBP) {
|
||||
reason1 = "rule for DW_REG_CFA: invalid |oldReg|";
|
||||
goto cant_summarise;
|
||||
}
|
||||
break;
|
||||
case DEREF:
|
||||
reason1 = "rule for DW_REG_CFA: invalid |how|";
|
||||
goto cant_summarise;
|
||||
case PFXEXPR: {
|
||||
// Check that the prefix expression only mentions tracked registers.
|
||||
const vector<PfxInstr>* pfxInstrs = mSecMap->GetPfxInstrs();
|
||||
reason2 = checkPfxExpr(pfxInstrs, offset);
|
||||
if (reason2) {
|
||||
reason1 = "rule for CFA: ";
|
||||
goto cant_summarise;
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
goto cant_summarise;
|
||||
}
|
||||
mCurrRules.mCfaExpr = LExpr(how, oldReg, offset);
|
||||
break;
|
||||
}
|
||||
|
||||
case DW_REG_INTEL_XSP:
|
||||
case DW_REG_INTEL_XBP:
|
||||
case DW_REG_INTEL_XIP: {
|
||||
// This is a new rule for XSP, XBP or XIP (the return address).
|
||||
switch (how) {
|
||||
case NODEREF:
|
||||
case DEREF:
|
||||
// Check the old register is one we're tracking.
|
||||
if (!registerIsTracked((DW_REG_NUMBER)oldReg) &&
|
||||
oldReg != DW_REG_CFA) {
|
||||
reason1 = "rule for XSP/XBP/XIP: uses untracked reg";
|
||||
goto cant_summarise;
|
||||
}
|
||||
break;
|
||||
case PFXEXPR: {
|
||||
// Check that the prefix expression only mentions tracked registers.
|
||||
const vector<PfxInstr>* pfxInstrs = mSecMap->GetPfxInstrs();
|
||||
reason2 = checkPfxExpr(pfxInstrs, offset);
|
||||
if (reason2) {
|
||||
reason1 = "rule for XSP/XBP/XIP: ";
|
||||
goto cant_summarise;
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
goto cant_summarise;
|
||||
}
|
||||
LExpr expr = LExpr(how, oldReg, offset);
|
||||
switch (aNewReg) {
|
||||
case DW_REG_INTEL_XBP:
|
||||
mCurrRules.mXbpExpr = expr;
|
||||
break;
|
||||
case DW_REG_INTEL_XSP:
|
||||
mCurrRules.mXspExpr = expr;
|
||||
break;
|
||||
case DW_REG_INTEL_XIP:
|
||||
mCurrRules.mXipExpr = expr;
|
||||
break;
|
||||
default:
|
||||
MOZ_CRASH("impossible value for aNewReg");
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
// Leave |reason1| and |reason2| unset here, for the reasons
|
||||
// explained in the analogous point in the ARM case just above.
|
||||
goto cant_summarise;
|
||||
}
|
||||
|
||||
// On Intel, it seems the old SP value before the call is always the
|
||||
// same as the CFA. Therefore, in the absence of any other way to
|
||||
// recover the SP, specify that the CFA should be copied.
|
||||
if (mCurrRules.mXspExpr.mHow == UNKNOWN) {
|
||||
mCurrRules.mXspExpr = LExpr(NODEREF, DW_REG_CFA, 0);
|
||||
}
|
||||
|
||||
// Also, gcc says "Undef" for BP when it is unchanged.
|
||||
if (mCurrRules.mXbpExpr.mHow == UNKNOWN) {
|
||||
mCurrRules.mXbpExpr = LExpr(NODEREF, DW_REG_INTEL_XBP, 0);
|
||||
}
|
||||
|
||||
#elif defined(GP_ARCH_mips64)
|
||||
// ---------------- mips ---------------- //
|
||||
//
|
||||
// Now, can we add the rule to our summary? This depends on whether
|
||||
// the registers and the overall expression are representable. This
|
||||
// is the heart of the summarisation process.
|
||||
switch (aNewReg) {
|
||||
case DW_REG_CFA:
|
||||
// This is a rule that defines the CFA. The only forms we can
|
||||
// represent are: = SP+offset or = FP+offset.
|
||||
if (how != NODEREF) {
|
||||
reason1 = "rule for DW_REG_CFA: invalid |how|";
|
||||
goto cant_summarise;
|
||||
}
|
||||
if (oldReg != DW_REG_MIPS_SP && oldReg != DW_REG_MIPS_FP) {
|
||||
reason1 = "rule for DW_REG_CFA: invalid |oldReg|";
|
||||
goto cant_summarise;
|
||||
}
|
||||
mCurrRules.mCfaExpr = LExpr(how, oldReg, offset);
|
||||
break;
|
||||
|
||||
case DW_REG_MIPS_SP:
|
||||
case DW_REG_MIPS_FP:
|
||||
case DW_REG_MIPS_PC: {
|
||||
// This is a new rule for SP, FP or PC (the return address).
|
||||
switch (how) {
|
||||
case NODEREF:
|
||||
case DEREF:
|
||||
// Check the old register is one we're tracking.
|
||||
if (!registerIsTracked((DW_REG_NUMBER)oldReg) &&
|
||||
oldReg != DW_REG_CFA) {
|
||||
reason1 = "rule for SP/FP/PC: uses untracked reg";
|
||||
goto cant_summarise;
|
||||
}
|
||||
break;
|
||||
case PFXEXPR: {
|
||||
// Check that the prefix expression only mentions tracked registers.
|
||||
const vector<PfxInstr>* pfxInstrs = mSecMap->GetPfxInstrs();
|
||||
reason2 = checkPfxExpr(pfxInstrs, offset);
|
||||
if (reason2) {
|
||||
reason1 = "rule for SP/FP/PC: ";
|
||||
goto cant_summarise;
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
goto cant_summarise;
|
||||
}
|
||||
LExpr expr = LExpr(how, oldReg, offset);
|
||||
switch (aNewReg) {
|
||||
case DW_REG_MIPS_FP:
|
||||
mCurrRules.mFPexpr = expr;
|
||||
break;
|
||||
case DW_REG_MIPS_SP:
|
||||
mCurrRules.mSPexpr = expr;
|
||||
break;
|
||||
case DW_REG_MIPS_PC:
|
||||
mCurrRules.mPCexpr = expr;
|
||||
break;
|
||||
default:
|
||||
MOZ_CRASH("impossible value for aNewReg");
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
// Leave |reason1| and |reason2| unset here, for the reasons
|
||||
// explained in the analogous point in the ARM case just above.
|
||||
goto cant_summarise;
|
||||
}
|
||||
|
||||
// On MIPS, it seems the old SP value before the call is always the
|
||||
// same as the CFA. Therefore, in the absence of any other way to
|
||||
// recover the SP, specify that the CFA should be copied.
|
||||
if (mCurrRules.mSPexpr.mHow == UNKNOWN) {
|
||||
mCurrRules.mSPexpr = LExpr(NODEREF, DW_REG_CFA, 0);
|
||||
}
|
||||
|
||||
// Also, gcc says "Undef" for FP when it is unchanged.
|
||||
if (mCurrRules.mFPexpr.mHow == UNKNOWN) {
|
||||
mCurrRules.mFPexpr = LExpr(NODEREF, DW_REG_MIPS_FP, 0);
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
# error "Unsupported arch"
|
||||
#endif
|
||||
|
||||
return;
|
||||
|
||||
cant_summarise:
|
||||
if (reason1 || reason2) {
|
||||
char buf[200];
|
||||
SprintfLiteral(buf,
|
||||
"LUL can't summarise: "
|
||||
"SVMA=0x%llx: %s%s, expr=LExpr(%s,%u,%lld)\n",
|
||||
(unsigned long long int)(aAddress - mTextBias),
|
||||
reason1 ? reason1 : "", reason2 ? reason2 : "",
|
||||
NameOf_LExprHow(how), (unsigned int)oldReg,
|
||||
(long long int)offset);
|
||||
mLog(buf);
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t Summariser::AddPfxInstr(PfxInstr pfxi) {
|
||||
return mSecMap->AddPfxInstr(pfxi);
|
||||
}
|
||||
|
||||
void Summariser::End() {
|
||||
if (DEBUG_SUMMARISER) {
|
||||
mLog("LUL End\n");
|
||||
}
|
||||
if (mCurrAddr < mMax1Addr) {
|
||||
mCurrRules.mAddr = mCurrAddr;
|
||||
mCurrRules.mLen = mMax1Addr - mCurrAddr;
|
||||
mSecMap->AddRuleSet(&mCurrRules);
|
||||
if (DEBUG_SUMMARISER) {
|
||||
mLog("LUL ");
|
||||
mCurrRules.Print(mLog);
|
||||
mLog("\n");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace lul
|
||||
@@ -1,64 +0,0 @@
|
||||
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#ifndef LulDwarfSummariser_h
|
||||
#define LulDwarfSummariser_h
|
||||
|
||||
#include "LulMainInt.h"
|
||||
|
||||
namespace lul {
|
||||
|
||||
class Summariser {
|
||||
public:
|
||||
Summariser(SecMap* aSecMap, uintptr_t aTextBias, void (*aLog)(const char*));
|
||||
|
||||
virtual void Entry(uintptr_t aAddress, uintptr_t aLength);
|
||||
virtual void End();
|
||||
|
||||
// Tell the summariser that the value for |aNewReg| at |aAddress| is
|
||||
// recovered using the LExpr that can be constructed using the
|
||||
// components |how|, |oldReg| and |offset|. The summariser will
|
||||
// inspect the components and may reject them for various reasons,
|
||||
// but the hope is that it will find them acceptable and record this
|
||||
// rule permanently.
|
||||
virtual void Rule(uintptr_t aAddress, int aNewReg, LExprHow how,
|
||||
int16_t oldReg, int64_t offset);
|
||||
|
||||
virtual uint32_t AddPfxInstr(PfxInstr pfxi);
|
||||
|
||||
// Send output to the logging sink, for debugging.
|
||||
virtual void Log(const char* str) { mLog(str); }
|
||||
|
||||
private:
|
||||
// The SecMap in which we park the finished summaries (RuleSets) and
|
||||
// also any PfxInstrs derived from Dwarf expressions.
|
||||
SecMap* mSecMap;
|
||||
|
||||
// Running state for the current summary (RuleSet) under construction.
|
||||
RuleSet mCurrRules;
|
||||
|
||||
// The start of the address range to which the RuleSet under
|
||||
// construction applies.
|
||||
uintptr_t mCurrAddr;
|
||||
|
||||
// The highest address, plus one, for which the RuleSet under
|
||||
// construction could possibly apply. If there are no further
|
||||
// incoming events then mCurrRules will eventually be emitted
|
||||
// as-is, for the range mCurrAddr.. mMax1Addr - 1, if that is
|
||||
// nonempty.
|
||||
uintptr_t mMax1Addr;
|
||||
|
||||
// The bias value (to add to the SVMAs, to get AVMAs) to be used
|
||||
// when adding entries into mSecMap.
|
||||
uintptr_t mTextBias;
|
||||
|
||||
// A logging sink, for debugging.
|
||||
void (*mLog)(const char* aFmt);
|
||||
};
|
||||
|
||||
} // namespace lul
|
||||
|
||||
#endif // LulDwarfSummariser_h
|
||||
@@ -1,871 +0,0 @@
|
||||
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
|
||||
|
||||
// Copyright (c) 2006, 2011, 2012 Google Inc.
|
||||
// All rights reserved.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are
|
||||
// met:
|
||||
//
|
||||
// * Redistributions of source code must retain the above copyright
|
||||
// notice, this list of conditions and the following disclaimer.
|
||||
// * Redistributions in binary form must reproduce the above
|
||||
// copyright notice, this list of conditions and the following disclaimer
|
||||
// in the documentation and/or other materials provided with the
|
||||
// distribution.
|
||||
// * Neither the name of Google Inc. nor the names of its
|
||||
// contributors may be used to endorse or promote products derived from
|
||||
// this software without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
// Restructured in 2009 by: Jim Blandy <jimb@mozilla.com> <jimb@red-bean.com>
|
||||
|
||||
// (derived from)
|
||||
// dump_symbols.cc: implement google_breakpad::WriteSymbolFile:
|
||||
// Find all the debugging info in a file and dump it as a Breakpad symbol file.
|
||||
//
|
||||
// dump_symbols.h: Read debugging information from an ELF file, and write
|
||||
// it out as a Breakpad symbol file.
|
||||
|
||||
// This file is derived from the following files in
|
||||
// toolkit/crashreporter/google-breakpad:
|
||||
// src/common/linux/dump_symbols.cc
|
||||
// src/common/linux/elfutils.cc
|
||||
// src/common/linux/file_id.cc
|
||||
|
||||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
#include <libgen.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <sys/mman.h>
|
||||
#include <sys/stat.h>
|
||||
#include <unistd.h>
|
||||
#include <arpa/inet.h>
|
||||
|
||||
#include <cstdlib>
|
||||
#include <set>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "mozilla/Assertions.h"
|
||||
#include "mozilla/Sprintf.h"
|
||||
|
||||
#include "PlatformMacros.h"
|
||||
#include "LulCommonExt.h"
|
||||
#include "LulDwarfExt.h"
|
||||
#include "LulElfInt.h"
|
||||
#include "LulMainInt.h"
|
||||
|
||||
#if defined(GP_PLAT_arm_android) && !defined(SHT_ARM_EXIDX)
|
||||
// bionic and older glibsc don't define it
|
||||
# define SHT_ARM_EXIDX (SHT_LOPROC + 1)
|
||||
#endif
|
||||
|
||||
// Old Linux header doesn't define EM_AARCH64
|
||||
#ifndef EM_AARCH64
|
||||
# define EM_AARCH64 183
|
||||
#endif
|
||||
|
||||
// This namespace contains helper functions.
|
||||
namespace {
|
||||
|
||||
using lul::DwarfCFIToModule;
|
||||
using lul::FindElfSectionByName;
|
||||
using lul::GetOffset;
|
||||
using lul::IsValidElf;
|
||||
using lul::Summariser;
|
||||
using lul::UniqueStringUniverse;
|
||||
using std::set;
|
||||
using std::string;
|
||||
using std::vector;
|
||||
|
||||
//
|
||||
// FDWrapper
|
||||
//
|
||||
// Wrapper class to make sure opened file is closed.
|
||||
//
|
||||
class FDWrapper {
|
||||
public:
|
||||
explicit FDWrapper(int fd) : fd_(fd) {}
|
||||
~FDWrapper() {
|
||||
if (fd_ != -1) close(fd_);
|
||||
}
|
||||
int get() { return fd_; }
|
||||
int release() {
|
||||
int fd = fd_;
|
||||
fd_ = -1;
|
||||
return fd;
|
||||
}
|
||||
|
||||
private:
|
||||
int fd_;
|
||||
};
|
||||
|
||||
//
|
||||
// MmapWrapper
|
||||
//
|
||||
// Wrapper class to make sure mapped regions are unmapped.
|
||||
//
|
||||
class MmapWrapper {
|
||||
public:
|
||||
MmapWrapper() : is_set_(false), base_(NULL), size_(0) {}
|
||||
~MmapWrapper() {
|
||||
if (is_set_ && base_ != NULL) {
|
||||
MOZ_ASSERT(size_ > 0);
|
||||
munmap(base_, size_);
|
||||
}
|
||||
}
|
||||
void set(void* mapped_address, size_t mapped_size) {
|
||||
is_set_ = true;
|
||||
base_ = mapped_address;
|
||||
size_ = mapped_size;
|
||||
}
|
||||
void release() {
|
||||
MOZ_ASSERT(is_set_);
|
||||
is_set_ = false;
|
||||
base_ = NULL;
|
||||
size_ = 0;
|
||||
}
|
||||
|
||||
private:
|
||||
bool is_set_;
|
||||
void* base_;
|
||||
size_t size_;
|
||||
};
|
||||
|
||||
// Set NUM_DW_REGNAMES to be the number of Dwarf register names
|
||||
// appropriate to the machine architecture given in HEADER. Return
|
||||
// true on success, or false if HEADER's machine architecture is not
|
||||
// supported.
|
||||
template <typename ElfClass>
|
||||
bool DwarfCFIRegisterNames(const typename ElfClass::Ehdr* elf_header,
|
||||
unsigned int* num_dw_regnames) {
|
||||
switch (elf_header->e_machine) {
|
||||
case EM_386:
|
||||
*num_dw_regnames = DwarfCFIToModule::RegisterNames::I386();
|
||||
return true;
|
||||
case EM_ARM:
|
||||
*num_dw_regnames = DwarfCFIToModule::RegisterNames::ARM();
|
||||
return true;
|
||||
case EM_X86_64:
|
||||
*num_dw_regnames = DwarfCFIToModule::RegisterNames::X86_64();
|
||||
return true;
|
||||
case EM_MIPS:
|
||||
*num_dw_regnames = DwarfCFIToModule::RegisterNames::MIPS();
|
||||
return true;
|
||||
case EM_AARCH64:
|
||||
*num_dw_regnames = DwarfCFIToModule::RegisterNames::ARM64();
|
||||
return true;
|
||||
default:
|
||||
MOZ_ASSERT(0);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
template <typename ElfClass>
|
||||
bool LoadDwarfCFI(const string& dwarf_filename,
|
||||
const typename ElfClass::Ehdr* elf_header,
|
||||
const char* section_name,
|
||||
const typename ElfClass::Shdr* section, const bool eh_frame,
|
||||
const typename ElfClass::Shdr* got_section,
|
||||
const typename ElfClass::Shdr* text_section,
|
||||
const bool big_endian, SecMap* smap, uintptr_t text_bias,
|
||||
UniqueStringUniverse* usu, void (*log)(const char*)) {
|
||||
// Find the appropriate set of register names for this file's
|
||||
// architecture.
|
||||
unsigned int num_dw_regs = 0;
|
||||
if (!DwarfCFIRegisterNames<ElfClass>(elf_header, &num_dw_regs)) {
|
||||
fprintf(stderr,
|
||||
"%s: unrecognized ELF machine architecture '%d';"
|
||||
" cannot convert DWARF call frame information\n",
|
||||
dwarf_filename.c_str(), elf_header->e_machine);
|
||||
return false;
|
||||
}
|
||||
|
||||
const lul::Endianness endianness =
|
||||
big_endian ? lul::ENDIANNESS_BIG : lul::ENDIANNESS_LITTLE;
|
||||
|
||||
// Find the call frame information and its size.
|
||||
const char* cfi = GetOffset<ElfClass, char>(elf_header, section->sh_offset);
|
||||
size_t cfi_size = section->sh_size;
|
||||
|
||||
// Plug together the parser, handler, and their entourages.
|
||||
|
||||
// Here's a summariser, which will receive the output of the
|
||||
// parser, create summaries, and add them to |smap|.
|
||||
Summariser summ(smap, text_bias, log);
|
||||
|
||||
lul::ByteReader reader(endianness);
|
||||
reader.SetAddressSize(ElfClass::kAddrSize);
|
||||
|
||||
DwarfCFIToModule::Reporter module_reporter(log, dwarf_filename, section_name);
|
||||
DwarfCFIToModule handler(num_dw_regs, &module_reporter, &reader, usu, &summ);
|
||||
|
||||
// Provide the base addresses for .eh_frame encoded pointers, if
|
||||
// possible.
|
||||
reader.SetCFIDataBase(section->sh_addr, cfi);
|
||||
if (got_section) reader.SetDataBase(got_section->sh_addr);
|
||||
if (text_section) reader.SetTextBase(text_section->sh_addr);
|
||||
|
||||
lul::CallFrameInfo::Reporter dwarf_reporter(log, dwarf_filename,
|
||||
section_name);
|
||||
lul::CallFrameInfo parser(cfi, cfi_size, &reader, &handler, &dwarf_reporter,
|
||||
eh_frame);
|
||||
parser.Start();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool LoadELF(const string& obj_file, MmapWrapper* map_wrapper,
|
||||
void** elf_header) {
|
||||
int obj_fd = open(obj_file.c_str(), O_RDONLY);
|
||||
if (obj_fd < 0) {
|
||||
fprintf(stderr, "Failed to open ELF file '%s': %s\n", obj_file.c_str(),
|
||||
strerror(errno));
|
||||
return false;
|
||||
}
|
||||
FDWrapper obj_fd_wrapper(obj_fd);
|
||||
struct stat st;
|
||||
if (fstat(obj_fd, &st) != 0 && st.st_size <= 0) {
|
||||
fprintf(stderr, "Unable to fstat ELF file '%s': %s\n", obj_file.c_str(),
|
||||
strerror(errno));
|
||||
return false;
|
||||
}
|
||||
// Mapping it read-only is good enough. In any case, mapping it
|
||||
// read-write confuses Valgrind's debuginfo acquire/discard
|
||||
// heuristics, making it hard to profile the profiler.
|
||||
void* obj_base = mmap(nullptr, st.st_size, PROT_READ, MAP_PRIVATE, obj_fd, 0);
|
||||
if (obj_base == MAP_FAILED) {
|
||||
fprintf(stderr, "Failed to mmap ELF file '%s': %s\n", obj_file.c_str(),
|
||||
strerror(errno));
|
||||
return false;
|
||||
}
|
||||
map_wrapper->set(obj_base, st.st_size);
|
||||
*elf_header = obj_base;
|
||||
if (!IsValidElf(*elf_header)) {
|
||||
fprintf(stderr, "Not a valid ELF file: %s\n", obj_file.c_str());
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// Get the endianness of ELF_HEADER. If it's invalid, return false.
|
||||
template <typename ElfClass>
|
||||
bool ElfEndianness(const typename ElfClass::Ehdr* elf_header,
|
||||
bool* big_endian) {
|
||||
if (elf_header->e_ident[EI_DATA] == ELFDATA2LSB) {
|
||||
*big_endian = false;
|
||||
return true;
|
||||
}
|
||||
if (elf_header->e_ident[EI_DATA] == ELFDATA2MSB) {
|
||||
*big_endian = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
fprintf(stderr, "bad data encoding in ELF header: %d\n",
|
||||
elf_header->e_ident[EI_DATA]);
|
||||
return false;
|
||||
}
|
||||
|
||||
//
|
||||
// LoadSymbolsInfo
|
||||
//
|
||||
// Holds the state between the two calls to LoadSymbols() in case it's necessary
|
||||
// to follow the .gnu_debuglink section and load debug information from a
|
||||
// different file.
|
||||
//
|
||||
template <typename ElfClass>
|
||||
class LoadSymbolsInfo {
|
||||
public:
|
||||
typedef typename ElfClass::Addr Addr;
|
||||
|
||||
explicit LoadSymbolsInfo(const vector<string>& dbg_dirs)
|
||||
: debug_dirs_(dbg_dirs), has_loading_addr_(false) {}
|
||||
|
||||
// Keeps track of which sections have been loaded so sections don't
|
||||
// accidentally get loaded twice from two different files.
|
||||
void LoadedSection(const string& section) {
|
||||
if (loaded_sections_.count(section) == 0) {
|
||||
loaded_sections_.insert(section);
|
||||
} else {
|
||||
fprintf(stderr, "Section %s has already been loaded.\n", section.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
string debuglink_file() const { return debuglink_file_; }
|
||||
|
||||
private:
|
||||
const vector<string>& debug_dirs_; // Directories in which to
|
||||
// search for the debug ELF file.
|
||||
|
||||
string debuglink_file_; // Full path to the debug ELF file.
|
||||
|
||||
bool has_loading_addr_; // Indicate if LOADING_ADDR_ is valid.
|
||||
|
||||
set<string> loaded_sections_; // Tracks the Loaded ELF sections
|
||||
// between calls to LoadSymbols().
|
||||
};
|
||||
|
||||
// Find the preferred loading address of the binary.
|
||||
template <typename ElfClass>
|
||||
typename ElfClass::Addr GetLoadingAddress(
|
||||
const typename ElfClass::Phdr* program_headers, int nheader) {
|
||||
typedef typename ElfClass::Phdr Phdr;
|
||||
|
||||
// For non-PIC executables (e_type == ET_EXEC), the load address is
|
||||
// the start address of the first PT_LOAD segment. (ELF requires
|
||||
// the segments to be sorted by load address.) For PIC executables
|
||||
// and dynamic libraries (e_type == ET_DYN), this address will
|
||||
// normally be zero.
|
||||
for (int i = 0; i < nheader; ++i) {
|
||||
const Phdr& header = program_headers[i];
|
||||
if (header.p_type == PT_LOAD) return header.p_vaddr;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
template <typename ElfClass>
|
||||
bool LoadSymbols(const string& obj_file, const bool big_endian,
|
||||
const typename ElfClass::Ehdr* elf_header,
|
||||
const bool read_gnu_debug_link,
|
||||
LoadSymbolsInfo<ElfClass>* info, SecMap* smap, void* rx_avma,
|
||||
size_t rx_size, UniqueStringUniverse* usu,
|
||||
void (*log)(const char*)) {
|
||||
typedef typename ElfClass::Phdr Phdr;
|
||||
typedef typename ElfClass::Shdr Shdr;
|
||||
|
||||
char buf[500];
|
||||
SprintfLiteral(buf, "LoadSymbols: BEGIN %s\n", obj_file.c_str());
|
||||
buf[sizeof(buf) - 1] = 0;
|
||||
log(buf);
|
||||
|
||||
// This is how the text bias is calculated.
|
||||
// BEGIN CALCULATE BIAS
|
||||
uintptr_t loading_addr = GetLoadingAddress<ElfClass>(
|
||||
GetOffset<ElfClass, Phdr>(elf_header, elf_header->e_phoff),
|
||||
elf_header->e_phnum);
|
||||
uintptr_t text_bias = ((uintptr_t)rx_avma) - loading_addr;
|
||||
SprintfLiteral(buf, "LoadSymbols: rx_avma=%llx, text_bias=%llx",
|
||||
(unsigned long long int)(uintptr_t)rx_avma,
|
||||
(unsigned long long int)text_bias);
|
||||
buf[sizeof(buf) - 1] = 0;
|
||||
log(buf);
|
||||
// END CALCULATE BIAS
|
||||
|
||||
const Shdr* sections =
|
||||
GetOffset<ElfClass, Shdr>(elf_header, elf_header->e_shoff);
|
||||
const Shdr* section_names = sections + elf_header->e_shstrndx;
|
||||
const char* names =
|
||||
GetOffset<ElfClass, char>(elf_header, section_names->sh_offset);
|
||||
const char* names_end = names + section_names->sh_size;
|
||||
bool found_usable_info = false;
|
||||
|
||||
// Dwarf Call Frame Information (CFI) is actually independent from
|
||||
// the other DWARF debugging information, and can be used alone.
|
||||
const Shdr* dwarf_cfi_section =
|
||||
FindElfSectionByName<ElfClass>(".debug_frame", SHT_PROGBITS, sections,
|
||||
names, names_end, elf_header->e_shnum);
|
||||
if (dwarf_cfi_section) {
|
||||
// Ignore the return value of this function; even without call frame
|
||||
// information, the other debugging information could be perfectly
|
||||
// useful.
|
||||
info->LoadedSection(".debug_frame");
|
||||
bool result = LoadDwarfCFI<ElfClass>(obj_file, elf_header, ".debug_frame",
|
||||
dwarf_cfi_section, false, 0, 0,
|
||||
big_endian, smap, text_bias, usu, log);
|
||||
found_usable_info = found_usable_info || result;
|
||||
if (result) log("LoadSymbols: read CFI from .debug_frame");
|
||||
}
|
||||
|
||||
// Linux C++ exception handling information can also provide
|
||||
// unwinding data.
|
||||
const Shdr* eh_frame_section =
|
||||
FindElfSectionByName<ElfClass>(".eh_frame", SHT_PROGBITS, sections, names,
|
||||
names_end, elf_header->e_shnum);
|
||||
if (eh_frame_section) {
|
||||
// Pointers in .eh_frame data may be relative to the base addresses of
|
||||
// certain sections. Provide those sections if present.
|
||||
const Shdr* got_section = FindElfSectionByName<ElfClass>(
|
||||
".got", SHT_PROGBITS, sections, names, names_end, elf_header->e_shnum);
|
||||
const Shdr* text_section = FindElfSectionByName<ElfClass>(
|
||||
".text", SHT_PROGBITS, sections, names, names_end, elf_header->e_shnum);
|
||||
info->LoadedSection(".eh_frame");
|
||||
// As above, ignore the return value of this function.
|
||||
bool result = LoadDwarfCFI<ElfClass>(
|
||||
obj_file, elf_header, ".eh_frame", eh_frame_section, true, got_section,
|
||||
text_section, big_endian, smap, text_bias, usu, log);
|
||||
found_usable_info = found_usable_info || result;
|
||||
if (result) log("LoadSymbols: read CFI from .eh_frame");
|
||||
}
|
||||
|
||||
SprintfLiteral(buf, "LoadSymbols: END %s\n", obj_file.c_str());
|
||||
buf[sizeof(buf) - 1] = 0;
|
||||
log(buf);
|
||||
|
||||
return found_usable_info;
|
||||
}
|
||||
|
||||
// Return the breakpad symbol file identifier for the architecture of
|
||||
// ELF_HEADER.
|
||||
template <typename ElfClass>
|
||||
const char* ElfArchitecture(const typename ElfClass::Ehdr* elf_header) {
|
||||
typedef typename ElfClass::Half Half;
|
||||
Half arch = elf_header->e_machine;
|
||||
switch (arch) {
|
||||
case EM_386:
|
||||
return "x86";
|
||||
case EM_ARM:
|
||||
return "arm";
|
||||
case EM_AARCH64:
|
||||
return "arm64";
|
||||
case EM_MIPS:
|
||||
return "mips";
|
||||
case EM_PPC64:
|
||||
return "ppc64";
|
||||
case EM_PPC:
|
||||
return "ppc";
|
||||
case EM_S390:
|
||||
return "s390";
|
||||
case EM_SPARC:
|
||||
return "sparc";
|
||||
case EM_SPARCV9:
|
||||
return "sparcv9";
|
||||
case EM_X86_64:
|
||||
return "x86_64";
|
||||
default:
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
// Format the Elf file identifier in IDENTIFIER as a UUID with the
|
||||
// dashes removed.
|
||||
string FormatIdentifier(unsigned char identifier[16]) {
|
||||
char identifier_str[40];
|
||||
lul::FileID::ConvertIdentifierToString(identifier, identifier_str,
|
||||
sizeof(identifier_str));
|
||||
string id_no_dash;
|
||||
for (int i = 0; identifier_str[i] != '\0'; ++i)
|
||||
if (identifier_str[i] != '-') id_no_dash += identifier_str[i];
|
||||
// Add an extra "0" by the end. PDB files on Windows have an 'age'
|
||||
// number appended to the end of the file identifier; this isn't
|
||||
// really used or necessary on other platforms, but be consistent.
|
||||
id_no_dash += '0';
|
||||
return id_no_dash;
|
||||
}
|
||||
|
||||
// Return the non-directory portion of FILENAME: the portion after the
|
||||
// last slash, or the whole filename if there are no slashes.
|
||||
string BaseFileName(const string& filename) {
|
||||
// Lots of copies! basename's behavior is less than ideal.
|
||||
char* c_filename = strdup(filename.c_str());
|
||||
string base = basename(c_filename);
|
||||
free(c_filename);
|
||||
return base;
|
||||
}
|
||||
|
||||
template <typename ElfClass>
|
||||
bool ReadSymbolDataElfClass(const typename ElfClass::Ehdr* elf_header,
|
||||
const string& obj_filename,
|
||||
const vector<string>& debug_dirs, SecMap* smap,
|
||||
void* rx_avma, size_t rx_size,
|
||||
UniqueStringUniverse* usu,
|
||||
void (*log)(const char*)) {
|
||||
typedef typename ElfClass::Ehdr Ehdr;
|
||||
|
||||
unsigned char identifier[16];
|
||||
if (!lul ::FileID::ElfFileIdentifierFromMappedFile(elf_header, identifier)) {
|
||||
fprintf(stderr, "%s: unable to generate file identifier\n",
|
||||
obj_filename.c_str());
|
||||
return false;
|
||||
}
|
||||
|
||||
const char* architecture = ElfArchitecture<ElfClass>(elf_header);
|
||||
if (!architecture) {
|
||||
fprintf(stderr, "%s: unrecognized ELF machine architecture: %d\n",
|
||||
obj_filename.c_str(), elf_header->e_machine);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Figure out what endianness this file is.
|
||||
bool big_endian;
|
||||
if (!ElfEndianness<ElfClass>(elf_header, &big_endian)) return false;
|
||||
|
||||
string name = BaseFileName(obj_filename);
|
||||
string os = "Linux";
|
||||
string id = FormatIdentifier(identifier);
|
||||
|
||||
LoadSymbolsInfo<ElfClass> info(debug_dirs);
|
||||
if (!LoadSymbols<ElfClass>(obj_filename, big_endian, elf_header,
|
||||
!debug_dirs.empty(), &info, smap, rx_avma, rx_size,
|
||||
usu, log)) {
|
||||
const string debuglink_file = info.debuglink_file();
|
||||
if (debuglink_file.empty()) return false;
|
||||
|
||||
// Load debuglink ELF file.
|
||||
fprintf(stderr, "Found debugging info in %s\n", debuglink_file.c_str());
|
||||
MmapWrapper debug_map_wrapper;
|
||||
Ehdr* debug_elf_header = NULL;
|
||||
if (!LoadELF(debuglink_file, &debug_map_wrapper,
|
||||
reinterpret_cast<void**>(&debug_elf_header)))
|
||||
return false;
|
||||
// Sanity checks to make sure everything matches up.
|
||||
const char* debug_architecture =
|
||||
ElfArchitecture<ElfClass>(debug_elf_header);
|
||||
if (!debug_architecture) {
|
||||
fprintf(stderr, "%s: unrecognized ELF machine architecture: %d\n",
|
||||
debuglink_file.c_str(), debug_elf_header->e_machine);
|
||||
return false;
|
||||
}
|
||||
if (strcmp(architecture, debug_architecture)) {
|
||||
fprintf(stderr,
|
||||
"%s with ELF machine architecture %s does not match "
|
||||
"%s with ELF architecture %s\n",
|
||||
debuglink_file.c_str(), debug_architecture, obj_filename.c_str(),
|
||||
architecture);
|
||||
return false;
|
||||
}
|
||||
|
||||
bool debug_big_endian;
|
||||
if (!ElfEndianness<ElfClass>(debug_elf_header, &debug_big_endian))
|
||||
return false;
|
||||
if (debug_big_endian != big_endian) {
|
||||
fprintf(stderr, "%s and %s does not match in endianness\n",
|
||||
obj_filename.c_str(), debuglink_file.c_str());
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!LoadSymbols<ElfClass>(debuglink_file, debug_big_endian,
|
||||
debug_elf_header, false, &info, smap, rx_avma,
|
||||
rx_size, usu, log)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
namespace lul {
|
||||
|
||||
bool ReadSymbolDataInternal(const uint8_t* obj_file, const string& obj_filename,
|
||||
const vector<string>& debug_dirs, SecMap* smap,
|
||||
void* rx_avma, size_t rx_size,
|
||||
UniqueStringUniverse* usu,
|
||||
void (*log)(const char*)) {
|
||||
if (!IsValidElf(obj_file)) {
|
||||
fprintf(stderr, "Not a valid ELF file: %s\n", obj_filename.c_str());
|
||||
return false;
|
||||
}
|
||||
|
||||
int elfclass = ElfClass(obj_file);
|
||||
if (elfclass == ELFCLASS32) {
|
||||
return ReadSymbolDataElfClass<ElfClass32>(
|
||||
reinterpret_cast<const Elf32_Ehdr*>(obj_file), obj_filename, debug_dirs,
|
||||
smap, rx_avma, rx_size, usu, log);
|
||||
}
|
||||
if (elfclass == ELFCLASS64) {
|
||||
return ReadSymbolDataElfClass<ElfClass64>(
|
||||
reinterpret_cast<const Elf64_Ehdr*>(obj_file), obj_filename, debug_dirs,
|
||||
smap, rx_avma, rx_size, usu, log);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool ReadSymbolData(const string& obj_file, const vector<string>& debug_dirs,
|
||||
SecMap* smap, void* rx_avma, size_t rx_size,
|
||||
UniqueStringUniverse* usu, void (*log)(const char*)) {
|
||||
MmapWrapper map_wrapper;
|
||||
void* elf_header = NULL;
|
||||
if (!LoadELF(obj_file, &map_wrapper, &elf_header)) return false;
|
||||
|
||||
return ReadSymbolDataInternal(reinterpret_cast<uint8_t*>(elf_header),
|
||||
obj_file, debug_dirs, smap, rx_avma, rx_size,
|
||||
usu, log);
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
template <typename ElfClass>
|
||||
void FindElfClassSection(const char* elf_base, const char* section_name,
|
||||
typename ElfClass::Word section_type,
|
||||
const void** section_start, int* section_size) {
|
||||
typedef typename ElfClass::Ehdr Ehdr;
|
||||
typedef typename ElfClass::Shdr Shdr;
|
||||
|
||||
MOZ_ASSERT(elf_base);
|
||||
MOZ_ASSERT(section_start);
|
||||
MOZ_ASSERT(section_size);
|
||||
|
||||
MOZ_ASSERT(strncmp(elf_base, ELFMAG, SELFMAG) == 0);
|
||||
|
||||
const Ehdr* elf_header = reinterpret_cast<const Ehdr*>(elf_base);
|
||||
MOZ_ASSERT(elf_header->e_ident[EI_CLASS] == ElfClass::kClass);
|
||||
|
||||
const Shdr* sections =
|
||||
GetOffset<ElfClass, Shdr>(elf_header, elf_header->e_shoff);
|
||||
const Shdr* section_names = sections + elf_header->e_shstrndx;
|
||||
const char* names =
|
||||
GetOffset<ElfClass, char>(elf_header, section_names->sh_offset);
|
||||
const char* names_end = names + section_names->sh_size;
|
||||
|
||||
const Shdr* section =
|
||||
FindElfSectionByName<ElfClass>(section_name, section_type, sections,
|
||||
names, names_end, elf_header->e_shnum);
|
||||
|
||||
if (section != NULL && section->sh_size > 0) {
|
||||
*section_start = elf_base + section->sh_offset;
|
||||
*section_size = section->sh_size;
|
||||
}
|
||||
}
|
||||
|
||||
template <typename ElfClass>
|
||||
void FindElfClassSegment(const char* elf_base,
|
||||
typename ElfClass::Word segment_type,
|
||||
const void** segment_start, int* segment_size) {
|
||||
typedef typename ElfClass::Ehdr Ehdr;
|
||||
typedef typename ElfClass::Phdr Phdr;
|
||||
|
||||
MOZ_ASSERT(elf_base);
|
||||
MOZ_ASSERT(segment_start);
|
||||
MOZ_ASSERT(segment_size);
|
||||
|
||||
MOZ_ASSERT(strncmp(elf_base, ELFMAG, SELFMAG) == 0);
|
||||
|
||||
const Ehdr* elf_header = reinterpret_cast<const Ehdr*>(elf_base);
|
||||
MOZ_ASSERT(elf_header->e_ident[EI_CLASS] == ElfClass::kClass);
|
||||
|
||||
const Phdr* phdrs =
|
||||
GetOffset<ElfClass, Phdr>(elf_header, elf_header->e_phoff);
|
||||
|
||||
for (int i = 0; i < elf_header->e_phnum; ++i) {
|
||||
if (phdrs[i].p_type == segment_type) {
|
||||
*segment_start = elf_base + phdrs[i].p_offset;
|
||||
*segment_size = phdrs[i].p_filesz;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
bool IsValidElf(const void* elf_base) {
|
||||
return strncmp(reinterpret_cast<const char*>(elf_base), ELFMAG, SELFMAG) == 0;
|
||||
}
|
||||
|
||||
int ElfClass(const void* elf_base) {
|
||||
const ElfW(Ehdr)* elf_header = reinterpret_cast<const ElfW(Ehdr)*>(elf_base);
|
||||
|
||||
return elf_header->e_ident[EI_CLASS];
|
||||
}
|
||||
|
||||
bool FindElfSection(const void* elf_mapped_base, const char* section_name,
|
||||
uint32_t section_type, const void** section_start,
|
||||
int* section_size, int* elfclass) {
|
||||
MOZ_ASSERT(elf_mapped_base);
|
||||
MOZ_ASSERT(section_start);
|
||||
MOZ_ASSERT(section_size);
|
||||
|
||||
*section_start = NULL;
|
||||
*section_size = 0;
|
||||
|
||||
if (!IsValidElf(elf_mapped_base)) return false;
|
||||
|
||||
int cls = ElfClass(elf_mapped_base);
|
||||
if (elfclass) {
|
||||
*elfclass = cls;
|
||||
}
|
||||
|
||||
const char* elf_base = static_cast<const char*>(elf_mapped_base);
|
||||
|
||||
if (cls == ELFCLASS32) {
|
||||
FindElfClassSection<ElfClass32>(elf_base, section_name, section_type,
|
||||
section_start, section_size);
|
||||
return *section_start != NULL;
|
||||
} else if (cls == ELFCLASS64) {
|
||||
FindElfClassSection<ElfClass64>(elf_base, section_name, section_type,
|
||||
section_start, section_size);
|
||||
return *section_start != NULL;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool FindElfSegment(const void* elf_mapped_base, uint32_t segment_type,
|
||||
const void** segment_start, int* segment_size,
|
||||
int* elfclass) {
|
||||
MOZ_ASSERT(elf_mapped_base);
|
||||
MOZ_ASSERT(segment_start);
|
||||
MOZ_ASSERT(segment_size);
|
||||
|
||||
*segment_start = NULL;
|
||||
*segment_size = 0;
|
||||
|
||||
if (!IsValidElf(elf_mapped_base)) return false;
|
||||
|
||||
int cls = ElfClass(elf_mapped_base);
|
||||
if (elfclass) {
|
||||
*elfclass = cls;
|
||||
}
|
||||
|
||||
const char* elf_base = static_cast<const char*>(elf_mapped_base);
|
||||
|
||||
if (cls == ELFCLASS32) {
|
||||
FindElfClassSegment<ElfClass32>(elf_base, segment_type, segment_start,
|
||||
segment_size);
|
||||
return *segment_start != NULL;
|
||||
} else if (cls == ELFCLASS64) {
|
||||
FindElfClassSegment<ElfClass64>(elf_base, segment_type, segment_start,
|
||||
segment_size);
|
||||
return *segment_start != NULL;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// (derived from)
|
||||
// file_id.cc: Return a unique identifier for a file
|
||||
//
|
||||
// See file_id.h for documentation
|
||||
//
|
||||
|
||||
// ELF note name and desc are 32-bits word padded.
|
||||
#define NOTE_PADDING(a) ((a + 3) & ~3)
|
||||
|
||||
// These functions are also used inside the crashed process, so be safe
|
||||
// and use the syscall/libc wrappers instead of direct syscalls or libc.
|
||||
|
||||
template <typename ElfClass>
|
||||
static bool ElfClassBuildIDNoteIdentifier(const void* section, int length,
|
||||
uint8_t identifier[kMDGUIDSize]) {
|
||||
typedef typename ElfClass::Nhdr Nhdr;
|
||||
|
||||
const void* section_end = reinterpret_cast<const char*>(section) + length;
|
||||
const Nhdr* note_header = reinterpret_cast<const Nhdr*>(section);
|
||||
while (reinterpret_cast<const void*>(note_header) < section_end) {
|
||||
if (note_header->n_type == NT_GNU_BUILD_ID) break;
|
||||
note_header = reinterpret_cast<const Nhdr*>(
|
||||
reinterpret_cast<const char*>(note_header) + sizeof(Nhdr) +
|
||||
NOTE_PADDING(note_header->n_namesz) +
|
||||
NOTE_PADDING(note_header->n_descsz));
|
||||
}
|
||||
if (reinterpret_cast<const void*>(note_header) >= section_end ||
|
||||
note_header->n_descsz == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const char* build_id = reinterpret_cast<const char*>(note_header) +
|
||||
sizeof(Nhdr) + NOTE_PADDING(note_header->n_namesz);
|
||||
// Copy as many bits of the build ID as will fit
|
||||
// into the GUID space.
|
||||
memset(identifier, 0, kMDGUIDSize);
|
||||
memcpy(identifier, build_id,
|
||||
std::min(kMDGUIDSize, (size_t)note_header->n_descsz));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// Attempt to locate a .note.gnu.build-id section in an ELF binary
|
||||
// and copy as many bytes of it as will fit into |identifier|.
|
||||
static bool FindElfBuildIDNote(const void* elf_mapped_base,
|
||||
uint8_t identifier[kMDGUIDSize]) {
|
||||
void* note_section;
|
||||
int note_size, elfclass;
|
||||
if ((!FindElfSegment(elf_mapped_base, PT_NOTE, (const void**)¬e_section,
|
||||
¬e_size, &elfclass) ||
|
||||
note_size == 0) &&
|
||||
(!FindElfSection(elf_mapped_base, ".note.gnu.build-id", SHT_NOTE,
|
||||
(const void**)¬e_section, ¬e_size, &elfclass) ||
|
||||
note_size == 0)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (elfclass == ELFCLASS32) {
|
||||
return ElfClassBuildIDNoteIdentifier<ElfClass32>(note_section, note_size,
|
||||
identifier);
|
||||
} else if (elfclass == ELFCLASS64) {
|
||||
return ElfClassBuildIDNoteIdentifier<ElfClass64>(note_section, note_size,
|
||||
identifier);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// Attempt to locate the .text section of an ELF binary and generate
|
||||
// a simple hash by XORing the first page worth of bytes into |identifier|.
|
||||
static bool HashElfTextSection(const void* elf_mapped_base,
|
||||
uint8_t identifier[kMDGUIDSize]) {
|
||||
void* text_section;
|
||||
int text_size;
|
||||
if (!FindElfSection(elf_mapped_base, ".text", SHT_PROGBITS,
|
||||
(const void**)&text_section, &text_size, NULL) ||
|
||||
text_size == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
memset(identifier, 0, kMDGUIDSize);
|
||||
const uint8_t* ptr = reinterpret_cast<const uint8_t*>(text_section);
|
||||
const uint8_t* ptr_end = ptr + std::min(text_size, 4096);
|
||||
while (ptr < ptr_end) {
|
||||
for (unsigned i = 0; i < kMDGUIDSize; i++) identifier[i] ^= ptr[i];
|
||||
ptr += kMDGUIDSize;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// static
|
||||
bool FileID::ElfFileIdentifierFromMappedFile(const void* base,
|
||||
uint8_t identifier[kMDGUIDSize]) {
|
||||
// Look for a build id note first.
|
||||
if (FindElfBuildIDNote(base, identifier)) return true;
|
||||
|
||||
// Fall back on hashing the first page of the text section.
|
||||
return HashElfTextSection(base, identifier);
|
||||
}
|
||||
|
||||
// static
|
||||
void FileID::ConvertIdentifierToString(const uint8_t identifier[kMDGUIDSize],
|
||||
char* buffer, int buffer_length) {
|
||||
uint8_t identifier_swapped[kMDGUIDSize];
|
||||
|
||||
// Endian-ness swap to match dump processor expectation.
|
||||
memcpy(identifier_swapped, identifier, kMDGUIDSize);
|
||||
uint32_t* data1 = reinterpret_cast<uint32_t*>(identifier_swapped);
|
||||
*data1 = htonl(*data1);
|
||||
uint16_t* data2 = reinterpret_cast<uint16_t*>(identifier_swapped + 4);
|
||||
*data2 = htons(*data2);
|
||||
uint16_t* data3 = reinterpret_cast<uint16_t*>(identifier_swapped + 6);
|
||||
*data3 = htons(*data3);
|
||||
|
||||
int buffer_idx = 0;
|
||||
for (unsigned int idx = 0;
|
||||
(buffer_idx < buffer_length) && (idx < kMDGUIDSize); ++idx) {
|
||||
int hi = (identifier_swapped[idx] >> 4) & 0x0F;
|
||||
int lo = (identifier_swapped[idx]) & 0x0F;
|
||||
|
||||
if (idx == 4 || idx == 6 || idx == 8 || idx == 10)
|
||||
buffer[buffer_idx++] = '-';
|
||||
|
||||
buffer[buffer_idx++] = (hi >= 10) ? 'A' + hi - 10 : '0' + hi;
|
||||
buffer[buffer_idx++] = (lo >= 10) ? 'A' + lo - 10 : '0' + lo;
|
||||
}
|
||||
|
||||
// NULL terminate
|
||||
buffer[(buffer_idx < buffer_length) ? buffer_idx : buffer_idx - 1] = 0;
|
||||
}
|
||||
|
||||
} // namespace lul
|
||||
@@ -1,69 +0,0 @@
|
||||
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
|
||||
|
||||
// Copyright (c) 2006, 2011, 2012 Google Inc.
|
||||
// All rights reserved.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are
|
||||
// met:
|
||||
//
|
||||
// * Redistributions of source code must retain the above copyright
|
||||
// notice, this list of conditions and the following disclaimer.
|
||||
// * Redistributions in binary form must reproduce the above
|
||||
// copyright notice, this list of conditions and the following disclaimer
|
||||
// in the documentation and/or other materials provided with the
|
||||
// distribution.
|
||||
// * Neither the name of Google Inc. nor the names of its
|
||||
// contributors may be used to endorse or promote products derived from
|
||||
// this software without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
// This file is derived from the following files in
|
||||
// toolkit/crashreporter/google-breakpad:
|
||||
// src/common/linux/dump_symbols.h
|
||||
|
||||
#ifndef LulElfExt_h
|
||||
#define LulElfExt_h
|
||||
|
||||
// These two functions are the external interface to the
|
||||
// ELF/Dwarf/EXIDX reader.
|
||||
|
||||
#include "LulMainInt.h"
|
||||
|
||||
using lul::SecMap;
|
||||
|
||||
namespace lul {
|
||||
|
||||
class UniqueStringUniverse;
|
||||
|
||||
// Find all the unwind information in OBJ_FILE, an ELF executable
|
||||
// or shared library, and add it to SMAP.
|
||||
bool ReadSymbolData(const std::string& obj_file,
|
||||
const std::vector<std::string>& debug_dirs, SecMap* smap,
|
||||
void* rx_avma, size_t rx_size, UniqueStringUniverse* usu,
|
||||
void (*log)(const char*));
|
||||
|
||||
// The same as ReadSymbolData, except that OBJ_FILE is assumed to
|
||||
// point to a mapped-in image of OBJ_FILENAME.
|
||||
bool ReadSymbolDataInternal(const uint8_t* obj_file,
|
||||
const std::string& obj_filename,
|
||||
const std::vector<std::string>& debug_dirs,
|
||||
SecMap* smap, void* rx_avma, size_t rx_size,
|
||||
UniqueStringUniverse* usu,
|
||||
void (*log)(const char*));
|
||||
|
||||
} // namespace lul
|
||||
|
||||
#endif // LulElfExt_h
|
||||
@@ -1,218 +0,0 @@
|
||||
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
|
||||
|
||||
// Copyright (c) 2006, 2012, Google Inc.
|
||||
// All rights reserved.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are
|
||||
// met:
|
||||
//
|
||||
// * Redistributions of source code must retain the above copyright
|
||||
// notice, this list of conditions and the following disclaimer.
|
||||
// * Redistributions in binary form must reproduce the above
|
||||
// copyright notice, this list of conditions and the following disclaimer
|
||||
// in the documentation and/or other materials provided with the
|
||||
// distribution.
|
||||
// * Neither the name of Google Inc. nor the names of its
|
||||
// contributors may be used to endorse or promote products derived from
|
||||
// this software without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
// This file is derived from the following files in
|
||||
// toolkit/crashreporter/google-breakpad:
|
||||
// src/common/android/include/elf.h
|
||||
// src/common/linux/elfutils.h
|
||||
// src/common/linux/file_id.h
|
||||
// src/common/linux/elfutils-inl.h
|
||||
|
||||
#ifndef LulElfInt_h
|
||||
#define LulElfInt_h
|
||||
|
||||
// This header defines functions etc internal to the ELF reader. It
|
||||
// should not be included outside of LulElf.cpp.
|
||||
|
||||
#include <elf.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#include "mozilla/Assertions.h"
|
||||
|
||||
#include "PlatformMacros.h"
|
||||
|
||||
// (derived from)
|
||||
// elfutils.h: Utilities for dealing with ELF files.
|
||||
//
|
||||
#include <link.h>
|
||||
|
||||
#if defined(GP_OS_android)
|
||||
|
||||
// From toolkit/crashreporter/google-breakpad/src/common/android/include/elf.h
|
||||
// The Android headers don't always define this constant.
|
||||
# ifndef EM_X86_64
|
||||
# define EM_X86_64 62
|
||||
# endif
|
||||
|
||||
# ifndef EM_PPC64
|
||||
# define EM_PPC64 21
|
||||
# endif
|
||||
|
||||
# ifndef EM_S390
|
||||
# define EM_S390 22
|
||||
# endif
|
||||
|
||||
# ifndef NT_GNU_BUILD_ID
|
||||
# define NT_GNU_BUILD_ID 3
|
||||
# endif
|
||||
|
||||
# ifndef ElfW
|
||||
# define ElfW(type) _ElfW(Elf, ELFSIZE, type)
|
||||
# define _ElfW(e, w, t) _ElfW_1(e, w, _##t)
|
||||
# define _ElfW_1(e, w, t) e##w##t
|
||||
# endif
|
||||
|
||||
#endif
|
||||
|
||||
#if defined(GP_OS_freebsd)
|
||||
|
||||
# ifndef ElfW
|
||||
# define ElfW(type) Elf_##type
|
||||
# endif
|
||||
|
||||
#endif
|
||||
|
||||
namespace lul {
|
||||
|
||||
// Traits classes so consumers can write templatized code to deal
|
||||
// with specific ELF bits.
|
||||
struct ElfClass32 {
|
||||
typedef Elf32_Addr Addr;
|
||||
typedef Elf32_Ehdr Ehdr;
|
||||
typedef Elf32_Nhdr Nhdr;
|
||||
typedef Elf32_Phdr Phdr;
|
||||
typedef Elf32_Shdr Shdr;
|
||||
typedef Elf32_Half Half;
|
||||
typedef Elf32_Off Off;
|
||||
typedef Elf32_Word Word;
|
||||
static const int kClass = ELFCLASS32;
|
||||
static const size_t kAddrSize = sizeof(Elf32_Addr);
|
||||
};
|
||||
|
||||
struct ElfClass64 {
|
||||
typedef Elf64_Addr Addr;
|
||||
typedef Elf64_Ehdr Ehdr;
|
||||
typedef Elf64_Nhdr Nhdr;
|
||||
typedef Elf64_Phdr Phdr;
|
||||
typedef Elf64_Shdr Shdr;
|
||||
typedef Elf64_Half Half;
|
||||
typedef Elf64_Off Off;
|
||||
typedef Elf64_Word Word;
|
||||
static const int kClass = ELFCLASS64;
|
||||
static const size_t kAddrSize = sizeof(Elf64_Addr);
|
||||
};
|
||||
|
||||
bool IsValidElf(const void* elf_header);
|
||||
int ElfClass(const void* elf_base);
|
||||
|
||||
// Attempt to find a section named |section_name| of type |section_type|
|
||||
// in the ELF binary data at |elf_mapped_base|. On success, returns true
|
||||
// and sets |*section_start| to point to the start of the section data,
|
||||
// and |*section_size| to the size of the section's data. If |elfclass|
|
||||
// is not NULL, set |*elfclass| to the ELF file class.
|
||||
bool FindElfSection(const void* elf_mapped_base, const char* section_name,
|
||||
uint32_t section_type, const void** section_start,
|
||||
int* section_size, int* elfclass);
|
||||
|
||||
// Internal helper method, exposed for convenience for callers
|
||||
// that already have more info.
|
||||
template <typename ElfClass>
|
||||
const typename ElfClass::Shdr* FindElfSectionByName(
|
||||
const char* name, typename ElfClass::Word section_type,
|
||||
const typename ElfClass::Shdr* sections, const char* section_names,
|
||||
const char* names_end, int nsection);
|
||||
|
||||
// Attempt to find the first segment of type |segment_type| in the ELF
|
||||
// binary data at |elf_mapped_base|. On success, returns true and sets
|
||||
// |*segment_start| to point to the start of the segment data, and
|
||||
// and |*segment_size| to the size of the segment's data. If |elfclass|
|
||||
// is not NULL, set |*elfclass| to the ELF file class.
|
||||
bool FindElfSegment(const void* elf_mapped_base, uint32_t segment_type,
|
||||
const void** segment_start, int* segment_size,
|
||||
int* elfclass);
|
||||
|
||||
// Convert an offset from an Elf header into a pointer to the mapped
|
||||
// address in the current process. Takes an extra template parameter
|
||||
// to specify the return type to avoid having to dynamic_cast the
|
||||
// result.
|
||||
template <typename ElfClass, typename T>
|
||||
const T* GetOffset(const typename ElfClass::Ehdr* elf_header,
|
||||
typename ElfClass::Off offset);
|
||||
|
||||
// (derived from)
|
||||
// file_id.h: Return a unique identifier for a file
|
||||
//
|
||||
|
||||
static const size_t kMDGUIDSize = sizeof(MDGUID);
|
||||
|
||||
class FileID {
|
||||
public:
|
||||
// Load the identifier for the elf file mapped into memory at |base| into
|
||||
// |identifier|. Return false if the identifier could not be created for the
|
||||
// file.
|
||||
static bool ElfFileIdentifierFromMappedFile(const void* base,
|
||||
uint8_t identifier[kMDGUIDSize]);
|
||||
|
||||
// Convert the |identifier| data to a NULL terminated string. The string will
|
||||
// be formatted as a UUID (e.g., 22F065BB-FC9C-49F7-80FE-26A7CEBD7BCE).
|
||||
// The |buffer| should be at least 37 bytes long to receive all of the data
|
||||
// and termination. Shorter buffers will contain truncated data.
|
||||
static void ConvertIdentifierToString(const uint8_t identifier[kMDGUIDSize],
|
||||
char* buffer, int buffer_length);
|
||||
};
|
||||
|
||||
template <typename ElfClass, typename T>
|
||||
const T* GetOffset(const typename ElfClass::Ehdr* elf_header,
|
||||
typename ElfClass::Off offset) {
|
||||
return reinterpret_cast<const T*>(reinterpret_cast<uintptr_t>(elf_header) +
|
||||
offset);
|
||||
}
|
||||
|
||||
template <typename ElfClass>
|
||||
const typename ElfClass::Shdr* FindElfSectionByName(
|
||||
const char* name, typename ElfClass::Word section_type,
|
||||
const typename ElfClass::Shdr* sections, const char* section_names,
|
||||
const char* names_end, int nsection) {
|
||||
MOZ_ASSERT(name != NULL);
|
||||
MOZ_ASSERT(sections != NULL);
|
||||
MOZ_ASSERT(nsection > 0);
|
||||
|
||||
int name_len = strlen(name);
|
||||
if (name_len == 0) return NULL;
|
||||
|
||||
for (int i = 0; i < nsection; ++i) {
|
||||
const char* section_name = section_names + sections[i].sh_name;
|
||||
if (sections[i].sh_type == section_type &&
|
||||
names_end - section_name >= name_len + 1 &&
|
||||
strcmp(name, section_name) == 0) {
|
||||
return sections + i;
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
} // namespace lul
|
||||
|
||||
// And finally, the external interface, offered to LulMain.cpp
|
||||
#include "LulElfExt.h"
|
||||
|
||||
#endif // LulElfInt_h
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,378 +0,0 @@
|
||||
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#ifndef LulMain_h
|
||||
#define LulMain_h
|
||||
|
||||
#include "PlatformMacros.h"
|
||||
#include "mozilla/Atomics.h"
|
||||
#include "mozilla/MemoryReporting.h"
|
||||
#include "mozilla/BaseProfilerUtils.h"
|
||||
|
||||
// LUL: A Lightweight Unwind Library.
|
||||
// This file provides the end-user (external) interface for LUL.
|
||||
|
||||
// Some comments about naming in the implementation. These are safe
|
||||
// to ignore if you are merely using LUL, but are important if you
|
||||
// hack on its internals.
|
||||
//
|
||||
// Debuginfo readers in general have tended to use the word "address"
|
||||
// to mean several different things. This sometimes makes them
|
||||
// difficult to understand and maintain. LUL tries hard to avoid
|
||||
// using the word "address" and instead uses the following more
|
||||
// precise terms:
|
||||
//
|
||||
// * SVMA ("Stated Virtual Memory Address"): this is an address of a
|
||||
// symbol (etc) as it is stated in the symbol table, or other
|
||||
// metadata, of an object. Such values are typically small and
|
||||
// start from zero or thereabouts, unless the object has been
|
||||
// prelinked.
|
||||
//
|
||||
// * AVMA ("Actual Virtual Memory Address"): this is the address of a
|
||||
// symbol (etc) in a running process, that is, once the associated
|
||||
// object has been mapped into a process. Such values are typically
|
||||
// much larger than SVMAs, since objects can get mapped arbitrarily
|
||||
// far along the address space.
|
||||
//
|
||||
// * "Bias": the difference between AVMA and SVMA for a given symbol
|
||||
// (specifically, AVMA - SVMA). The bias is always an integral
|
||||
// number of pages. Once we know the bias for a given object's
|
||||
// text section (for example), we can compute the AVMAs of all of
|
||||
// its text symbols by adding the bias to their SVMAs.
|
||||
//
|
||||
// * "Image address": typically, to read debuginfo from an object we
|
||||
// will temporarily mmap in the file so as to read symbol tables
|
||||
// etc. Addresses in this temporary mapping are called "Image
|
||||
// addresses". Note that the temporary mapping is entirely
|
||||
// unrelated to the mappings of the file that the dynamic linker
|
||||
// must perform merely in order to get the program to run. Hence
|
||||
// image addresses are unrelated to either SVMAs or AVMAs.
|
||||
|
||||
namespace lul {
|
||||
|
||||
// A machine word plus validity tag.
|
||||
class TaggedUWord {
|
||||
public:
|
||||
// RUNS IN NO-MALLOC CONTEXT
|
||||
// Construct a valid one.
|
||||
explicit TaggedUWord(uintptr_t w) : mValue(w), mValid(true) {}
|
||||
|
||||
// RUNS IN NO-MALLOC CONTEXT
|
||||
// Construct an invalid one.
|
||||
TaggedUWord() : mValue(0), mValid(false) {}
|
||||
|
||||
// RUNS IN NO-MALLOC CONTEXT
|
||||
TaggedUWord operator+(TaggedUWord rhs) const {
|
||||
return (Valid() && rhs.Valid()) ? TaggedUWord(Value() + rhs.Value())
|
||||
: TaggedUWord();
|
||||
}
|
||||
|
||||
// RUNS IN NO-MALLOC CONTEXT
|
||||
TaggedUWord operator-(TaggedUWord rhs) const {
|
||||
return (Valid() && rhs.Valid()) ? TaggedUWord(Value() - rhs.Value())
|
||||
: TaggedUWord();
|
||||
}
|
||||
|
||||
// RUNS IN NO-MALLOC CONTEXT
|
||||
TaggedUWord operator&(TaggedUWord rhs) const {
|
||||
return (Valid() && rhs.Valid()) ? TaggedUWord(Value() & rhs.Value())
|
||||
: TaggedUWord();
|
||||
}
|
||||
|
||||
// RUNS IN NO-MALLOC CONTEXT
|
||||
TaggedUWord operator|(TaggedUWord rhs) const {
|
||||
return (Valid() && rhs.Valid()) ? TaggedUWord(Value() | rhs.Value())
|
||||
: TaggedUWord();
|
||||
}
|
||||
|
||||
// RUNS IN NO-MALLOC CONTEXT
|
||||
TaggedUWord CmpGEs(TaggedUWord rhs) const {
|
||||
if (Valid() && rhs.Valid()) {
|
||||
intptr_t s1 = (intptr_t)Value();
|
||||
intptr_t s2 = (intptr_t)rhs.Value();
|
||||
return TaggedUWord(s1 >= s2 ? 1 : 0);
|
||||
}
|
||||
return TaggedUWord();
|
||||
}
|
||||
|
||||
// RUNS IN NO-MALLOC CONTEXT
|
||||
TaggedUWord operator<<(TaggedUWord rhs) const {
|
||||
if (Valid() && rhs.Valid()) {
|
||||
uintptr_t shift = rhs.Value();
|
||||
if (shift < 8 * sizeof(uintptr_t)) return TaggedUWord(Value() << shift);
|
||||
}
|
||||
return TaggedUWord();
|
||||
}
|
||||
|
||||
// RUNS IN NO-MALLOC CONTEXT
|
||||
// Is equal? Note: non-validity on either side gives non-equality.
|
||||
bool operator==(TaggedUWord other) const {
|
||||
return (mValid && other.Valid()) ? (mValue == other.Value()) : false;
|
||||
}
|
||||
|
||||
// RUNS IN NO-MALLOC CONTEXT
|
||||
// Is it word-aligned?
|
||||
bool IsAligned() const {
|
||||
return mValid && (mValue & (sizeof(uintptr_t) - 1)) == 0;
|
||||
}
|
||||
|
||||
// RUNS IN NO-MALLOC CONTEXT
|
||||
uintptr_t Value() const { return mValue; }
|
||||
|
||||
// RUNS IN NO-MALLOC CONTEXT
|
||||
bool Valid() const { return mValid; }
|
||||
|
||||
private:
|
||||
uintptr_t mValue;
|
||||
bool mValid;
|
||||
};
|
||||
|
||||
// The registers, with validity tags, that will be unwound.
|
||||
|
||||
struct UnwindRegs {
|
||||
#if defined(GP_ARCH_arm)
|
||||
TaggedUWord r7;
|
||||
TaggedUWord r11;
|
||||
TaggedUWord r12;
|
||||
TaggedUWord r13;
|
||||
TaggedUWord r14;
|
||||
TaggedUWord r15;
|
||||
#elif defined(GP_ARCH_arm64)
|
||||
TaggedUWord x29;
|
||||
TaggedUWord x30;
|
||||
TaggedUWord sp;
|
||||
TaggedUWord pc;
|
||||
#elif defined(GP_ARCH_amd64) || defined(GP_ARCH_x86)
|
||||
TaggedUWord xbp;
|
||||
TaggedUWord xsp;
|
||||
TaggedUWord xip;
|
||||
#elif defined(GP_ARCH_mips64)
|
||||
TaggedUWord sp;
|
||||
TaggedUWord fp;
|
||||
TaggedUWord pc;
|
||||
#else
|
||||
# error "Unknown plat"
|
||||
#endif
|
||||
};
|
||||
|
||||
// The maximum number of bytes in a stack snapshot. This value can be increased
|
||||
// if necessary, but testing showed that 160k is enough to obtain good
|
||||
// backtraces on x86_64 Linux. Most backtraces fit comfortably into 4-8k of
|
||||
// stack space, but we do have some very deep stacks occasionally. Please see
|
||||
// the comments in DoNativeBacktrace as to why it's OK to have this value be so
|
||||
// large.
|
||||
static const size_t N_STACK_BYTES = 160 * 1024;
|
||||
|
||||
// The stack chunk image that will be unwound.
|
||||
struct StackImage {
|
||||
// [start_avma, +len) specify the address range in the buffer.
|
||||
// Obviously we require 0 <= len <= N_STACK_BYTES.
|
||||
uintptr_t mStartAvma;
|
||||
size_t mLen;
|
||||
uint8_t mContents[N_STACK_BYTES];
|
||||
};
|
||||
|
||||
// Statistics collection for the unwinder.
|
||||
template <typename T>
|
||||
class LULStats {
|
||||
public:
|
||||
LULStats() : mContext(0), mCFI(0), mFP(0) {}
|
||||
|
||||
template <typename S>
|
||||
explicit LULStats(const LULStats<S>& aOther)
|
||||
: mContext(aOther.mContext), mCFI(aOther.mCFI), mFP(aOther.mFP) {}
|
||||
|
||||
template <typename S>
|
||||
LULStats<T>& operator=(const LULStats<S>& aOther) {
|
||||
mContext = aOther.mContext;
|
||||
mCFI = aOther.mCFI;
|
||||
mFP = aOther.mFP;
|
||||
return *this;
|
||||
}
|
||||
|
||||
template <typename S>
|
||||
uint32_t operator-(const LULStats<S>& aOther) {
|
||||
return (mContext - aOther.mContext) + (mCFI - aOther.mCFI) +
|
||||
(mFP - aOther.mFP);
|
||||
}
|
||||
|
||||
T mContext; // Number of context frames
|
||||
T mCFI; // Number of CFI/EXIDX frames
|
||||
T mFP; // Number of frame-pointer recovered frames
|
||||
};
|
||||
|
||||
// The core unwinder library class. Just one of these is needed, and
|
||||
// it can be shared by multiple unwinder threads.
|
||||
//
|
||||
// The library operates in one of two modes.
|
||||
//
|
||||
// * Admin mode. The library is this state after creation. In Admin
|
||||
// mode, no unwinding may be performed. It is however allowable to
|
||||
// perform administrative tasks -- primarily, loading of unwind info
|
||||
// -- in this mode. In particular, it is safe for the library to
|
||||
// perform dynamic memory allocation in this mode. Safe in the
|
||||
// sense that there is no risk of deadlock against unwinding threads
|
||||
// that might -- because of where they have been sampled -- hold the
|
||||
// system's malloc lock.
|
||||
//
|
||||
// * Unwind mode. In this mode, calls to ::Unwind may be made, but
|
||||
// nothing else. ::Unwind guarantees not to make any dynamic memory
|
||||
// requests, so as to guarantee that the calling thread won't
|
||||
// deadlock in the case where it already holds the system's malloc lock.
|
||||
//
|
||||
// The library is created in Admin mode. After debuginfo is loaded,
|
||||
// the caller must switch it into Unwind mode by calling
|
||||
// ::EnableUnwinding. There is no way to switch it back to Admin mode
|
||||
// after that. To safely switch back to Admin mode would require the
|
||||
// caller (or other external agent) to guarantee that there are no
|
||||
// pending ::Unwind calls.
|
||||
|
||||
class PriMap;
|
||||
class SegArray;
|
||||
class UniqueStringUniverse;
|
||||
|
||||
class LUL {
|
||||
public:
|
||||
// Create; supply a logging sink. Sets the object in Admin mode.
|
||||
explicit LUL(void (*aLog)(const char*));
|
||||
|
||||
// Destroy. Caller is responsible for ensuring that no other
|
||||
// threads are in Unwind calls. All resources are freed and all
|
||||
// registered unwinder threads are deregistered. Can be called
|
||||
// either in Admin or Unwind mode.
|
||||
~LUL();
|
||||
|
||||
// Notify the library that unwinding is now allowed and so
|
||||
// admin-mode calls are no longer allowed. The object is initially
|
||||
// created in admin mode. The only possible transition is
|
||||
// admin->unwinding, therefore.
|
||||
void EnableUnwinding();
|
||||
|
||||
// Notify of a new r-x mapping, and load the associated unwind info.
|
||||
// The filename is strdup'd and used for debug printing. If
|
||||
// aMappedImage is NULL, this function will mmap/munmap the file
|
||||
// itself, so as to be able to read the unwind info. If
|
||||
// aMappedImage is non-NULL then it is assumed to point to a
|
||||
// called-supplied and caller-managed mapped image of the file.
|
||||
// May only be called in Admin mode.
|
||||
void NotifyAfterMap(uintptr_t aRXavma, size_t aSize, const char* aFileName,
|
||||
const void* aMappedImage);
|
||||
|
||||
// In rare cases we know an executable area exists but don't know
|
||||
// what the associated file is. This call notifies LUL of such
|
||||
// areas. This is important for correct functioning of stack
|
||||
// scanning and of the x86-{linux,android} special-case
|
||||
// __kernel_syscall function handling.
|
||||
// This must be called only after the code area in
|
||||
// question really has been mapped.
|
||||
// May only be called in Admin mode.
|
||||
void NotifyExecutableArea(uintptr_t aRXavma, size_t aSize);
|
||||
|
||||
// Notify that a mapped area has been unmapped; discard any
|
||||
// associated unwind info. Acquires mRWlock for writing. Note that
|
||||
// to avoid segfaulting the stack-scan unwinder, which inspects code
|
||||
// areas, this must be called before the code area in question is
|
||||
// really unmapped. Note that, unlike NotifyAfterMap(), this
|
||||
// function takes the start and end addresses of the range to be
|
||||
// unmapped, rather than a start and a length parameter. This is so
|
||||
// as to make it possible to notify an unmap for the entire address
|
||||
// space using a single call.
|
||||
// May only be called in Admin mode.
|
||||
void NotifyBeforeUnmap(uintptr_t aAvmaMin, uintptr_t aAvmaMax);
|
||||
|
||||
// Apply NotifyBeforeUnmap to the entire address space. This causes
|
||||
// LUL to discard all unwind and executable-area information for the
|
||||
// entire address space.
|
||||
// May only be called in Admin mode.
|
||||
void NotifyBeforeUnmapAll() { NotifyBeforeUnmap(0, UINTPTR_MAX); }
|
||||
|
||||
// Returns the number of mappings currently registered.
|
||||
// May only be called in Admin mode.
|
||||
size_t CountMappings();
|
||||
|
||||
// Unwind |aStackImg| starting with the context in |aStartRegs|.
|
||||
// Write the number of frames recovered in *aFramesUsed. Put
|
||||
// the PC values in aFramePCs[0 .. *aFramesUsed-1] and
|
||||
// the SP values in aFrameSPs[0 .. *aFramesUsed-1].
|
||||
// |aFramesAvail| is the size of the two output arrays and hence the
|
||||
// largest possible value of *aFramesUsed. PC values are always
|
||||
// valid, and the unwind will stop when the PC becomes invalid, but
|
||||
// the SP values might be invalid, in which case the value zero will
|
||||
// be written in the relevant frameSPs[] slot.
|
||||
//
|
||||
// This function assumes that the SP values increase as it unwinds
|
||||
// away from the innermost frame -- that is, that the stack grows
|
||||
// down. It monitors SP values as it unwinds to check they
|
||||
// decrease, so as to avoid looping on corrupted stacks.
|
||||
//
|
||||
// May only be called in Unwind mode. Multiple threads may unwind
|
||||
// at once. LUL user is responsible for ensuring that no thread makes
|
||||
// any Admin calls whilst in Unwind mode.
|
||||
// MOZ_CRASHes if the calling thread is not registered for unwinding.
|
||||
//
|
||||
// The calling thread must previously have been registered via a call to
|
||||
// RegisterSampledThread.
|
||||
void Unwind(/*OUT*/ uintptr_t* aFramePCs,
|
||||
/*OUT*/ uintptr_t* aFrameSPs,
|
||||
/*OUT*/ size_t* aFramesUsed,
|
||||
/*OUT*/ size_t* aFramePointerFramesAcquired, size_t aFramesAvail,
|
||||
UnwindRegs* aStartRegs, StackImage* aStackImg);
|
||||
|
||||
// The logging sink. Call to send debug strings to the caller-
|
||||
// specified destination. Can only be called by the Admin thread.
|
||||
void (*mLog)(const char*);
|
||||
|
||||
// Statistics relating to unwinding. These have to be atomic since
|
||||
// unwinding can occur on different threads simultaneously.
|
||||
LULStats<mozilla::Atomic<uint32_t>> mStats;
|
||||
|
||||
// Possibly show the statistics. This may not be called from any
|
||||
// registered sampling thread, since it involves I/O.
|
||||
void MaybeShowStats();
|
||||
|
||||
size_t SizeOfIncludingThis(mozilla::MallocSizeOf) const;
|
||||
|
||||
private:
|
||||
// The statistics counters at the point where they were last printed.
|
||||
LULStats<uint32_t> mStatsPrevious;
|
||||
|
||||
// Are we in admin mode? Initially |true| but changes to |false|
|
||||
// once unwinding begins.
|
||||
bool mAdminMode;
|
||||
|
||||
// The thread ID associated with admin mode. This is the only thread
|
||||
// that is allowed do perform non-Unwind calls on this object. Conversely,
|
||||
// no registered Unwinding thread may be the admin thread. This is so
|
||||
// as to clearly partition the one thread that may do dynamic memory
|
||||
// allocation from the threads that are being sampled, since the latter
|
||||
// absolutely may not do dynamic memory allocation.
|
||||
mozilla::baseprofiler::BaseProfilerThreadId mAdminThreadId;
|
||||
|
||||
// The top level mapping from code address ranges to postprocessed
|
||||
// unwind info. Basically a sorted array of (addr, len, info)
|
||||
// records. This field is updated by NotifyAfterMap and NotifyBeforeUnmap.
|
||||
PriMap* mPriMap;
|
||||
|
||||
// An auxiliary structure that records which address ranges are
|
||||
// mapped r-x, for the benefit of the stack scanner.
|
||||
SegArray* mSegArray;
|
||||
|
||||
// A UniqueStringUniverse that holds all the strdup'd strings created
|
||||
// whilst reading unwind information. This is included so as to make
|
||||
// it possible to free them in ~LUL.
|
||||
UniqueStringUniverse* mUSU;
|
||||
};
|
||||
|
||||
// Run unit tests on an initialised, loaded-up LUL instance, and print
|
||||
// summary results on |aLUL|'s logging sink. Also return the number
|
||||
// of tests run in *aNTests and the number that passed in
|
||||
// *aNTestsPassed.
|
||||
void RunLulUnitTests(/*OUT*/ int* aNTests, /*OUT*/ int* aNTestsPassed,
|
||||
LUL* aLUL);
|
||||
|
||||
} // namespace lul
|
||||
|
||||
#endif // LulMain_h
|
||||
@@ -1,420 +0,0 @@
|
||||
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#ifndef LulMainInt_h
|
||||
#define LulMainInt_h
|
||||
|
||||
#include "PlatformMacros.h"
|
||||
#include "LulMain.h" // for TaggedUWord
|
||||
|
||||
#include "mozilla/Assertions.h"
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
// This file is provides internal interface inside LUL. If you are an
|
||||
// end-user of LUL, do not include it in your code. The end-user
|
||||
// interface is in LulMain.h.
|
||||
|
||||
namespace lul {
|
||||
|
||||
using std::vector;
|
||||
|
||||
////////////////////////////////////////////////////////////////
|
||||
// DW_REG_ constants //
|
||||
////////////////////////////////////////////////////////////////
|
||||
|
||||
// These are the Dwarf CFI register numbers, as (presumably) defined
|
||||
// in the ELF ABI supplements for each architecture.
|
||||
|
||||
enum DW_REG_NUMBER {
|
||||
// No real register has this number. It's convenient to be able to
|
||||
// treat the CFA (Canonical Frame Address) as "just another
|
||||
// register", though.
|
||||
DW_REG_CFA = -1,
|
||||
#if defined(GP_ARCH_arm)
|
||||
// ARM registers
|
||||
DW_REG_ARM_R7 = 7,
|
||||
DW_REG_ARM_R11 = 11,
|
||||
DW_REG_ARM_R12 = 12,
|
||||
DW_REG_ARM_R13 = 13,
|
||||
DW_REG_ARM_R14 = 14,
|
||||
DW_REG_ARM_R15 = 15,
|
||||
#elif defined(GP_ARCH_arm64)
|
||||
// aarch64 registers
|
||||
DW_REG_AARCH64_X29 = 29,
|
||||
DW_REG_AARCH64_X30 = 30,
|
||||
DW_REG_AARCH64_SP = 31,
|
||||
#elif defined(GP_ARCH_amd64)
|
||||
// Because the X86 (32 bit) and AMD64 (64 bit) summarisers are
|
||||
// combined, a merged set of register constants is needed.
|
||||
DW_REG_INTEL_XBP = 6,
|
||||
DW_REG_INTEL_XSP = 7,
|
||||
DW_REG_INTEL_XIP = 16,
|
||||
#elif defined(GP_ARCH_x86)
|
||||
DW_REG_INTEL_XBP = 5,
|
||||
DW_REG_INTEL_XSP = 4,
|
||||
DW_REG_INTEL_XIP = 8,
|
||||
#elif defined(GP_ARCH_mips64)
|
||||
DW_REG_MIPS_SP = 29,
|
||||
DW_REG_MIPS_FP = 30,
|
||||
DW_REG_MIPS_PC = 34,
|
||||
#else
|
||||
# error "Unknown arch"
|
||||
#endif
|
||||
};
|
||||
|
||||
////////////////////////////////////////////////////////////////
|
||||
// PfxExpr //
|
||||
////////////////////////////////////////////////////////////////
|
||||
|
||||
enum PfxExprOp {
|
||||
// meaning of mOperand effect on stack
|
||||
PX_Start, // bool start-with-CFA? start, with CFA on stack, or not
|
||||
PX_End, // none stop; result is at top of stack
|
||||
PX_SImm32, // int32 push signed int32
|
||||
PX_DwReg, // DW_REG_NUMBER push value of the specified reg
|
||||
PX_Deref, // none pop X ; push *X
|
||||
PX_Add, // none pop X ; pop Y ; push Y + X
|
||||
PX_Sub, // none pop X ; pop Y ; push Y - X
|
||||
PX_And, // none pop X ; pop Y ; push Y & X
|
||||
PX_Or, // none pop X ; pop Y ; push Y | X
|
||||
PX_CmpGES, // none pop X ; pop Y ; push (Y >=s X) ? 1 : 0
|
||||
PX_Shl // none pop X ; pop Y ; push Y << X
|
||||
};
|
||||
|
||||
struct PfxInstr {
|
||||
PfxInstr(PfxExprOp opcode, int32_t operand)
|
||||
: mOpcode(opcode), mOperand(operand) {}
|
||||
explicit PfxInstr(PfxExprOp opcode) : mOpcode(opcode), mOperand(0) {}
|
||||
bool operator==(const PfxInstr& other) const {
|
||||
return mOpcode == other.mOpcode && mOperand == other.mOperand;
|
||||
}
|
||||
PfxExprOp mOpcode;
|
||||
int32_t mOperand;
|
||||
};
|
||||
|
||||
static_assert(sizeof(PfxInstr) <= 8, "PfxInstr size changed unexpectedly");
|
||||
|
||||
// Evaluate the prefix expression whose PfxInstrs start at aPfxInstrs[start].
|
||||
// In the case of any mishap (stack over/underflow, running off the end of
|
||||
// the instruction vector, obviously malformed sequences),
|
||||
// return an invalid TaggedUWord.
|
||||
// RUNS IN NO-MALLOC CONTEXT
|
||||
TaggedUWord EvaluatePfxExpr(int32_t start, const UnwindRegs* aOldRegs,
|
||||
TaggedUWord aCFA, const StackImage* aStackImg,
|
||||
const vector<PfxInstr>& aPfxInstrs);
|
||||
|
||||
////////////////////////////////////////////////////////////////
|
||||
// LExpr //
|
||||
////////////////////////////////////////////////////////////////
|
||||
|
||||
// An expression -- very primitive. Denotes either "register +
|
||||
// offset", a dereferenced version of the same, or a reference to a
|
||||
// prefix expression stored elsewhere. So as to allow convenient
|
||||
// handling of Dwarf-derived unwind info, the register may also denote
|
||||
// the CFA. A large number of these need to be stored, so we ensure
|
||||
// it fits into 8 bytes. See comment below on RuleSet to see how
|
||||
// expressions fit into the bigger picture.
|
||||
|
||||
enum LExprHow {
|
||||
UNKNOWN = 0, // This LExpr denotes no value.
|
||||
NODEREF, // Value is (mReg + mOffset).
|
||||
DEREF, // Value is *(mReg + mOffset).
|
||||
PFXEXPR // Value is EvaluatePfxExpr(secMap->mPfxInstrs[mOffset])
|
||||
};
|
||||
|
||||
inline static const char* NameOf_LExprHow(LExprHow how) {
|
||||
switch (how) {
|
||||
case UNKNOWN:
|
||||
return "UNKNOWN";
|
||||
case NODEREF:
|
||||
return "NODEREF";
|
||||
case DEREF:
|
||||
return "DEREF";
|
||||
case PFXEXPR:
|
||||
return "PFXEXPR";
|
||||
default:
|
||||
return "LExpr-??";
|
||||
}
|
||||
}
|
||||
|
||||
struct LExpr {
|
||||
// Denotes an expression with no value.
|
||||
LExpr() : mHow(UNKNOWN), mReg(0), mOffset(0) {}
|
||||
|
||||
// Denotes any expressible expression.
|
||||
LExpr(LExprHow how, int16_t reg, int32_t offset)
|
||||
: mHow(how), mReg(reg), mOffset(offset) {
|
||||
switch (how) {
|
||||
case UNKNOWN:
|
||||
MOZ_ASSERT(reg == 0 && offset == 0);
|
||||
break;
|
||||
case NODEREF:
|
||||
break;
|
||||
case DEREF:
|
||||
break;
|
||||
case PFXEXPR:
|
||||
MOZ_ASSERT(reg == 0 && offset >= 0);
|
||||
break;
|
||||
default:
|
||||
MOZ_ASSERT(0, "LExpr::LExpr: invalid how");
|
||||
}
|
||||
}
|
||||
|
||||
// Change the offset for an expression that references memory.
|
||||
LExpr add_delta(long delta) {
|
||||
MOZ_ASSERT(mHow == NODEREF);
|
||||
// If this is a non-debug build and the above assertion would have
|
||||
// failed, at least return LExpr() so that the machinery that uses
|
||||
// the resulting expression fails in a repeatable way.
|
||||
return (mHow == NODEREF) ? LExpr(mHow, mReg, mOffset + delta)
|
||||
: LExpr(); // Gone bad
|
||||
}
|
||||
|
||||
// Dereference an expression that denotes a memory address.
|
||||
LExpr deref() {
|
||||
MOZ_ASSERT(mHow == NODEREF);
|
||||
// Same rationale as for add_delta().
|
||||
return (mHow == NODEREF) ? LExpr(DEREF, mReg, mOffset)
|
||||
: LExpr(); // Gone bad
|
||||
}
|
||||
|
||||
// Print a rule for recovery of |aNewReg| whose recovered value
|
||||
// is this LExpr.
|
||||
std::string ShowRule(const char* aNewReg) const;
|
||||
|
||||
// Evaluate this expression, producing a TaggedUWord. |aOldRegs|
|
||||
// holds register values that may be referred to by the expression.
|
||||
// |aCFA| holds the CFA value, if any, that applies. |aStackImg|
|
||||
// contains a chuck of stack that will be consulted if the expression
|
||||
// references memory. |aPfxInstrs| holds the vector of PfxInstrs
|
||||
// that will be consulted if this is a PFXEXPR.
|
||||
// RUNS IN NO-MALLOC CONTEXT
|
||||
TaggedUWord EvaluateExpr(const UnwindRegs* aOldRegs, TaggedUWord aCFA,
|
||||
const StackImage* aStackImg,
|
||||
const vector<PfxInstr>* aPfxInstrs) const;
|
||||
|
||||
// Representation of expressions. If |mReg| is DW_REG_CFA (-1) then
|
||||
// it denotes the CFA. All other allowed values for |mReg| are
|
||||
// nonnegative and are DW_REG_ values.
|
||||
LExprHow mHow : 8;
|
||||
int16_t mReg; // A DW_REG_ value
|
||||
int32_t mOffset; // 32-bit signed offset should be more than enough.
|
||||
};
|
||||
|
||||
static_assert(sizeof(LExpr) <= 8, "LExpr size changed unexpectedly");
|
||||
|
||||
////////////////////////////////////////////////////////////////
|
||||
// RuleSet //
|
||||
////////////////////////////////////////////////////////////////
|
||||
|
||||
// This is platform-dependent. For some address range, describes how
|
||||
// to recover the CFA and then how to recover the registers for the
|
||||
// previous frame.
|
||||
//
|
||||
// The set of LExprs contained in a given RuleSet describe a DAG which
|
||||
// says how to compute the caller's registers ("new registers") from
|
||||
// the callee's registers ("old registers"). The DAG can contain a
|
||||
// single internal node, which is the value of the CFA for the callee.
|
||||
// It would be possible to construct a DAG that omits the CFA, but
|
||||
// including it makes the summarisers simpler, and the Dwarf CFI spec
|
||||
// has the CFA as a central concept.
|
||||
//
|
||||
// For this to make sense, |mCfaExpr| can't have
|
||||
// |mReg| == DW_REG_CFA since we have no previous value for the CFA.
|
||||
// All of the other |Expr| fields can -- and usually do -- specify
|
||||
// |mReg| == DW_REG_CFA.
|
||||
//
|
||||
// With that in place, the unwind algorithm proceeds as follows.
|
||||
//
|
||||
// (0) Initially: we have values for the old registers, and a memory
|
||||
// image.
|
||||
//
|
||||
// (1) Compute the CFA by evaluating |mCfaExpr|. Add the computed
|
||||
// value to the set of "old registers".
|
||||
//
|
||||
// (2) Compute values for the registers by evaluating all of the other
|
||||
// |Expr| fields in the RuleSet. These can depend on both the old
|
||||
// register values and the just-computed CFA.
|
||||
//
|
||||
// If we are unwinding without computing a CFA, perhaps because the
|
||||
// RuleSets are derived from EXIDX instead of Dwarf, then
|
||||
// |mCfaExpr.mHow| will be LExpr::UNKNOWN, so the computed value will
|
||||
// be invalid -- that is, TaggedUWord() -- and so any attempt to use
|
||||
// that will result in the same value. But that's OK because the
|
||||
// RuleSet would make no sense if depended on the CFA but specified no
|
||||
// way to compute it.
|
||||
//
|
||||
// A RuleSet is not allowed to cover zero address range. Having zero
|
||||
// length would break binary searching in SecMaps and PriMaps.
|
||||
|
||||
class RuleSet {
|
||||
public:
|
||||
RuleSet();
|
||||
void Print(void (*aLog)(const char*)) const;
|
||||
|
||||
// Find the LExpr* for a given DW_REG_ value in this class.
|
||||
LExpr* ExprForRegno(DW_REG_NUMBER aRegno);
|
||||
|
||||
uintptr_t mAddr;
|
||||
uintptr_t mLen;
|
||||
// How to compute the CFA.
|
||||
LExpr mCfaExpr;
|
||||
// How to compute caller register values. These may reference the
|
||||
// value defined by |mCfaExpr|.
|
||||
#if defined(GP_ARCH_amd64) || defined(GP_ARCH_x86)
|
||||
LExpr mXipExpr; // return address
|
||||
LExpr mXspExpr;
|
||||
LExpr mXbpExpr;
|
||||
#elif defined(GP_ARCH_arm)
|
||||
LExpr mR15expr; // return address
|
||||
LExpr mR14expr;
|
||||
LExpr mR13expr;
|
||||
LExpr mR12expr;
|
||||
LExpr mR11expr;
|
||||
LExpr mR7expr;
|
||||
#elif defined(GP_ARCH_arm64)
|
||||
LExpr mX29expr; // frame pointer register
|
||||
LExpr mX30expr; // link register
|
||||
LExpr mSPexpr;
|
||||
#elif defined(GP_ARCH_mips64)
|
||||
LExpr mPCexpr;
|
||||
LExpr mFPexpr;
|
||||
LExpr mSPexpr;
|
||||
#else
|
||||
# error "Unknown arch"
|
||||
#endif
|
||||
};
|
||||
|
||||
// Returns |true| for Dwarf register numbers which are members
|
||||
// of the set of registers that LUL unwinds on this target.
|
||||
static inline bool registerIsTracked(DW_REG_NUMBER reg) {
|
||||
switch (reg) {
|
||||
#if defined(GP_ARCH_amd64) || defined(GP_ARCH_x86)
|
||||
case DW_REG_INTEL_XBP:
|
||||
case DW_REG_INTEL_XSP:
|
||||
case DW_REG_INTEL_XIP:
|
||||
return true;
|
||||
#elif defined(GP_ARCH_arm)
|
||||
case DW_REG_ARM_R7:
|
||||
case DW_REG_ARM_R11:
|
||||
case DW_REG_ARM_R12:
|
||||
case DW_REG_ARM_R13:
|
||||
case DW_REG_ARM_R14:
|
||||
case DW_REG_ARM_R15:
|
||||
return true;
|
||||
#elif defined(GP_ARCH_arm64)
|
||||
case DW_REG_AARCH64_X29:
|
||||
case DW_REG_AARCH64_X30:
|
||||
case DW_REG_AARCH64_SP:
|
||||
return true;
|
||||
#elif defined(GP_ARCH_mips64)
|
||||
case DW_REG_MIPS_FP:
|
||||
case DW_REG_MIPS_SP:
|
||||
case DW_REG_MIPS_PC:
|
||||
return true;
|
||||
#else
|
||||
# error "Unknown arch"
|
||||
#endif
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////
|
||||
// SecMap //
|
||||
////////////////////////////////////////////////////////////////
|
||||
|
||||
// A SecMap may have zero address range, temporarily, whilst RuleSets
|
||||
// are being added to it. But adding a zero-range SecMap to a PriMap
|
||||
// will make it impossible to maintain the total order of the PriMap
|
||||
// entries, and so that can't be allowed to happen.
|
||||
|
||||
class SecMap {
|
||||
public:
|
||||
// These summarise the contained mRuleSets, in that they give
|
||||
// exactly the lowest and highest addresses that any of the entries
|
||||
// in this SecMap cover. Hence invariants:
|
||||
//
|
||||
// mRuleSets is nonempty
|
||||
// <=> mSummaryMinAddr <= mSummaryMaxAddr
|
||||
// && mSummaryMinAddr == mRuleSets[0].mAddr
|
||||
// && mSummaryMaxAddr == mRuleSets[#rulesets-1].mAddr
|
||||
// + mRuleSets[#rulesets-1].mLen - 1;
|
||||
//
|
||||
// This requires that no RuleSet has zero length.
|
||||
//
|
||||
// mRuleSets is empty
|
||||
// <=> mSummaryMinAddr > mSummaryMaxAddr
|
||||
//
|
||||
// This doesn't constrain mSummaryMinAddr and mSummaryMaxAddr uniquely,
|
||||
// so let's use mSummaryMinAddr == 1 and mSummaryMaxAddr == 0 to denote
|
||||
// this case.
|
||||
|
||||
explicit SecMap(void (*aLog)(const char*));
|
||||
~SecMap();
|
||||
|
||||
// Binary search mRuleSets to find one that brackets |ia|, or nullptr
|
||||
// if none is found. It's not allowable to do this until PrepareRuleSets
|
||||
// has been called first.
|
||||
RuleSet* FindRuleSet(uintptr_t ia);
|
||||
|
||||
// Add a RuleSet to the collection. The rule is copied in. Calling
|
||||
// this makes the map non-searchable.
|
||||
void AddRuleSet(const RuleSet* rs);
|
||||
|
||||
// Add a PfxInstr to the vector of such instrs, and return the index
|
||||
// in the vector. Calling this makes the map non-searchable.
|
||||
uint32_t AddPfxInstr(PfxInstr pfxi);
|
||||
|
||||
// Returns the entire vector of PfxInstrs.
|
||||
const vector<PfxInstr>* GetPfxInstrs() { return &mPfxInstrs; }
|
||||
|
||||
// Prepare the map for searching. Also, remove any rules for code
|
||||
// address ranges which don't fall inside [start, +len). |len| may
|
||||
// not be zero.
|
||||
void PrepareRuleSets(uintptr_t start, size_t len);
|
||||
|
||||
bool IsEmpty();
|
||||
|
||||
size_t Size() { return mRuleSets.size(); }
|
||||
|
||||
size_t SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const;
|
||||
|
||||
// The min and max addresses of the addresses in the contained
|
||||
// RuleSets. See comment above for invariants.
|
||||
uintptr_t mSummaryMinAddr;
|
||||
uintptr_t mSummaryMaxAddr;
|
||||
|
||||
private:
|
||||
// False whilst adding entries; true once it is safe to call FindRuleSet.
|
||||
// Transition (false->true) is caused by calling PrepareRuleSets().
|
||||
bool mUsable;
|
||||
|
||||
// A vector of RuleSets, sorted, nonoverlapping (post Prepare()).
|
||||
vector<RuleSet> mRuleSets;
|
||||
|
||||
// A vector of PfxInstrs, which are referred to by the RuleSets.
|
||||
// These are provided as a representation of Dwarf expressions
|
||||
// (DW_CFA_val_expression, DW_CFA_expression, DW_CFA_def_cfa_expression),
|
||||
// are relatively expensive to evaluate, and and are therefore
|
||||
// expected to be used only occasionally.
|
||||
//
|
||||
// The vector holds a bunch of separate PfxInstr programs, each one
|
||||
// starting with a PX_Start and terminated by a PX_End, all
|
||||
// concatenated together. When a RuleSet can't recover a value
|
||||
// using a self-contained LExpr, it uses a PFXEXPR whose mOffset is
|
||||
// the index in this vector of start of the necessary PfxInstr program.
|
||||
vector<PfxInstr> mPfxInstrs;
|
||||
|
||||
// A logging sink, for debugging.
|
||||
void (*mLog)(const char*);
|
||||
};
|
||||
|
||||
} // namespace lul
|
||||
|
||||
#endif // ndef LulMainInt_h
|
||||
@@ -1,76 +0,0 @@
|
||||
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#include <stdio.h>
|
||||
#include <signal.h>
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
#include <time.h>
|
||||
|
||||
#include "AutoObjectMapper.h"
|
||||
#include "BaseProfiler.h"
|
||||
#include "SharedLibraries.h"
|
||||
#include "platform.h"
|
||||
#include "PlatformMacros.h"
|
||||
#include "LulMain.h"
|
||||
|
||||
// Contains miscellaneous helpers that are used to connect the Gecko Profiler
|
||||
// and LUL.
|
||||
|
||||
// Find out, in a platform-dependent way, where the code modules got
|
||||
// mapped in the process' virtual address space, and get |aLUL| to
|
||||
// load unwind info for them.
|
||||
void read_procmaps(lul::LUL* aLUL) {
|
||||
MOZ_ASSERT(aLUL->CountMappings() == 0);
|
||||
|
||||
#if defined(GP_OS_linux) || defined(GP_OS_android) || defined(GP_OS_freebsd)
|
||||
SharedLibraryInfo info = SharedLibraryInfo::GetInfoForSelf();
|
||||
|
||||
for (size_t i = 0; i < info.GetSize(); i++) {
|
||||
const SharedLibrary& lib = info.GetEntry(i);
|
||||
|
||||
std::string nativePath = lib.GetDebugPath();
|
||||
|
||||
// We can use the standard POSIX-based mapper.
|
||||
AutoObjectMapperPOSIX mapper(aLUL->mLog);
|
||||
|
||||
// Ask |mapper| to map the object. Then hand its mapped address
|
||||
// to NotifyAfterMap().
|
||||
void* image = nullptr;
|
||||
size_t size = 0;
|
||||
bool ok = mapper.Map(&image, &size, nativePath);
|
||||
if (ok && image && size > 0) {
|
||||
aLUL->NotifyAfterMap(lib.GetStart(), lib.GetEnd() - lib.GetStart(),
|
||||
nativePath.c_str(), image);
|
||||
} else if (!ok && lib.GetDebugName().empty()) {
|
||||
// The object has no name and (as a consequence) the mapper failed to map
|
||||
// it. This happens on Linux, where GetInfoForSelf() produces such a
|
||||
// mapping for the VDSO. This is a problem on x86-{linux,android} because
|
||||
// lack of knowledge about the mapped area inhibits LUL's special
|
||||
// __kernel_syscall handling. Hence notify |aLUL| at least of the
|
||||
// mapping, even though it can't read any unwind information for the area.
|
||||
aLUL->NotifyExecutableArea(lib.GetStart(), lib.GetEnd() - lib.GetStart());
|
||||
}
|
||||
|
||||
// |mapper| goes out of scope at this point and so its destructor
|
||||
// unmaps the object.
|
||||
}
|
||||
|
||||
#else
|
||||
# error "Unknown platform"
|
||||
#endif
|
||||
}
|
||||
|
||||
// LUL needs a callback for its logging sink.
|
||||
void logging_sink_for_LUL(const char* str) {
|
||||
// These are only printed when Verbose logging is enabled (e.g. with
|
||||
// MOZ_BASE_PROFILER_VERBOSE_LOGGING=1). This is because LUL's logging is much
|
||||
// more verbose than the rest of the profiler's logging, which occurs at the
|
||||
// Info (3) and Debug (4) levels.
|
||||
// FIXME: This causes a build failure in memory/replace/dmd/test/SmokeDMD (!)
|
||||
// and other places, because it doesn't link the implementation in
|
||||
// platform.cpp.
|
||||
// VERBOSE_LOG("[%d] %s", profiler_current_process_id(), str);
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#ifndef MOZ_PLATFORM_LINUX_LUL_H
|
||||
#define MOZ_PLATFORM_LINUX_LUL_H
|
||||
|
||||
#include "platform.h"
|
||||
|
||||
#include "BaseProfiler.h"
|
||||
|
||||
// Find out, in a platform-dependent way, where the code modules got
|
||||
// mapped in the process' virtual address space, and get |aLUL| to
|
||||
// load unwind info for them.
|
||||
void read_procmaps(lul::LUL* aLUL);
|
||||
|
||||
// LUL needs a callback for its logging sink.
|
||||
void logging_sink_for_LUL(const char* str);
|
||||
|
||||
#endif /* ndef MOZ_PLATFORM_LINUX_LUL_H */
|
||||
@@ -29,16 +29,6 @@ if CONFIG["MOZ_GECKO_PROFILER"]:
|
||||
]
|
||||
|
||||
if CONFIG["OS_TARGET"] in ("Android", "Linux", "FreeBSD"):
|
||||
if CONFIG["TARGET_CPU"] in ("arm", "aarch64", "x86", "x86_64", "mips64"):
|
||||
UNIFIED_SOURCES += [
|
||||
"lul/AutoObjectMapper.cpp",
|
||||
"lul/LulCommon.cpp",
|
||||
"lul/LulDwarf.cpp",
|
||||
"lul/LulDwarfSummariser.cpp",
|
||||
"lul/LulElf.cpp",
|
||||
"lul/LulMain.cpp",
|
||||
"lul/platform-linux-lul.cpp",
|
||||
]
|
||||
# These files cannot be built in unified mode because of name clashes with mozglue headers on Android.
|
||||
SOURCES += [
|
||||
"core/shared-libraries-linux.cc",
|
||||
|
||||
Reference in New Issue
Block a user