mirror of
https://repo.dactyloidae.xyz/Dactyloidae/UXP.git
synced 2026-09-30 04:17:31 +09:00
import FIREFOX_52_6_0esr_RELEASE from mozilla-esr52 hg repo
This commit is contained in:
commit
dcd9973243
150858 changed files with 23884658 additions and 0 deletions
678
tools/profiler/core/EHABIStackWalk.cpp
Normal file
678
tools/profiler/core/EHABIStackWalk.cpp
Normal file
|
|
@ -0,0 +1,678 @@
|
|||
/* -*- 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/. */
|
||||
|
||||
/*
|
||||
* This is an implementation of stack unwinding according to a subset
|
||||
* of the ARM Exception Handling ABI, as described in:
|
||||
* http://infocenter.arm.com/help/topic/com.arm.doc.ihi0038a/IHI0038A_ehabi.pdf
|
||||
*
|
||||
* This handles only the ARM-defined "personality routines" (chapter
|
||||
* 9), and don't track the value of FP registers, because profiling
|
||||
* needs only chain of PC/SP values.
|
||||
*
|
||||
* Because the exception handling info may not be accurate for all
|
||||
* possible places where an async signal could occur (e.g., in a
|
||||
* prologue or epilogue), this bounds-checks all stack accesses.
|
||||
*
|
||||
* This file uses "struct" for structures in the exception tables and
|
||||
* "class" otherwise. We should avoid violating the C++11
|
||||
* standard-layout rules in the former.
|
||||
*/
|
||||
|
||||
#include "EHABIStackWalk.h"
|
||||
|
||||
#include "shared-libraries.h"
|
||||
#include "platform.h"
|
||||
|
||||
#include "mozilla/Atomics.h"
|
||||
#include "mozilla/Attributes.h"
|
||||
#include "mozilla/DebugOnly.h"
|
||||
#include "mozilla/EndianUtils.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <elf.h>
|
||||
#include <stdint.h>
|
||||
#include <vector>
|
||||
#include <string>
|
||||
|
||||
#ifndef PT_ARM_EXIDX
|
||||
#define PT_ARM_EXIDX 0x70000001
|
||||
#endif
|
||||
|
||||
// Bug 1082817: ICS B2G has a buggy linker that doesn't always ensure
|
||||
// that the EXIDX is sorted by address, as the spec requires. So in
|
||||
// that case we build and sort an array of pointers into the index,
|
||||
// and binary-search that; otherwise, we search the index in place
|
||||
// (avoiding the time and space overhead of the indirection).
|
||||
#if defined(ANDROID_VERSION) && ANDROID_VERSION < 16
|
||||
#define HAVE_UNSORTED_EXIDX
|
||||
#endif
|
||||
|
||||
namespace mozilla {
|
||||
|
||||
struct PRel31 {
|
||||
uint32_t mBits;
|
||||
bool topBit() const { return mBits & 0x80000000; }
|
||||
uint32_t value() const { return mBits & 0x7fffffff; }
|
||||
int32_t offset() const { return (static_cast<int32_t>(mBits) << 1) >> 1; }
|
||||
const void *compute() const {
|
||||
return reinterpret_cast<const char *>(this) + offset();
|
||||
}
|
||||
private:
|
||||
PRel31(const PRel31 &copied) = delete;
|
||||
PRel31() = delete;
|
||||
};
|
||||
|
||||
struct EHEntry {
|
||||
PRel31 startPC;
|
||||
PRel31 exidx;
|
||||
private:
|
||||
EHEntry(const EHEntry &copied) = delete;
|
||||
EHEntry() = delete;
|
||||
};
|
||||
|
||||
class EHState {
|
||||
// Note that any core register can be used as a "frame pointer" to
|
||||
// influence the unwinding process, so this must track all of them.
|
||||
uint32_t mRegs[16];
|
||||
public:
|
||||
bool unwind(const EHEntry *aEntry, const void *stackBase);
|
||||
uint32_t &operator[](int i) { return mRegs[i]; }
|
||||
const uint32_t &operator[](int i) const { return mRegs[i]; }
|
||||
EHState(const mcontext_t &);
|
||||
};
|
||||
|
||||
enum {
|
||||
R_SP = 13,
|
||||
R_LR = 14,
|
||||
R_PC = 15
|
||||
};
|
||||
|
||||
#ifdef HAVE_UNSORTED_EXIDX
|
||||
class EHEntryHandle {
|
||||
const EHEntry *mValue;
|
||||
public:
|
||||
EHEntryHandle(const EHEntry *aEntry) : mValue(aEntry) { }
|
||||
const EHEntry *value() const { return mValue; }
|
||||
};
|
||||
|
||||
bool operator<(const EHEntryHandle &lhs, const EHEntryHandle &rhs) {
|
||||
return lhs.value()->startPC.compute() < rhs.value()->startPC.compute();
|
||||
}
|
||||
#endif
|
||||
|
||||
class EHTable {
|
||||
uint32_t mStartPC;
|
||||
uint32_t mEndPC;
|
||||
uint32_t mLoadOffset;
|
||||
#ifdef HAVE_UNSORTED_EXIDX
|
||||
// In principle we should be able to binary-search the index section in
|
||||
// place, but the ICS toolchain's linker is noncompliant and produces
|
||||
// indices that aren't entirely sorted (e.g., libc). So we have this:
|
||||
std::vector<EHEntryHandle> mEntries;
|
||||
typedef std::vector<EHEntryHandle>::const_iterator EntryIterator;
|
||||
EntryIterator entriesBegin() const { return mEntries.begin(); }
|
||||
EntryIterator entriesEnd() const { return mEntries.end(); }
|
||||
static const EHEntry* entryGet(EntryIterator aEntry) {
|
||||
return aEntry->value();
|
||||
}
|
||||
#else
|
||||
typedef const EHEntry *EntryIterator;
|
||||
EntryIterator mEntriesBegin, mEntriesEnd;
|
||||
EntryIterator entriesBegin() const { return mEntriesBegin; }
|
||||
EntryIterator entriesEnd() const { return mEntriesEnd; }
|
||||
static const EHEntry* entryGet(EntryIterator aEntry) { return aEntry; }
|
||||
#endif
|
||||
std::string mName;
|
||||
public:
|
||||
EHTable(const void *aELF, size_t aSize, const std::string &aName);
|
||||
const EHEntry *lookup(uint32_t aPC) const;
|
||||
bool isValid() const { return entriesEnd() != entriesBegin(); }
|
||||
const std::string &name() const { return mName; }
|
||||
uint32_t startPC() const { return mStartPC; }
|
||||
uint32_t endPC() const { return mEndPC; }
|
||||
uint32_t loadOffset() const { return mLoadOffset; }
|
||||
};
|
||||
|
||||
class EHAddrSpace {
|
||||
std::vector<uint32_t> mStarts;
|
||||
std::vector<EHTable> mTables;
|
||||
static mozilla::Atomic<const EHAddrSpace*> sCurrent;
|
||||
public:
|
||||
explicit EHAddrSpace(const std::vector<EHTable>& aTables);
|
||||
const EHTable *lookup(uint32_t aPC) const;
|
||||
static void Update();
|
||||
static const EHAddrSpace *Get();
|
||||
};
|
||||
|
||||
|
||||
void EHABIStackWalkInit()
|
||||
{
|
||||
EHAddrSpace::Update();
|
||||
}
|
||||
|
||||
size_t EHABIStackWalk(const mcontext_t &aContext, void *stackBase,
|
||||
void **aSPs, void **aPCs, const size_t aNumFrames)
|
||||
{
|
||||
const EHAddrSpace *space = EHAddrSpace::Get();
|
||||
EHState state(aContext);
|
||||
size_t count = 0;
|
||||
|
||||
while (count < aNumFrames) {
|
||||
uint32_t pc = state[R_PC], sp = state[R_SP];
|
||||
aPCs[count] = reinterpret_cast<void *>(pc);
|
||||
aSPs[count] = reinterpret_cast<void *>(sp);
|
||||
count++;
|
||||
|
||||
if (!space)
|
||||
break;
|
||||
// TODO: cache these lookups. Binary-searching libxul is
|
||||
// expensive (possibly more expensive than doing the actual
|
||||
// unwind), and even a small cache should help.
|
||||
const EHTable *table = space->lookup(pc);
|
||||
if (!table)
|
||||
break;
|
||||
const EHEntry *entry = table->lookup(pc);
|
||||
if (!entry)
|
||||
break;
|
||||
if (!state.unwind(entry, stackBase))
|
||||
break;
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
|
||||
class EHInterp {
|
||||
public:
|
||||
// Note that stackLimit is exclusive and stackBase is inclusive
|
||||
// (i.e, stackLimit < SP <= stackBase), following the convention
|
||||
// set by the AAPCS spec.
|
||||
EHInterp(EHState &aState, const EHEntry *aEntry,
|
||||
uint32_t aStackLimit, uint32_t aStackBase)
|
||||
: mState(aState),
|
||||
mStackLimit(aStackLimit),
|
||||
mStackBase(aStackBase),
|
||||
mNextWord(0),
|
||||
mWordsLeft(0),
|
||||
mFailed(false)
|
||||
{
|
||||
const PRel31 &exidx = aEntry->exidx;
|
||||
uint32_t firstWord;
|
||||
|
||||
if (exidx.mBits == 1) { // EXIDX_CANTUNWIND
|
||||
mFailed = true;
|
||||
return;
|
||||
}
|
||||
if (exidx.topBit()) {
|
||||
firstWord = exidx.mBits;
|
||||
} else {
|
||||
mNextWord = reinterpret_cast<const uint32_t *>(exidx.compute());
|
||||
firstWord = *mNextWord++;
|
||||
}
|
||||
|
||||
switch (firstWord >> 24) {
|
||||
case 0x80: // short
|
||||
mWord = firstWord << 8;
|
||||
mBytesLeft = 3;
|
||||
break;
|
||||
case 0x81: case 0x82: // long; catch descriptor size ignored
|
||||
mWord = firstWord << 16;
|
||||
mBytesLeft = 2;
|
||||
mWordsLeft = (firstWord >> 16) & 0xff;
|
||||
break;
|
||||
default:
|
||||
// unknown personality
|
||||
mFailed = true;
|
||||
}
|
||||
}
|
||||
|
||||
bool unwind();
|
||||
|
||||
private:
|
||||
// TODO: GCC has been observed not CSEing repeated reads of
|
||||
// mState[R_SP] with writes to mFailed between them, suggesting that
|
||||
// it hasn't determined that they can't alias and is thus missing
|
||||
// optimization opportunities. So, we may want to flatten EHState
|
||||
// into this class; this may also make the code simpler.
|
||||
EHState &mState;
|
||||
uint32_t mStackLimit;
|
||||
uint32_t mStackBase;
|
||||
const uint32_t *mNextWord;
|
||||
uint32_t mWord;
|
||||
uint8_t mWordsLeft;
|
||||
uint8_t mBytesLeft;
|
||||
bool mFailed;
|
||||
|
||||
enum {
|
||||
I_ADDSP = 0x00, // 0sxxxxxx (subtract if s)
|
||||
M_ADDSP = 0x80,
|
||||
I_POPMASK = 0x80, // 1000iiii iiiiiiii (if any i set)
|
||||
M_POPMASK = 0xf0,
|
||||
I_MOVSP = 0x90, // 1001nnnn
|
||||
M_MOVSP = 0xf0,
|
||||
I_POPN = 0xa0, // 1010lnnn
|
||||
M_POPN = 0xf0,
|
||||
I_FINISH = 0xb0, // 10110000
|
||||
I_POPLO = 0xb1, // 10110001 0000iiii (if any i set)
|
||||
I_ADDSPBIG = 0xb2, // 10110010 uleb128
|
||||
I_POPFDX = 0xb3, // 10110011 sssscccc
|
||||
I_POPFDX8 = 0xb8, // 10111nnn
|
||||
M_POPFDX8 = 0xf8,
|
||||
// "Intel Wireless MMX" extensions omitted.
|
||||
I_POPFDD = 0xc8, // 1100100h sssscccc
|
||||
M_POPFDD = 0xfe,
|
||||
I_POPFDD8 = 0xd0, // 11010nnn
|
||||
M_POPFDD8 = 0xf8
|
||||
};
|
||||
|
||||
uint8_t next() {
|
||||
if (mBytesLeft == 0) {
|
||||
if (mWordsLeft == 0) {
|
||||
return I_FINISH;
|
||||
}
|
||||
mWordsLeft--;
|
||||
mWord = *mNextWord++;
|
||||
mBytesLeft = 4;
|
||||
}
|
||||
mBytesLeft--;
|
||||
mWord = (mWord << 8) | (mWord >> 24); // rotate
|
||||
return mWord;
|
||||
}
|
||||
|
||||
uint32_t &vSP() { return mState[R_SP]; }
|
||||
uint32_t *ptrSP() { return reinterpret_cast<uint32_t *>(vSP()); }
|
||||
|
||||
void checkStackBase() { if (vSP() > mStackBase) mFailed = true; }
|
||||
void checkStackLimit() { if (vSP() <= mStackLimit) mFailed = true; }
|
||||
void checkStackAlign() { if ((vSP() & 3) != 0) mFailed = true; }
|
||||
void checkStack() {
|
||||
checkStackBase();
|
||||
checkStackLimit();
|
||||
checkStackAlign();
|
||||
}
|
||||
|
||||
void popRange(uint8_t first, uint8_t last, uint16_t mask) {
|
||||
bool hasSP = false;
|
||||
uint32_t tmpSP;
|
||||
if (mask == 0)
|
||||
mFailed = true;
|
||||
for (uint8_t r = first; r <= last; ++r) {
|
||||
if (mask & 1) {
|
||||
if (r == R_SP) {
|
||||
hasSP = true;
|
||||
tmpSP = *ptrSP();
|
||||
} else
|
||||
mState[r] = *ptrSP();
|
||||
vSP() += 4;
|
||||
checkStackBase();
|
||||
if (mFailed)
|
||||
return;
|
||||
}
|
||||
mask >>= 1;
|
||||
}
|
||||
if (hasSP) {
|
||||
vSP() = tmpSP;
|
||||
checkStack();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
bool EHState::unwind(const EHEntry *aEntry, const void *stackBasePtr) {
|
||||
// The unwinding program cannot set SP to less than the initial value.
|
||||
uint32_t stackLimit = mRegs[R_SP] - 4;
|
||||
uint32_t stackBase = reinterpret_cast<uint32_t>(stackBasePtr);
|
||||
EHInterp interp(*this, aEntry, stackLimit, stackBase);
|
||||
return interp.unwind();
|
||||
}
|
||||
|
||||
bool EHInterp::unwind() {
|
||||
mState[R_PC] = 0;
|
||||
checkStack();
|
||||
while (!mFailed) {
|
||||
uint8_t insn = next();
|
||||
#if DEBUG_EHABI_UNWIND
|
||||
LOGF("unwind insn = %02x", (unsigned)insn);
|
||||
#endif
|
||||
// Try to put the common cases first.
|
||||
|
||||
// 00xxxxxx: vsp = vsp + (xxxxxx << 2) + 4
|
||||
// 01xxxxxx: vsp = vsp - (xxxxxx << 2) - 4
|
||||
if ((insn & M_ADDSP) == I_ADDSP) {
|
||||
uint32_t offset = ((insn & 0x3f) << 2) + 4;
|
||||
if (insn & 0x40) {
|
||||
vSP() -= offset;
|
||||
checkStackLimit();
|
||||
} else {
|
||||
vSP() += offset;
|
||||
checkStackBase();
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
// 10100nnn: Pop r4-r[4+nnn]
|
||||
// 10101nnn: Pop r4-r[4+nnn], r14
|
||||
if ((insn & M_POPN) == I_POPN) {
|
||||
uint8_t n = (insn & 0x07) + 1;
|
||||
bool lr = insn & 0x08;
|
||||
uint32_t *ptr = ptrSP();
|
||||
vSP() += (n + (lr ? 1 : 0)) * 4;
|
||||
checkStackBase();
|
||||
for (uint8_t r = 4; r < 4 + n; ++r)
|
||||
mState[r] = *ptr++;
|
||||
if (lr)
|
||||
mState[R_LR] = *ptr++;
|
||||
continue;
|
||||
}
|
||||
|
||||
// 1011000: Finish
|
||||
if (insn == I_FINISH) {
|
||||
if (mState[R_PC] == 0) {
|
||||
mState[R_PC] = mState[R_LR];
|
||||
// Non-standard change (bug 916106): Prevent the caller from
|
||||
// re-using LR. Since the caller is by definition not a leaf
|
||||
// routine, it will have to restore LR from somewhere to
|
||||
// return to its own caller, so we can safely zero it here.
|
||||
// This makes a difference only if an error in unwinding
|
||||
// (e.g., caused by starting from within a prologue/epilogue)
|
||||
// causes us to load a pointer to a leaf routine as LR; if we
|
||||
// don't do something, we'll go into an infinite loop of
|
||||
// "returning" to that same function.
|
||||
mState[R_LR] = 0;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// 1001nnnn: Set vsp = r[nnnn]
|
||||
if ((insn & M_MOVSP) == I_MOVSP) {
|
||||
vSP() = mState[insn & 0x0f];
|
||||
checkStack();
|
||||
continue;
|
||||
}
|
||||
|
||||
// 11001000 sssscccc: Pop VFP regs D[16+ssss]-D[16+ssss+cccc] (as FLDMFDD)
|
||||
// 11001001 sssscccc: Pop VFP regs D[ssss]-D[ssss+cccc] (as FLDMFDD)
|
||||
if ((insn & M_POPFDD) == I_POPFDD) {
|
||||
uint8_t n = (next() & 0x0f) + 1;
|
||||
// Note: if the 16+ssss+cccc > 31, the encoding is reserved.
|
||||
// As the space is currently unused, we don't try to check.
|
||||
vSP() += 8 * n;
|
||||
checkStackBase();
|
||||
continue;
|
||||
}
|
||||
|
||||
// 11010nnn: Pop VFP regs D[8]-D[8+nnn] (as FLDMFDD)
|
||||
if ((insn & M_POPFDD8) == I_POPFDD8) {
|
||||
uint8_t n = (insn & 0x07) + 1;
|
||||
vSP() += 8 * n;
|
||||
checkStackBase();
|
||||
continue;
|
||||
}
|
||||
|
||||
// 10110010 uleb128: vsp = vsp + 0x204 + (uleb128 << 2)
|
||||
if (insn == I_ADDSPBIG) {
|
||||
uint32_t acc = 0;
|
||||
uint8_t shift = 0;
|
||||
uint8_t byte;
|
||||
do {
|
||||
if (shift >= 32)
|
||||
return false;
|
||||
byte = next();
|
||||
acc |= (byte & 0x7f) << shift;
|
||||
shift += 7;
|
||||
} while (byte & 0x80);
|
||||
uint32_t offset = 0x204 + (acc << 2);
|
||||
// The calculations above could have overflowed.
|
||||
// But the one we care about is this:
|
||||
if (vSP() + offset < vSP())
|
||||
mFailed = true;
|
||||
vSP() += offset;
|
||||
// ...so that this is the only other check needed:
|
||||
checkStackBase();
|
||||
continue;
|
||||
}
|
||||
|
||||
// 1000iiii iiiiiiii (i not all 0): Pop under masks {r15-r12}, {r11-r4}
|
||||
if ((insn & M_POPMASK) == I_POPMASK) {
|
||||
popRange(4, 15, ((insn & 0x0f) << 8) | next());
|
||||
continue;
|
||||
}
|
||||
|
||||
// 1011001 0000iiii (i not all 0): Pop under mask {r3-r0}
|
||||
if (insn == I_POPLO) {
|
||||
popRange(0, 3, next() & 0x0f);
|
||||
continue;
|
||||
}
|
||||
|
||||
// 10110011 sssscccc: Pop VFP regs D[ssss]-D[ssss+cccc] (as FLDMFDX)
|
||||
if (insn == I_POPFDX) {
|
||||
uint8_t n = (next() & 0x0f) + 1;
|
||||
vSP() += 8 * n + 4;
|
||||
checkStackBase();
|
||||
continue;
|
||||
}
|
||||
|
||||
// 10111nnn: Pop VFP regs D[8]-D[8+nnn] (as FLDMFDX)
|
||||
if ((insn & M_POPFDX8) == I_POPFDX8) {
|
||||
uint8_t n = (insn & 0x07) + 1;
|
||||
vSP() += 8 * n + 4;
|
||||
checkStackBase();
|
||||
continue;
|
||||
}
|
||||
|
||||
// unhandled instruction
|
||||
#ifdef DEBUG_EHABI_UNWIND
|
||||
LOGF("Unhandled EHABI instruction 0x%02x", insn);
|
||||
#endif
|
||||
mFailed = true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
bool operator<(const EHTable &lhs, const EHTable &rhs) {
|
||||
return lhs.startPC() < rhs.startPC();
|
||||
}
|
||||
|
||||
// Async signal unsafe.
|
||||
EHAddrSpace::EHAddrSpace(const std::vector<EHTable>& aTables)
|
||||
: mTables(aTables)
|
||||
{
|
||||
std::sort(mTables.begin(), mTables.end());
|
||||
DebugOnly<uint32_t> lastEnd = 0;
|
||||
for (std::vector<EHTable>::iterator i = mTables.begin();
|
||||
i != mTables.end(); ++i) {
|
||||
MOZ_ASSERT(i->startPC() >= lastEnd);
|
||||
mStarts.push_back(i->startPC());
|
||||
lastEnd = i->endPC();
|
||||
}
|
||||
}
|
||||
|
||||
const EHTable *EHAddrSpace::lookup(uint32_t aPC) const {
|
||||
ptrdiff_t i = (std::upper_bound(mStarts.begin(), mStarts.end(), aPC)
|
||||
- mStarts.begin()) - 1;
|
||||
|
||||
if (i < 0 || aPC >= mTables[i].endPC())
|
||||
return 0;
|
||||
return &mTables[i];
|
||||
}
|
||||
|
||||
|
||||
const EHEntry *EHTable::lookup(uint32_t aPC) const {
|
||||
MOZ_ASSERT(aPC >= mStartPC);
|
||||
if (aPC >= mEndPC)
|
||||
return nullptr;
|
||||
|
||||
EntryIterator begin = entriesBegin();
|
||||
EntryIterator end = entriesEnd();
|
||||
MOZ_ASSERT(begin < end);
|
||||
if (aPC < reinterpret_cast<uint32_t>(entryGet(begin)->startPC.compute()))
|
||||
return nullptr;
|
||||
|
||||
while (end - begin > 1) {
|
||||
#ifdef EHABI_UNWIND_MORE_ASSERTS
|
||||
if (entryGet(end - 1)->startPC.compute()
|
||||
< entryGet(begin)->startPC.compute()) {
|
||||
MOZ_CRASH("unsorted exidx");
|
||||
}
|
||||
#endif
|
||||
EntryIterator mid = begin + (end - begin) / 2;
|
||||
if (aPC < reinterpret_cast<uint32_t>(entryGet(mid)->startPC.compute()))
|
||||
end = mid;
|
||||
else
|
||||
begin = mid;
|
||||
}
|
||||
return entryGet(begin);
|
||||
}
|
||||
|
||||
|
||||
#if MOZ_LITTLE_ENDIAN
|
||||
static const unsigned char hostEndian = ELFDATA2LSB;
|
||||
#elif MOZ_BIG_ENDIAN
|
||||
static const unsigned char hostEndian = ELFDATA2MSB;
|
||||
#else
|
||||
#error "No endian?"
|
||||
#endif
|
||||
|
||||
// Async signal unsafe: std::vector::reserve, std::string copy ctor.
|
||||
EHTable::EHTable(const void *aELF, size_t aSize, const std::string &aName)
|
||||
: mStartPC(~0), // largest uint32_t
|
||||
mEndPC(0),
|
||||
#ifndef HAVE_UNSORTED_EXIDX
|
||||
mEntriesBegin(nullptr),
|
||||
mEntriesEnd(nullptr),
|
||||
#endif
|
||||
mName(aName)
|
||||
{
|
||||
const uint32_t base = reinterpret_cast<uint32_t>(aELF);
|
||||
|
||||
if (aSize < sizeof(Elf32_Ehdr))
|
||||
return;
|
||||
|
||||
const Elf32_Ehdr &file = *(reinterpret_cast<Elf32_Ehdr *>(base));
|
||||
if (memcmp(&file.e_ident[EI_MAG0], ELFMAG, SELFMAG) != 0 ||
|
||||
file.e_ident[EI_CLASS] != ELFCLASS32 ||
|
||||
file.e_ident[EI_DATA] != hostEndian ||
|
||||
file.e_ident[EI_VERSION] != EV_CURRENT ||
|
||||
file.e_ident[EI_OSABI] != ELFOSABI_SYSV ||
|
||||
#ifdef EI_ABIVERSION
|
||||
file.e_ident[EI_ABIVERSION] != 0 ||
|
||||
#endif
|
||||
file.e_machine != EM_ARM ||
|
||||
file.e_version != EV_CURRENT)
|
||||
// e_flags?
|
||||
return;
|
||||
|
||||
MOZ_ASSERT(file.e_phoff + file.e_phnum * file.e_phentsize <= aSize);
|
||||
const Elf32_Phdr *exidxHdr = 0, *zeroHdr = 0;
|
||||
for (unsigned i = 0; i < file.e_phnum; ++i) {
|
||||
const Elf32_Phdr &phdr =
|
||||
*(reinterpret_cast<Elf32_Phdr *>(base + file.e_phoff
|
||||
+ i * file.e_phentsize));
|
||||
if (phdr.p_type == PT_ARM_EXIDX) {
|
||||
exidxHdr = &phdr;
|
||||
} else if (phdr.p_type == PT_LOAD) {
|
||||
if (phdr.p_offset == 0) {
|
||||
zeroHdr = &phdr;
|
||||
}
|
||||
if (phdr.p_flags & PF_X) {
|
||||
mStartPC = std::min(mStartPC, phdr.p_vaddr);
|
||||
mEndPC = std::max(mEndPC, phdr.p_vaddr + phdr.p_memsz);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!exidxHdr)
|
||||
return;
|
||||
if (!zeroHdr)
|
||||
return;
|
||||
mLoadOffset = base - zeroHdr->p_vaddr;
|
||||
mStartPC += mLoadOffset;
|
||||
mEndPC += mLoadOffset;
|
||||
|
||||
// Create a sorted index of the index to work around linker bugs.
|
||||
const EHEntry *startTable =
|
||||
reinterpret_cast<const EHEntry *>(mLoadOffset + exidxHdr->p_vaddr);
|
||||
const EHEntry *endTable =
|
||||
reinterpret_cast<const EHEntry *>(mLoadOffset + exidxHdr->p_vaddr
|
||||
+ exidxHdr->p_memsz);
|
||||
#ifdef HAVE_UNSORTED_EXIDX
|
||||
mEntries.reserve(endTable - startTable);
|
||||
for (const EHEntry *i = startTable; i < endTable; ++i)
|
||||
mEntries.push_back(i);
|
||||
std::sort(mEntries.begin(), mEntries.end());
|
||||
#else
|
||||
mEntriesBegin = startTable;
|
||||
mEntriesEnd = endTable;
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
mozilla::Atomic<const EHAddrSpace*> EHAddrSpace::sCurrent(nullptr);
|
||||
|
||||
// Async signal safe; can fail if Update() hasn't returned yet.
|
||||
const EHAddrSpace *EHAddrSpace::Get() {
|
||||
return sCurrent;
|
||||
}
|
||||
|
||||
// Collect unwinding information from loaded objects. Calls after the
|
||||
// first have no effect. Async signal unsafe.
|
||||
void EHAddrSpace::Update() {
|
||||
const EHAddrSpace *space = sCurrent;
|
||||
if (space)
|
||||
return;
|
||||
|
||||
SharedLibraryInfo info = SharedLibraryInfo::GetInfoForSelf();
|
||||
std::vector<EHTable> tables;
|
||||
|
||||
for (size_t i = 0; i < info.GetSize(); ++i) {
|
||||
const SharedLibrary &lib = info.GetEntry(i);
|
||||
if (lib.GetOffset() != 0)
|
||||
// TODO: if it has a name, and we haven't seen a mapping of
|
||||
// offset 0 for that file, try opening it and reading the
|
||||
// headers instead. The only thing I've seen so far that's
|
||||
// linked so as to need that treatment is the dynamic linker
|
||||
// itself.
|
||||
continue;
|
||||
EHTable tab(reinterpret_cast<const void *>(lib.GetStart()),
|
||||
lib.GetEnd() - lib.GetStart(), lib.GetName());
|
||||
if (tab.isValid())
|
||||
tables.push_back(tab);
|
||||
}
|
||||
space = new EHAddrSpace(tables);
|
||||
|
||||
if (!sCurrent.compareExchange(nullptr, space)) {
|
||||
delete space;
|
||||
space = sCurrent;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
EHState::EHState(const mcontext_t &context) {
|
||||
#ifdef linux
|
||||
mRegs[0] = context.arm_r0;
|
||||
mRegs[1] = context.arm_r1;
|
||||
mRegs[2] = context.arm_r2;
|
||||
mRegs[3] = context.arm_r3;
|
||||
mRegs[4] = context.arm_r4;
|
||||
mRegs[5] = context.arm_r5;
|
||||
mRegs[6] = context.arm_r6;
|
||||
mRegs[7] = context.arm_r7;
|
||||
mRegs[8] = context.arm_r8;
|
||||
mRegs[9] = context.arm_r9;
|
||||
mRegs[10] = context.arm_r10;
|
||||
mRegs[11] = context.arm_fp;
|
||||
mRegs[12] = context.arm_ip;
|
||||
mRegs[13] = context.arm_sp;
|
||||
mRegs[14] = context.arm_lr;
|
||||
mRegs[15] = context.arm_pc;
|
||||
#else
|
||||
# error "Unhandled OS for ARM EHABI unwinding"
|
||||
#endif
|
||||
}
|
||||
|
||||
} // namespace mozilla
|
||||
|
||||
28
tools/profiler/core/EHABIStackWalk.h
Normal file
28
tools/profiler/core/EHABIStackWalk.h
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
/* -*- 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/. */
|
||||
|
||||
/*
|
||||
* This is an implementation of stack unwinding according to a subset
|
||||
* of the ARM Exception Handling ABI; see the comment at the top of
|
||||
* the .cpp file for details.
|
||||
*/
|
||||
|
||||
#ifndef mozilla_EHABIStackWalk_h__
|
||||
#define mozilla_EHABIStackWalk_h__
|
||||
|
||||
#include <stddef.h>
|
||||
#include <ucontext.h>
|
||||
|
||||
namespace mozilla {
|
||||
|
||||
void EHABIStackWalkInit();
|
||||
|
||||
size_t EHABIStackWalk(const mcontext_t &aContext, void *stackBase,
|
||||
void **aSPs, void **aPCs, size_t aNumFrames);
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
1306
tools/profiler/core/GeckoSampler.cpp
Normal file
1306
tools/profiler/core/GeckoSampler.cpp
Normal file
File diff suppressed because it is too large
Load diff
181
tools/profiler/core/GeckoSampler.h
Normal file
181
tools/profiler/core/GeckoSampler.h
Normal file
|
|
@ -0,0 +1,181 @@
|
|||
/* -*- 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 GeckoSampler_h
|
||||
#define GeckoSampler_h
|
||||
|
||||
#include "platform.h"
|
||||
#include "ProfileEntry.h"
|
||||
#include "mozilla/Vector.h"
|
||||
#include "ThreadProfile.h"
|
||||
#include "ThreadInfo.h"
|
||||
#ifndef SPS_STANDALONE
|
||||
#include "IntelPowerGadget.h"
|
||||
#endif
|
||||
#ifdef MOZ_TASK_TRACER
|
||||
#include "GeckoTaskTracer.h"
|
||||
#endif
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
namespace mozilla {
|
||||
class ProfileGatherer;
|
||||
} // namespace mozilla
|
||||
|
||||
typedef mozilla::Vector<std::string> ThreadNameFilterList;
|
||||
typedef mozilla::Vector<std::string> FeatureList;
|
||||
|
||||
static bool
|
||||
threadSelected(ThreadInfo* aInfo, const ThreadNameFilterList &aThreadNameFilters) {
|
||||
if (aThreadNameFilters.empty()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
std::string name = aInfo->Name();
|
||||
std::transform(name.begin(), name.end(), name.begin(), ::tolower);
|
||||
|
||||
for (uint32_t i = 0; i < aThreadNameFilters.length(); ++i) {
|
||||
std::string filter = aThreadNameFilters[i];
|
||||
std::transform(filter.begin(), filter.end(), filter.begin(), ::tolower);
|
||||
|
||||
// Crude, non UTF-8 compatible, case insensitive substring search
|
||||
if (name.find(filter) != std::string::npos) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
extern mozilla::TimeStamp sLastTracerEvent;
|
||||
extern int sFrameNumber;
|
||||
extern int sLastFrameNumber;
|
||||
|
||||
class GeckoSampler: public Sampler {
|
||||
public:
|
||||
GeckoSampler(double aInterval, int aEntrySize,
|
||||
const char** aFeatures, uint32_t aFeatureCount,
|
||||
const char** aThreadNameFilters, uint32_t aFilterCount);
|
||||
~GeckoSampler();
|
||||
|
||||
void RegisterThread(ThreadInfo* aInfo) {
|
||||
if (!aInfo->IsMainThread() && !mProfileThreads) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!threadSelected(aInfo, mThreadNameFilters)) {
|
||||
return;
|
||||
}
|
||||
|
||||
ThreadProfile* profile = new ThreadProfile(aInfo, mBuffer);
|
||||
aInfo->SetProfile(profile);
|
||||
}
|
||||
|
||||
// Called within a signal. This function must be reentrant
|
||||
virtual void Tick(TickSample* sample) override;
|
||||
|
||||
// Immediately captures the calling thread's call stack and returns it.
|
||||
virtual SyncProfile* GetBacktrace() override;
|
||||
|
||||
// Called within a signal. This function must be reentrant
|
||||
virtual void RequestSave() override
|
||||
{
|
||||
mSaveRequested = true;
|
||||
#ifdef MOZ_TASK_TRACER
|
||||
if (mTaskTracer) {
|
||||
mozilla::tasktracer::StopLogging();
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
virtual void HandleSaveRequest() override;
|
||||
virtual void DeleteExpiredMarkers() override;
|
||||
|
||||
ThreadProfile* GetPrimaryThreadProfile()
|
||||
{
|
||||
if (!mPrimaryThreadProfile) {
|
||||
::MutexAutoLock lock(*sRegisteredThreadsMutex);
|
||||
|
||||
for (uint32_t i = 0; i < sRegisteredThreads->size(); i++) {
|
||||
ThreadInfo* info = sRegisteredThreads->at(i);
|
||||
if (info->IsMainThread() && !info->IsPendingDelete()) {
|
||||
mPrimaryThreadProfile = info->Profile();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return mPrimaryThreadProfile;
|
||||
}
|
||||
|
||||
void ToStreamAsJSON(std::ostream& stream, double aSinceTime = 0);
|
||||
#ifndef SPS_STANDALONE
|
||||
virtual JSObject *ToJSObject(JSContext *aCx, double aSinceTime = 0);
|
||||
void GetGatherer(nsISupports** aRetVal);
|
||||
#endif
|
||||
mozilla::UniquePtr<char[]> ToJSON(double aSinceTime = 0);
|
||||
virtual void ToJSObjectAsync(double aSinceTime = 0, mozilla::dom::Promise* aPromise = 0);
|
||||
void StreamMetaJSCustomObject(SpliceableJSONWriter& aWriter);
|
||||
void StreamTaskTracer(SpliceableJSONWriter& aWriter);
|
||||
void FlushOnJSShutdown(JSContext* aContext);
|
||||
bool ProfileJS() const { return mProfileJS; }
|
||||
bool ProfileJava() const { return mProfileJava; }
|
||||
bool ProfileGPU() const { return mProfileGPU; }
|
||||
bool ProfilePower() const { return mProfilePower; }
|
||||
bool ProfileThreads() const override { return mProfileThreads; }
|
||||
bool InPrivacyMode() const { return mPrivacyMode; }
|
||||
bool AddMainThreadIO() const { return mAddMainThreadIO; }
|
||||
bool ProfileMemory() const { return mProfileMemory; }
|
||||
bool TaskTracer() const { return mTaskTracer; }
|
||||
bool LayersDump() const { return mLayersDump; }
|
||||
bool DisplayListDump() const { return mDisplayListDump; }
|
||||
bool ProfileRestyle() const { return mProfileRestyle; }
|
||||
const ThreadNameFilterList& ThreadNameFilters() { return mThreadNameFilters; }
|
||||
const FeatureList& Features() { return mFeatures; }
|
||||
|
||||
void GetBufferInfo(uint32_t *aCurrentPosition, uint32_t *aTotalSize, uint32_t *aGeneration);
|
||||
|
||||
protected:
|
||||
// Called within a signal. This function must be reentrant
|
||||
virtual void InplaceTick(TickSample* sample);
|
||||
|
||||
// Not implemented on platforms which do not support backtracing
|
||||
void doNativeBacktrace(ThreadProfile &aProfile, TickSample* aSample);
|
||||
|
||||
void StreamJSON(SpliceableJSONWriter& aWriter, double aSinceTime);
|
||||
|
||||
// This represent the application's main thread (SAMPLER_INIT)
|
||||
ThreadProfile* mPrimaryThreadProfile;
|
||||
RefPtr<ProfileBuffer> mBuffer;
|
||||
bool mSaveRequested;
|
||||
bool mAddLeafAddresses;
|
||||
bool mUseStackWalk;
|
||||
bool mProfileJS;
|
||||
bool mProfileGPU;
|
||||
bool mProfileThreads;
|
||||
bool mProfileJava;
|
||||
bool mProfilePower;
|
||||
bool mLayersDump;
|
||||
bool mDisplayListDump;
|
||||
bool mProfileRestyle;
|
||||
|
||||
// Keep the thread filter to check against new thread that
|
||||
// are started while profiling
|
||||
ThreadNameFilterList mThreadNameFilters;
|
||||
FeatureList mFeatures;
|
||||
bool mPrivacyMode;
|
||||
bool mAddMainThreadIO;
|
||||
bool mProfileMemory;
|
||||
bool mTaskTracer;
|
||||
#if defined(XP_WIN)
|
||||
IntelPowerGadget* mIntelPowerGadget;
|
||||
#endif
|
||||
|
||||
private:
|
||||
RefPtr<mozilla::ProfileGatherer> mGatherer;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
310
tools/profiler/core/IntelPowerGadget.cpp
Normal file
310
tools/profiler/core/IntelPowerGadget.cpp
Normal file
|
|
@ -0,0 +1,310 @@
|
|||
/*
|
||||
* Copyright 2013, Intel Corporation
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
* Author: Joe Olivas <joseph.k.olivas@intel.com>
|
||||
*/
|
||||
|
||||
#include "nsDebug.h"
|
||||
#include "nsString.h"
|
||||
#include "IntelPowerGadget.h"
|
||||
#include "prenv.h"
|
||||
|
||||
IntelPowerGadget::IntelPowerGadget() :
|
||||
libpowergadget(nullptr),
|
||||
Initialize(nullptr),
|
||||
GetNumNodes(nullptr),
|
||||
GetMsrName(nullptr),
|
||||
GetMsrFunc(nullptr),
|
||||
ReadMSR(nullptr),
|
||||
WriteMSR(nullptr),
|
||||
GetIAFrequency(nullptr),
|
||||
GetTDP(nullptr),
|
||||
GetMaxTemperature(nullptr),
|
||||
GetThresholds(nullptr),
|
||||
GetTemperature(nullptr),
|
||||
ReadSample(nullptr),
|
||||
GetSysTime(nullptr),
|
||||
GetRDTSC(nullptr),
|
||||
GetTimeInterval(nullptr),
|
||||
GetBaseFrequency(nullptr),
|
||||
GetPowerData(nullptr),
|
||||
StartLog(nullptr),
|
||||
StopLog(nullptr),
|
||||
GetNumMsrs(nullptr),
|
||||
packageMSR(-1),
|
||||
cpuMSR(-1),
|
||||
freqMSR(-1),
|
||||
tempMSR(-1)
|
||||
{
|
||||
}
|
||||
|
||||
bool
|
||||
IntelPowerGadget::Init()
|
||||
{
|
||||
bool success = false;
|
||||
const char *path = PR_GetEnv("IPG_Dir");
|
||||
nsCString ipg_library;
|
||||
if (path && *path) {
|
||||
ipg_library.Append(path);
|
||||
ipg_library.Append('/');
|
||||
ipg_library.AppendLiteral(PG_LIBRARY_NAME);
|
||||
libpowergadget = PR_LoadLibrary(ipg_library.get());
|
||||
}
|
||||
|
||||
if(libpowergadget) {
|
||||
Initialize = (IPGInitialize) PR_FindFunctionSymbol(libpowergadget, "IntelEnergyLibInitialize");
|
||||
GetNumNodes = (IPGGetNumNodes) PR_FindFunctionSymbol(libpowergadget, "GetNumNodes");
|
||||
GetMsrName = (IPGGetMsrName) PR_FindFunctionSymbol(libpowergadget, "GetMsrName");
|
||||
GetMsrFunc = (IPGGetMsrFunc) PR_FindFunctionSymbol(libpowergadget, "GetMsrFunc");
|
||||
ReadMSR = (IPGReadMSR) PR_FindFunctionSymbol(libpowergadget, "ReadMSR");
|
||||
WriteMSR = (IPGWriteMSR) PR_FindFunctionSymbol(libpowergadget, "WriteMSR");
|
||||
GetIAFrequency = (IPGGetIAFrequency) PR_FindFunctionSymbol(libpowergadget, "GetIAFrequency");
|
||||
GetTDP = (IPGGetTDP) PR_FindFunctionSymbol(libpowergadget, "GetTDP");
|
||||
GetMaxTemperature = (IPGGetMaxTemperature) PR_FindFunctionSymbol(libpowergadget, "GetMaxTemperature");
|
||||
GetThresholds = (IPGGetThresholds) PR_FindFunctionSymbol(libpowergadget, "GetThresholds");
|
||||
GetTemperature = (IPGGetTemperature) PR_FindFunctionSymbol(libpowergadget, "GetTemperature");
|
||||
ReadSample = (IPGReadSample) PR_FindFunctionSymbol(libpowergadget, "ReadSample");
|
||||
GetSysTime = (IPGGetSysTime) PR_FindFunctionSymbol(libpowergadget, "GetSysTime");
|
||||
GetRDTSC = (IPGGetRDTSC) PR_FindFunctionSymbol(libpowergadget, "GetRDTSC");
|
||||
GetTimeInterval = (IPGGetTimeInterval) PR_FindFunctionSymbol(libpowergadget, "GetTimeInterval");
|
||||
GetBaseFrequency = (IPGGetBaseFrequency) PR_FindFunctionSymbol(libpowergadget, "GetBaseFrequency");
|
||||
GetPowerData = (IPGGetPowerData) PR_FindFunctionSymbol(libpowergadget, "GetPowerData");
|
||||
StartLog = (IPGStartLog) PR_FindFunctionSymbol(libpowergadget, "StartLog");
|
||||
StopLog = (IPGStopLog) PR_FindFunctionSymbol(libpowergadget, "StopLog");
|
||||
GetNumMsrs = (IPGGetNumMsrs) PR_FindFunctionSymbol(libpowergadget, "GetNumMsrs");
|
||||
}
|
||||
|
||||
if(Initialize) {
|
||||
Initialize();
|
||||
int msrCount = GetNumberMsrs();
|
||||
wchar_t name[1024] = {0};
|
||||
for(int i = 0; i < msrCount; ++i) {
|
||||
GetMsrName(i, name);
|
||||
int func = 0;
|
||||
GetMsrFunc(i, &func);
|
||||
// MSR for frequency
|
||||
if(wcscmp(name, L"CPU Frequency") == 0 && (func == 0)) {
|
||||
this->freqMSR = i;
|
||||
}
|
||||
// MSR for Package
|
||||
else if(wcscmp(name, L"Processor") == 0 && (func == 1)) {
|
||||
this->packageMSR = i;
|
||||
}
|
||||
// MSR for CPU
|
||||
else if(wcscmp(name, L"IA") == 0 && (func == 1)) {
|
||||
this->cpuMSR = i;
|
||||
}
|
||||
// MSR for Temperature
|
||||
else if(wcscmp(name, L"Package") == 0 && (func == 2)) {
|
||||
this->tempMSR = i;
|
||||
}
|
||||
}
|
||||
// Grab one sample at startup for a diff
|
||||
TakeSample();
|
||||
success = true;
|
||||
}
|
||||
return success;
|
||||
}
|
||||
|
||||
IntelPowerGadget::~IntelPowerGadget()
|
||||
{
|
||||
if(libpowergadget) {
|
||||
NS_WARNING("Unloading PowerGadget library!\n");
|
||||
PR_UnloadLibrary(libpowergadget);
|
||||
libpowergadget = nullptr;
|
||||
Initialize = nullptr;
|
||||
GetNumNodes = nullptr;
|
||||
GetMsrName = nullptr;
|
||||
GetMsrFunc = nullptr;
|
||||
ReadMSR = nullptr;
|
||||
WriteMSR = nullptr;
|
||||
GetIAFrequency = nullptr;
|
||||
GetTDP = nullptr;
|
||||
GetMaxTemperature = nullptr;
|
||||
GetThresholds = nullptr;
|
||||
GetTemperature = nullptr;
|
||||
ReadSample = nullptr;
|
||||
GetSysTime = nullptr;
|
||||
GetRDTSC = nullptr;
|
||||
GetTimeInterval = nullptr;
|
||||
GetBaseFrequency = nullptr;
|
||||
GetPowerData = nullptr;
|
||||
StartLog = nullptr;
|
||||
StopLog = nullptr;
|
||||
GetNumMsrs = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
int
|
||||
IntelPowerGadget::GetNumberNodes()
|
||||
{
|
||||
int nodes = 0;
|
||||
if(GetNumNodes) {
|
||||
int ok = GetNumNodes(&nodes);
|
||||
}
|
||||
return nodes;
|
||||
}
|
||||
|
||||
int
|
||||
IntelPowerGadget::GetNumberMsrs()
|
||||
{
|
||||
int msrs = 0;
|
||||
if(GetNumMsrs) {
|
||||
int ok = GetNumMsrs(&msrs);
|
||||
}
|
||||
return msrs;
|
||||
}
|
||||
|
||||
int
|
||||
IntelPowerGadget::GetCPUFrequency(int node)
|
||||
{
|
||||
int frequency = 0;
|
||||
if(GetIAFrequency) {
|
||||
int ok = GetIAFrequency(node, &frequency);
|
||||
}
|
||||
return frequency;
|
||||
}
|
||||
|
||||
double
|
||||
IntelPowerGadget::GetTdp(int node)
|
||||
{
|
||||
double tdp = 0.0;
|
||||
if(GetTDP) {
|
||||
int ok = GetTDP(node, &tdp);
|
||||
}
|
||||
return tdp;
|
||||
}
|
||||
|
||||
int
|
||||
IntelPowerGadget::GetMaxTemp(int node)
|
||||
{
|
||||
int maxTemperatureC = 0;
|
||||
if(GetMaxTemperature) {
|
||||
int ok = GetMaxTemperature(node, &maxTemperatureC);
|
||||
}
|
||||
return maxTemperatureC;
|
||||
}
|
||||
|
||||
int
|
||||
IntelPowerGadget::GetTemp(int node)
|
||||
{
|
||||
int temperatureC = 0;
|
||||
if(GetTemperature) {
|
||||
int ok = GetTemperature(node, &temperatureC);
|
||||
}
|
||||
return temperatureC;
|
||||
}
|
||||
|
||||
int
|
||||
IntelPowerGadget::TakeSample()
|
||||
{
|
||||
int ok = 0;
|
||||
if(ReadSample) {
|
||||
ok = ReadSample();
|
||||
}
|
||||
return ok;
|
||||
}
|
||||
|
||||
uint64_t
|
||||
IntelPowerGadget::GetRdtsc()
|
||||
{
|
||||
uint64_t rdtsc = 0;
|
||||
if(GetRDTSC) {
|
||||
int ok = GetRDTSC(&rdtsc);
|
||||
}
|
||||
return rdtsc;
|
||||
}
|
||||
|
||||
double
|
||||
IntelPowerGadget::GetInterval()
|
||||
{
|
||||
double interval = 0.0;
|
||||
if(GetTimeInterval) {
|
||||
int ok = GetTimeInterval(&interval);
|
||||
}
|
||||
return interval;
|
||||
}
|
||||
|
||||
double
|
||||
IntelPowerGadget::GetCPUBaseFrequency(int node)
|
||||
{
|
||||
double freq = 0.0;
|
||||
if(GetBaseFrequency) {
|
||||
int ok = GetBaseFrequency(node, &freq);
|
||||
}
|
||||
return freq;
|
||||
}
|
||||
|
||||
double
|
||||
IntelPowerGadget::GetTotalPackagePowerInWatts()
|
||||
{
|
||||
int nodes = GetNumberNodes();
|
||||
double totalPower = 0.0;
|
||||
for(int i = 0; i < nodes; ++i) {
|
||||
totalPower += GetPackagePowerInWatts(i);
|
||||
}
|
||||
return totalPower;
|
||||
}
|
||||
|
||||
double
|
||||
IntelPowerGadget::GetPackagePowerInWatts(int node)
|
||||
{
|
||||
int numResult = 0;
|
||||
double result[] = {0.0, 0.0, 0.0};
|
||||
if(GetPowerData && packageMSR != -1) {
|
||||
int ok = GetPowerData(node, packageMSR, result, &numResult);
|
||||
}
|
||||
return result[0];
|
||||
}
|
||||
|
||||
double
|
||||
IntelPowerGadget::GetTotalCPUPowerInWatts()
|
||||
{
|
||||
int nodes = GetNumberNodes();
|
||||
double totalPower = 0.0;
|
||||
for(int i = 0; i < nodes; ++i) {
|
||||
totalPower += GetCPUPowerInWatts(i);
|
||||
}
|
||||
return totalPower;
|
||||
}
|
||||
|
||||
double
|
||||
IntelPowerGadget::GetCPUPowerInWatts(int node)
|
||||
{
|
||||
int numResult = 0;
|
||||
double result[] = {0.0, 0.0, 0.0};
|
||||
if(GetPowerData && cpuMSR != -1) {
|
||||
int ok = GetPowerData(node, cpuMSR, result, &numResult);
|
||||
}
|
||||
return result[0];
|
||||
}
|
||||
|
||||
double
|
||||
IntelPowerGadget::GetTotalGPUPowerInWatts()
|
||||
{
|
||||
int nodes = GetNumberNodes();
|
||||
double totalPower = 0.0;
|
||||
for(int i = 0; i < nodes; ++i) {
|
||||
totalPower += GetGPUPowerInWatts(i);
|
||||
}
|
||||
return totalPower;
|
||||
}
|
||||
|
||||
double
|
||||
IntelPowerGadget::GetGPUPowerInWatts(int node)
|
||||
{
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
150
tools/profiler/core/IntelPowerGadget.h
Normal file
150
tools/profiler/core/IntelPowerGadget.h
Normal file
|
|
@ -0,0 +1,150 @@
|
|||
/*
|
||||
* Copyright 2013, Intel Corporation
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
* Author: Joe Olivas <joseph.k.olivas@intel.com>
|
||||
*/
|
||||
|
||||
#ifndef profiler_IntelPowerGadget_h
|
||||
#define profiler_IntelPowerGadget_h
|
||||
|
||||
#ifdef _MSC_VER
|
||||
typedef __int32 int32_t;
|
||||
typedef unsigned __int32 uint32_t;
|
||||
typedef __int64 int64_t;
|
||||
typedef unsigned __int64 uint64_t;
|
||||
#else
|
||||
#include <stdint.h>
|
||||
#endif
|
||||
#include "prlink.h"
|
||||
|
||||
typedef int (*IPGInitialize) ();
|
||||
typedef int (*IPGGetNumNodes) (int *nNodes);
|
||||
typedef int (*IPGGetNumMsrs) (int *nMsr);
|
||||
typedef int (*IPGGetMsrName) (int iMsr, wchar_t *szName);
|
||||
typedef int (*IPGGetMsrFunc) (int iMsr, int *pFuncID);
|
||||
typedef int (*IPGReadMSR) (int iNode, unsigned int address, uint64_t *value);
|
||||
typedef int (*IPGWriteMSR) (int iNode, unsigned int address, uint64_t value);
|
||||
typedef int (*IPGGetIAFrequency) (int iNode, int *freqInMHz);
|
||||
typedef int (*IPGGetTDP) (int iNode, double *TDP);
|
||||
typedef int (*IPGGetMaxTemperature) (int iNode, int *degreeC);
|
||||
typedef int (*IPGGetThresholds) (int iNode, int *degree1C, int *degree2C);
|
||||
typedef int (*IPGGetTemperature) (int iNode, int *degreeC);
|
||||
typedef int (*IPGReadSample) ();
|
||||
typedef int (*IPGGetSysTime) (void *pSysTime);
|
||||
typedef int (*IPGGetRDTSC) (uint64_t *pTSC);
|
||||
typedef int (*IPGGetTimeInterval) (double *pOffset);
|
||||
typedef int (*IPGGetBaseFrequency) (int iNode, double *pBaseFrequency);
|
||||
typedef int (*IPGGetPowerData) (int iNode, int iMSR, double *pResult, int *nResult);
|
||||
typedef int (*IPGStartLog) (wchar_t *szFileName);
|
||||
typedef int (*IPGStopLog) ();
|
||||
|
||||
#if defined(__x86_64__) || defined(__x86_64) || defined(_M_AMD64)
|
||||
#define PG_LIBRARY_NAME "EnergyLib64"
|
||||
#else
|
||||
#define PG_LIBRARY_NAME "EnergyLib32"
|
||||
#endif
|
||||
|
||||
|
||||
class IntelPowerGadget
|
||||
{
|
||||
public:
|
||||
|
||||
IntelPowerGadget();
|
||||
~IntelPowerGadget();
|
||||
|
||||
// Fails if initialization is incomplete
|
||||
bool Init();
|
||||
|
||||
// Returns the number of packages on the system
|
||||
int GetNumberNodes();
|
||||
|
||||
// Returns the number of MSRs being tracked
|
||||
int GetNumberMsrs();
|
||||
|
||||
// Given a node, returns the temperature
|
||||
int GetCPUFrequency(int);
|
||||
|
||||
// Returns the TDP of the given node
|
||||
double GetTdp(int);
|
||||
|
||||
// Returns the maximum temperature for the given node
|
||||
int GetMaxTemp(int);
|
||||
|
||||
// Returns the current temperature in degrees C
|
||||
// of the given node
|
||||
int GetTemp(int);
|
||||
|
||||
// Takes a sample of data. Must be called before
|
||||
// any current data is retrieved.
|
||||
int TakeSample();
|
||||
|
||||
// Gets the timestamp of the most recent sample
|
||||
uint64_t GetRdtsc();
|
||||
|
||||
// returns number of seconds between the last
|
||||
// two samples
|
||||
double GetInterval();
|
||||
|
||||
// Returns the base frequency for the given node
|
||||
double GetCPUBaseFrequency(int node);
|
||||
|
||||
// Returns the combined package power for all
|
||||
// packages on the system for the last sample.
|
||||
double GetTotalPackagePowerInWatts();
|
||||
double GetPackagePowerInWatts(int node);
|
||||
|
||||
// Returns the combined CPU power for all
|
||||
// packages on the system for the last sample.
|
||||
// If the reading is not available, returns 0.0
|
||||
double GetTotalCPUPowerInWatts();
|
||||
double GetCPUPowerInWatts(int node);
|
||||
|
||||
// Returns the combined GPU power for all
|
||||
// packages on the system for the last sample.
|
||||
// If the reading is not available, returns 0.0
|
||||
double GetTotalGPUPowerInWatts();
|
||||
double GetGPUPowerInWatts(int node);
|
||||
|
||||
private:
|
||||
|
||||
PRLibrary *libpowergadget;
|
||||
IPGInitialize Initialize;
|
||||
IPGGetNumNodes GetNumNodes;
|
||||
IPGGetNumMsrs GetNumMsrs;
|
||||
IPGGetMsrName GetMsrName;
|
||||
IPGGetMsrFunc GetMsrFunc;
|
||||
IPGReadMSR ReadMSR;
|
||||
IPGWriteMSR WriteMSR;
|
||||
IPGGetIAFrequency GetIAFrequency;
|
||||
IPGGetTDP GetTDP;
|
||||
IPGGetMaxTemperature GetMaxTemperature;
|
||||
IPGGetThresholds GetThresholds;
|
||||
IPGGetTemperature GetTemperature;
|
||||
IPGReadSample ReadSample;
|
||||
IPGGetSysTime GetSysTime;
|
||||
IPGGetRDTSC GetRDTSC;
|
||||
IPGGetTimeInterval GetTimeInterval;
|
||||
IPGGetBaseFrequency GetBaseFrequency;
|
||||
IPGGetPowerData GetPowerData;
|
||||
IPGStartLog StartLog;
|
||||
IPGStopLog StopLog;
|
||||
|
||||
int packageMSR;
|
||||
int cpuMSR;
|
||||
int freqMSR;
|
||||
int tempMSR;
|
||||
};
|
||||
|
||||
#endif // profiler_IntelPowerGadget_h
|
||||
76
tools/profiler/core/PlatformMacros.h
Normal file
76
tools/profiler/core/PlatformMacros.h
Normal file
|
|
@ -0,0 +1,76 @@
|
|||
/* -*- 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 SPS_PLATFORM_MACROS_H
|
||||
#define SPS_PLATFORM_MACROS_H
|
||||
|
||||
/* Define platform selection macros in a consistent way. Don't add
|
||||
anything else to this file, so it can remain freestanding. The
|
||||
primary factorisation is on (ARCH,OS) pairs ("PLATforms") but ARCH_
|
||||
and OS_ macros are defined too, since they are sometimes
|
||||
convenient. */
|
||||
|
||||
#undef SPS_PLAT_arm_android
|
||||
#undef SPS_PLAT_amd64_linux
|
||||
#undef SPS_PLAT_x86_linux
|
||||
#undef SPS_PLAT_amd64_darwin
|
||||
#undef SPS_PLAT_x86_darwin
|
||||
#undef SPS_PLAT_x86_windows
|
||||
#undef SPS_PLAT_amd64_windows
|
||||
|
||||
#undef SPS_ARCH_arm
|
||||
#undef SPS_ARCH_x86
|
||||
#undef SPS_ARCH_amd64
|
||||
|
||||
#undef SPS_OS_android
|
||||
#undef SPS_OS_linux
|
||||
#undef SPS_OS_darwin
|
||||
#undef SPS_OS_windows
|
||||
|
||||
#if defined(__linux__) && defined(__x86_64__)
|
||||
# define SPS_PLAT_amd64_linux 1
|
||||
# define SPS_ARCH_amd64 1
|
||||
# define SPS_OS_linux 1
|
||||
|
||||
#elif defined(__ANDROID__) && defined(__arm__)
|
||||
# define SPS_PLAT_arm_android 1
|
||||
# define SPS_ARCH_arm 1
|
||||
# define SPS_OS_android 1
|
||||
|
||||
#elif defined(__ANDROID__) && defined(__i386__)
|
||||
# define SPS_PLAT_x86_android 1
|
||||
# define SPS_ARCH_x86 1
|
||||
# define SPS_OS_android 1
|
||||
|
||||
#elif defined(__linux__) && defined(__i386__)
|
||||
# define SPS_PLAT_x86_linux 1
|
||||
# define SPS_ARCH_x86 1
|
||||
# define SPS_OS_linux 1
|
||||
|
||||
#elif defined(__APPLE__) && defined(__x86_64__)
|
||||
# define SPS_PLAT_amd64_darwin 1
|
||||
# define SPS_ARCH_amd64 1
|
||||
# define SPS_OS_darwin 1
|
||||
|
||||
#elif defined(__APPLE__) && defined(__i386__)
|
||||
# define SPS_PLAT_x86_darwin 1
|
||||
# define SPS_ARCH_x86 1
|
||||
# define SPS_OS_darwin 1
|
||||
|
||||
#elif (defined(_MSC_VER) || defined(__MINGW32__)) && (defined(_M_IX86) || defined(__i386__))
|
||||
# define SPS_PLAT_x86_windows 1
|
||||
# define SPS_ARCH_x86 1
|
||||
# define SPS_OS_windows 1
|
||||
|
||||
#elif (defined(_MSC_VER) || defined(__MINGW32__)) && (defined(_M_X64) || defined(__x86_64__))
|
||||
# define SPS_PLAT_amd64_windows 1
|
||||
# define SPS_ARCH_amd64 1
|
||||
# define SPS_OS_windows 1
|
||||
|
||||
#else
|
||||
# error "Unsupported platform"
|
||||
#endif
|
||||
|
||||
#endif /* ndef SPS_PLATFORM_MACROS_H */
|
||||
89
tools/profiler/core/ProfileBuffer.cpp
Normal file
89
tools/profiler/core/ProfileBuffer.cpp
Normal file
|
|
@ -0,0 +1,89 @@
|
|||
/* -*- 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 "ProfileBuffer.h"
|
||||
|
||||
ProfileBuffer::ProfileBuffer(int aEntrySize)
|
||||
: mEntries(MakeUnique<ProfileEntry[]>(aEntrySize))
|
||||
, mWritePos(0)
|
||||
, mReadPos(0)
|
||||
, mEntrySize(aEntrySize)
|
||||
, mGeneration(0)
|
||||
{
|
||||
}
|
||||
|
||||
ProfileBuffer::~ProfileBuffer()
|
||||
{
|
||||
while (mStoredMarkers.peek()) {
|
||||
delete mStoredMarkers.popHead();
|
||||
}
|
||||
}
|
||||
|
||||
// Called from signal, call only reentrant functions
|
||||
void ProfileBuffer::addTag(const ProfileEntry& aTag)
|
||||
{
|
||||
mEntries[mWritePos++] = aTag;
|
||||
if (mWritePos == mEntrySize) {
|
||||
// Wrapping around may result in things referenced in the buffer (e.g.,
|
||||
// JIT code addresses and markers) being incorrectly collected.
|
||||
MOZ_ASSERT(mGeneration != UINT32_MAX);
|
||||
mGeneration++;
|
||||
mWritePos = 0;
|
||||
}
|
||||
if (mWritePos == mReadPos) {
|
||||
// Keep one slot open.
|
||||
mEntries[mReadPos] = ProfileEntry();
|
||||
mReadPos = (mReadPos + 1) % mEntrySize;
|
||||
}
|
||||
}
|
||||
|
||||
void ProfileBuffer::addStoredMarker(ProfilerMarker *aStoredMarker) {
|
||||
aStoredMarker->SetGeneration(mGeneration);
|
||||
mStoredMarkers.insert(aStoredMarker);
|
||||
}
|
||||
|
||||
void ProfileBuffer::deleteExpiredStoredMarkers() {
|
||||
// Delete markers of samples that have been overwritten due to circular
|
||||
// buffer wraparound.
|
||||
uint32_t generation = mGeneration;
|
||||
while (mStoredMarkers.peek() &&
|
||||
mStoredMarkers.peek()->HasExpired(generation)) {
|
||||
delete mStoredMarkers.popHead();
|
||||
}
|
||||
}
|
||||
|
||||
void ProfileBuffer::reset() {
|
||||
mGeneration += 2;
|
||||
mReadPos = mWritePos = 0;
|
||||
}
|
||||
|
||||
#define DYNAMIC_MAX_STRING 8192
|
||||
|
||||
char* ProfileBuffer::processDynamicTag(int readPos,
|
||||
int* tagsConsumed, char* tagBuff)
|
||||
{
|
||||
int readAheadPos = (readPos + 1) % mEntrySize;
|
||||
int tagBuffPos = 0;
|
||||
|
||||
// Read the string stored in mTagData until the null character is seen
|
||||
bool seenNullByte = false;
|
||||
while (readAheadPos != mWritePos && !seenNullByte) {
|
||||
(*tagsConsumed)++;
|
||||
ProfileEntry readAheadEntry = mEntries[readAheadPos];
|
||||
for (size_t pos = 0; pos < sizeof(void*); pos++) {
|
||||
tagBuff[tagBuffPos] = readAheadEntry.mTagChars[pos];
|
||||
if (tagBuff[tagBuffPos] == '\0' || tagBuffPos == DYNAMIC_MAX_STRING-2) {
|
||||
seenNullByte = true;
|
||||
break;
|
||||
}
|
||||
tagBuffPos++;
|
||||
}
|
||||
if (!seenNullByte)
|
||||
readAheadPos = (readAheadPos + 1) % mEntrySize;
|
||||
}
|
||||
return tagBuff;
|
||||
}
|
||||
|
||||
|
||||
61
tools/profiler/core/ProfileBuffer.h
Normal file
61
tools/profiler/core/ProfileBuffer.h
Normal file
|
|
@ -0,0 +1,61 @@
|
|||
/* -*- 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_PROFILE_BUFFER_H
|
||||
#define MOZ_PROFILE_BUFFER_H
|
||||
|
||||
#include "ProfileEntry.h"
|
||||
#include "platform.h"
|
||||
#include "ProfileJSONWriter.h"
|
||||
#include "mozilla/RefPtr.h"
|
||||
#include "mozilla/RefCounted.h"
|
||||
|
||||
class ProfileBuffer : public mozilla::RefCounted<ProfileBuffer> {
|
||||
public:
|
||||
MOZ_DECLARE_REFCOUNTED_VIRTUAL_TYPENAME(ProfileBuffer)
|
||||
|
||||
explicit ProfileBuffer(int aEntrySize);
|
||||
|
||||
virtual ~ProfileBuffer();
|
||||
|
||||
void addTag(const ProfileEntry& aTag);
|
||||
void StreamSamplesToJSON(SpliceableJSONWriter& aWriter, int aThreadId, double aSinceTime,
|
||||
JSContext* cx, UniqueStacks& aUniqueStacks);
|
||||
void StreamMarkersToJSON(SpliceableJSONWriter& aWriter, int aThreadId, double aSinceTime,
|
||||
UniqueStacks& aUniqueStacks);
|
||||
void DuplicateLastSample(int aThreadId);
|
||||
|
||||
void addStoredMarker(ProfilerMarker* aStoredMarker);
|
||||
|
||||
// The following two methods are not signal safe! They delete markers.
|
||||
void deleteExpiredStoredMarkers();
|
||||
void reset();
|
||||
|
||||
protected:
|
||||
char* processDynamicTag(int readPos, int* tagsConsumed, char* tagBuff);
|
||||
int FindLastSampleOfThread(int aThreadId);
|
||||
|
||||
public:
|
||||
// Circular buffer 'Keep One Slot Open' implementation for simplicity
|
||||
mozilla::UniquePtr<ProfileEntry[]> mEntries;
|
||||
|
||||
// Points to the next entry we will write to, which is also the one at which
|
||||
// we need to stop reading.
|
||||
int mWritePos;
|
||||
|
||||
// Points to the entry at which we can start reading.
|
||||
int mReadPos;
|
||||
|
||||
// The number of entries in our buffer.
|
||||
int mEntrySize;
|
||||
|
||||
// How many times mWritePos has wrapped around.
|
||||
uint32_t mGeneration;
|
||||
|
||||
// Markers that marker entries in the buffer might refer to.
|
||||
ProfilerMarkerLinkedList mStoredMarkers;
|
||||
};
|
||||
|
||||
#endif
|
||||
881
tools/profiler/core/ProfileEntry.cpp
Normal file
881
tools/profiler/core/ProfileEntry.cpp
Normal file
|
|
@ -0,0 +1,881 @@
|
|||
/* -*- 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 <ostream>
|
||||
#include "platform.h"
|
||||
#include "mozilla/HashFunctions.h"
|
||||
|
||||
#ifndef SPS_STANDALONE
|
||||
#include "nsThreadUtils.h"
|
||||
#include "nsXULAppAPI.h"
|
||||
|
||||
// JS
|
||||
#include "jsapi.h"
|
||||
#include "jsfriendapi.h"
|
||||
#include "js/TrackedOptimizationInfo.h"
|
||||
#endif
|
||||
|
||||
// Self
|
||||
#include "ProfileEntry.h"
|
||||
|
||||
using mozilla::MakeUnique;
|
||||
using mozilla::UniquePtr;
|
||||
using mozilla::Maybe;
|
||||
using mozilla::Some;
|
||||
using mozilla::Nothing;
|
||||
using mozilla::JSONWriter;
|
||||
|
||||
|
||||
////////////////////////////////////////////////////////////////////////
|
||||
// BEGIN ProfileEntry
|
||||
|
||||
ProfileEntry::ProfileEntry()
|
||||
: mTagData(nullptr)
|
||||
, mTagName(0)
|
||||
{ }
|
||||
|
||||
// aTagData must not need release (i.e. be a string from the text segment)
|
||||
ProfileEntry::ProfileEntry(char aTagName, const char *aTagData)
|
||||
: mTagData(aTagData)
|
||||
, mTagName(aTagName)
|
||||
{ }
|
||||
|
||||
ProfileEntry::ProfileEntry(char aTagName, ProfilerMarker *aTagMarker)
|
||||
: mTagMarker(aTagMarker)
|
||||
, mTagName(aTagName)
|
||||
{ }
|
||||
|
||||
ProfileEntry::ProfileEntry(char aTagName, void *aTagPtr)
|
||||
: mTagPtr(aTagPtr)
|
||||
, mTagName(aTagName)
|
||||
{ }
|
||||
|
||||
ProfileEntry::ProfileEntry(char aTagName, double aTagDouble)
|
||||
: mTagDouble(aTagDouble)
|
||||
, mTagName(aTagName)
|
||||
{ }
|
||||
|
||||
ProfileEntry::ProfileEntry(char aTagName, uintptr_t aTagOffset)
|
||||
: mTagOffset(aTagOffset)
|
||||
, mTagName(aTagName)
|
||||
{ }
|
||||
|
||||
ProfileEntry::ProfileEntry(char aTagName, Address aTagAddress)
|
||||
: mTagAddress(aTagAddress)
|
||||
, mTagName(aTagName)
|
||||
{ }
|
||||
|
||||
ProfileEntry::ProfileEntry(char aTagName, int aTagInt)
|
||||
: mTagInt(aTagInt)
|
||||
, mTagName(aTagName)
|
||||
{ }
|
||||
|
||||
ProfileEntry::ProfileEntry(char aTagName, char aTagChar)
|
||||
: mTagChar(aTagChar)
|
||||
, mTagName(aTagName)
|
||||
{ }
|
||||
|
||||
bool ProfileEntry::is_ent_hint(char hintChar) {
|
||||
return mTagName == 'h' && mTagChar == hintChar;
|
||||
}
|
||||
|
||||
bool ProfileEntry::is_ent_hint() {
|
||||
return mTagName == 'h';
|
||||
}
|
||||
|
||||
bool ProfileEntry::is_ent(char tagChar) {
|
||||
return mTagName == tagChar;
|
||||
}
|
||||
|
||||
void* ProfileEntry::get_tagPtr() {
|
||||
// No consistency checking. Oh well.
|
||||
return mTagPtr;
|
||||
}
|
||||
|
||||
// END ProfileEntry
|
||||
////////////////////////////////////////////////////////////////////////
|
||||
|
||||
class JSONSchemaWriter
|
||||
{
|
||||
JSONWriter& mWriter;
|
||||
uint32_t mIndex;
|
||||
|
||||
public:
|
||||
explicit JSONSchemaWriter(JSONWriter& aWriter)
|
||||
: mWriter(aWriter)
|
||||
, mIndex(0)
|
||||
{
|
||||
aWriter.StartObjectProperty("schema");
|
||||
}
|
||||
|
||||
void WriteField(const char* aName) {
|
||||
mWriter.IntProperty(aName, mIndex++);
|
||||
}
|
||||
|
||||
~JSONSchemaWriter() {
|
||||
mWriter.EndObject();
|
||||
}
|
||||
};
|
||||
|
||||
#ifndef SPS_STANDALONE
|
||||
class StreamOptimizationTypeInfoOp : public JS::ForEachTrackedOptimizationTypeInfoOp
|
||||
{
|
||||
JSONWriter& mWriter;
|
||||
UniqueJSONStrings& mUniqueStrings;
|
||||
bool mStartedTypeList;
|
||||
|
||||
public:
|
||||
StreamOptimizationTypeInfoOp(JSONWriter& aWriter, UniqueJSONStrings& aUniqueStrings)
|
||||
: mWriter(aWriter)
|
||||
, mUniqueStrings(aUniqueStrings)
|
||||
, mStartedTypeList(false)
|
||||
{ }
|
||||
|
||||
void readType(const char* keyedBy, const char* name,
|
||||
const char* location, Maybe<unsigned> lineno) override {
|
||||
if (!mStartedTypeList) {
|
||||
mStartedTypeList = true;
|
||||
mWriter.StartObjectElement();
|
||||
mWriter.StartArrayProperty("typeset");
|
||||
}
|
||||
|
||||
mWriter.StartObjectElement();
|
||||
{
|
||||
mUniqueStrings.WriteProperty(mWriter, "keyedBy", keyedBy);
|
||||
if (name) {
|
||||
mUniqueStrings.WriteProperty(mWriter, "name", name);
|
||||
}
|
||||
if (location) {
|
||||
mUniqueStrings.WriteProperty(mWriter, "location", location);
|
||||
}
|
||||
if (lineno.isSome()) {
|
||||
mWriter.IntProperty("line", *lineno);
|
||||
}
|
||||
}
|
||||
mWriter.EndObject();
|
||||
}
|
||||
|
||||
void operator()(JS::TrackedTypeSite site, const char* mirType) override {
|
||||
if (mStartedTypeList) {
|
||||
mWriter.EndArray();
|
||||
mStartedTypeList = false;
|
||||
} else {
|
||||
mWriter.StartObjectElement();
|
||||
}
|
||||
|
||||
{
|
||||
mUniqueStrings.WriteProperty(mWriter, "site", JS::TrackedTypeSiteString(site));
|
||||
mUniqueStrings.WriteProperty(mWriter, "mirType", mirType);
|
||||
}
|
||||
mWriter.EndObject();
|
||||
}
|
||||
};
|
||||
|
||||
// As mentioned in ProfileEntry.h, the JSON format contains many arrays whose
|
||||
// elements are laid out according to various schemas to help
|
||||
// de-duplication. This RAII class helps write these arrays by keeping track of
|
||||
// the last non-null element written and adding the appropriate number of null
|
||||
// elements when writing new non-null elements. It also automatically opens and
|
||||
// closes an array element on the given JSON writer.
|
||||
//
|
||||
// Example usage:
|
||||
//
|
||||
// // Define the schema of elements in this type of array: [FOO, BAR, BAZ]
|
||||
// enum Schema : uint32_t {
|
||||
// FOO = 0,
|
||||
// BAR = 1,
|
||||
// BAZ = 2
|
||||
// };
|
||||
//
|
||||
// AutoArraySchemaWriter writer(someJsonWriter, someUniqueStrings);
|
||||
// if (shouldWriteFoo) {
|
||||
// writer.IntElement(FOO, getFoo());
|
||||
// }
|
||||
// ... etc ...
|
||||
class MOZ_RAII AutoArraySchemaWriter
|
||||
{
|
||||
friend class AutoObjectWriter;
|
||||
|
||||
SpliceableJSONWriter& mJSONWriter;
|
||||
UniqueJSONStrings* mStrings;
|
||||
uint32_t mNextFreeIndex;
|
||||
|
||||
public:
|
||||
AutoArraySchemaWriter(SpliceableJSONWriter& aWriter, UniqueJSONStrings& aStrings)
|
||||
: mJSONWriter(aWriter)
|
||||
, mStrings(&aStrings)
|
||||
, mNextFreeIndex(0)
|
||||
{
|
||||
mJSONWriter.StartArrayElement();
|
||||
}
|
||||
|
||||
// If you don't have access to a UniqueStrings, you had better not try and
|
||||
// write a string element down the line!
|
||||
explicit AutoArraySchemaWriter(SpliceableJSONWriter& aWriter)
|
||||
: mJSONWriter(aWriter)
|
||||
, mStrings(nullptr)
|
||||
, mNextFreeIndex(0)
|
||||
{
|
||||
mJSONWriter.StartArrayElement();
|
||||
}
|
||||
|
||||
~AutoArraySchemaWriter() {
|
||||
mJSONWriter.EndArray();
|
||||
}
|
||||
|
||||
void FillUpTo(uint32_t aIndex) {
|
||||
MOZ_ASSERT(aIndex >= mNextFreeIndex);
|
||||
mJSONWriter.NullElements(aIndex - mNextFreeIndex);
|
||||
mNextFreeIndex = aIndex + 1;
|
||||
}
|
||||
|
||||
void IntElement(uint32_t aIndex, uint32_t aValue) {
|
||||
FillUpTo(aIndex);
|
||||
mJSONWriter.IntElement(aValue);
|
||||
}
|
||||
|
||||
void DoubleElement(uint32_t aIndex, double aValue) {
|
||||
FillUpTo(aIndex);
|
||||
mJSONWriter.DoubleElement(aValue);
|
||||
}
|
||||
|
||||
void StringElement(uint32_t aIndex, const char* aValue) {
|
||||
MOZ_RELEASE_ASSERT(mStrings);
|
||||
FillUpTo(aIndex);
|
||||
mStrings->WriteElement(mJSONWriter, aValue);
|
||||
}
|
||||
};
|
||||
|
||||
class StreamOptimizationAttemptsOp : public JS::ForEachTrackedOptimizationAttemptOp
|
||||
{
|
||||
SpliceableJSONWriter& mWriter;
|
||||
UniqueJSONStrings& mUniqueStrings;
|
||||
|
||||
public:
|
||||
StreamOptimizationAttemptsOp(SpliceableJSONWriter& aWriter, UniqueJSONStrings& aUniqueStrings)
|
||||
: mWriter(aWriter),
|
||||
mUniqueStrings(aUniqueStrings)
|
||||
{ }
|
||||
|
||||
void operator()(JS::TrackedStrategy strategy, JS::TrackedOutcome outcome) override {
|
||||
enum Schema : uint32_t {
|
||||
STRATEGY = 0,
|
||||
OUTCOME = 1
|
||||
};
|
||||
|
||||
AutoArraySchemaWriter writer(mWriter, mUniqueStrings);
|
||||
writer.StringElement(STRATEGY, JS::TrackedStrategyString(strategy));
|
||||
writer.StringElement(OUTCOME, JS::TrackedOutcomeString(outcome));
|
||||
}
|
||||
};
|
||||
|
||||
class StreamJSFramesOp : public JS::ForEachProfiledFrameOp
|
||||
{
|
||||
void* mReturnAddress;
|
||||
UniqueStacks::Stack& mStack;
|
||||
unsigned mDepth;
|
||||
|
||||
public:
|
||||
StreamJSFramesOp(void* aReturnAddr, UniqueStacks::Stack& aStack)
|
||||
: mReturnAddress(aReturnAddr)
|
||||
, mStack(aStack)
|
||||
, mDepth(0)
|
||||
{ }
|
||||
|
||||
unsigned depth() const {
|
||||
MOZ_ASSERT(mDepth > 0);
|
||||
return mDepth;
|
||||
}
|
||||
|
||||
void operator()(const JS::ForEachProfiledFrameOp::FrameHandle& aFrameHandle) override {
|
||||
UniqueStacks::OnStackFrameKey frameKey(mReturnAddress, mDepth, aFrameHandle);
|
||||
mStack.AppendFrame(frameKey);
|
||||
mDepth++;
|
||||
}
|
||||
};
|
||||
#endif
|
||||
|
||||
uint32_t UniqueJSONStrings::GetOrAddIndex(const char* aStr)
|
||||
{
|
||||
uint32_t index;
|
||||
StringKey key(aStr);
|
||||
|
||||
auto it = mStringToIndexMap.find(key);
|
||||
|
||||
if (it != mStringToIndexMap.end()) {
|
||||
return it->second;
|
||||
}
|
||||
index = mStringToIndexMap.size();
|
||||
mStringToIndexMap[key] = index;
|
||||
mStringTableWriter.StringElement(aStr);
|
||||
return index;
|
||||
}
|
||||
|
||||
bool UniqueStacks::FrameKey::operator==(const FrameKey& aOther) const
|
||||
{
|
||||
return mLocation == aOther.mLocation &&
|
||||
mLine == aOther.mLine &&
|
||||
mCategory == aOther.mCategory &&
|
||||
mJITAddress == aOther.mJITAddress &&
|
||||
mJITDepth == aOther.mJITDepth;
|
||||
}
|
||||
|
||||
bool UniqueStacks::StackKey::operator==(const StackKey& aOther) const
|
||||
{
|
||||
MOZ_ASSERT_IF(mPrefix == aOther.mPrefix, mPrefixHash == aOther.mPrefixHash);
|
||||
return mPrefix == aOther.mPrefix && mFrame == aOther.mFrame;
|
||||
}
|
||||
|
||||
UniqueStacks::Stack::Stack(UniqueStacks& aUniqueStacks, const OnStackFrameKey& aRoot)
|
||||
: mUniqueStacks(aUniqueStacks)
|
||||
, mStack(aUniqueStacks.GetOrAddFrameIndex(aRoot))
|
||||
{
|
||||
}
|
||||
|
||||
void UniqueStacks::Stack::AppendFrame(const OnStackFrameKey& aFrame)
|
||||
{
|
||||
// Compute the prefix hash and index before mutating mStack.
|
||||
uint32_t prefixHash = mStack.Hash();
|
||||
uint32_t prefix = mUniqueStacks.GetOrAddStackIndex(mStack);
|
||||
mStack.UpdateHash(prefixHash, prefix, mUniqueStacks.GetOrAddFrameIndex(aFrame));
|
||||
}
|
||||
|
||||
uint32_t UniqueStacks::Stack::GetOrAddIndex() const
|
||||
{
|
||||
return mUniqueStacks.GetOrAddStackIndex(mStack);
|
||||
}
|
||||
|
||||
uint32_t UniqueStacks::FrameKey::Hash() const
|
||||
{
|
||||
uint32_t hash = 0;
|
||||
if (!mLocation.IsEmpty()) {
|
||||
#ifdef SPS_STANDALONE
|
||||
hash = mozilla::HashString(mLocation.c_str());
|
||||
#else
|
||||
hash = mozilla::HashString(mLocation.get());
|
||||
#endif
|
||||
}
|
||||
if (mLine.isSome()) {
|
||||
hash = mozilla::AddToHash(hash, *mLine);
|
||||
}
|
||||
if (mCategory.isSome()) {
|
||||
hash = mozilla::AddToHash(hash, *mCategory);
|
||||
}
|
||||
if (mJITAddress.isSome()) {
|
||||
hash = mozilla::AddToHash(hash, *mJITAddress);
|
||||
if (mJITDepth.isSome()) {
|
||||
hash = mozilla::AddToHash(hash, *mJITDepth);
|
||||
}
|
||||
}
|
||||
return hash;
|
||||
}
|
||||
|
||||
uint32_t UniqueStacks::StackKey::Hash() const
|
||||
{
|
||||
if (mPrefix.isNothing()) {
|
||||
return mozilla::HashGeneric(mFrame);
|
||||
}
|
||||
return mozilla::AddToHash(*mPrefixHash, mFrame);
|
||||
}
|
||||
|
||||
UniqueStacks::Stack UniqueStacks::BeginStack(const OnStackFrameKey& aRoot)
|
||||
{
|
||||
return Stack(*this, aRoot);
|
||||
}
|
||||
|
||||
UniqueStacks::UniqueStacks(JSContext* aContext)
|
||||
: mContext(aContext)
|
||||
, mFrameCount(0)
|
||||
{
|
||||
mFrameTableWriter.StartBareList();
|
||||
mStackTableWriter.StartBareList();
|
||||
}
|
||||
|
||||
#ifdef SPS_STANDALONE
|
||||
uint32_t UniqueStacks::GetOrAddStackIndex(const StackKey& aStack)
|
||||
{
|
||||
uint32_t index;
|
||||
auto it = mStackToIndexMap.find(aStack);
|
||||
|
||||
if (it != mStackToIndexMap.end()) {
|
||||
return it->second;
|
||||
}
|
||||
|
||||
index = mStackToIndexMap.size();
|
||||
mStackToIndexMap[aStack] = index;
|
||||
StreamStack(aStack);
|
||||
return index;
|
||||
}
|
||||
#else
|
||||
uint32_t UniqueStacks::GetOrAddStackIndex(const StackKey& aStack)
|
||||
{
|
||||
uint32_t index;
|
||||
if (mStackToIndexMap.Get(aStack, &index)) {
|
||||
MOZ_ASSERT(index < mStackToIndexMap.Count());
|
||||
return index;
|
||||
}
|
||||
|
||||
index = mStackToIndexMap.Count();
|
||||
mStackToIndexMap.Put(aStack, index);
|
||||
StreamStack(aStack);
|
||||
return index;
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef SPS_STANDALONE
|
||||
uint32_t UniqueStacks::GetOrAddFrameIndex(const OnStackFrameKey& aFrame)
|
||||
{
|
||||
uint32_t index;
|
||||
auto it = mFrameToIndexMap.find(aFrame);
|
||||
if (it != mFrameToIndexMap.end()) {
|
||||
MOZ_ASSERT(it->second < mFrameCount);
|
||||
return it->second;
|
||||
}
|
||||
|
||||
// A manual count is used instead of mFrameToIndexMap.Count() due to
|
||||
// forwarding of canonical JIT frames above.
|
||||
index = mFrameCount++;
|
||||
mFrameToIndexMap[aFrame] = index;
|
||||
StreamFrame(aFrame);
|
||||
return index;
|
||||
}
|
||||
#else
|
||||
uint32_t UniqueStacks::GetOrAddFrameIndex(const OnStackFrameKey& aFrame)
|
||||
{
|
||||
uint32_t index;
|
||||
if (mFrameToIndexMap.Get(aFrame, &index)) {
|
||||
MOZ_ASSERT(index < mFrameCount);
|
||||
return index;
|
||||
}
|
||||
|
||||
// If aFrame isn't canonical, forward it to the canonical frame's index.
|
||||
if (aFrame.mJITFrameHandle) {
|
||||
void* canonicalAddr = aFrame.mJITFrameHandle->canonicalAddress();
|
||||
if (canonicalAddr != *aFrame.mJITAddress) {
|
||||
OnStackFrameKey canonicalKey(canonicalAddr, *aFrame.mJITDepth, *aFrame.mJITFrameHandle);
|
||||
uint32_t canonicalIndex = GetOrAddFrameIndex(canonicalKey);
|
||||
mFrameToIndexMap.Put(aFrame, canonicalIndex);
|
||||
return canonicalIndex;
|
||||
}
|
||||
}
|
||||
|
||||
// A manual count is used instead of mFrameToIndexMap.Count() due to
|
||||
// forwarding of canonical JIT frames above.
|
||||
index = mFrameCount++;
|
||||
mFrameToIndexMap.Put(aFrame, index);
|
||||
StreamFrame(aFrame);
|
||||
return index;
|
||||
}
|
||||
#endif
|
||||
|
||||
uint32_t UniqueStacks::LookupJITFrameDepth(void* aAddr)
|
||||
{
|
||||
uint32_t depth;
|
||||
|
||||
auto it = mJITFrameDepthMap.find(aAddr);
|
||||
if (it != mJITFrameDepthMap.end()) {
|
||||
depth = it->second;
|
||||
MOZ_ASSERT(depth > 0);
|
||||
return depth;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
void UniqueStacks::AddJITFrameDepth(void* aAddr, unsigned depth)
|
||||
{
|
||||
mJITFrameDepthMap[aAddr] = depth;
|
||||
}
|
||||
|
||||
void UniqueStacks::SpliceFrameTableElements(SpliceableJSONWriter& aWriter)
|
||||
{
|
||||
mFrameTableWriter.EndBareList();
|
||||
aWriter.TakeAndSplice(mFrameTableWriter.WriteFunc());
|
||||
}
|
||||
|
||||
void UniqueStacks::SpliceStackTableElements(SpliceableJSONWriter& aWriter)
|
||||
{
|
||||
mStackTableWriter.EndBareList();
|
||||
aWriter.TakeAndSplice(mStackTableWriter.WriteFunc());
|
||||
}
|
||||
|
||||
void UniqueStacks::StreamStack(const StackKey& aStack)
|
||||
{
|
||||
enum Schema : uint32_t {
|
||||
PREFIX = 0,
|
||||
FRAME = 1
|
||||
};
|
||||
|
||||
AutoArraySchemaWriter writer(mStackTableWriter, mUniqueStrings);
|
||||
if (aStack.mPrefix.isSome()) {
|
||||
writer.IntElement(PREFIX, *aStack.mPrefix);
|
||||
}
|
||||
writer.IntElement(FRAME, aStack.mFrame);
|
||||
}
|
||||
|
||||
void UniqueStacks::StreamFrame(const OnStackFrameKey& aFrame)
|
||||
{
|
||||
enum Schema : uint32_t {
|
||||
LOCATION = 0,
|
||||
IMPLEMENTATION = 1,
|
||||
OPTIMIZATIONS = 2,
|
||||
LINE = 3,
|
||||
CATEGORY = 4
|
||||
};
|
||||
|
||||
AutoArraySchemaWriter writer(mFrameTableWriter, mUniqueStrings);
|
||||
|
||||
#ifndef SPS_STANDALONE
|
||||
if (!aFrame.mJITFrameHandle) {
|
||||
#else
|
||||
{
|
||||
#endif
|
||||
#ifdef SPS_STANDALONE
|
||||
writer.StringElement(LOCATION, aFrame.mLocation.c_str());
|
||||
#else
|
||||
writer.StringElement(LOCATION, aFrame.mLocation.get());
|
||||
#endif
|
||||
if (aFrame.mLine.isSome()) {
|
||||
writer.IntElement(LINE, *aFrame.mLine);
|
||||
}
|
||||
if (aFrame.mCategory.isSome()) {
|
||||
writer.IntElement(CATEGORY, *aFrame.mCategory);
|
||||
}
|
||||
}
|
||||
#ifndef SPS_STANDALONE
|
||||
else {
|
||||
const JS::ForEachProfiledFrameOp::FrameHandle& jitFrame = *aFrame.mJITFrameHandle;
|
||||
|
||||
writer.StringElement(LOCATION, jitFrame.label());
|
||||
|
||||
JS::ProfilingFrameIterator::FrameKind frameKind = jitFrame.frameKind();
|
||||
MOZ_ASSERT(frameKind == JS::ProfilingFrameIterator::Frame_Ion ||
|
||||
frameKind == JS::ProfilingFrameIterator::Frame_Baseline);
|
||||
writer.StringElement(IMPLEMENTATION,
|
||||
frameKind == JS::ProfilingFrameIterator::Frame_Ion
|
||||
? "ion"
|
||||
: "baseline");
|
||||
|
||||
if (jitFrame.hasTrackedOptimizations()) {
|
||||
writer.FillUpTo(OPTIMIZATIONS);
|
||||
mFrameTableWriter.StartObjectElement();
|
||||
{
|
||||
mFrameTableWriter.StartArrayProperty("types");
|
||||
{
|
||||
StreamOptimizationTypeInfoOp typeInfoOp(mFrameTableWriter, mUniqueStrings);
|
||||
jitFrame.forEachOptimizationTypeInfo(typeInfoOp);
|
||||
}
|
||||
mFrameTableWriter.EndArray();
|
||||
|
||||
JS::Rooted<JSScript*> script(mContext);
|
||||
jsbytecode* pc;
|
||||
mFrameTableWriter.StartObjectProperty("attempts");
|
||||
{
|
||||
{
|
||||
JSONSchemaWriter schema(mFrameTableWriter);
|
||||
schema.WriteField("strategy");
|
||||
schema.WriteField("outcome");
|
||||
}
|
||||
|
||||
mFrameTableWriter.StartArrayProperty("data");
|
||||
{
|
||||
StreamOptimizationAttemptsOp attemptOp(mFrameTableWriter, mUniqueStrings);
|
||||
jitFrame.forEachOptimizationAttempt(attemptOp, script.address(), &pc);
|
||||
}
|
||||
mFrameTableWriter.EndArray();
|
||||
}
|
||||
mFrameTableWriter.EndObject();
|
||||
|
||||
if (JSAtom* name = js::GetPropertyNameFromPC(script, pc)) {
|
||||
char buf[512];
|
||||
JS_PutEscapedFlatString(buf, mozilla::ArrayLength(buf), js::AtomToFlatString(name), 0);
|
||||
mUniqueStrings.WriteProperty(mFrameTableWriter, "propertyName", buf);
|
||||
}
|
||||
|
||||
unsigned line, column;
|
||||
line = JS_PCToLineNumber(script, pc, &column);
|
||||
mFrameTableWriter.IntProperty("line", line);
|
||||
mFrameTableWriter.IntProperty("column", column);
|
||||
}
|
||||
mFrameTableWriter.EndObject();
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
struct ProfileSample
|
||||
{
|
||||
uint32_t mStack;
|
||||
Maybe<double> mTime;
|
||||
Maybe<double> mResponsiveness;
|
||||
Maybe<double> mRSS;
|
||||
Maybe<double> mUSS;
|
||||
Maybe<int> mFrameNumber;
|
||||
Maybe<double> mPower;
|
||||
};
|
||||
|
||||
static void WriteSample(SpliceableJSONWriter& aWriter, ProfileSample& aSample)
|
||||
{
|
||||
enum Schema : uint32_t {
|
||||
STACK = 0,
|
||||
TIME = 1,
|
||||
RESPONSIVENESS = 2,
|
||||
RSS = 3,
|
||||
USS = 4,
|
||||
FRAME_NUMBER = 5,
|
||||
POWER = 6
|
||||
};
|
||||
|
||||
AutoArraySchemaWriter writer(aWriter);
|
||||
|
||||
writer.IntElement(STACK, aSample.mStack);
|
||||
|
||||
if (aSample.mTime.isSome()) {
|
||||
writer.DoubleElement(TIME, *aSample.mTime);
|
||||
}
|
||||
|
||||
if (aSample.mResponsiveness.isSome()) {
|
||||
writer.DoubleElement(RESPONSIVENESS, *aSample.mResponsiveness);
|
||||
}
|
||||
|
||||
if (aSample.mRSS.isSome()) {
|
||||
writer.DoubleElement(RSS, *aSample.mRSS);
|
||||
}
|
||||
|
||||
if (aSample.mUSS.isSome()) {
|
||||
writer.DoubleElement(USS, *aSample.mUSS);
|
||||
}
|
||||
|
||||
if (aSample.mFrameNumber.isSome()) {
|
||||
writer.IntElement(FRAME_NUMBER, *aSample.mFrameNumber);
|
||||
}
|
||||
|
||||
if (aSample.mPower.isSome()) {
|
||||
writer.DoubleElement(POWER, *aSample.mPower);
|
||||
}
|
||||
}
|
||||
|
||||
void ProfileBuffer::StreamSamplesToJSON(SpliceableJSONWriter& aWriter, int aThreadId,
|
||||
double aSinceTime, JSContext* aContext,
|
||||
UniqueStacks& aUniqueStacks)
|
||||
{
|
||||
Maybe<ProfileSample> sample;
|
||||
int readPos = mReadPos;
|
||||
int currentThreadID = -1;
|
||||
Maybe<double> currentTime;
|
||||
UniquePtr<char[]> tagBuff = MakeUnique<char[]>(DYNAMIC_MAX_STRING);
|
||||
|
||||
while (readPos != mWritePos) {
|
||||
ProfileEntry entry = mEntries[readPos];
|
||||
if (entry.mTagName == 'T') {
|
||||
currentThreadID = entry.mTagInt;
|
||||
currentTime.reset();
|
||||
int readAheadPos = (readPos + 1) % mEntrySize;
|
||||
if (readAheadPos != mWritePos) {
|
||||
ProfileEntry readAheadEntry = mEntries[readAheadPos];
|
||||
if (readAheadEntry.mTagName == 't') {
|
||||
currentTime = Some(readAheadEntry.mTagDouble);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (currentThreadID == aThreadId && (currentTime.isNothing() || *currentTime >= aSinceTime)) {
|
||||
switch (entry.mTagName) {
|
||||
case 'r':
|
||||
if (sample.isSome()) {
|
||||
sample->mResponsiveness = Some(entry.mTagDouble);
|
||||
}
|
||||
break;
|
||||
case 'p':
|
||||
if (sample.isSome()) {
|
||||
sample->mPower = Some(entry.mTagDouble);
|
||||
}
|
||||
break;
|
||||
case 'R':
|
||||
if (sample.isSome()) {
|
||||
sample->mRSS = Some(entry.mTagDouble);
|
||||
}
|
||||
break;
|
||||
case 'U':
|
||||
if (sample.isSome()) {
|
||||
sample->mUSS = Some(entry.mTagDouble);
|
||||
}
|
||||
break;
|
||||
case 'f':
|
||||
if (sample.isSome()) {
|
||||
sample->mFrameNumber = Some(entry.mTagInt);
|
||||
}
|
||||
break;
|
||||
case 's':
|
||||
{
|
||||
// end the previous sample if there was one
|
||||
if (sample.isSome()) {
|
||||
WriteSample(aWriter, *sample);
|
||||
sample.reset();
|
||||
}
|
||||
// begin the next sample
|
||||
sample.emplace();
|
||||
sample->mTime = currentTime;
|
||||
|
||||
// Seek forward through the entire sample, looking for frames
|
||||
// this is an easier approach to reason about than adding more
|
||||
// control variables and cases to the loop that goes through the buffer once
|
||||
|
||||
UniqueStacks::Stack stack =
|
||||
aUniqueStacks.BeginStack(UniqueStacks::OnStackFrameKey("(root)"));
|
||||
|
||||
int framePos = (readPos + 1) % mEntrySize;
|
||||
ProfileEntry frame = mEntries[framePos];
|
||||
while (framePos != mWritePos && frame.mTagName != 's' && frame.mTagName != 'T') {
|
||||
int incBy = 1;
|
||||
frame = mEntries[framePos];
|
||||
|
||||
// Read ahead to the next tag, if it's a 'd' tag process it now
|
||||
const char* tagStringData = frame.mTagData;
|
||||
int readAheadPos = (framePos + 1) % mEntrySize;
|
||||
// Make sure the string is always null terminated if it fills up
|
||||
// DYNAMIC_MAX_STRING-2
|
||||
tagBuff[DYNAMIC_MAX_STRING-1] = '\0';
|
||||
|
||||
if (readAheadPos != mWritePos && mEntries[readAheadPos].mTagName == 'd') {
|
||||
tagStringData = processDynamicTag(framePos, &incBy, tagBuff.get());
|
||||
}
|
||||
|
||||
// Write one frame. It can have either
|
||||
// 1. only location - 'l' containing a memory address
|
||||
// 2. location and line number - 'c' followed by 'd's,
|
||||
// an optional 'n' and an optional 'y'
|
||||
// 3. a JIT return address - 'j' containing native code address
|
||||
if (frame.mTagName == 'l') {
|
||||
// Bug 753041
|
||||
// We need a double cast here to tell GCC that we don't want to sign
|
||||
// extend 32-bit addresses starting with 0xFXXXXXX.
|
||||
unsigned long long pc = (unsigned long long)(uintptr_t)frame.mTagPtr;
|
||||
snprintf(tagBuff.get(), DYNAMIC_MAX_STRING, "%#llx", pc);
|
||||
stack.AppendFrame(UniqueStacks::OnStackFrameKey(tagBuff.get()));
|
||||
} else if (frame.mTagName == 'c') {
|
||||
UniqueStacks::OnStackFrameKey frameKey(tagStringData);
|
||||
readAheadPos = (framePos + incBy) % mEntrySize;
|
||||
if (readAheadPos != mWritePos &&
|
||||
mEntries[readAheadPos].mTagName == 'n') {
|
||||
frameKey.mLine = Some((unsigned) mEntries[readAheadPos].mTagInt);
|
||||
incBy++;
|
||||
}
|
||||
readAheadPos = (framePos + incBy) % mEntrySize;
|
||||
if (readAheadPos != mWritePos &&
|
||||
mEntries[readAheadPos].mTagName == 'y') {
|
||||
frameKey.mCategory = Some((unsigned) mEntries[readAheadPos].mTagInt);
|
||||
incBy++;
|
||||
}
|
||||
stack.AppendFrame(frameKey);
|
||||
#ifndef SPS_STANDALONE
|
||||
} else if (frame.mTagName == 'J') {
|
||||
// A JIT frame may expand to multiple frames due to inlining.
|
||||
void* pc = frame.mTagPtr;
|
||||
unsigned depth = aUniqueStacks.LookupJITFrameDepth(pc);
|
||||
if (depth == 0) {
|
||||
StreamJSFramesOp framesOp(pc, stack);
|
||||
JS::ForEachProfiledFrame(aContext, pc, framesOp);
|
||||
aUniqueStacks.AddJITFrameDepth(pc, framesOp.depth());
|
||||
} else {
|
||||
for (unsigned i = 0; i < depth; i++) {
|
||||
UniqueStacks::OnStackFrameKey inlineFrameKey(pc, i);
|
||||
stack.AppendFrame(inlineFrameKey);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
framePos = (framePos + incBy) % mEntrySize;
|
||||
}
|
||||
|
||||
sample->mStack = stack.GetOrAddIndex();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
readPos = (readPos + 1) % mEntrySize;
|
||||
}
|
||||
if (sample.isSome()) {
|
||||
WriteSample(aWriter, *sample);
|
||||
}
|
||||
}
|
||||
|
||||
void ProfileBuffer::StreamMarkersToJSON(SpliceableJSONWriter& aWriter, int aThreadId,
|
||||
double aSinceTime, UniqueStacks& aUniqueStacks)
|
||||
{
|
||||
int readPos = mReadPos;
|
||||
int currentThreadID = -1;
|
||||
while (readPos != mWritePos) {
|
||||
ProfileEntry entry = mEntries[readPos];
|
||||
if (entry.mTagName == 'T') {
|
||||
currentThreadID = entry.mTagInt;
|
||||
} else if (currentThreadID == aThreadId && entry.mTagName == 'm') {
|
||||
const ProfilerMarker* marker = entry.getMarker();
|
||||
if (marker->GetTime() >= aSinceTime) {
|
||||
entry.getMarker()->StreamJSON(aWriter, aUniqueStacks);
|
||||
}
|
||||
}
|
||||
readPos = (readPos + 1) % mEntrySize;
|
||||
}
|
||||
}
|
||||
|
||||
int ProfileBuffer::FindLastSampleOfThread(int aThreadId)
|
||||
{
|
||||
// We search backwards from mWritePos-1 to mReadPos.
|
||||
// Adding mEntrySize makes the result of the modulus positive.
|
||||
for (int readPos = (mWritePos + mEntrySize - 1) % mEntrySize;
|
||||
readPos != (mReadPos + mEntrySize - 1) % mEntrySize;
|
||||
readPos = (readPos + mEntrySize - 1) % mEntrySize) {
|
||||
ProfileEntry entry = mEntries[readPos];
|
||||
if (entry.mTagName == 'T' && entry.mTagInt == aThreadId) {
|
||||
return readPos;
|
||||
}
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
void ProfileBuffer::DuplicateLastSample(int aThreadId)
|
||||
{
|
||||
int lastSampleStartPos = FindLastSampleOfThread(aThreadId);
|
||||
if (lastSampleStartPos == -1) {
|
||||
return;
|
||||
}
|
||||
|
||||
MOZ_ASSERT(mEntries[lastSampleStartPos].mTagName == 'T');
|
||||
|
||||
addTag(mEntries[lastSampleStartPos]);
|
||||
|
||||
// Go through the whole entry and duplicate it, until we find the next one.
|
||||
for (int readPos = (lastSampleStartPos + 1) % mEntrySize;
|
||||
readPos != mWritePos;
|
||||
readPos = (readPos + 1) % mEntrySize) {
|
||||
switch (mEntries[readPos].mTagName) {
|
||||
case 'T':
|
||||
// We're done.
|
||||
return;
|
||||
case 't':
|
||||
// Copy with new time
|
||||
addTag(ProfileEntry('t', (mozilla::TimeStamp::Now() - sStartTime).ToMilliseconds()));
|
||||
break;
|
||||
case 'm':
|
||||
// Don't copy markers
|
||||
break;
|
||||
// Copy anything else we don't know about
|
||||
// L, B, S, c, s, d, l, f, h, r, t, p
|
||||
default:
|
||||
addTag(mEntries[readPos]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// END ProfileBuffer
|
||||
////////////////////////////////////////////////////////////////////////
|
||||
|
||||
|
||||
////////////////////////////////////////////////////////////////////////
|
||||
// BEGIN ThreadProfile
|
||||
|
||||
// END ThreadProfile
|
||||
////////////////////////////////////////////////////////////////////////
|
||||
407
tools/profiler/core/ProfileEntry.h
Normal file
407
tools/profiler/core/ProfileEntry.h
Normal file
|
|
@ -0,0 +1,407 @@
|
|||
/* -*- Mode: C++; tab-width: 2; 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 MOZ_PROFILE_ENTRY_H
|
||||
#define MOZ_PROFILE_ENTRY_H
|
||||
|
||||
#include <ostream>
|
||||
#include "GeckoProfiler.h"
|
||||
#include "platform.h"
|
||||
#include "ProfileJSONWriter.h"
|
||||
#include "ProfilerBacktrace.h"
|
||||
#include "mozilla/RefPtr.h"
|
||||
#include <string>
|
||||
#include <map>
|
||||
#ifndef SPS_STANDALONE
|
||||
#include "js/ProfilingFrameIterator.h"
|
||||
#include "js/TrackedOptimizationInfo.h"
|
||||
#include "nsHashKeys.h"
|
||||
#include "nsDataHashtable.h"
|
||||
#endif
|
||||
#include "mozilla/Maybe.h"
|
||||
#include "mozilla/Vector.h"
|
||||
#ifndef SPS_STANDALONE
|
||||
#include "gtest/MozGtestFriend.h"
|
||||
#else
|
||||
#define FRIEND_TEST(a, b) // TODO Support standalone gtest
|
||||
#endif
|
||||
#include "mozilla/HashFunctions.h"
|
||||
#include "mozilla/UniquePtr.h"
|
||||
|
||||
class ThreadProfile;
|
||||
|
||||
// NB: Packing this structure has been shown to cause SIGBUS issues on ARM.
|
||||
#ifndef __arm__
|
||||
#pragma pack(push, 1)
|
||||
#endif
|
||||
|
||||
class ProfileEntry
|
||||
{
|
||||
public:
|
||||
ProfileEntry();
|
||||
|
||||
// aTagData must not need release (i.e. be a string from the text segment)
|
||||
ProfileEntry(char aTagName, const char *aTagData);
|
||||
ProfileEntry(char aTagName, void *aTagPtr);
|
||||
ProfileEntry(char aTagName, ProfilerMarker *aTagMarker);
|
||||
ProfileEntry(char aTagName, double aTagDouble);
|
||||
ProfileEntry(char aTagName, uintptr_t aTagOffset);
|
||||
ProfileEntry(char aTagName, Address aTagAddress);
|
||||
ProfileEntry(char aTagName, int aTagLine);
|
||||
ProfileEntry(char aTagName, char aTagChar);
|
||||
bool is_ent_hint(char hintChar);
|
||||
bool is_ent_hint();
|
||||
bool is_ent(char tagName);
|
||||
void* get_tagPtr();
|
||||
const ProfilerMarker* getMarker() {
|
||||
MOZ_ASSERT(mTagName == 'm');
|
||||
return mTagMarker;
|
||||
}
|
||||
|
||||
char getTagName() const { return mTagName; }
|
||||
|
||||
private:
|
||||
FRIEND_TEST(ThreadProfile, InsertOneTag);
|
||||
FRIEND_TEST(ThreadProfile, InsertOneTagWithTinyBuffer);
|
||||
FRIEND_TEST(ThreadProfile, InsertTagsNoWrap);
|
||||
FRIEND_TEST(ThreadProfile, InsertTagsWrap);
|
||||
FRIEND_TEST(ThreadProfile, MemoryMeasure);
|
||||
friend class ProfileBuffer;
|
||||
union {
|
||||
const char* mTagData;
|
||||
char mTagChars[sizeof(void*)];
|
||||
void* mTagPtr;
|
||||
ProfilerMarker* mTagMarker;
|
||||
double mTagDouble;
|
||||
Address mTagAddress;
|
||||
uintptr_t mTagOffset;
|
||||
int mTagInt;
|
||||
char mTagChar;
|
||||
};
|
||||
char mTagName;
|
||||
};
|
||||
|
||||
#ifndef __arm__
|
||||
#pragma pack(pop)
|
||||
#endif
|
||||
|
||||
class UniqueJSONStrings
|
||||
{
|
||||
public:
|
||||
UniqueJSONStrings() {
|
||||
mStringTableWriter.StartBareList();
|
||||
}
|
||||
|
||||
void SpliceStringTableElements(SpliceableJSONWriter& aWriter) {
|
||||
aWriter.TakeAndSplice(mStringTableWriter.WriteFunc());
|
||||
}
|
||||
|
||||
void WriteProperty(mozilla::JSONWriter& aWriter, const char* aName, const char* aStr) {
|
||||
aWriter.IntProperty(aName, GetOrAddIndex(aStr));
|
||||
}
|
||||
|
||||
void WriteElement(mozilla::JSONWriter& aWriter, const char* aStr) {
|
||||
aWriter.IntElement(GetOrAddIndex(aStr));
|
||||
}
|
||||
|
||||
uint32_t GetOrAddIndex(const char* aStr);
|
||||
|
||||
struct StringKey {
|
||||
|
||||
explicit StringKey(const char* aStr)
|
||||
: mStr(strdup(aStr))
|
||||
{
|
||||
mHash = mozilla::HashString(mStr);
|
||||
}
|
||||
|
||||
StringKey(const StringKey& aOther)
|
||||
: mStr(strdup(aOther.mStr))
|
||||
{
|
||||
mHash = aOther.mHash;
|
||||
}
|
||||
|
||||
~StringKey() {
|
||||
free(mStr);
|
||||
}
|
||||
|
||||
uint32_t Hash() const;
|
||||
bool operator==(const StringKey& aOther) const {
|
||||
return strcmp(mStr, aOther.mStr) == 0;
|
||||
}
|
||||
bool operator<(const StringKey& aOther) const {
|
||||
return mHash < aOther.mHash;
|
||||
}
|
||||
|
||||
private:
|
||||
uint32_t mHash;
|
||||
char* mStr;
|
||||
};
|
||||
private:
|
||||
SpliceableChunkedJSONWriter mStringTableWriter;
|
||||
std::map<StringKey, uint32_t> mStringToIndexMap;
|
||||
};
|
||||
|
||||
class UniqueStacks
|
||||
{
|
||||
public:
|
||||
struct FrameKey {
|
||||
#ifdef SPS_STANDALONE
|
||||
std::string mLocation;
|
||||
#else
|
||||
// This cannot be a std::string, as it is not memmove compatible, which
|
||||
// is used by nsHashTable
|
||||
nsCString mLocation;
|
||||
#endif
|
||||
mozilla::Maybe<unsigned> mLine;
|
||||
mozilla::Maybe<unsigned> mCategory;
|
||||
mozilla::Maybe<void*> mJITAddress;
|
||||
mozilla::Maybe<uint32_t> mJITDepth;
|
||||
|
||||
explicit FrameKey(const char* aLocation)
|
||||
: mLocation(aLocation)
|
||||
{
|
||||
mHash = Hash();
|
||||
}
|
||||
|
||||
FrameKey(const FrameKey& aToCopy)
|
||||
: mLocation(aToCopy.mLocation)
|
||||
, mLine(aToCopy.mLine)
|
||||
, mCategory(aToCopy.mCategory)
|
||||
, mJITAddress(aToCopy.mJITAddress)
|
||||
, mJITDepth(aToCopy.mJITDepth)
|
||||
{
|
||||
mHash = Hash();
|
||||
}
|
||||
|
||||
FrameKey(void* aJITAddress, uint32_t aJITDepth)
|
||||
: mJITAddress(mozilla::Some(aJITAddress))
|
||||
, mJITDepth(mozilla::Some(aJITDepth))
|
||||
{
|
||||
mHash = Hash();
|
||||
}
|
||||
|
||||
uint32_t Hash() const;
|
||||
bool operator==(const FrameKey& aOther) const;
|
||||
bool operator<(const FrameKey& aOther) const {
|
||||
return mHash < aOther.mHash;
|
||||
}
|
||||
|
||||
private:
|
||||
uint32_t mHash;
|
||||
};
|
||||
|
||||
// A FrameKey that holds a scoped reference to a JIT FrameHandle.
|
||||
struct MOZ_STACK_CLASS OnStackFrameKey : public FrameKey {
|
||||
explicit OnStackFrameKey(const char* aLocation)
|
||||
: FrameKey(aLocation)
|
||||
#ifndef SPS_STANDALONE
|
||||
, mJITFrameHandle(nullptr)
|
||||
#endif
|
||||
{ }
|
||||
|
||||
OnStackFrameKey(const OnStackFrameKey& aToCopy)
|
||||
: FrameKey(aToCopy)
|
||||
#ifndef SPS_STANDALONE
|
||||
, mJITFrameHandle(aToCopy.mJITFrameHandle)
|
||||
#endif
|
||||
{ }
|
||||
|
||||
#ifndef SPS_STANDALONE
|
||||
const JS::ForEachProfiledFrameOp::FrameHandle* mJITFrameHandle;
|
||||
|
||||
OnStackFrameKey(void* aJITAddress, unsigned aJITDepth)
|
||||
: FrameKey(aJITAddress, aJITDepth)
|
||||
, mJITFrameHandle(nullptr)
|
||||
{ }
|
||||
|
||||
OnStackFrameKey(void* aJITAddress, unsigned aJITDepth,
|
||||
const JS::ForEachProfiledFrameOp::FrameHandle& aJITFrameHandle)
|
||||
: FrameKey(aJITAddress, aJITDepth)
|
||||
, mJITFrameHandle(&aJITFrameHandle)
|
||||
{ }
|
||||
#endif
|
||||
};
|
||||
|
||||
struct StackKey {
|
||||
mozilla::Maybe<uint32_t> mPrefixHash;
|
||||
mozilla::Maybe<uint32_t> mPrefix;
|
||||
uint32_t mFrame;
|
||||
|
||||
explicit StackKey(uint32_t aFrame)
|
||||
: mFrame(aFrame)
|
||||
{
|
||||
mHash = Hash();
|
||||
}
|
||||
|
||||
uint32_t Hash() const;
|
||||
bool operator==(const StackKey& aOther) const;
|
||||
bool operator<(const StackKey& aOther) const {
|
||||
return mHash < aOther.mHash;
|
||||
}
|
||||
|
||||
void UpdateHash(uint32_t aPrefixHash, uint32_t aPrefix, uint32_t aFrame) {
|
||||
mPrefixHash = mozilla::Some(aPrefixHash);
|
||||
mPrefix = mozilla::Some(aPrefix);
|
||||
mFrame = aFrame;
|
||||
mHash = Hash();
|
||||
}
|
||||
|
||||
private:
|
||||
uint32_t mHash;
|
||||
};
|
||||
|
||||
class Stack {
|
||||
public:
|
||||
Stack(UniqueStacks& aUniqueStacks, const OnStackFrameKey& aRoot);
|
||||
|
||||
void AppendFrame(const OnStackFrameKey& aFrame);
|
||||
uint32_t GetOrAddIndex() const;
|
||||
|
||||
private:
|
||||
UniqueStacks& mUniqueStacks;
|
||||
StackKey mStack;
|
||||
};
|
||||
|
||||
explicit UniqueStacks(JSContext* aContext);
|
||||
|
||||
Stack BeginStack(const OnStackFrameKey& aRoot);
|
||||
uint32_t LookupJITFrameDepth(void* aAddr);
|
||||
void AddJITFrameDepth(void* aAddr, unsigned depth);
|
||||
void SpliceFrameTableElements(SpliceableJSONWriter& aWriter);
|
||||
void SpliceStackTableElements(SpliceableJSONWriter& aWriter);
|
||||
|
||||
private:
|
||||
uint32_t GetOrAddFrameIndex(const OnStackFrameKey& aFrame);
|
||||
uint32_t GetOrAddStackIndex(const StackKey& aStack);
|
||||
void StreamFrame(const OnStackFrameKey& aFrame);
|
||||
void StreamStack(const StackKey& aStack);
|
||||
|
||||
public:
|
||||
UniqueJSONStrings mUniqueStrings;
|
||||
|
||||
private:
|
||||
JSContext* mContext;
|
||||
|
||||
// To avoid incurring JitcodeGlobalTable lookup costs for every JIT frame,
|
||||
// we cache the depth of frames keyed by JIT code address. If an address a
|
||||
// maps to a depth d, then frames keyed by a for depths 0 to d are
|
||||
// guaranteed to be in mFrameToIndexMap.
|
||||
std::map<void*, uint32_t> mJITFrameDepthMap;
|
||||
|
||||
uint32_t mFrameCount;
|
||||
SpliceableChunkedJSONWriter mFrameTableWriter;
|
||||
#ifdef SPS_STANDALNOE
|
||||
std::map<FrameKey, uint32_t> mFrameToIndexMap;
|
||||
#else
|
||||
nsDataHashtable<nsGenericHashKey<FrameKey>, uint32_t> mFrameToIndexMap;
|
||||
#endif
|
||||
|
||||
SpliceableChunkedJSONWriter mStackTableWriter;
|
||||
|
||||
// This sucks but this is really performance critical, nsDataHashtable is way faster
|
||||
// than map/unordered_map but nsDataHashtable is tied to xpcom so we ifdef
|
||||
// until we can find a better solution.
|
||||
#ifdef SPS_STANDALONE
|
||||
std::map<StackKey, uint32_t> mStackToIndexMap;
|
||||
#else
|
||||
nsDataHashtable<nsGenericHashKey<StackKey>, uint32_t> mStackToIndexMap;
|
||||
#endif
|
||||
};
|
||||
|
||||
//
|
||||
// ThreadProfile JSON Format
|
||||
// -------------------------
|
||||
//
|
||||
// The profile contains much duplicate information. The output JSON of the
|
||||
// profile attempts to deduplicate strings, frames, and stack prefixes, to cut
|
||||
// down on size and to increase JSON streaming speed. Deduplicated values are
|
||||
// streamed as indices into their respective tables.
|
||||
//
|
||||
// Further, arrays of objects with the same set of properties (e.g., samples,
|
||||
// frames) are output as arrays according to a schema instead of an object
|
||||
// with property names. A property that is not present is represented in the
|
||||
// array as null or undefined.
|
||||
//
|
||||
// The format of the thread profile JSON is shown by the following example
|
||||
// with 1 sample and 1 marker:
|
||||
//
|
||||
// {
|
||||
// "name": "Foo",
|
||||
// "tid": 42,
|
||||
// "samples":
|
||||
// {
|
||||
// "schema":
|
||||
// {
|
||||
// "stack": 0, /* index into stackTable */
|
||||
// "time": 1, /* number */
|
||||
// "responsiveness": 2, /* number */
|
||||
// "rss": 3, /* number */
|
||||
// "uss": 4, /* number */
|
||||
// "frameNumber": 5, /* number */
|
||||
// "power": 6 /* number */
|
||||
// },
|
||||
// "data":
|
||||
// [
|
||||
// [ 1, 0.0, 0.0 ] /* { stack: 1, time: 0.0, responsiveness: 0.0 } */
|
||||
// ]
|
||||
// },
|
||||
//
|
||||
// "markers":
|
||||
// {
|
||||
// "schema":
|
||||
// {
|
||||
// "name": 0, /* index into stringTable */
|
||||
// "time": 1, /* number */
|
||||
// "data": 2 /* arbitrary JSON */
|
||||
// },
|
||||
// "data":
|
||||
// [
|
||||
// [ 3, 0.1 ] /* { name: 'example marker', time: 0.1 } */
|
||||
// ]
|
||||
// },
|
||||
//
|
||||
// "stackTable":
|
||||
// {
|
||||
// "schema":
|
||||
// {
|
||||
// "prefix": 0, /* index into stackTable */
|
||||
// "frame": 1 /* index into frameTable */
|
||||
// },
|
||||
// "data":
|
||||
// [
|
||||
// [ null, 0 ], /* (root) */
|
||||
// [ 0, 1 ] /* (root) > foo.js */
|
||||
// ]
|
||||
// },
|
||||
//
|
||||
// "frameTable":
|
||||
// {
|
||||
// "schema":
|
||||
// {
|
||||
// "location": 0, /* index into stringTable */
|
||||
// "implementation": 1, /* index into stringTable */
|
||||
// "optimizations": 2, /* arbitrary JSON */
|
||||
// "line": 3, /* number */
|
||||
// "category": 4 /* number */
|
||||
// },
|
||||
// "data":
|
||||
// [
|
||||
// [ 0 ], /* { location: '(root)' } */
|
||||
// [ 1, 2 ] /* { location: 'foo.js', implementation: 'baseline' } */
|
||||
// ]
|
||||
// },
|
||||
//
|
||||
// "stringTable":
|
||||
// [
|
||||
// "(root)",
|
||||
// "foo.js",
|
||||
// "baseline",
|
||||
// "example marker"
|
||||
// ]
|
||||
// }
|
||||
//
|
||||
|
||||
#endif /* ndef MOZ_PROFILE_ENTRY_H */
|
||||
115
tools/profiler/core/ProfileJSONWriter.cpp
Normal file
115
tools/profiler/core/ProfileJSONWriter.cpp
Normal file
|
|
@ -0,0 +1,115 @@
|
|||
/* -*- 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 "mozilla/HashFunctions.h"
|
||||
|
||||
#include "ProfileJSONWriter.h"
|
||||
|
||||
void
|
||||
ChunkedJSONWriteFunc::Write(const char* aStr)
|
||||
{
|
||||
MOZ_ASSERT(mChunkPtr >= mChunkList.back().get() && mChunkPtr <= mChunkEnd);
|
||||
MOZ_ASSERT(mChunkEnd >= mChunkList.back().get() + mChunkLengths.back());
|
||||
MOZ_ASSERT(*mChunkPtr == '\0');
|
||||
|
||||
size_t len = strlen(aStr);
|
||||
|
||||
// Most strings to be written are small, but subprocess profiles (e.g.,
|
||||
// from the content process in e10s) may be huge. If the string is larger
|
||||
// than a chunk, allocate its own chunk.
|
||||
char* newPtr;
|
||||
if (len >= kChunkSize) {
|
||||
AllocChunk(len + 1);
|
||||
newPtr = mChunkPtr + len;
|
||||
} else {
|
||||
newPtr = mChunkPtr + len;
|
||||
if (newPtr >= mChunkEnd) {
|
||||
AllocChunk(kChunkSize);
|
||||
newPtr = mChunkPtr + len;
|
||||
}
|
||||
}
|
||||
|
||||
memcpy(mChunkPtr, aStr, len);
|
||||
*newPtr = '\0';
|
||||
mChunkPtr = newPtr;
|
||||
mChunkLengths.back() += len;
|
||||
}
|
||||
|
||||
mozilla::UniquePtr<char[]>
|
||||
ChunkedJSONWriteFunc::CopyData() const
|
||||
{
|
||||
MOZ_ASSERT(mChunkLengths.length() == mChunkList.length());
|
||||
size_t totalLen = 1;
|
||||
for (size_t i = 0; i < mChunkLengths.length(); i++) {
|
||||
MOZ_ASSERT(strlen(mChunkList[i].get()) == mChunkLengths[i]);
|
||||
totalLen += mChunkLengths[i];
|
||||
}
|
||||
mozilla::UniquePtr<char[]> c = mozilla::MakeUnique<char[]>(totalLen);
|
||||
char* ptr = c.get();
|
||||
for (size_t i = 0; i < mChunkList.length(); i++) {
|
||||
size_t len = mChunkLengths[i];
|
||||
memcpy(ptr, mChunkList[i].get(), len);
|
||||
ptr += len;
|
||||
}
|
||||
*ptr = '\0';
|
||||
return c;
|
||||
}
|
||||
|
||||
void
|
||||
ChunkedJSONWriteFunc::Take(ChunkedJSONWriteFunc&& aOther)
|
||||
{
|
||||
for (size_t i = 0; i < aOther.mChunkList.length(); i++) {
|
||||
MOZ_ALWAYS_TRUE(mChunkLengths.append(aOther.mChunkLengths[i]));
|
||||
MOZ_ALWAYS_TRUE(mChunkList.append(mozilla::Move(aOther.mChunkList[i])));
|
||||
}
|
||||
mChunkPtr = mChunkList.back().get() + mChunkLengths.back();
|
||||
mChunkEnd = mChunkPtr;
|
||||
aOther.mChunkPtr = nullptr;
|
||||
aOther.mChunkEnd = nullptr;
|
||||
aOther.mChunkList.clear();
|
||||
aOther.mChunkLengths.clear();
|
||||
}
|
||||
|
||||
void
|
||||
ChunkedJSONWriteFunc::AllocChunk(size_t aChunkSize)
|
||||
{
|
||||
MOZ_ASSERT(mChunkLengths.length() == mChunkList.length());
|
||||
mozilla::UniquePtr<char[]> newChunk = mozilla::MakeUnique<char[]>(aChunkSize);
|
||||
mChunkPtr = newChunk.get();
|
||||
mChunkEnd = mChunkPtr + aChunkSize;
|
||||
*mChunkPtr = '\0';
|
||||
MOZ_ALWAYS_TRUE(mChunkLengths.append(0));
|
||||
MOZ_ALWAYS_TRUE(mChunkList.append(mozilla::Move(newChunk)));
|
||||
}
|
||||
|
||||
void
|
||||
SpliceableJSONWriter::TakeAndSplice(ChunkedJSONWriteFunc* aFunc)
|
||||
{
|
||||
Separator();
|
||||
for (size_t i = 0; i < aFunc->mChunkList.length(); i++) {
|
||||
WriteFunc()->Write(aFunc->mChunkList[i].get());
|
||||
}
|
||||
aFunc->mChunkPtr = nullptr;
|
||||
aFunc->mChunkEnd = nullptr;
|
||||
aFunc->mChunkList.clear();
|
||||
aFunc->mChunkLengths.clear();
|
||||
mNeedComma[mDepth] = true;
|
||||
}
|
||||
|
||||
void
|
||||
SpliceableJSONWriter::Splice(const char* aStr)
|
||||
{
|
||||
Separator();
|
||||
WriteFunc()->Write(aStr);
|
||||
mNeedComma[mDepth] = true;
|
||||
}
|
||||
|
||||
void
|
||||
SpliceableChunkedJSONWriter::TakeAndSplice(ChunkedJSONWriteFunc* aFunc)
|
||||
{
|
||||
Separator();
|
||||
WriteFunc()->Take(mozilla::Move(*aFunc));
|
||||
mNeedComma[mDepth] = true;
|
||||
}
|
||||
126
tools/profiler/core/ProfileJSONWriter.h
Normal file
126
tools/profiler/core/ProfileJSONWriter.h
Normal file
|
|
@ -0,0 +1,126 @@
|
|||
/* -*- 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 PROFILEJSONWRITER_H
|
||||
#define PROFILEJSONWRITER_H
|
||||
|
||||
#include <ostream>
|
||||
#include <string>
|
||||
#include <string.h>
|
||||
|
||||
#include "mozilla/JSONWriter.h"
|
||||
#include "mozilla/UniquePtr.h"
|
||||
|
||||
class SpliceableChunkedJSONWriter;
|
||||
|
||||
// On average, profile JSONs are large enough such that we want to avoid
|
||||
// reallocating its buffer when expanding. Additionally, the contents of the
|
||||
// profile are not accessed until the profile is entirely written. For these
|
||||
// reasons we use a chunked writer that keeps an array of chunks, which is
|
||||
// concatenated together after writing is finished.
|
||||
class ChunkedJSONWriteFunc : public mozilla::JSONWriteFunc
|
||||
{
|
||||
public:
|
||||
friend class SpliceableJSONWriter;
|
||||
|
||||
ChunkedJSONWriteFunc() {
|
||||
AllocChunk(kChunkSize);
|
||||
}
|
||||
|
||||
bool IsEmpty() const {
|
||||
MOZ_ASSERT_IF(!mChunkPtr, !mChunkEnd &&
|
||||
mChunkList.length() == 0 &&
|
||||
mChunkLengths.length() == 0);
|
||||
return !mChunkPtr;
|
||||
}
|
||||
|
||||
void Write(const char* aStr) override;
|
||||
mozilla::UniquePtr<char[]> CopyData() const;
|
||||
void Take(ChunkedJSONWriteFunc&& aOther);
|
||||
|
||||
private:
|
||||
void AllocChunk(size_t aChunkSize);
|
||||
|
||||
static const size_t kChunkSize = 4096 * 512;
|
||||
|
||||
// Pointer for writing inside the current chunk.
|
||||
//
|
||||
// The current chunk is always at the back of mChunkList, i.e.,
|
||||
// mChunkList.back() <= mChunkPtr <= mChunkEnd.
|
||||
char* mChunkPtr;
|
||||
|
||||
// Pointer to the end of the current chunk.
|
||||
//
|
||||
// The current chunk is always at the back of mChunkList, i.e.,
|
||||
// mChunkEnd >= mChunkList.back() + mChunkLengths.back().
|
||||
char* mChunkEnd;
|
||||
|
||||
// List of chunks and their lengths.
|
||||
//
|
||||
// For all i, the length of the string in mChunkList[i] is
|
||||
// mChunkLengths[i].
|
||||
mozilla::Vector<mozilla::UniquePtr<char[]>> mChunkList;
|
||||
mozilla::Vector<size_t> mChunkLengths;
|
||||
};
|
||||
|
||||
struct OStreamJSONWriteFunc : public mozilla::JSONWriteFunc
|
||||
{
|
||||
explicit OStreamJSONWriteFunc(std::ostream& aStream)
|
||||
: mStream(aStream)
|
||||
{ }
|
||||
|
||||
void Write(const char* aStr) override {
|
||||
mStream << aStr;
|
||||
}
|
||||
|
||||
std::ostream& mStream;
|
||||
};
|
||||
|
||||
class SpliceableJSONWriter : public mozilla::JSONWriter
|
||||
{
|
||||
public:
|
||||
explicit SpliceableJSONWriter(mozilla::UniquePtr<mozilla::JSONWriteFunc> aWriter)
|
||||
: JSONWriter(mozilla::Move(aWriter))
|
||||
{ }
|
||||
|
||||
void StartBareList(CollectionStyle aStyle = SingleLineStyle) {
|
||||
StartCollection(nullptr, "", aStyle);
|
||||
}
|
||||
|
||||
void EndBareList() {
|
||||
EndCollection("");
|
||||
}
|
||||
|
||||
void NullElements(uint32_t aCount) {
|
||||
for (uint32_t i = 0; i < aCount; i++) {
|
||||
NullElement();
|
||||
}
|
||||
}
|
||||
|
||||
void Splice(const ChunkedJSONWriteFunc* aFunc);
|
||||
void Splice(const char* aStr);
|
||||
|
||||
// Takes the chunks from aFunc and write them. If move is not possible
|
||||
// (e.g., using OStreamJSONWriteFunc), aFunc's chunks are copied and its
|
||||
// storage cleared.
|
||||
virtual void TakeAndSplice(ChunkedJSONWriteFunc* aFunc);
|
||||
};
|
||||
|
||||
class SpliceableChunkedJSONWriter : public SpliceableJSONWriter
|
||||
{
|
||||
public:
|
||||
explicit SpliceableChunkedJSONWriter()
|
||||
: SpliceableJSONWriter(mozilla::MakeUnique<ChunkedJSONWriteFunc>())
|
||||
{ }
|
||||
|
||||
ChunkedJSONWriteFunc* WriteFunc() const {
|
||||
return static_cast<ChunkedJSONWriteFunc*>(JSONWriter::WriteFunc());
|
||||
}
|
||||
|
||||
// Adopts the chunks from aFunc without copying.
|
||||
virtual void TakeAndSplice(ChunkedJSONWriteFunc* aFunc) override;
|
||||
};
|
||||
|
||||
#endif // PROFILEJSONWRITER_H
|
||||
33
tools/profiler/core/ProfilerBacktrace.cpp
Normal file
33
tools/profiler/core/ProfilerBacktrace.cpp
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
/* -*- 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 "ProfilerBacktrace.h"
|
||||
|
||||
#include "ProfileJSONWriter.h"
|
||||
#include "SyncProfile.h"
|
||||
|
||||
ProfilerBacktrace::ProfilerBacktrace(SyncProfile* aProfile)
|
||||
: mProfile(aProfile)
|
||||
{
|
||||
MOZ_COUNT_CTOR(ProfilerBacktrace);
|
||||
MOZ_ASSERT(aProfile);
|
||||
}
|
||||
|
||||
ProfilerBacktrace::~ProfilerBacktrace()
|
||||
{
|
||||
MOZ_COUNT_DTOR(ProfilerBacktrace);
|
||||
if (mProfile->ShouldDestroy()) {
|
||||
delete mProfile;
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
ProfilerBacktrace::StreamJSON(SpliceableJSONWriter& aWriter,
|
||||
UniqueStacks& aUniqueStacks)
|
||||
{
|
||||
::MutexAutoLock lock(mProfile->GetMutex());
|
||||
mProfile->StreamJSON(aWriter, aUniqueStacks);
|
||||
}
|
||||
210
tools/profiler/core/ProfilerMarkers.cpp
Normal file
210
tools/profiler/core/ProfilerMarkers.cpp
Normal file
|
|
@ -0,0 +1,210 @@
|
|||
/* -*- 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 "GeckoProfiler.h"
|
||||
#include "ProfilerBacktrace.h"
|
||||
#include "ProfilerMarkers.h"
|
||||
#include "SyncProfile.h"
|
||||
#ifndef SPS_STANDALONE
|
||||
#include "gfxASurface.h"
|
||||
#include "Layers.h"
|
||||
#include "mozilla/Sprintf.h"
|
||||
#endif
|
||||
|
||||
ProfilerMarkerPayload::ProfilerMarkerPayload(ProfilerBacktrace* aStack)
|
||||
: mStack(aStack)
|
||||
{}
|
||||
|
||||
ProfilerMarkerPayload::ProfilerMarkerPayload(const mozilla::TimeStamp& aStartTime,
|
||||
const mozilla::TimeStamp& aEndTime,
|
||||
ProfilerBacktrace* aStack)
|
||||
: mStartTime(aStartTime)
|
||||
, mEndTime(aEndTime)
|
||||
, mStack(aStack)
|
||||
{}
|
||||
|
||||
ProfilerMarkerPayload::~ProfilerMarkerPayload()
|
||||
{
|
||||
profiler_free_backtrace(mStack);
|
||||
}
|
||||
|
||||
void
|
||||
ProfilerMarkerPayload::streamCommonProps(const char* aMarkerType,
|
||||
SpliceableJSONWriter& aWriter,
|
||||
UniqueStacks& aUniqueStacks)
|
||||
{
|
||||
MOZ_ASSERT(aMarkerType);
|
||||
aWriter.StringProperty("type", aMarkerType);
|
||||
if (!mStartTime.IsNull()) {
|
||||
aWriter.DoubleProperty("startTime", profiler_time(mStartTime));
|
||||
}
|
||||
if (!mEndTime.IsNull()) {
|
||||
aWriter.DoubleProperty("endTime", profiler_time(mEndTime));
|
||||
}
|
||||
if (mStack) {
|
||||
aWriter.StartObjectProperty("stack");
|
||||
{
|
||||
mStack->StreamJSON(aWriter, aUniqueStacks);
|
||||
}
|
||||
aWriter.EndObject();
|
||||
}
|
||||
}
|
||||
|
||||
ProfilerMarkerTracing::ProfilerMarkerTracing(const char* aCategory, TracingMetadata aMetaData)
|
||||
: mCategory(aCategory)
|
||||
, mMetaData(aMetaData)
|
||||
{
|
||||
if (aMetaData == TRACING_EVENT_BACKTRACE) {
|
||||
SetStack(profiler_get_backtrace());
|
||||
}
|
||||
}
|
||||
|
||||
ProfilerMarkerTracing::ProfilerMarkerTracing(const char* aCategory, TracingMetadata aMetaData,
|
||||
ProfilerBacktrace* aCause)
|
||||
: mCategory(aCategory)
|
||||
, mMetaData(aMetaData)
|
||||
{
|
||||
if (aCause) {
|
||||
SetStack(aCause);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
ProfilerMarkerTracing::StreamPayload(SpliceableJSONWriter& aWriter,
|
||||
UniqueStacks& aUniqueStacks)
|
||||
{
|
||||
streamCommonProps("tracing", aWriter, aUniqueStacks);
|
||||
|
||||
if (GetCategory()) {
|
||||
aWriter.StringProperty("category", GetCategory());
|
||||
}
|
||||
if (GetMetaData() != TRACING_DEFAULT) {
|
||||
if (GetMetaData() == TRACING_INTERVAL_START) {
|
||||
aWriter.StringProperty("interval", "start");
|
||||
} else if (GetMetaData() == TRACING_INTERVAL_END) {
|
||||
aWriter.StringProperty("interval", "end");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#ifndef SPS_STANDALONE
|
||||
GPUMarkerPayload::GPUMarkerPayload(
|
||||
const mozilla::TimeStamp& aCpuTimeStart,
|
||||
const mozilla::TimeStamp& aCpuTimeEnd,
|
||||
uint64_t aGpuTimeStart,
|
||||
uint64_t aGpuTimeEnd)
|
||||
|
||||
: ProfilerMarkerPayload(aCpuTimeStart, aCpuTimeEnd)
|
||||
, mCpuTimeStart(aCpuTimeStart)
|
||||
, mCpuTimeEnd(aCpuTimeEnd)
|
||||
, mGpuTimeStart(aGpuTimeStart)
|
||||
, mGpuTimeEnd(aGpuTimeEnd)
|
||||
{ }
|
||||
|
||||
void
|
||||
GPUMarkerPayload::StreamPayload(SpliceableJSONWriter& aWriter,
|
||||
UniqueStacks& aUniqueStacks)
|
||||
{
|
||||
streamCommonProps("gpu_timer_query", aWriter, aUniqueStacks);
|
||||
|
||||
aWriter.DoubleProperty("cpustart", profiler_time(mCpuTimeStart));
|
||||
aWriter.DoubleProperty("cpuend", profiler_time(mCpuTimeEnd));
|
||||
aWriter.IntProperty("gpustart", (int)mGpuTimeStart);
|
||||
aWriter.IntProperty("gpuend", (int)mGpuTimeEnd);
|
||||
}
|
||||
|
||||
ProfilerMarkerImagePayload::ProfilerMarkerImagePayload(gfxASurface *aImg)
|
||||
: mImg(aImg)
|
||||
{ }
|
||||
|
||||
void
|
||||
ProfilerMarkerImagePayload::StreamPayload(SpliceableJSONWriter& aWriter,
|
||||
UniqueStacks& aUniqueStacks)
|
||||
{
|
||||
streamCommonProps("innerHTML", aWriter, aUniqueStacks);
|
||||
// TODO: Finish me
|
||||
//aWriter.NameValue("innerHTML", "<img src=''/>");
|
||||
}
|
||||
|
||||
IOMarkerPayload::IOMarkerPayload(const char* aSource,
|
||||
const char* aFilename,
|
||||
const mozilla::TimeStamp& aStartTime,
|
||||
const mozilla::TimeStamp& aEndTime,
|
||||
ProfilerBacktrace* aStack)
|
||||
: ProfilerMarkerPayload(aStartTime, aEndTime, aStack),
|
||||
mSource(aSource)
|
||||
{
|
||||
mFilename = aFilename ? strdup(aFilename) : nullptr;
|
||||
MOZ_ASSERT(aSource);
|
||||
}
|
||||
|
||||
IOMarkerPayload::~IOMarkerPayload(){
|
||||
free(mFilename);
|
||||
}
|
||||
|
||||
void
|
||||
IOMarkerPayload::StreamPayload(SpliceableJSONWriter& aWriter, UniqueStacks& aUniqueStacks)
|
||||
{
|
||||
streamCommonProps("io", aWriter, aUniqueStacks);
|
||||
aWriter.StringProperty("source", mSource);
|
||||
if (mFilename != nullptr) {
|
||||
aWriter.StringProperty("filename", mFilename);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
ProfilerJSEventMarker(const char *event)
|
||||
{
|
||||
PROFILER_MARKER(event);
|
||||
}
|
||||
|
||||
LayerTranslationPayload::LayerTranslationPayload(mozilla::layers::Layer* aLayer,
|
||||
mozilla::gfx::Point aPoint)
|
||||
: ProfilerMarkerPayload(mozilla::TimeStamp::Now(), mozilla::TimeStamp::Now(), nullptr)
|
||||
, mLayer(aLayer)
|
||||
, mPoint(aPoint)
|
||||
{
|
||||
}
|
||||
|
||||
void
|
||||
LayerTranslationPayload::StreamPayload(SpliceableJSONWriter& aWriter,
|
||||
UniqueStacks& aUniqueStacks)
|
||||
{
|
||||
const size_t bufferSize = 32;
|
||||
char buffer[bufferSize];
|
||||
SprintfLiteral(buffer, "%p", mLayer);
|
||||
|
||||
aWriter.StringProperty("layer", buffer);
|
||||
aWriter.IntProperty("x", mPoint.x);
|
||||
aWriter.IntProperty("y", mPoint.y);
|
||||
aWriter.StringProperty("category", "LayerTranslation");
|
||||
}
|
||||
|
||||
TouchDataPayload::TouchDataPayload(const mozilla::ScreenIntPoint& aPoint)
|
||||
: ProfilerMarkerPayload(mozilla::TimeStamp::Now(), mozilla::TimeStamp::Now(), nullptr)
|
||||
{
|
||||
mPoint = aPoint;
|
||||
}
|
||||
|
||||
void
|
||||
TouchDataPayload::StreamPayload(SpliceableJSONWriter& aWriter, UniqueStacks& aUniqueStacks)
|
||||
{
|
||||
aWriter.IntProperty("x", mPoint.x);
|
||||
aWriter.IntProperty("y", mPoint.y);
|
||||
}
|
||||
|
||||
VsyncPayload::VsyncPayload(mozilla::TimeStamp aVsyncTimestamp)
|
||||
: ProfilerMarkerPayload(aVsyncTimestamp, aVsyncTimestamp, nullptr)
|
||||
, mVsyncTimestamp(aVsyncTimestamp)
|
||||
{
|
||||
}
|
||||
|
||||
void
|
||||
VsyncPayload::StreamPayload(SpliceableJSONWriter& aWriter, UniqueStacks& aUniqueStacks)
|
||||
{
|
||||
aWriter.DoubleProperty("vsync", profiler_time(mVsyncTimestamp));
|
||||
aWriter.StringProperty("category", "VsyncTimestamp");
|
||||
}
|
||||
#endif
|
||||
48
tools/profiler/core/StackTop.cpp
Normal file
48
tools/profiler/core/StackTop.cpp
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
/* -*- Mode: C++; tab-width: 2; 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/. */
|
||||
|
||||
#ifdef XP_MACOSX
|
||||
#include <mach/task.h>
|
||||
#include <mach/thread_act.h>
|
||||
#include <pthread.h>
|
||||
#elif XP_WIN
|
||||
#include <windows.h>
|
||||
#endif
|
||||
|
||||
#include "StackTop.h"
|
||||
|
||||
void *GetStackTop(void *guess) {
|
||||
#if defined(XP_MACOSX)
|
||||
pthread_t thread = pthread_self();
|
||||
return pthread_get_stackaddr_np(thread);
|
||||
#elif defined(XP_WIN)
|
||||
#if defined(_MSC_VER) && defined(_M_IX86)
|
||||
// offset 0x18 from the FS segment register gives a pointer to
|
||||
// the thread information block for the current thread
|
||||
NT_TIB* pTib;
|
||||
__asm {
|
||||
MOV EAX, FS:[18h]
|
||||
MOV pTib, EAX
|
||||
}
|
||||
return static_cast<void*>(pTib->StackBase);
|
||||
#elif defined(__GNUC__) && defined(i386)
|
||||
// offset 0x18 from the FS segment register gives a pointer to
|
||||
// the thread information block for the current thread
|
||||
NT_TIB* pTib;
|
||||
asm ( "movl %%fs:0x18, %0\n"
|
||||
: "=r" (pTib)
|
||||
);
|
||||
return static_cast<void*>(pTib->StackBase);
|
||||
#elif defined(_M_X64) || defined(__x86_64)
|
||||
PNT_TIB64 pTib = reinterpret_cast<PNT_TIB64>(NtCurrentTeb());
|
||||
return reinterpret_cast<void*>(pTib->StackBase);
|
||||
#else
|
||||
#error Need a way to get the stack bounds on this platform (Windows)
|
||||
#endif
|
||||
#else
|
||||
return guess;
|
||||
#endif
|
||||
}
|
||||
10
tools/profiler/core/StackTop.h
Normal file
10
tools/profiler/core/StackTop.h
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
/* -*- Mode: C++; tab-width: 2; 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 MOZ_STACK_TOP_H
|
||||
#define MOZ_STACK_TOP_H
|
||||
void *GetStackTop(void *guess);
|
||||
#endif
|
||||
57
tools/profiler/core/SyncProfile.cpp
Normal file
57
tools/profiler/core/SyncProfile.cpp
Normal file
|
|
@ -0,0 +1,57 @@
|
|||
/* -*- Mode: C++; tab-width: 2; 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 "SyncProfile.h"
|
||||
|
||||
SyncProfile::SyncProfile(ThreadInfo* aInfo, int aEntrySize)
|
||||
: ThreadProfile(aInfo, new ProfileBuffer(aEntrySize))
|
||||
, mOwnerState(REFERENCED)
|
||||
{
|
||||
MOZ_COUNT_CTOR(SyncProfile);
|
||||
}
|
||||
|
||||
SyncProfile::~SyncProfile()
|
||||
{
|
||||
MOZ_COUNT_DTOR(SyncProfile);
|
||||
|
||||
// SyncProfile owns the ThreadInfo; see NewSyncProfile.
|
||||
ThreadInfo* info = GetThreadInfo();
|
||||
delete info;
|
||||
}
|
||||
|
||||
bool
|
||||
SyncProfile::ShouldDestroy()
|
||||
{
|
||||
::MutexAutoLock lock(GetMutex());
|
||||
if (mOwnerState == OWNED) {
|
||||
mOwnerState = OWNER_DESTROYING;
|
||||
return true;
|
||||
}
|
||||
mOwnerState = ORPHANED;
|
||||
return false;
|
||||
}
|
||||
|
||||
void
|
||||
SyncProfile::EndUnwind()
|
||||
{
|
||||
if (mOwnerState != ORPHANED) {
|
||||
mOwnerState = OWNED;
|
||||
}
|
||||
// Save mOwnerState before we release the mutex
|
||||
OwnerState ownerState = mOwnerState;
|
||||
ThreadProfile::EndUnwind();
|
||||
if (ownerState == ORPHANED) {
|
||||
delete this;
|
||||
}
|
||||
}
|
||||
|
||||
// SyncProfiles' stacks are deduplicated in the context of the containing
|
||||
// profile in which the backtrace is as a marker payload.
|
||||
void
|
||||
SyncProfile::StreamJSON(SpliceableJSONWriter& aWriter, UniqueStacks& aUniqueStacks)
|
||||
{
|
||||
ThreadProfile::StreamSamplesAndMarkers(aWriter, /* aSinceTime = */ 0, aUniqueStacks);
|
||||
}
|
||||
43
tools/profiler/core/SyncProfile.h
Normal file
43
tools/profiler/core/SyncProfile.h
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
/* -*- Mode: C++; tab-width: 2; 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 __SYNCPROFILE_H
|
||||
#define __SYNCPROFILE_H
|
||||
|
||||
#include "ProfileEntry.h"
|
||||
#include "ThreadProfile.h"
|
||||
|
||||
class SyncProfile : public ThreadProfile
|
||||
{
|
||||
public:
|
||||
SyncProfile(ThreadInfo* aInfo, int aEntrySize);
|
||||
~SyncProfile();
|
||||
|
||||
// SyncProfiles' stacks are deduplicated in the context of the containing
|
||||
// profile in which the backtrace is as a marker payload.
|
||||
void StreamJSON(SpliceableJSONWriter& aWriter, UniqueStacks& aUniqueStacks);
|
||||
|
||||
virtual void EndUnwind();
|
||||
virtual SyncProfile* AsSyncProfile() { return this; }
|
||||
|
||||
private:
|
||||
friend class ProfilerBacktrace;
|
||||
|
||||
enum OwnerState
|
||||
{
|
||||
REFERENCED, // ProfilerBacktrace has a pointer to this but doesn't own
|
||||
OWNED, // ProfilerBacktrace is responsible for destroying this
|
||||
OWNER_DESTROYING, // ProfilerBacktrace owns this and is destroying
|
||||
ORPHANED // No owner, we must destroy ourselves
|
||||
};
|
||||
|
||||
bool ShouldDestroy();
|
||||
|
||||
OwnerState mOwnerState;
|
||||
};
|
||||
|
||||
#endif // __SYNCPROFILE_H
|
||||
|
||||
73
tools/profiler/core/ThreadInfo.cpp
Normal file
73
tools/profiler/core/ThreadInfo.cpp
Normal file
|
|
@ -0,0 +1,73 @@
|
|||
/* -*- Mode: C++; tab-width: 2; 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 "ThreadInfo.h"
|
||||
#include "ThreadProfile.h"
|
||||
|
||||
#include "mozilla/DebugOnly.h"
|
||||
|
||||
ThreadInfo::ThreadInfo(const char* aName, int aThreadId,
|
||||
bool aIsMainThread, PseudoStack* aPseudoStack,
|
||||
void* aStackTop)
|
||||
: mName(strdup(aName))
|
||||
, mThreadId(aThreadId)
|
||||
, mIsMainThread(aIsMainThread)
|
||||
, mPseudoStack(aPseudoStack)
|
||||
, mPlatformData(Sampler::AllocPlatformData(aThreadId))
|
||||
, mProfile(nullptr)
|
||||
, mStackTop(aStackTop)
|
||||
, mPendingDelete(false)
|
||||
{
|
||||
MOZ_COUNT_CTOR(ThreadInfo);
|
||||
#ifndef SPS_STANDALONE
|
||||
mThread = NS_GetCurrentThread();
|
||||
#endif
|
||||
|
||||
// We don't have to guess on mac
|
||||
#ifdef XP_MACOSX
|
||||
pthread_t self = pthread_self();
|
||||
mStackTop = pthread_get_stackaddr_np(self);
|
||||
#endif
|
||||
}
|
||||
|
||||
ThreadInfo::~ThreadInfo() {
|
||||
MOZ_COUNT_DTOR(ThreadInfo);
|
||||
free(mName);
|
||||
|
||||
if (mProfile)
|
||||
delete mProfile;
|
||||
|
||||
Sampler::FreePlatformData(mPlatformData);
|
||||
}
|
||||
|
||||
void
|
||||
ThreadInfo::SetPendingDelete()
|
||||
{
|
||||
mPendingDelete = true;
|
||||
// We don't own the pseudostack so disconnect it.
|
||||
mPseudoStack = nullptr;
|
||||
if (mProfile) {
|
||||
mProfile->SetPendingDelete();
|
||||
}
|
||||
}
|
||||
|
||||
bool
|
||||
ThreadInfo::CanInvokeJS() const
|
||||
{
|
||||
#ifdef SPS_STANDALONE
|
||||
return false;
|
||||
#else
|
||||
nsIThread* thread = GetThread();
|
||||
if (!thread) {
|
||||
MOZ_ASSERT(IsMainThread());
|
||||
return true;
|
||||
}
|
||||
bool result;
|
||||
mozilla::DebugOnly<nsresult> rv = thread->GetCanInvokeJS(&result);
|
||||
MOZ_ASSERT(NS_SUCCEEDED(rv));
|
||||
return result;
|
||||
#endif
|
||||
}
|
||||
66
tools/profiler/core/ThreadInfo.h
Normal file
66
tools/profiler/core/ThreadInfo.h
Normal file
|
|
@ -0,0 +1,66 @@
|
|||
/* -*- Mode: C++; tab-width: 2; 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 MOZ_THREAD_INFO_H
|
||||
#define MOZ_THREAD_INFO_H
|
||||
|
||||
#include "platform.h"
|
||||
|
||||
class ThreadInfo {
|
||||
public:
|
||||
ThreadInfo(const char* aName, int aThreadId, bool aIsMainThread, PseudoStack* aPseudoStack, void* aStackTop);
|
||||
|
||||
virtual ~ThreadInfo();
|
||||
|
||||
const char* Name() const { return mName; }
|
||||
int ThreadId() const { return mThreadId; }
|
||||
|
||||
bool IsMainThread() const { return mIsMainThread; }
|
||||
PseudoStack* Stack() const { return mPseudoStack; }
|
||||
|
||||
void SetProfile(ThreadProfile* aProfile) { mProfile = aProfile; }
|
||||
ThreadProfile* Profile() const { return mProfile; }
|
||||
|
||||
PlatformData* GetPlatformData() const { return mPlatformData; }
|
||||
void* StackTop() const { return mStackTop; }
|
||||
|
||||
virtual void SetPendingDelete();
|
||||
bool IsPendingDelete() const { return mPendingDelete; }
|
||||
|
||||
#ifndef SPS_STANDALONE
|
||||
/**
|
||||
* May be null for the main thread if the profiler was started during startup
|
||||
*/
|
||||
nsIThread* GetThread() const { return mThread.get(); }
|
||||
|
||||
#endif
|
||||
|
||||
bool CanInvokeJS() const;
|
||||
|
||||
private:
|
||||
char* mName;
|
||||
int mThreadId;
|
||||
const bool mIsMainThread;
|
||||
PseudoStack* mPseudoStack;
|
||||
PlatformData* mPlatformData;
|
||||
ThreadProfile* mProfile;
|
||||
void* mStackTop;
|
||||
#ifndef SPS_STANDALONE
|
||||
nsCOMPtr<nsIThread> mThread;
|
||||
#endif
|
||||
bool mPendingDelete;
|
||||
};
|
||||
|
||||
// Just like ThreadInfo, but owns a reference to the PseudoStack.
|
||||
class StackOwningThreadInfo : public ThreadInfo {
|
||||
public:
|
||||
StackOwningThreadInfo(const char* aName, int aThreadId, bool aIsMainThread, PseudoStack* aPseudoStack, void* aStackTop);
|
||||
virtual ~StackOwningThreadInfo();
|
||||
|
||||
virtual void SetPendingDelete();
|
||||
};
|
||||
|
||||
#endif
|
||||
260
tools/profiler/core/ThreadProfile.cpp
Normal file
260
tools/profiler/core/ThreadProfile.cpp
Normal file
|
|
@ -0,0 +1,260 @@
|
|||
/* -*- Mode: C++; tab-width: 2; 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/. */
|
||||
|
||||
ThreadProfile::ThreadProfile(ThreadInfo* aInfo, ProfileBuffer* aBuffer)
|
||||
: mThreadInfo(aInfo)
|
||||
, mBuffer(aBuffer)
|
||||
, mPseudoStack(aInfo->Stack())
|
||||
, mMutex(OS::CreateMutex("ThreadProfile::mMutex"))
|
||||
, mThreadId(int(aInfo->ThreadId()))
|
||||
, mIsMainThread(aInfo->IsMainThread())
|
||||
, mPlatformData(aInfo->GetPlatformData())
|
||||
, mStackTop(aInfo->StackTop())
|
||||
#ifndef SPS_STANDALONE
|
||||
, mRespInfo(this)
|
||||
#endif
|
||||
#ifdef XP_LINUX
|
||||
, mRssMemory(0)
|
||||
, mUssMemory(0)
|
||||
#endif
|
||||
{
|
||||
MOZ_COUNT_CTOR(ThreadProfile);
|
||||
MOZ_ASSERT(aBuffer);
|
||||
|
||||
// I don't know if we can assert this. But we should warn.
|
||||
MOZ_ASSERT(aInfo->ThreadId() >= 0, "native thread ID is < 0");
|
||||
MOZ_ASSERT(aInfo->ThreadId() <= INT32_MAX, "native thread ID is > INT32_MAX");
|
||||
}
|
||||
|
||||
ThreadProfile::~ThreadProfile()
|
||||
{
|
||||
MOZ_COUNT_DTOR(ThreadProfile);
|
||||
}
|
||||
|
||||
void ThreadProfile::addTag(const ProfileEntry& aTag)
|
||||
{
|
||||
mBuffer->addTag(aTag);
|
||||
}
|
||||
|
||||
void ThreadProfile::addStoredMarker(ProfilerMarker *aStoredMarker) {
|
||||
mBuffer->addStoredMarker(aStoredMarker);
|
||||
}
|
||||
|
||||
void ThreadProfile::StreamJSON(SpliceableJSONWriter& aWriter, double aSinceTime)
|
||||
{
|
||||
// mUniqueStacks may already be emplaced from FlushSamplesAndMarkers.
|
||||
if (!mUniqueStacks.isSome()) {
|
||||
#ifndef SPS_STANDALONE
|
||||
mUniqueStacks.emplace(mPseudoStack->mContext);
|
||||
#else
|
||||
mUniqueStacks.emplace(nullptr);
|
||||
#endif
|
||||
}
|
||||
|
||||
aWriter.Start(SpliceableJSONWriter::SingleLineStyle);
|
||||
{
|
||||
StreamSamplesAndMarkers(aWriter, aSinceTime, *mUniqueStacks);
|
||||
|
||||
aWriter.StartObjectProperty("stackTable");
|
||||
{
|
||||
{
|
||||
JSONSchemaWriter schema(aWriter);
|
||||
schema.WriteField("prefix");
|
||||
schema.WriteField("frame");
|
||||
}
|
||||
|
||||
aWriter.StartArrayProperty("data");
|
||||
{
|
||||
mUniqueStacks->SpliceStackTableElements(aWriter);
|
||||
}
|
||||
aWriter.EndArray();
|
||||
}
|
||||
aWriter.EndObject();
|
||||
|
||||
aWriter.StartObjectProperty("frameTable");
|
||||
{
|
||||
{
|
||||
JSONSchemaWriter schema(aWriter);
|
||||
schema.WriteField("location");
|
||||
schema.WriteField("implementation");
|
||||
schema.WriteField("optimizations");
|
||||
schema.WriteField("line");
|
||||
schema.WriteField("category");
|
||||
}
|
||||
|
||||
aWriter.StartArrayProperty("data");
|
||||
{
|
||||
mUniqueStacks->SpliceFrameTableElements(aWriter);
|
||||
}
|
||||
aWriter.EndArray();
|
||||
}
|
||||
aWriter.EndObject();
|
||||
|
||||
aWriter.StartArrayProperty("stringTable");
|
||||
{
|
||||
mUniqueStacks->mUniqueStrings.SpliceStringTableElements(aWriter);
|
||||
}
|
||||
aWriter.EndArray();
|
||||
}
|
||||
aWriter.End();
|
||||
|
||||
mUniqueStacks.reset();
|
||||
}
|
||||
|
||||
void ThreadProfile::StreamSamplesAndMarkers(SpliceableJSONWriter& aWriter, double aSinceTime,
|
||||
UniqueStacks& aUniqueStacks)
|
||||
{
|
||||
#ifndef SPS_STANDALONE
|
||||
// Thread meta data
|
||||
if (XRE_GetProcessType() == GeckoProcessType_Plugin) {
|
||||
// TODO Add the proper plugin name
|
||||
aWriter.StringProperty("name", "Plugin");
|
||||
} else if (XRE_GetProcessType() == GeckoProcessType_Content) {
|
||||
// This isn't going to really help once we have multiple content
|
||||
// processes, but it'll do for now.
|
||||
aWriter.StringProperty("name", "Content");
|
||||
} else {
|
||||
aWriter.StringProperty("name", Name());
|
||||
}
|
||||
#else
|
||||
aWriter.StringProperty("name", Name());
|
||||
#endif
|
||||
|
||||
aWriter.IntProperty("tid", static_cast<int>(mThreadId));
|
||||
|
||||
aWriter.StartObjectProperty("samples");
|
||||
{
|
||||
{
|
||||
JSONSchemaWriter schema(aWriter);
|
||||
schema.WriteField("stack");
|
||||
schema.WriteField("time");
|
||||
schema.WriteField("responsiveness");
|
||||
schema.WriteField("rss");
|
||||
schema.WriteField("uss");
|
||||
schema.WriteField("frameNumber");
|
||||
schema.WriteField("power");
|
||||
}
|
||||
|
||||
aWriter.StartArrayProperty("data");
|
||||
{
|
||||
if (mSavedStreamedSamples) {
|
||||
// We would only have saved streamed samples during shutdown
|
||||
// streaming, which cares about dumping the entire buffer, and thus
|
||||
// should have passed in 0 for aSinceTime.
|
||||
MOZ_ASSERT(aSinceTime == 0);
|
||||
aWriter.Splice(mSavedStreamedSamples.get());
|
||||
mSavedStreamedSamples.reset();
|
||||
}
|
||||
mBuffer->StreamSamplesToJSON(aWriter, mThreadId, aSinceTime,
|
||||
#ifndef SPS_STANDALONE
|
||||
mPseudoStack->mContext,
|
||||
#else
|
||||
nullptr,
|
||||
#endif
|
||||
aUniqueStacks);
|
||||
}
|
||||
aWriter.EndArray();
|
||||
}
|
||||
aWriter.EndObject();
|
||||
|
||||
aWriter.StartObjectProperty("markers");
|
||||
{
|
||||
{
|
||||
JSONSchemaWriter schema(aWriter);
|
||||
schema.WriteField("name");
|
||||
schema.WriteField("time");
|
||||
schema.WriteField("data");
|
||||
}
|
||||
|
||||
aWriter.StartArrayProperty("data");
|
||||
{
|
||||
if (mSavedStreamedMarkers) {
|
||||
MOZ_ASSERT(aSinceTime == 0);
|
||||
aWriter.Splice(mSavedStreamedMarkers.get());
|
||||
mSavedStreamedMarkers.reset();
|
||||
}
|
||||
mBuffer->StreamMarkersToJSON(aWriter, mThreadId, aSinceTime, aUniqueStacks);
|
||||
}
|
||||
aWriter.EndArray();
|
||||
}
|
||||
aWriter.EndObject();
|
||||
}
|
||||
|
||||
void ThreadProfile::FlushSamplesAndMarkers()
|
||||
{
|
||||
// This function is used to serialize the current buffer just before
|
||||
// JSContext destruction.
|
||||
MOZ_ASSERT(mPseudoStack->mContext);
|
||||
|
||||
// Unlike StreamJSObject, do not surround the samples in brackets by calling
|
||||
// aWriter.{Start,End}BareList. The result string will be a comma-separated
|
||||
// list of JSON object literals that will prepended by StreamJSObject into
|
||||
// an existing array.
|
||||
//
|
||||
// Note that the UniqueStacks instance is persisted so that the frame-index
|
||||
// mapping is stable across JS shutdown.
|
||||
#ifndef SPS_STANDALONE
|
||||
mUniqueStacks.emplace(mPseudoStack->mContext);
|
||||
#else
|
||||
mUniqueStacks.emplace(nullptr);
|
||||
#endif
|
||||
|
||||
{
|
||||
SpliceableChunkedJSONWriter b;
|
||||
b.StartBareList();
|
||||
{
|
||||
mBuffer->StreamSamplesToJSON(b, mThreadId, /* aSinceTime = */ 0,
|
||||
#ifndef SPS_STANDALONE
|
||||
mPseudoStack->mContext,
|
||||
#else
|
||||
nullptr,
|
||||
#endif
|
||||
*mUniqueStacks);
|
||||
}
|
||||
b.EndBareList();
|
||||
mSavedStreamedSamples = b.WriteFunc()->CopyData();
|
||||
}
|
||||
|
||||
{
|
||||
SpliceableChunkedJSONWriter b;
|
||||
b.StartBareList();
|
||||
{
|
||||
mBuffer->StreamMarkersToJSON(b, mThreadId, /* aSinceTime = */ 0, *mUniqueStacks);
|
||||
}
|
||||
b.EndBareList();
|
||||
mSavedStreamedMarkers = b.WriteFunc()->CopyData();
|
||||
}
|
||||
|
||||
// Reset the buffer. Attempting to symbolicate JS samples after mContext has
|
||||
// gone away will crash.
|
||||
mBuffer->reset();
|
||||
}
|
||||
|
||||
PseudoStack* ThreadProfile::GetPseudoStack()
|
||||
{
|
||||
return mPseudoStack;
|
||||
}
|
||||
|
||||
void ThreadProfile::BeginUnwind()
|
||||
{
|
||||
mMutex->Lock();
|
||||
}
|
||||
|
||||
void ThreadProfile::EndUnwind()
|
||||
{
|
||||
mMutex->Unlock();
|
||||
}
|
||||
|
||||
::Mutex& ThreadProfile::GetMutex()
|
||||
{
|
||||
return *mMutex.get();
|
||||
}
|
||||
|
||||
void ThreadProfile::DuplicateLastSample()
|
||||
{
|
||||
mBuffer->DuplicateLastSample(mThreadId);
|
||||
}
|
||||
|
||||
107
tools/profiler/core/ThreadProfile.h
Normal file
107
tools/profiler/core/ThreadProfile.h
Normal file
|
|
@ -0,0 +1,107 @@
|
|||
/* -*- Mode: C++; tab-width: 2; 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 MOZ_THREAD_PROFILE_H
|
||||
#define MOZ_THREAD_PROFILE_H
|
||||
|
||||
#include "ProfileBuffer.h"
|
||||
#include "ThreadInfo.h"
|
||||
|
||||
class ThreadProfile
|
||||
{
|
||||
public:
|
||||
ThreadProfile(ThreadInfo* aThreadInfo, ProfileBuffer* aBuffer);
|
||||
virtual ~ThreadProfile();
|
||||
void addTag(const ProfileEntry& aTag);
|
||||
|
||||
/**
|
||||
* Track a marker which has been inserted into the ThreadProfile.
|
||||
* This marker can safely be deleted once the generation has
|
||||
* expired.
|
||||
*/
|
||||
void addStoredMarker(ProfilerMarker *aStoredMarker);
|
||||
PseudoStack* GetPseudoStack();
|
||||
::Mutex& GetMutex();
|
||||
void StreamJSON(SpliceableJSONWriter& aWriter, double aSinceTime = 0);
|
||||
|
||||
/**
|
||||
* Call this method when the JS entries inside the buffer are about to
|
||||
* become invalid, i.e., just before JS shutdown.
|
||||
*/
|
||||
void FlushSamplesAndMarkers();
|
||||
|
||||
void BeginUnwind();
|
||||
virtual void EndUnwind();
|
||||
virtual SyncProfile* AsSyncProfile() { return nullptr; }
|
||||
|
||||
bool IsMainThread() const { return mIsMainThread; }
|
||||
const char* Name() const { return mThreadInfo->Name(); }
|
||||
int ThreadId() const { return mThreadId; }
|
||||
|
||||
PlatformData* GetPlatformData() const { return mPlatformData; }
|
||||
void* GetStackTop() const { return mStackTop; }
|
||||
void DuplicateLastSample();
|
||||
|
||||
ThreadInfo* GetThreadInfo() const { return mThreadInfo; }
|
||||
#ifndef SPS_STANDALONE
|
||||
ThreadResponsiveness* GetThreadResponsiveness() { return &mRespInfo; }
|
||||
#endif
|
||||
|
||||
bool CanInvokeJS() const { return mThreadInfo->CanInvokeJS(); }
|
||||
|
||||
void SetPendingDelete()
|
||||
{
|
||||
mPseudoStack = nullptr;
|
||||
mPlatformData = nullptr;
|
||||
}
|
||||
|
||||
uint32_t bufferGeneration() const {
|
||||
return mBuffer->mGeneration;
|
||||
}
|
||||
|
||||
protected:
|
||||
void StreamSamplesAndMarkers(SpliceableJSONWriter& aWriter, double aSinceTime,
|
||||
UniqueStacks& aUniqueStacks);
|
||||
|
||||
private:
|
||||
FRIEND_TEST(ThreadProfile, InsertOneTag);
|
||||
FRIEND_TEST(ThreadProfile, InsertOneTagWithTinyBuffer);
|
||||
FRIEND_TEST(ThreadProfile, InsertTagsNoWrap);
|
||||
FRIEND_TEST(ThreadProfile, InsertTagsWrap);
|
||||
FRIEND_TEST(ThreadProfile, MemoryMeasure);
|
||||
ThreadInfo* mThreadInfo;
|
||||
|
||||
const RefPtr<ProfileBuffer> mBuffer;
|
||||
|
||||
// JS frames in the buffer may require a live JSRuntime to stream (e.g.,
|
||||
// stringifying JIT frames). In the case of JSRuntime destruction,
|
||||
// FlushSamplesAndMarkers should be called to save them. These are spliced
|
||||
// into the final stream.
|
||||
mozilla::UniquePtr<char[]> mSavedStreamedSamples;
|
||||
mozilla::UniquePtr<char[]> mSavedStreamedMarkers;
|
||||
mozilla::Maybe<UniqueStacks> mUniqueStacks;
|
||||
|
||||
PseudoStack* mPseudoStack;
|
||||
mozilla::UniquePtr<Mutex> mMutex;
|
||||
int mThreadId;
|
||||
bool mIsMainThread;
|
||||
PlatformData* mPlatformData; // Platform specific data.
|
||||
void* const mStackTop;
|
||||
#ifndef SPS_STANDALONE
|
||||
ThreadResponsiveness mRespInfo;
|
||||
#endif
|
||||
|
||||
// Only Linux is using a signal sender, instead of stopping the thread, so we
|
||||
// need some space to store the data which cannot be collected in the signal
|
||||
// handler code.
|
||||
#ifdef XP_LINUX
|
||||
public:
|
||||
int64_t mRssMemory;
|
||||
int64_t mUssMemory;
|
||||
#endif
|
||||
};
|
||||
|
||||
#endif
|
||||
715
tools/profiler/core/platform-linux.cc
Normal file
715
tools/profiler/core/platform-linux.cc
Normal file
|
|
@ -0,0 +1,715 @@
|
|||
// Copyright (c) 2006-2011 The Chromium Authors. 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.
|
||||
|
||||
/*
|
||||
# vim: sw=2
|
||||
*/
|
||||
#include <stdio.h>
|
||||
#include <math.h>
|
||||
|
||||
#include <pthread.h>
|
||||
#include <semaphore.h>
|
||||
#include <signal.h>
|
||||
#include <sys/time.h>
|
||||
#include <sys/resource.h>
|
||||
#include <sys/syscall.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/prctl.h> // set name
|
||||
#include <stdlib.h>
|
||||
#include <sched.h>
|
||||
#ifdef ANDROID
|
||||
#include <android/log.h>
|
||||
#else
|
||||
#define __android_log_print(a, ...)
|
||||
#endif
|
||||
#include <ucontext.h>
|
||||
// Ubuntu Dapper requires memory pages to be marked as
|
||||
// executable. Otherwise, OS raises an exception when executing code
|
||||
// in that page.
|
||||
#include <sys/types.h> // mmap & munmap
|
||||
#include <sys/mman.h> // mmap & munmap
|
||||
#include <sys/stat.h> // open
|
||||
#include <fcntl.h> // open
|
||||
#include <unistd.h> // sysconf
|
||||
#include <semaphore.h>
|
||||
#ifdef __GLIBC__
|
||||
#include <execinfo.h> // backtrace, backtrace_symbols
|
||||
#endif // def __GLIBC__
|
||||
#include <strings.h> // index
|
||||
#include <errno.h>
|
||||
#include <stdarg.h>
|
||||
#include "prenv.h"
|
||||
#include "platform.h"
|
||||
#include "GeckoProfiler.h"
|
||||
#include "mozilla/Mutex.h"
|
||||
#include "mozilla/Atomics.h"
|
||||
#include "mozilla/LinuxSignal.h"
|
||||
#include "mozilla/TimeStamp.h"
|
||||
#include "mozilla/DebugOnly.h"
|
||||
#include "ProfileEntry.h"
|
||||
#include "nsThreadUtils.h"
|
||||
#include "GeckoSampler.h"
|
||||
#include "ThreadResponsiveness.h"
|
||||
|
||||
#if defined(__ARM_EABI__) && defined(ANDROID)
|
||||
// Should also work on other Android and ARM Linux, but not tested there yet.
|
||||
# define USE_EHABI_STACKWALK
|
||||
# include "EHABIStackWalk.h"
|
||||
#elif defined(SPS_PLAT_amd64_linux) || defined(SPS_PLAT_x86_linux)
|
||||
# define USE_LUL_STACKWALK
|
||||
# include "lul/LulMain.h"
|
||||
# include "lul/platform-linux-lul.h"
|
||||
#endif
|
||||
|
||||
// Memory profile
|
||||
#include "nsMemoryReporterManager.h"
|
||||
|
||||
#include <string.h>
|
||||
#include <list>
|
||||
|
||||
#define SIGNAL_SAVE_PROFILE SIGUSR2
|
||||
|
||||
using namespace mozilla;
|
||||
|
||||
#if defined(USE_LUL_STACKWALK)
|
||||
// A singleton instance of the library. It is initialised at first
|
||||
// use. Currently only the main thread can call Sampler::Start, so
|
||||
// there is no need for a mechanism to ensure that it is only
|
||||
// created once in a multi-thread-use situation.
|
||||
lul::LUL* sLUL = nullptr;
|
||||
|
||||
// This is the sLUL initialization routine.
|
||||
static void sLUL_initialization_routine(void)
|
||||
{
|
||||
MOZ_ASSERT(!sLUL);
|
||||
MOZ_ASSERT(gettid() == getpid()); /* "this is the main thread" */
|
||||
sLUL = new lul::LUL(logging_sink_for_LUL);
|
||||
// Read all the unwind info currently available.
|
||||
read_procmaps(sLUL);
|
||||
}
|
||||
#endif
|
||||
|
||||
/* static */ Thread::tid_t
|
||||
Thread::GetCurrentId()
|
||||
{
|
||||
return gettid();
|
||||
}
|
||||
|
||||
#if !defined(ANDROID)
|
||||
// Keep track of when any of our threads calls fork(), so we can
|
||||
// temporarily disable signal delivery during the fork() call. Not
|
||||
// doing so appears to cause a kind of race, in which signals keep
|
||||
// getting delivered to the thread doing fork(), which keeps causing
|
||||
// it to fail and be restarted; hence forward progress is delayed a
|
||||
// great deal. A side effect of this is to permanently disable
|
||||
// sampling in the child process. See bug 837390.
|
||||
|
||||
// Unfortunately this is only doable on non-Android, since Bionic
|
||||
// doesn't have pthread_atfork.
|
||||
|
||||
// This records the current state at the time we paused it.
|
||||
static bool was_paused = false;
|
||||
|
||||
// In the parent, just before the fork, record the pausedness state,
|
||||
// and then pause.
|
||||
static void paf_prepare(void) {
|
||||
if (Sampler::GetActiveSampler()) {
|
||||
was_paused = Sampler::GetActiveSampler()->IsPaused();
|
||||
Sampler::GetActiveSampler()->SetPaused(true);
|
||||
} else {
|
||||
was_paused = false;
|
||||
}
|
||||
}
|
||||
|
||||
// In the parent, just after the fork, return pausedness to the
|
||||
// pre-fork state.
|
||||
static void paf_parent(void) {
|
||||
if (Sampler::GetActiveSampler())
|
||||
Sampler::GetActiveSampler()->SetPaused(was_paused);
|
||||
}
|
||||
|
||||
// Set up the fork handlers.
|
||||
static void* setup_atfork() {
|
||||
pthread_atfork(paf_prepare, paf_parent, NULL);
|
||||
return NULL;
|
||||
}
|
||||
#endif /* !defined(ANDROID) */
|
||||
|
||||
struct SamplerRegistry {
|
||||
static void AddActiveSampler(Sampler *sampler) {
|
||||
ASSERT(!SamplerRegistry::sampler);
|
||||
SamplerRegistry::sampler = sampler;
|
||||
}
|
||||
static void RemoveActiveSampler(Sampler *sampler) {
|
||||
SamplerRegistry::sampler = NULL;
|
||||
}
|
||||
static Sampler *sampler;
|
||||
};
|
||||
|
||||
Sampler *SamplerRegistry::sampler = NULL;
|
||||
|
||||
static mozilla::Atomic<ThreadProfile*> sCurrentThreadProfile;
|
||||
static sem_t sSignalHandlingDone;
|
||||
|
||||
static void ProfilerSaveSignalHandler(int signal, siginfo_t* info, void* context) {
|
||||
Sampler::GetActiveSampler()->RequestSave();
|
||||
}
|
||||
|
||||
static void SetSampleContext(TickSample* sample, void* context)
|
||||
{
|
||||
// Extracting the sample from the context is extremely machine dependent.
|
||||
ucontext_t* ucontext = reinterpret_cast<ucontext_t*>(context);
|
||||
mcontext_t& mcontext = ucontext->uc_mcontext;
|
||||
#if V8_HOST_ARCH_IA32
|
||||
sample->pc = reinterpret_cast<Address>(mcontext.gregs[REG_EIP]);
|
||||
sample->sp = reinterpret_cast<Address>(mcontext.gregs[REG_ESP]);
|
||||
sample->fp = reinterpret_cast<Address>(mcontext.gregs[REG_EBP]);
|
||||
#elif V8_HOST_ARCH_X64
|
||||
sample->pc = reinterpret_cast<Address>(mcontext.gregs[REG_RIP]);
|
||||
sample->sp = reinterpret_cast<Address>(mcontext.gregs[REG_RSP]);
|
||||
sample->fp = reinterpret_cast<Address>(mcontext.gregs[REG_RBP]);
|
||||
#elif V8_HOST_ARCH_ARM
|
||||
// An undefined macro evaluates to 0, so this applies to Android's Bionic also.
|
||||
#if !defined(ANDROID) && (__GLIBC__ < 2 || (__GLIBC__ == 2 && __GLIBC_MINOR__ <= 3))
|
||||
sample->pc = reinterpret_cast<Address>(mcontext.gregs[R15]);
|
||||
sample->sp = reinterpret_cast<Address>(mcontext.gregs[R13]);
|
||||
sample->fp = reinterpret_cast<Address>(mcontext.gregs[R11]);
|
||||
#ifdef ENABLE_ARM_LR_SAVING
|
||||
sample->lr = reinterpret_cast<Address>(mcontext.gregs[R14]);
|
||||
#endif
|
||||
#else
|
||||
sample->pc = reinterpret_cast<Address>(mcontext.arm_pc);
|
||||
sample->sp = reinterpret_cast<Address>(mcontext.arm_sp);
|
||||
sample->fp = reinterpret_cast<Address>(mcontext.arm_fp);
|
||||
#ifdef ENABLE_ARM_LR_SAVING
|
||||
sample->lr = reinterpret_cast<Address>(mcontext.arm_lr);
|
||||
#endif
|
||||
#endif
|
||||
#elif V8_HOST_ARCH_MIPS
|
||||
// Implement this on MIPS.
|
||||
UNIMPLEMENTED();
|
||||
#endif
|
||||
}
|
||||
|
||||
#ifdef ANDROID
|
||||
#define V8_HOST_ARCH_ARM 1
|
||||
#define SYS_gettid __NR_gettid
|
||||
#define SYS_tgkill __NR_tgkill
|
||||
#else
|
||||
#define V8_HOST_ARCH_X64 1
|
||||
#endif
|
||||
|
||||
namespace {
|
||||
|
||||
void ProfilerSignalHandler(int signal, siginfo_t* info, void* context) {
|
||||
// Avoid TSan warning about clobbering errno.
|
||||
int savedErrno = errno;
|
||||
|
||||
if (!Sampler::GetActiveSampler()) {
|
||||
sem_post(&sSignalHandlingDone);
|
||||
errno = savedErrno;
|
||||
return;
|
||||
}
|
||||
|
||||
TickSample sample_obj;
|
||||
TickSample* sample = &sample_obj;
|
||||
sample->context = context;
|
||||
|
||||
// If profiling, we extract the current pc and sp.
|
||||
if (Sampler::GetActiveSampler()->IsProfiling()) {
|
||||
SetSampleContext(sample, context);
|
||||
}
|
||||
sample->threadProfile = sCurrentThreadProfile;
|
||||
sample->timestamp = mozilla::TimeStamp::Now();
|
||||
sample->rssMemory = sample->threadProfile->mRssMemory;
|
||||
sample->ussMemory = sample->threadProfile->mUssMemory;
|
||||
|
||||
Sampler::GetActiveSampler()->Tick(sample);
|
||||
|
||||
sCurrentThreadProfile = NULL;
|
||||
sem_post(&sSignalHandlingDone);
|
||||
errno = savedErrno;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
static void ProfilerSignalThread(ThreadProfile *profile,
|
||||
bool isFirstProfiledThread)
|
||||
{
|
||||
if (isFirstProfiledThread && Sampler::GetActiveSampler()->ProfileMemory()) {
|
||||
profile->mRssMemory = nsMemoryReporterManager::ResidentFast();
|
||||
profile->mUssMemory = nsMemoryReporterManager::ResidentUnique();
|
||||
} else {
|
||||
profile->mRssMemory = 0;
|
||||
profile->mUssMemory = 0;
|
||||
}
|
||||
}
|
||||
|
||||
int tgkill(pid_t tgid, pid_t tid, int signalno) {
|
||||
return syscall(SYS_tgkill, tgid, tid, signalno);
|
||||
}
|
||||
|
||||
class PlatformData {
|
||||
public:
|
||||
PlatformData()
|
||||
{
|
||||
MOZ_COUNT_CTOR(PlatformData);
|
||||
}
|
||||
|
||||
~PlatformData()
|
||||
{
|
||||
MOZ_COUNT_DTOR(PlatformData);
|
||||
}
|
||||
};
|
||||
|
||||
/* static */ PlatformData*
|
||||
Sampler::AllocPlatformData(int aThreadId)
|
||||
{
|
||||
return new PlatformData;
|
||||
}
|
||||
|
||||
/* static */ void
|
||||
Sampler::FreePlatformData(PlatformData* aData)
|
||||
{
|
||||
delete aData;
|
||||
}
|
||||
|
||||
static void* SignalSender(void* arg) {
|
||||
// Taken from platform_thread_posix.cc
|
||||
prctl(PR_SET_NAME, "SamplerThread", 0, 0, 0);
|
||||
|
||||
int vm_tgid_ = getpid();
|
||||
DebugOnly<int> my_tid = gettid();
|
||||
|
||||
unsigned int nSignalsSent = 0;
|
||||
|
||||
TimeDuration lastSleepOverhead = 0;
|
||||
TimeStamp sampleStart = TimeStamp::Now();
|
||||
while (SamplerRegistry::sampler->IsActive()) {
|
||||
|
||||
SamplerRegistry::sampler->HandleSaveRequest();
|
||||
SamplerRegistry::sampler->DeleteExpiredMarkers();
|
||||
|
||||
if (!SamplerRegistry::sampler->IsPaused()) {
|
||||
::MutexAutoLock lock(*Sampler::sRegisteredThreadsMutex);
|
||||
std::vector<ThreadInfo*> threads =
|
||||
SamplerRegistry::sampler->GetRegisteredThreads();
|
||||
|
||||
bool isFirstProfiledThread = true;
|
||||
for (uint32_t i = 0; i < threads.size(); i++) {
|
||||
ThreadInfo* info = threads[i];
|
||||
|
||||
// This will be null if we're not interested in profiling this thread.
|
||||
if (!info->Profile() || info->IsPendingDelete())
|
||||
continue;
|
||||
|
||||
PseudoStack::SleepState sleeping = info->Stack()->observeSleeping();
|
||||
if (sleeping == PseudoStack::SLEEPING_AGAIN) {
|
||||
info->Profile()->DuplicateLastSample();
|
||||
continue;
|
||||
}
|
||||
|
||||
info->Profile()->GetThreadResponsiveness()->Update();
|
||||
|
||||
// We use sCurrentThreadProfile the ThreadProfile for the
|
||||
// thread we're profiling to the signal handler
|
||||
sCurrentThreadProfile = info->Profile();
|
||||
|
||||
int threadId = info->ThreadId();
|
||||
MOZ_ASSERT(threadId != my_tid);
|
||||
|
||||
// Profile from the signal sender for information which is not signal
|
||||
// safe, and will have low variation between the emission of the signal
|
||||
// and the signal handler catch.
|
||||
ProfilerSignalThread(sCurrentThreadProfile, isFirstProfiledThread);
|
||||
|
||||
// Profile from the signal handler for information which is signal safe
|
||||
// and needs to be precise too, such as the stack of the interrupted
|
||||
// thread.
|
||||
if (tgkill(vm_tgid_, threadId, SIGPROF) != 0) {
|
||||
printf_stderr("profiler failed to signal tid=%d\n", threadId);
|
||||
#ifdef DEBUG
|
||||
abort();
|
||||
#else
|
||||
continue;
|
||||
#endif
|
||||
}
|
||||
|
||||
// Wait for the signal handler to run before moving on to the next one
|
||||
sem_wait(&sSignalHandlingDone);
|
||||
isFirstProfiledThread = false;
|
||||
|
||||
// The LUL unwind object accumulates frame statistics.
|
||||
// Periodically we should poke it to give it a chance to print
|
||||
// those statistics. This involves doing I/O (fprintf,
|
||||
// __android_log_print, etc) and so can't safely be done from
|
||||
// the unwinder threads, which is why it is done here.
|
||||
if ((++nSignalsSent & 0xF) == 0) {
|
||||
# if defined(USE_LUL_STACKWALK)
|
||||
sLUL->MaybeShowStats();
|
||||
# endif
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TimeStamp targetSleepEndTime = sampleStart + TimeDuration::FromMicroseconds(SamplerRegistry::sampler->interval() * 1000);
|
||||
TimeStamp beforeSleep = TimeStamp::Now();
|
||||
TimeDuration targetSleepDuration = targetSleepEndTime - beforeSleep;
|
||||
double sleepTime = std::max(0.0, (targetSleepDuration - lastSleepOverhead).ToMicroseconds());
|
||||
OS::SleepMicro(sleepTime);
|
||||
sampleStart = TimeStamp::Now();
|
||||
lastSleepOverhead = sampleStart - (beforeSleep + TimeDuration::FromMicroseconds(sleepTime));
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
Sampler::Sampler(double interval, bool profiling, int entrySize)
|
||||
: interval_(interval),
|
||||
profiling_(profiling),
|
||||
paused_(false),
|
||||
active_(false),
|
||||
entrySize_(entrySize) {
|
||||
MOZ_COUNT_CTOR(Sampler);
|
||||
}
|
||||
|
||||
Sampler::~Sampler() {
|
||||
MOZ_COUNT_DTOR(Sampler);
|
||||
ASSERT(!signal_sender_launched_);
|
||||
}
|
||||
|
||||
|
||||
void Sampler::Start() {
|
||||
LOG("Sampler started");
|
||||
|
||||
#if defined(USE_EHABI_STACKWALK)
|
||||
mozilla::EHABIStackWalkInit();
|
||||
#elif defined(USE_LUL_STACKWALK)
|
||||
// NOTE: this isn't thread-safe. But we expect Sampler::Start to be
|
||||
// called only from the main thread, so this is OK in general.
|
||||
if (!sLUL) {
|
||||
sLUL_initialization_routine();
|
||||
}
|
||||
#endif
|
||||
|
||||
SamplerRegistry::AddActiveSampler(this);
|
||||
|
||||
// Initialize signal handler communication
|
||||
sCurrentThreadProfile = NULL;
|
||||
if (sem_init(&sSignalHandlingDone, /* pshared: */ 0, /* value: */ 0) != 0) {
|
||||
LOG("Error initializing semaphore");
|
||||
return;
|
||||
}
|
||||
|
||||
// Request profiling signals.
|
||||
LOG("Request signal");
|
||||
struct sigaction sa;
|
||||
sa.sa_sigaction = MOZ_SIGNAL_TRAMPOLINE(ProfilerSignalHandler);
|
||||
sigemptyset(&sa.sa_mask);
|
||||
sa.sa_flags = SA_RESTART | SA_SIGINFO;
|
||||
if (sigaction(SIGPROF, &sa, &old_sigprof_signal_handler_) != 0) {
|
||||
LOG("Error installing signal");
|
||||
return;
|
||||
}
|
||||
|
||||
// Request save profile signals
|
||||
struct sigaction sa2;
|
||||
sa2.sa_sigaction = ProfilerSaveSignalHandler;
|
||||
sigemptyset(&sa2.sa_mask);
|
||||
sa2.sa_flags = SA_RESTART | SA_SIGINFO;
|
||||
if (sigaction(SIGNAL_SAVE_PROFILE, &sa2, &old_sigsave_signal_handler_) != 0) {
|
||||
LOG("Error installing start signal");
|
||||
return;
|
||||
}
|
||||
LOG("Signal installed");
|
||||
signal_handler_installed_ = true;
|
||||
|
||||
#if defined(USE_LUL_STACKWALK)
|
||||
// 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.
|
||||
sLUL->EnableUnwinding();
|
||||
|
||||
// Has a test been requested?
|
||||
if (PR_GetEnv("MOZ_PROFILER_LUL_TEST")) {
|
||||
int nTests = 0, nTestsPassed = 0;
|
||||
RunLulUnitTests(&nTests, &nTestsPassed, sLUL);
|
||||
}
|
||||
#endif
|
||||
|
||||
// Start a thread that sends SIGPROF signal to VM thread.
|
||||
// Sending the signal ourselves instead of relying on itimer provides
|
||||
// much better accuracy.
|
||||
SetActive(true);
|
||||
if (pthread_create(
|
||||
&signal_sender_thread_, NULL, SignalSender, NULL) == 0) {
|
||||
signal_sender_launched_ = true;
|
||||
}
|
||||
LOG("Profiler thread started");
|
||||
}
|
||||
|
||||
|
||||
void Sampler::Stop() {
|
||||
SetActive(false);
|
||||
|
||||
// Wait for signal sender termination (it will exit after setting
|
||||
// active_ to false).
|
||||
if (signal_sender_launched_) {
|
||||
pthread_join(signal_sender_thread_, NULL);
|
||||
signal_sender_launched_ = false;
|
||||
}
|
||||
|
||||
SamplerRegistry::RemoveActiveSampler(this);
|
||||
|
||||
// Restore old signal handler
|
||||
if (signal_handler_installed_) {
|
||||
sigaction(SIGNAL_SAVE_PROFILE, &old_sigsave_signal_handler_, 0);
|
||||
sigaction(SIGPROF, &old_sigprof_signal_handler_, 0);
|
||||
signal_handler_installed_ = false;
|
||||
}
|
||||
}
|
||||
|
||||
bool Sampler::RegisterCurrentThread(const char* aName,
|
||||
PseudoStack* aPseudoStack,
|
||||
bool aIsMainThread, void* stackTop)
|
||||
{
|
||||
if (!Sampler::sRegisteredThreadsMutex)
|
||||
return false;
|
||||
|
||||
::MutexAutoLock lock(*Sampler::sRegisteredThreadsMutex);
|
||||
|
||||
int id = gettid();
|
||||
for (uint32_t i = 0; i < sRegisteredThreads->size(); i++) {
|
||||
ThreadInfo* info = sRegisteredThreads->at(i);
|
||||
if (info->ThreadId() == id && !info->IsPendingDelete()) {
|
||||
// Thread already registered. This means the first unregister will be
|
||||
// too early.
|
||||
ASSERT(false);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
set_tls_stack_top(stackTop);
|
||||
|
||||
ThreadInfo* info = new StackOwningThreadInfo(aName, id,
|
||||
aIsMainThread, aPseudoStack, stackTop);
|
||||
|
||||
if (sActiveSampler) {
|
||||
sActiveSampler->RegisterThread(info);
|
||||
}
|
||||
|
||||
sRegisteredThreads->push_back(info);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void Sampler::UnregisterCurrentThread()
|
||||
{
|
||||
if (!Sampler::sRegisteredThreadsMutex)
|
||||
return;
|
||||
|
||||
tlsStackTop.set(nullptr);
|
||||
|
||||
::MutexAutoLock lock(*Sampler::sRegisteredThreadsMutex);
|
||||
|
||||
int id = gettid();
|
||||
|
||||
for (uint32_t i = 0; i < sRegisteredThreads->size(); i++) {
|
||||
ThreadInfo* info = sRegisteredThreads->at(i);
|
||||
if (info->ThreadId() == id && !info->IsPendingDelete()) {
|
||||
if (profiler_is_active()) {
|
||||
// We still want to show the results of this thread if you
|
||||
// save the profile shortly after a thread is terminated.
|
||||
// For now we will defer the delete to profile stop.
|
||||
info->SetPendingDelete();
|
||||
break;
|
||||
} else {
|
||||
delete info;
|
||||
sRegisteredThreads->erase(sRegisteredThreads->begin() + i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef ANDROID
|
||||
static struct sigaction old_sigstart_signal_handler;
|
||||
const int SIGSTART = SIGUSR2;
|
||||
|
||||
static void freeArray(const char** array, int size) {
|
||||
for (int i = 0; i < size; i++) {
|
||||
free((void*) array[i]);
|
||||
}
|
||||
}
|
||||
|
||||
static uint32_t readCSVArray(char* csvList, const char** buffer) {
|
||||
uint32_t count;
|
||||
char* savePtr;
|
||||
int newlinePos = strlen(csvList) - 1;
|
||||
if (csvList[newlinePos] == '\n') {
|
||||
csvList[newlinePos] = '\0';
|
||||
}
|
||||
|
||||
char* item = strtok_r(csvList, ",", &savePtr);
|
||||
for (count = 0; item; item = strtok_r(NULL, ",", &savePtr)) {
|
||||
int length = strlen(item) + 1; // Include \0
|
||||
char* newBuf = (char*) malloc(sizeof(char) * length);
|
||||
buffer[count] = newBuf;
|
||||
strncpy(newBuf, item, length);
|
||||
count++;
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
// Currently support only the env variables
|
||||
// reported in read_profiler_env
|
||||
static void ReadProfilerVars(const char* fileName, const char** features,
|
||||
uint32_t* featureCount, const char** threadNames, uint32_t* threadCount) {
|
||||
FILE* file = fopen(fileName, "r");
|
||||
const int bufferSize = 1024;
|
||||
char line[bufferSize];
|
||||
char* feature;
|
||||
char* value;
|
||||
char* savePtr;
|
||||
|
||||
if (file) {
|
||||
while (fgets(line, bufferSize, file) != NULL) {
|
||||
feature = strtok_r(line, "=", &savePtr);
|
||||
value = strtok_r(NULL, "", &savePtr);
|
||||
|
||||
if (strncmp(feature, PROFILER_INTERVAL, bufferSize) == 0) {
|
||||
set_profiler_interval(value);
|
||||
} else if (strncmp(feature, PROFILER_ENTRIES, bufferSize) == 0) {
|
||||
set_profiler_entries(value);
|
||||
} else if (strncmp(feature, PROFILER_STACK, bufferSize) == 0) {
|
||||
set_profiler_scan(value);
|
||||
} else if (strncmp(feature, PROFILER_FEATURES, bufferSize) == 0) {
|
||||
*featureCount = readCSVArray(value, features);
|
||||
} else if (strncmp(feature, "threads", bufferSize) == 0) {
|
||||
*threadCount = readCSVArray(value, threadNames);
|
||||
}
|
||||
}
|
||||
|
||||
fclose(file);
|
||||
}
|
||||
}
|
||||
|
||||
static void DoStartTask() {
|
||||
uint32_t featureCount = 0;
|
||||
uint32_t threadCount = 0;
|
||||
|
||||
// Just allocate 10 features for now
|
||||
// FIXME: these don't really point to const chars*
|
||||
// So we free them later, but we don't want to change the const char**
|
||||
// declaration in profiler_start. Annoying but ok for now.
|
||||
const char* threadNames[10];
|
||||
const char* features[10];
|
||||
const char* profilerConfigFile = "/data/local/tmp/profiler.options";
|
||||
|
||||
ReadProfilerVars(profilerConfigFile, features, &featureCount, threadNames, &threadCount);
|
||||
MOZ_ASSERT(featureCount < 10);
|
||||
MOZ_ASSERT(threadCount < 10);
|
||||
|
||||
profiler_start(PROFILE_DEFAULT_ENTRY, 1,
|
||||
features, featureCount,
|
||||
threadNames, threadCount);
|
||||
|
||||
freeArray(threadNames, threadCount);
|
||||
freeArray(features, featureCount);
|
||||
}
|
||||
|
||||
static void StartSignalHandler(int signal, siginfo_t* info, void* context) {
|
||||
class StartTask : public Runnable {
|
||||
public:
|
||||
NS_IMETHOD Run() override {
|
||||
DoStartTask();
|
||||
return NS_OK;
|
||||
}
|
||||
};
|
||||
// XXX: technically NS_DispatchToMainThread is NOT async signal safe. We risk
|
||||
// nasty things like deadlocks, but the probability is very low and we
|
||||
// typically only do this once so it tends to be ok. See bug 909403.
|
||||
NS_DispatchToMainThread(new StartTask());
|
||||
}
|
||||
|
||||
void OS::Startup()
|
||||
{
|
||||
LOG("Registering start signal");
|
||||
struct sigaction sa;
|
||||
sa.sa_sigaction = StartSignalHandler;
|
||||
sigemptyset(&sa.sa_mask);
|
||||
sa.sa_flags = SA_RESTART | SA_SIGINFO;
|
||||
if (sigaction(SIGSTART, &sa, &old_sigstart_signal_handler) != 0) {
|
||||
LOG("Error installing signal");
|
||||
}
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
void OS::Startup() {
|
||||
// Set up the fork handlers.
|
||||
setup_atfork();
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
|
||||
|
||||
void TickSample::PopulateContext(void* aContext)
|
||||
{
|
||||
MOZ_ASSERT(aContext);
|
||||
ucontext_t* pContext = reinterpret_cast<ucontext_t*>(aContext);
|
||||
if (!getcontext(pContext)) {
|
||||
context = pContext;
|
||||
SetSampleContext(this, aContext);
|
||||
}
|
||||
}
|
||||
|
||||
void OS::SleepMicro(int microseconds)
|
||||
{
|
||||
if (MOZ_UNLIKELY(microseconds >= 1000000)) {
|
||||
// Use usleep for larger intervals, because the nanosleep
|
||||
// code below only supports intervals < 1 second.
|
||||
MOZ_ALWAYS_TRUE(!::usleep(microseconds));
|
||||
return;
|
||||
}
|
||||
|
||||
struct timespec ts;
|
||||
ts.tv_sec = 0;
|
||||
ts.tv_nsec = microseconds * 1000UL;
|
||||
|
||||
int rv = ::nanosleep(&ts, &ts);
|
||||
|
||||
while (rv != 0 && errno == EINTR) {
|
||||
// Keep waiting in case of interrupt.
|
||||
// nanosleep puts the remaining time back into ts.
|
||||
rv = ::nanosleep(&ts, &ts);
|
||||
}
|
||||
|
||||
MOZ_ASSERT(!rv, "nanosleep call failed");
|
||||
}
|
||||
469
tools/profiler/core/platform-macos.cc
Normal file
469
tools/profiler/core/platform-macos.cc
Normal file
|
|
@ -0,0 +1,469 @@
|
|||
/* 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 <dlfcn.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/mman.h>
|
||||
#include <mach/mach_init.h>
|
||||
#include <mach-o/dyld.h>
|
||||
#include <mach-o/getsect.h>
|
||||
|
||||
#include <AvailabilityMacros.h>
|
||||
|
||||
#include <pthread.h>
|
||||
#include <semaphore.h>
|
||||
#include <signal.h>
|
||||
#include <libkern/OSAtomic.h>
|
||||
#include <mach/mach.h>
|
||||
#include <mach/semaphore.h>
|
||||
#include <mach/task.h>
|
||||
#include <mach/vm_statistics.h>
|
||||
#include <sys/time.h>
|
||||
#include <sys/resource.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/sysctl.h>
|
||||
#include <stdarg.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <errno.h>
|
||||
#include <math.h>
|
||||
|
||||
#ifndef SPS_STANDALONE
|
||||
#include "ThreadResponsiveness.h"
|
||||
#include "nsThreadUtils.h"
|
||||
|
||||
// Memory profile
|
||||
#include "nsMemoryReporterManager.h"
|
||||
#endif
|
||||
|
||||
#include "platform.h"
|
||||
#include "GeckoSampler.h"
|
||||
#include "mozilla/TimeStamp.h"
|
||||
|
||||
using mozilla::TimeStamp;
|
||||
using mozilla::TimeDuration;
|
||||
|
||||
// this port is based off of v8 svn revision 9837
|
||||
|
||||
// XXX: this is a very stubbed out implementation
|
||||
// that only supports a single Sampler
|
||||
struct SamplerRegistry {
|
||||
static void AddActiveSampler(Sampler *sampler) {
|
||||
ASSERT(!SamplerRegistry::sampler);
|
||||
SamplerRegistry::sampler = sampler;
|
||||
}
|
||||
static void RemoveActiveSampler(Sampler *sampler) {
|
||||
SamplerRegistry::sampler = NULL;
|
||||
}
|
||||
static Sampler *sampler;
|
||||
};
|
||||
|
||||
Sampler *SamplerRegistry::sampler = NULL;
|
||||
|
||||
#ifdef DEBUG
|
||||
// 0 is never a valid thread id on MacOSX since a pthread_t is a pointer.
|
||||
static const pthread_t kNoThread = (pthread_t) 0;
|
||||
#endif
|
||||
|
||||
void OS::Startup() {
|
||||
}
|
||||
|
||||
void OS::Sleep(int milliseconds) {
|
||||
usleep(1000 * milliseconds);
|
||||
}
|
||||
|
||||
void OS::SleepMicro(int microseconds) {
|
||||
usleep(microseconds);
|
||||
}
|
||||
|
||||
Thread::Thread(const char* name)
|
||||
: stack_size_(0) {
|
||||
set_name(name);
|
||||
}
|
||||
|
||||
|
||||
Thread::~Thread() {
|
||||
}
|
||||
|
||||
|
||||
static void SetThreadName(const char* name) {
|
||||
// pthread_setname_np is only available in 10.6 or later, so test
|
||||
// for it at runtime.
|
||||
int (*dynamic_pthread_setname_np)(const char*);
|
||||
*reinterpret_cast<void**>(&dynamic_pthread_setname_np) =
|
||||
dlsym(RTLD_DEFAULT, "pthread_setname_np");
|
||||
if (!dynamic_pthread_setname_np)
|
||||
return;
|
||||
|
||||
// Mac OS X does not expose the length limit of the name, so hardcode it.
|
||||
static const int kMaxNameLength = 63;
|
||||
USE(kMaxNameLength);
|
||||
ASSERT(Thread::kMaxThreadNameLength <= kMaxNameLength);
|
||||
dynamic_pthread_setname_np(name);
|
||||
}
|
||||
|
||||
|
||||
static void* ThreadEntry(void* arg) {
|
||||
Thread* thread = reinterpret_cast<Thread*>(arg);
|
||||
|
||||
thread->thread_ = pthread_self();
|
||||
SetThreadName(thread->name());
|
||||
ASSERT(thread->thread_ != kNoThread);
|
||||
thread->Run();
|
||||
return NULL;
|
||||
}
|
||||
|
||||
|
||||
void Thread::set_name(const char* name) {
|
||||
strncpy(name_, name, sizeof(name_));
|
||||
name_[sizeof(name_) - 1] = '\0';
|
||||
}
|
||||
|
||||
|
||||
void Thread::Start() {
|
||||
pthread_attr_t* attr_ptr = NULL;
|
||||
pthread_attr_t attr;
|
||||
if (stack_size_ > 0) {
|
||||
pthread_attr_init(&attr);
|
||||
pthread_attr_setstacksize(&attr, static_cast<size_t>(stack_size_));
|
||||
attr_ptr = &attr;
|
||||
}
|
||||
pthread_create(&thread_, attr_ptr, ThreadEntry, this);
|
||||
ASSERT(thread_ != kNoThread);
|
||||
}
|
||||
|
||||
void Thread::Join() {
|
||||
pthread_join(thread_, NULL);
|
||||
}
|
||||
|
||||
class PlatformData {
|
||||
public:
|
||||
PlatformData() : profiled_thread_(mach_thread_self())
|
||||
{
|
||||
profiled_pthread_ = pthread_from_mach_thread_np(profiled_thread_);
|
||||
}
|
||||
|
||||
~PlatformData() {
|
||||
// Deallocate Mach port for thread.
|
||||
mach_port_deallocate(mach_task_self(), profiled_thread_);
|
||||
}
|
||||
|
||||
thread_act_t profiled_thread() { return profiled_thread_; }
|
||||
pthread_t profiled_pthread() { return profiled_pthread_; }
|
||||
|
||||
private:
|
||||
// Note: for profiled_thread_ Mach primitives are used instead of PThread's
|
||||
// because the latter doesn't provide thread manipulation primitives required.
|
||||
// For details, consult "Mac OS X Internals" book, Section 7.3.
|
||||
thread_act_t profiled_thread_;
|
||||
// we also store the pthread because Mach threads have no concept of stack
|
||||
// and we want to be able to get the stack size when we need to unwind the
|
||||
// stack using frame pointers.
|
||||
pthread_t profiled_pthread_;
|
||||
};
|
||||
|
||||
/* static */ PlatformData*
|
||||
Sampler::AllocPlatformData(int aThreadId)
|
||||
{
|
||||
return new PlatformData;
|
||||
}
|
||||
|
||||
/* static */ void
|
||||
Sampler::FreePlatformData(PlatformData* aData)
|
||||
{
|
||||
delete aData;
|
||||
}
|
||||
|
||||
class SamplerThread : public Thread {
|
||||
public:
|
||||
explicit SamplerThread(double interval)
|
||||
: Thread("SamplerThread")
|
||||
, intervalMicro_(floor(interval * 1000 + 0.5))
|
||||
{
|
||||
if (intervalMicro_ <= 0) {
|
||||
intervalMicro_ = 1;
|
||||
}
|
||||
}
|
||||
|
||||
static void AddActiveSampler(Sampler* sampler) {
|
||||
SamplerRegistry::AddActiveSampler(sampler);
|
||||
if (instance_ == NULL) {
|
||||
instance_ = new SamplerThread(sampler->interval());
|
||||
instance_->Start();
|
||||
}
|
||||
}
|
||||
|
||||
static void RemoveActiveSampler(Sampler* sampler) {
|
||||
instance_->Join();
|
||||
//XXX: unlike v8 we need to remove the active sampler after doing the Join
|
||||
// because we drop the sampler immediately
|
||||
SamplerRegistry::RemoveActiveSampler(sampler);
|
||||
delete instance_;
|
||||
instance_ = NULL;
|
||||
}
|
||||
|
||||
// Implement Thread::Run().
|
||||
virtual void Run() {
|
||||
TimeDuration lastSleepOverhead = 0;
|
||||
TimeStamp sampleStart = TimeStamp::Now();
|
||||
while (SamplerRegistry::sampler->IsActive()) {
|
||||
SamplerRegistry::sampler->DeleteExpiredMarkers();
|
||||
if (!SamplerRegistry::sampler->IsPaused()) {
|
||||
::MutexAutoLock lock(*Sampler::sRegisteredThreadsMutex);
|
||||
std::vector<ThreadInfo*> threads =
|
||||
SamplerRegistry::sampler->GetRegisteredThreads();
|
||||
bool isFirstProfiledThread = true;
|
||||
for (uint32_t i = 0; i < threads.size(); i++) {
|
||||
ThreadInfo* info = threads[i];
|
||||
|
||||
// This will be null if we're not interested in profiling this thread.
|
||||
if (!info->Profile() || info->IsPendingDelete())
|
||||
continue;
|
||||
|
||||
PseudoStack::SleepState sleeping = info->Stack()->observeSleeping();
|
||||
if (sleeping == PseudoStack::SLEEPING_AGAIN) {
|
||||
info->Profile()->DuplicateLastSample();
|
||||
continue;
|
||||
}
|
||||
|
||||
#ifndef SPS_STANDALONE
|
||||
info->Profile()->GetThreadResponsiveness()->Update();
|
||||
#endif
|
||||
|
||||
ThreadProfile* thread_profile = info->Profile();
|
||||
|
||||
SampleContext(SamplerRegistry::sampler, thread_profile,
|
||||
isFirstProfiledThread);
|
||||
isFirstProfiledThread = false;
|
||||
}
|
||||
}
|
||||
|
||||
TimeStamp targetSleepEndTime = sampleStart + TimeDuration::FromMicroseconds(intervalMicro_);
|
||||
TimeStamp beforeSleep = TimeStamp::Now();
|
||||
TimeDuration targetSleepDuration = targetSleepEndTime - beforeSleep;
|
||||
double sleepTime = std::max(0.0, (targetSleepDuration - lastSleepOverhead).ToMicroseconds());
|
||||
OS::SleepMicro(sleepTime);
|
||||
sampleStart = TimeStamp::Now();
|
||||
lastSleepOverhead = sampleStart - (beforeSleep + TimeDuration::FromMicroseconds(sleepTime));
|
||||
}
|
||||
}
|
||||
|
||||
void SampleContext(Sampler* sampler, ThreadProfile* thread_profile,
|
||||
bool isFirstProfiledThread)
|
||||
{
|
||||
thread_act_t profiled_thread =
|
||||
thread_profile->GetPlatformData()->profiled_thread();
|
||||
|
||||
TickSample sample_obj;
|
||||
TickSample* sample = &sample_obj;
|
||||
|
||||
// Unique Set Size is not supported on Mac.
|
||||
sample->ussMemory = 0;
|
||||
sample->rssMemory = 0;
|
||||
|
||||
#ifndef SPS_STANDALONE
|
||||
if (isFirstProfiledThread && Sampler::GetActiveSampler()->ProfileMemory()) {
|
||||
sample->rssMemory = nsMemoryReporterManager::ResidentFast();
|
||||
}
|
||||
#endif
|
||||
|
||||
// We're using thread_suspend on OS X because pthread_kill (which is what
|
||||
// we're using on Linux) has less consistent performance and causes
|
||||
// strange crashes, see bug 1166778 and bug 1166808.
|
||||
|
||||
if (KERN_SUCCESS != thread_suspend(profiled_thread)) return;
|
||||
|
||||
#if V8_HOST_ARCH_X64
|
||||
thread_state_flavor_t flavor = x86_THREAD_STATE64;
|
||||
x86_thread_state64_t state;
|
||||
mach_msg_type_number_t count = x86_THREAD_STATE64_COUNT;
|
||||
#if __DARWIN_UNIX03
|
||||
#define REGISTER_FIELD(name) __r ## name
|
||||
#else
|
||||
#define REGISTER_FIELD(name) r ## name
|
||||
#endif // __DARWIN_UNIX03
|
||||
#elif V8_HOST_ARCH_IA32
|
||||
thread_state_flavor_t flavor = i386_THREAD_STATE;
|
||||
i386_thread_state_t state;
|
||||
mach_msg_type_number_t count = i386_THREAD_STATE_COUNT;
|
||||
#if __DARWIN_UNIX03
|
||||
#define REGISTER_FIELD(name) __e ## name
|
||||
#else
|
||||
#define REGISTER_FIELD(name) e ## name
|
||||
#endif // __DARWIN_UNIX03
|
||||
#else
|
||||
#error Unsupported Mac OS X host architecture.
|
||||
#endif // V8_HOST_ARCH
|
||||
|
||||
if (thread_get_state(profiled_thread,
|
||||
flavor,
|
||||
reinterpret_cast<natural_t*>(&state),
|
||||
&count) == KERN_SUCCESS) {
|
||||
sample->pc = reinterpret_cast<Address>(state.REGISTER_FIELD(ip));
|
||||
sample->sp = reinterpret_cast<Address>(state.REGISTER_FIELD(sp));
|
||||
sample->fp = reinterpret_cast<Address>(state.REGISTER_FIELD(bp));
|
||||
sample->timestamp = mozilla::TimeStamp::Now();
|
||||
sample->threadProfile = thread_profile;
|
||||
|
||||
sampler->Tick(sample);
|
||||
}
|
||||
thread_resume(profiled_thread);
|
||||
}
|
||||
|
||||
int intervalMicro_;
|
||||
//RuntimeProfilerRateLimiter rate_limiter_;
|
||||
|
||||
static SamplerThread* instance_;
|
||||
|
||||
DISALLOW_COPY_AND_ASSIGN(SamplerThread);
|
||||
};
|
||||
|
||||
#undef REGISTER_FIELD
|
||||
|
||||
SamplerThread* SamplerThread::instance_ = NULL;
|
||||
|
||||
Sampler::Sampler(double interval, bool profiling, int entrySize)
|
||||
: // isolate_(isolate),
|
||||
interval_(interval),
|
||||
profiling_(profiling),
|
||||
paused_(false),
|
||||
active_(false),
|
||||
entrySize_(entrySize) /*,
|
||||
samples_taken_(0)*/ {
|
||||
}
|
||||
|
||||
|
||||
Sampler::~Sampler() {
|
||||
ASSERT(!IsActive());
|
||||
}
|
||||
|
||||
|
||||
void Sampler::Start() {
|
||||
ASSERT(!IsActive());
|
||||
SetActive(true);
|
||||
SamplerThread::AddActiveSampler(this);
|
||||
}
|
||||
|
||||
|
||||
void Sampler::Stop() {
|
||||
ASSERT(IsActive());
|
||||
SetActive(false);
|
||||
SamplerThread::RemoveActiveSampler(this);
|
||||
}
|
||||
|
||||
pthread_t
|
||||
Sampler::GetProfiledThread(PlatformData* aData)
|
||||
{
|
||||
return aData->profiled_pthread();
|
||||
}
|
||||
|
||||
#include <sys/syscall.h>
|
||||
pid_t gettid()
|
||||
{
|
||||
return (pid_t) syscall(SYS_thread_selfid);
|
||||
}
|
||||
|
||||
/* static */ Thread::tid_t
|
||||
Thread::GetCurrentId()
|
||||
{
|
||||
return gettid();
|
||||
}
|
||||
|
||||
bool Sampler::RegisterCurrentThread(const char* aName,
|
||||
PseudoStack* aPseudoStack,
|
||||
bool aIsMainThread, void* stackTop)
|
||||
{
|
||||
if (!Sampler::sRegisteredThreadsMutex)
|
||||
return false;
|
||||
|
||||
|
||||
::MutexAutoLock lock(*Sampler::sRegisteredThreadsMutex);
|
||||
|
||||
int id = gettid();
|
||||
for (uint32_t i = 0; i < sRegisteredThreads->size(); i++) {
|
||||
ThreadInfo* info = sRegisteredThreads->at(i);
|
||||
if (info->ThreadId() == id && !info->IsPendingDelete()) {
|
||||
// Thread already registered. This means the first unregister will be
|
||||
// too early.
|
||||
ASSERT(false);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
set_tls_stack_top(stackTop);
|
||||
|
||||
ThreadInfo* info = new StackOwningThreadInfo(aName, id,
|
||||
aIsMainThread, aPseudoStack, stackTop);
|
||||
|
||||
if (sActiveSampler) {
|
||||
sActiveSampler->RegisterThread(info);
|
||||
}
|
||||
|
||||
sRegisteredThreads->push_back(info);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void Sampler::UnregisterCurrentThread()
|
||||
{
|
||||
if (!Sampler::sRegisteredThreadsMutex)
|
||||
return;
|
||||
|
||||
tlsStackTop.set(nullptr);
|
||||
|
||||
::MutexAutoLock lock(*Sampler::sRegisteredThreadsMutex);
|
||||
|
||||
int id = gettid();
|
||||
|
||||
for (uint32_t i = 0; i < sRegisteredThreads->size(); i++) {
|
||||
ThreadInfo* info = sRegisteredThreads->at(i);
|
||||
if (info->ThreadId() == id && !info->IsPendingDelete()) {
|
||||
if (profiler_is_active()) {
|
||||
// We still want to show the results of this thread if you
|
||||
// save the profile shortly after a thread is terminated.
|
||||
// For now we will defer the delete to profile stop.
|
||||
info->SetPendingDelete();
|
||||
break;
|
||||
} else {
|
||||
delete info;
|
||||
sRegisteredThreads->erase(sRegisteredThreads->begin() + i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void TickSample::PopulateContext(void* aContext)
|
||||
{
|
||||
// Note that this asm changes if PopulateContext's parameter list is altered
|
||||
#if defined(SPS_PLAT_amd64_darwin)
|
||||
asm (
|
||||
// Compute caller's %rsp by adding to %rbp:
|
||||
// 8 bytes for previous %rbp, 8 bytes for return address
|
||||
"leaq 0x10(%%rbp), %0\n\t"
|
||||
// Dereference %rbp to get previous %rbp
|
||||
"movq (%%rbp), %1\n\t"
|
||||
:
|
||||
"=r"(sp),
|
||||
"=r"(fp)
|
||||
);
|
||||
#elif defined(SPS_PLAT_x86_darwin)
|
||||
asm (
|
||||
// Compute caller's %esp by adding to %ebp:
|
||||
// 4 bytes for aContext + 4 bytes for return address +
|
||||
// 4 bytes for previous %ebp
|
||||
"leal 0xc(%%ebp), %0\n\t"
|
||||
// Dereference %ebp to get previous %ebp
|
||||
"movl (%%ebp), %1\n\t"
|
||||
:
|
||||
"=r"(sp),
|
||||
"=r"(fp)
|
||||
);
|
||||
#else
|
||||
# error "Unsupported architecture"
|
||||
#endif
|
||||
pc = reinterpret_cast<Address>(__builtin_extract_return_addr(
|
||||
__builtin_return_address(0)));
|
||||
}
|
||||
|
||||
431
tools/profiler/core/platform-win32.cc
Normal file
431
tools/profiler/core/platform-win32.cc
Normal file
|
|
@ -0,0 +1,431 @@
|
|||
// Copyright (c) 2006-2011 The Chromium Authors. 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.
|
||||
|
||||
#include <windows.h>
|
||||
#include <mmsystem.h>
|
||||
#include <process.h>
|
||||
#include "platform.h"
|
||||
#include "GeckoSampler.h"
|
||||
#include "ThreadResponsiveness.h"
|
||||
#include "ProfileEntry.h"
|
||||
|
||||
// Memory profile
|
||||
#include "nsMemoryReporterManager.h"
|
||||
|
||||
#include "mozilla/StackWalk_windows.h"
|
||||
|
||||
|
||||
class PlatformData {
|
||||
public:
|
||||
// Get a handle to the calling thread. This is the thread that we are
|
||||
// going to profile. We need to make a copy of the handle because we are
|
||||
// going to use it in the sampler thread. Using GetThreadHandle() will
|
||||
// not work in this case. We're using OpenThread because DuplicateHandle
|
||||
// for some reason doesn't work in Chrome's sandbox.
|
||||
PlatformData(int aThreadId) : profiled_thread_(OpenThread(THREAD_GET_CONTEXT |
|
||||
THREAD_SUSPEND_RESUME |
|
||||
THREAD_QUERY_INFORMATION,
|
||||
false,
|
||||
aThreadId)) {}
|
||||
|
||||
~PlatformData() {
|
||||
if (profiled_thread_ != NULL) {
|
||||
CloseHandle(profiled_thread_);
|
||||
profiled_thread_ = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
HANDLE profiled_thread() { return profiled_thread_; }
|
||||
|
||||
private:
|
||||
HANDLE profiled_thread_;
|
||||
};
|
||||
|
||||
/* static */ PlatformData*
|
||||
Sampler::AllocPlatformData(int aThreadId)
|
||||
{
|
||||
return new PlatformData(aThreadId);
|
||||
}
|
||||
|
||||
/* static */ void
|
||||
Sampler::FreePlatformData(PlatformData* aData)
|
||||
{
|
||||
delete aData;
|
||||
}
|
||||
|
||||
uintptr_t
|
||||
Sampler::GetThreadHandle(PlatformData* aData)
|
||||
{
|
||||
return (uintptr_t) aData->profiled_thread();
|
||||
}
|
||||
|
||||
class SamplerThread : public Thread {
|
||||
public:
|
||||
SamplerThread(double interval, Sampler* sampler)
|
||||
: Thread("SamplerThread")
|
||||
, sampler_(sampler)
|
||||
, interval_(interval)
|
||||
{
|
||||
interval_ = floor(interval + 0.5);
|
||||
if (interval_ <= 0) {
|
||||
interval_ = 1;
|
||||
}
|
||||
}
|
||||
|
||||
static void StartSampler(Sampler* sampler) {
|
||||
if (instance_ == NULL) {
|
||||
instance_ = new SamplerThread(sampler->interval(), sampler);
|
||||
instance_->Start();
|
||||
} else {
|
||||
ASSERT(instance_->interval_ == sampler->interval());
|
||||
}
|
||||
}
|
||||
|
||||
static void StopSampler() {
|
||||
instance_->Join();
|
||||
delete instance_;
|
||||
instance_ = NULL;
|
||||
}
|
||||
|
||||
// Implement Thread::Run().
|
||||
virtual void Run() {
|
||||
|
||||
// By default we'll not adjust the timer resolution which tends to be around
|
||||
// 16ms. However, if the requested interval is sufficiently low we'll try to
|
||||
// adjust the resolution to match.
|
||||
if (interval_ < 10)
|
||||
::timeBeginPeriod(interval_);
|
||||
|
||||
while (sampler_->IsActive()) {
|
||||
sampler_->DeleteExpiredMarkers();
|
||||
|
||||
if (!sampler_->IsPaused()) {
|
||||
::MutexAutoLock lock(*Sampler::sRegisteredThreadsMutex);
|
||||
std::vector<ThreadInfo*> threads =
|
||||
sampler_->GetRegisteredThreads();
|
||||
bool isFirstProfiledThread = true;
|
||||
for (uint32_t i = 0; i < threads.size(); i++) {
|
||||
ThreadInfo* info = threads[i];
|
||||
|
||||
// This will be null if we're not interested in profiling this thread.
|
||||
if (!info->Profile() || info->IsPendingDelete())
|
||||
continue;
|
||||
|
||||
PseudoStack::SleepState sleeping = info->Stack()->observeSleeping();
|
||||
if (sleeping == PseudoStack::SLEEPING_AGAIN) {
|
||||
info->Profile()->DuplicateLastSample();
|
||||
continue;
|
||||
}
|
||||
|
||||
info->Profile()->GetThreadResponsiveness()->Update();
|
||||
|
||||
ThreadProfile* thread_profile = info->Profile();
|
||||
|
||||
SampleContext(sampler_, thread_profile, isFirstProfiledThread);
|
||||
isFirstProfiledThread = false;
|
||||
}
|
||||
}
|
||||
OS::Sleep(interval_);
|
||||
}
|
||||
|
||||
// disable any timer resolution changes we've made
|
||||
if (interval_ < 10)
|
||||
::timeEndPeriod(interval_);
|
||||
}
|
||||
|
||||
void SampleContext(Sampler* sampler, ThreadProfile* thread_profile,
|
||||
bool isFirstProfiledThread)
|
||||
{
|
||||
uintptr_t thread = Sampler::GetThreadHandle(
|
||||
thread_profile->GetPlatformData());
|
||||
HANDLE profiled_thread = reinterpret_cast<HANDLE>(thread);
|
||||
if (profiled_thread == NULL)
|
||||
return;
|
||||
|
||||
// Context used for sampling the register state of the profiled thread.
|
||||
CONTEXT context;
|
||||
memset(&context, 0, sizeof(context));
|
||||
|
||||
TickSample sample_obj;
|
||||
TickSample* sample = &sample_obj;
|
||||
|
||||
// Grab the timestamp before pausing the thread, to avoid deadlocks.
|
||||
sample->timestamp = mozilla::TimeStamp::Now();
|
||||
sample->threadProfile = thread_profile;
|
||||
|
||||
if (isFirstProfiledThread && Sampler::GetActiveSampler()->ProfileMemory()) {
|
||||
sample->rssMemory = nsMemoryReporterManager::ResidentFast();
|
||||
} else {
|
||||
sample->rssMemory = 0;
|
||||
}
|
||||
|
||||
// Unique Set Size is not supported on Windows.
|
||||
sample->ussMemory = 0;
|
||||
|
||||
static const DWORD kSuspendFailed = static_cast<DWORD>(-1);
|
||||
if (SuspendThread(profiled_thread) == kSuspendFailed)
|
||||
return;
|
||||
|
||||
// SuspendThread is asynchronous, so the thread may still be running.
|
||||
// Call GetThreadContext first to ensure the thread is really suspended.
|
||||
// See https://blogs.msdn.microsoft.com/oldnewthing/20150205-00/?p=44743.
|
||||
|
||||
// Using only CONTEXT_CONTROL is faster but on 64-bit it causes crashes in
|
||||
// RtlVirtualUnwind (see bug 1120126) so we set all the flags.
|
||||
#if V8_HOST_ARCH_X64
|
||||
context.ContextFlags = CONTEXT_FULL;
|
||||
#else
|
||||
context.ContextFlags = CONTEXT_CONTROL;
|
||||
#endif
|
||||
if (!GetThreadContext(profiled_thread, &context)) {
|
||||
ResumeThread(profiled_thread);
|
||||
return;
|
||||
}
|
||||
|
||||
// Threads that may invoke JS require extra attention. Since, on windows,
|
||||
// the jits also need to modify the same dynamic function table that we need
|
||||
// to get a stack trace, we have to be wary of that to avoid deadlock.
|
||||
//
|
||||
// When embedded in Gecko, for threads that aren't the main thread,
|
||||
// CanInvokeJS consults an unlocked value in the nsIThread, so we must
|
||||
// consult this after suspending the profiled thread to avoid racing
|
||||
// against a value change.
|
||||
if (thread_profile->CanInvokeJS()) {
|
||||
if (!TryAcquireStackWalkWorkaroundLock()) {
|
||||
ResumeThread(profiled_thread);
|
||||
return;
|
||||
}
|
||||
|
||||
// It is safe to immediately drop the lock. We only need to contend with
|
||||
// the case in which the profiled thread held needed system resources.
|
||||
// If the profiled thread had held those resources, the trylock would have
|
||||
// failed. Anyone else who grabs those resources will continue to make
|
||||
// progress, since those threads are not suspended. Because of this,
|
||||
// we cannot deadlock with them, and should let them run as they please.
|
||||
ReleaseStackWalkWorkaroundLock();
|
||||
}
|
||||
|
||||
#if V8_HOST_ARCH_X64
|
||||
sample->pc = reinterpret_cast<Address>(context.Rip);
|
||||
sample->sp = reinterpret_cast<Address>(context.Rsp);
|
||||
sample->fp = reinterpret_cast<Address>(context.Rbp);
|
||||
#else
|
||||
sample->pc = reinterpret_cast<Address>(context.Eip);
|
||||
sample->sp = reinterpret_cast<Address>(context.Esp);
|
||||
sample->fp = reinterpret_cast<Address>(context.Ebp);
|
||||
#endif
|
||||
|
||||
sample->context = &context;
|
||||
sampler->Tick(sample);
|
||||
|
||||
ResumeThread(profiled_thread);
|
||||
}
|
||||
|
||||
Sampler* sampler_;
|
||||
int interval_; // units: ms
|
||||
|
||||
// Protects the process wide state below.
|
||||
static SamplerThread* instance_;
|
||||
|
||||
DISALLOW_COPY_AND_ASSIGN(SamplerThread);
|
||||
};
|
||||
|
||||
SamplerThread* SamplerThread::instance_ = NULL;
|
||||
|
||||
|
||||
Sampler::Sampler(double interval, bool profiling, int entrySize)
|
||||
: interval_(interval),
|
||||
profiling_(profiling),
|
||||
paused_(false),
|
||||
active_(false),
|
||||
entrySize_(entrySize) {
|
||||
}
|
||||
|
||||
Sampler::~Sampler() {
|
||||
ASSERT(!IsActive());
|
||||
}
|
||||
|
||||
void Sampler::Start() {
|
||||
ASSERT(!IsActive());
|
||||
SetActive(true);
|
||||
SamplerThread::StartSampler(this);
|
||||
}
|
||||
|
||||
void Sampler::Stop() {
|
||||
ASSERT(IsActive());
|
||||
SetActive(false);
|
||||
SamplerThread::StopSampler();
|
||||
}
|
||||
|
||||
|
||||
static const HANDLE kNoThread = INVALID_HANDLE_VALUE;
|
||||
|
||||
static unsigned int __stdcall ThreadEntry(void* arg) {
|
||||
Thread* thread = reinterpret_cast<Thread*>(arg);
|
||||
thread->Run();
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Initialize a Win32 thread object. The thread has an invalid thread
|
||||
// handle until it is started.
|
||||
Thread::Thread(const char* name)
|
||||
: stack_size_(0) {
|
||||
thread_ = kNoThread;
|
||||
set_name(name);
|
||||
}
|
||||
|
||||
void Thread::set_name(const char* name) {
|
||||
strncpy(name_, name, sizeof(name_));
|
||||
name_[sizeof(name_) - 1] = '\0';
|
||||
}
|
||||
|
||||
// Close our own handle for the thread.
|
||||
Thread::~Thread() {
|
||||
if (thread_ != kNoThread) CloseHandle(thread_);
|
||||
}
|
||||
|
||||
// Create a new thread. It is important to use _beginthreadex() instead of
|
||||
// the Win32 function CreateThread(), because the CreateThread() does not
|
||||
// initialize thread specific structures in the C runtime library.
|
||||
void Thread::Start() {
|
||||
thread_ = reinterpret_cast<HANDLE>(
|
||||
_beginthreadex(NULL,
|
||||
static_cast<unsigned>(stack_size_),
|
||||
ThreadEntry,
|
||||
this,
|
||||
0,
|
||||
(unsigned int*) &thread_id_));
|
||||
}
|
||||
|
||||
// Wait for thread to terminate.
|
||||
void Thread::Join() {
|
||||
if (thread_id_ != GetCurrentId()) {
|
||||
WaitForSingleObject(thread_, INFINITE);
|
||||
}
|
||||
}
|
||||
|
||||
/* static */ Thread::tid_t
|
||||
Thread::GetCurrentId()
|
||||
{
|
||||
return GetCurrentThreadId();
|
||||
}
|
||||
|
||||
void OS::Startup() {
|
||||
}
|
||||
|
||||
void OS::Sleep(int milliseconds) {
|
||||
::Sleep(milliseconds);
|
||||
}
|
||||
|
||||
bool Sampler::RegisterCurrentThread(const char* aName,
|
||||
PseudoStack* aPseudoStack,
|
||||
bool aIsMainThread, void* stackTop)
|
||||
{
|
||||
if (!Sampler::sRegisteredThreadsMutex)
|
||||
return false;
|
||||
|
||||
|
||||
::MutexAutoLock lock(*Sampler::sRegisteredThreadsMutex);
|
||||
|
||||
int id = GetCurrentThreadId();
|
||||
|
||||
for (uint32_t i = 0; i < sRegisteredThreads->size(); i++) {
|
||||
ThreadInfo* info = sRegisteredThreads->at(i);
|
||||
if (info->ThreadId() == id && !info->IsPendingDelete()) {
|
||||
// Thread already registered. This means the first unregister will be
|
||||
// too early.
|
||||
ASSERT(false);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
set_tls_stack_top(stackTop);
|
||||
|
||||
ThreadInfo* info = new StackOwningThreadInfo(aName, id,
|
||||
aIsMainThread, aPseudoStack, stackTop);
|
||||
|
||||
if (sActiveSampler) {
|
||||
sActiveSampler->RegisterThread(info);
|
||||
}
|
||||
|
||||
sRegisteredThreads->push_back(info);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void Sampler::UnregisterCurrentThread()
|
||||
{
|
||||
if (!Sampler::sRegisteredThreadsMutex)
|
||||
return;
|
||||
|
||||
tlsStackTop.set(nullptr);
|
||||
|
||||
::MutexAutoLock lock(*Sampler::sRegisteredThreadsMutex);
|
||||
|
||||
int id = GetCurrentThreadId();
|
||||
|
||||
for (uint32_t i = 0; i < sRegisteredThreads->size(); i++) {
|
||||
ThreadInfo* info = sRegisteredThreads->at(i);
|
||||
if (info->ThreadId() == id && !info->IsPendingDelete()) {
|
||||
if (profiler_is_active()) {
|
||||
// We still want to show the results of this thread if you
|
||||
// save the profile shortly after a thread is terminated.
|
||||
// For now we will defer the delete to profile stop.
|
||||
info->SetPendingDelete();
|
||||
break;
|
||||
} else {
|
||||
delete info;
|
||||
sRegisteredThreads->erase(sRegisteredThreads->begin() + i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void TickSample::PopulateContext(void* aContext)
|
||||
{
|
||||
MOZ_ASSERT(aContext);
|
||||
CONTEXT* pContext = reinterpret_cast<CONTEXT*>(aContext);
|
||||
context = pContext;
|
||||
RtlCaptureContext(pContext);
|
||||
|
||||
#if defined(SPS_PLAT_amd64_windows)
|
||||
|
||||
pc = reinterpret_cast<Address>(pContext->Rip);
|
||||
sp = reinterpret_cast<Address>(pContext->Rsp);
|
||||
fp = reinterpret_cast<Address>(pContext->Rbp);
|
||||
|
||||
#elif defined(SPS_PLAT_x86_windows)
|
||||
|
||||
pc = reinterpret_cast<Address>(pContext->Eip);
|
||||
sp = reinterpret_cast<Address>(pContext->Esp);
|
||||
fp = reinterpret_cast<Address>(pContext->Ebp);
|
||||
|
||||
#endif
|
||||
}
|
||||
|
||||
1266
tools/profiler/core/platform.cpp
Normal file
1266
tools/profiler/core/platform.cpp
Normal file
File diff suppressed because it is too large
Load diff
431
tools/profiler/core/platform.h
Normal file
431
tools/profiler/core/platform.h
Normal file
|
|
@ -0,0 +1,431 @@
|
|||
// Copyright (c) 2006-2011 The Chromium Authors. 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.
|
||||
|
||||
#ifndef TOOLS_PLATFORM_H_
|
||||
#define TOOLS_PLATFORM_H_
|
||||
|
||||
#ifdef SPS_STANDALONE
|
||||
#define MOZ_COUNT_CTOR(name)
|
||||
#define MOZ_COUNT_DTOR(name)
|
||||
#endif
|
||||
|
||||
#ifdef ANDROID
|
||||
#include <android/log.h>
|
||||
#else
|
||||
#define __android_log_print(a, ...)
|
||||
#endif
|
||||
|
||||
#ifdef XP_UNIX
|
||||
#include <pthread.h>
|
||||
#endif
|
||||
|
||||
#include <stdint.h>
|
||||
#include <math.h>
|
||||
#ifndef SPS_STANDALONE
|
||||
#include "MainThreadUtils.h"
|
||||
#include "mozilla/Mutex.h"
|
||||
#include "ThreadResponsiveness.h"
|
||||
#endif
|
||||
#include "mozilla/TimeStamp.h"
|
||||
#include "mozilla/UniquePtr.h"
|
||||
#include "mozilla/Unused.h"
|
||||
#include "PlatformMacros.h"
|
||||
#include "v8-support.h"
|
||||
#include <vector>
|
||||
#include "StackTop.h"
|
||||
|
||||
// We need a definition of gettid(), but Linux libc implementations don't
|
||||
// provide a wrapper for it (except for Bionic)
|
||||
#if defined(__linux__)
|
||||
#include <unistd.h>
|
||||
#if !defined(__BIONIC__)
|
||||
#include <sys/syscall.h>
|
||||
static inline pid_t gettid()
|
||||
{
|
||||
return (pid_t) syscall(SYS_gettid);
|
||||
}
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#ifdef XP_WIN
|
||||
#include <windows.h>
|
||||
#endif
|
||||
|
||||
#define ASSERT(a) MOZ_ASSERT(a)
|
||||
|
||||
bool moz_profiler_verbose();
|
||||
|
||||
#ifdef ANDROID
|
||||
# if defined(__arm__) || defined(__thumb__)
|
||||
# define ENABLE_SPS_LEAF_DATA
|
||||
# define ENABLE_ARM_LR_SAVING
|
||||
# endif
|
||||
# define LOG(text) \
|
||||
do { if (moz_profiler_verbose()) \
|
||||
__android_log_write(ANDROID_LOG_ERROR, "Profiler", text); \
|
||||
} while (0)
|
||||
# define LOGF(format, ...) \
|
||||
do { if (moz_profiler_verbose()) \
|
||||
__android_log_print(ANDROID_LOG_ERROR, "Profiler", format, \
|
||||
__VA_ARGS__); \
|
||||
} while (0)
|
||||
|
||||
#else
|
||||
# define LOG(text) \
|
||||
do { if (moz_profiler_verbose()) fprintf(stderr, "Profiler: %s\n", text); \
|
||||
} while (0)
|
||||
# define LOGF(format, ...) \
|
||||
do { if (moz_profiler_verbose()) fprintf(stderr, "Profiler: " format \
|
||||
"\n", __VA_ARGS__); \
|
||||
} while (0)
|
||||
|
||||
#endif
|
||||
|
||||
#if defined(XP_MACOSX) || defined(XP_WIN) || defined(XP_LINUX)
|
||||
#define ENABLE_SPS_LEAF_DATA
|
||||
#endif
|
||||
|
||||
typedef int32_t Atomic32;
|
||||
|
||||
extern mozilla::TimeStamp sStartTime;
|
||||
|
||||
typedef uint8_t* Address;
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// Mutex
|
||||
//
|
||||
// Mutexes are used for serializing access to non-reentrant sections of code.
|
||||
// The implementations of mutex should allow for nested/recursive locking.
|
||||
|
||||
class Mutex {
|
||||
public:
|
||||
virtual ~Mutex() {}
|
||||
|
||||
// Locks the given mutex. If the mutex is currently unlocked, it becomes
|
||||
// locked and owned by the calling thread, and immediately. If the mutex
|
||||
// is already locked by another thread, suspends the calling thread until
|
||||
// the mutex is unlocked.
|
||||
virtual int Lock() = 0;
|
||||
|
||||
// Unlocks the given mutex. The mutex is assumed to be locked and owned by
|
||||
// the calling thread on entrance.
|
||||
virtual int Unlock() = 0;
|
||||
};
|
||||
|
||||
class MutexAutoLock {
|
||||
public:
|
||||
explicit MutexAutoLock(::Mutex& aMutex)
|
||||
: mMutex(&aMutex)
|
||||
{
|
||||
mMutex->Lock();
|
||||
}
|
||||
|
||||
~MutexAutoLock() {
|
||||
mMutex->Unlock();
|
||||
}
|
||||
|
||||
private:
|
||||
Mutex* mMutex;
|
||||
};
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// OS
|
||||
//
|
||||
// This class has static methods for the different platform specific
|
||||
// functions. Add methods here to cope with differences between the
|
||||
// supported platforms.
|
||||
|
||||
class OS {
|
||||
public:
|
||||
|
||||
// Sleep for a number of milliseconds.
|
||||
static void Sleep(const int milliseconds);
|
||||
|
||||
// Sleep for a number of microseconds.
|
||||
static void SleepMicro(const int microseconds);
|
||||
|
||||
// Called on startup to initialize platform specific things
|
||||
static void Startup();
|
||||
|
||||
static mozilla::UniquePtr< ::Mutex> CreateMutex(const char* aDesc);
|
||||
|
||||
private:
|
||||
static const int msPerSecond = 1000;
|
||||
|
||||
};
|
||||
|
||||
|
||||
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// Thread
|
||||
//
|
||||
// Thread objects are used for creating and running threads. When the start()
|
||||
// method is called the new thread starts running the run() method in the new
|
||||
// thread. The Thread object should not be deallocated before the thread has
|
||||
// terminated.
|
||||
|
||||
class Thread {
|
||||
public:
|
||||
// Create new thread.
|
||||
explicit Thread(const char* name);
|
||||
virtual ~Thread();
|
||||
|
||||
// Start new thread by calling the Run() method in the new thread.
|
||||
void Start();
|
||||
|
||||
void Join();
|
||||
|
||||
inline const char* name() const {
|
||||
return name_;
|
||||
}
|
||||
|
||||
// Abstract method for run handler.
|
||||
virtual void Run() = 0;
|
||||
|
||||
// The thread name length is limited to 16 based on Linux's implementation of
|
||||
// prctl().
|
||||
static const int kMaxThreadNameLength = 16;
|
||||
|
||||
#ifdef XP_WIN
|
||||
HANDLE thread_;
|
||||
typedef DWORD tid_t;
|
||||
tid_t thread_id_;
|
||||
#else
|
||||
typedef ::pid_t tid_t;
|
||||
#endif
|
||||
#if defined(XP_MACOSX)
|
||||
pthread_t thread_;
|
||||
#endif
|
||||
|
||||
static tid_t GetCurrentId();
|
||||
|
||||
private:
|
||||
void set_name(const char *name);
|
||||
|
||||
char name_[kMaxThreadNameLength];
|
||||
int stack_size_;
|
||||
|
||||
DISALLOW_COPY_AND_ASSIGN(Thread);
|
||||
};
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// HAVE_NATIVE_UNWIND
|
||||
//
|
||||
// Pseudo backtraces are available on all platforms. Native
|
||||
// backtraces are available only on selected platforms. Breakpad is
|
||||
// the only supported native unwinder. HAVE_NATIVE_UNWIND is set at
|
||||
// build time to indicate whether native unwinding is possible on this
|
||||
// platform.
|
||||
|
||||
#undef HAVE_NATIVE_UNWIND
|
||||
#if defined(MOZ_PROFILING) \
|
||||
&& (defined(SPS_PLAT_amd64_linux) || defined(SPS_PLAT_arm_android) \
|
||||
|| (defined(MOZ_WIDGET_ANDROID) && defined(__arm__)) \
|
||||
|| defined(SPS_PLAT_x86_linux) \
|
||||
|| defined(SPS_OS_windows) \
|
||||
|| defined(SPS_OS_darwin))
|
||||
# define HAVE_NATIVE_UNWIND
|
||||
#endif
|
||||
|
||||
/* Some values extracted at startup from environment variables, that
|
||||
control the behaviour of the breakpad unwinder. */
|
||||
extern const char* PROFILER_INTERVAL;
|
||||
extern const char* PROFILER_ENTRIES;
|
||||
extern const char* PROFILER_STACK;
|
||||
extern const char* PROFILER_FEATURES;
|
||||
|
||||
void read_profiler_env_vars();
|
||||
void profiler_usage();
|
||||
|
||||
// Helper methods to expose modifying profiler behavior
|
||||
bool set_profiler_interval(const char*);
|
||||
bool set_profiler_entries(const char*);
|
||||
bool set_profiler_scan(const char*);
|
||||
bool is_native_unwinding_avail();
|
||||
|
||||
void set_tls_stack_top(void* stackTop);
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// Sampler
|
||||
//
|
||||
// A sampler periodically samples the state of the VM and optionally
|
||||
// (if used for profiling) the program counter and stack pointer for
|
||||
// the thread that created it.
|
||||
|
||||
struct PseudoStack;
|
||||
class ThreadProfile;
|
||||
|
||||
// TickSample captures the information collected for each sample.
|
||||
class TickSample {
|
||||
public:
|
||||
TickSample()
|
||||
: pc(NULL)
|
||||
, sp(NULL)
|
||||
, fp(NULL)
|
||||
#ifdef ENABLE_ARM_LR_SAVING
|
||||
, lr(NULL)
|
||||
#endif
|
||||
, context(NULL)
|
||||
, isSamplingCurrentThread(false)
|
||||
, threadProfile(nullptr)
|
||||
, rssMemory(0)
|
||||
, ussMemory(0)
|
||||
{}
|
||||
|
||||
void PopulateContext(void* aContext);
|
||||
|
||||
Address pc; // Instruction pointer.
|
||||
Address sp; // Stack pointer.
|
||||
Address fp; // Frame pointer.
|
||||
#ifdef ENABLE_ARM_LR_SAVING
|
||||
Address lr; // ARM link register
|
||||
#endif
|
||||
void* context; // The context from the signal handler, if available. On
|
||||
// Win32 this may contain the windows thread context.
|
||||
bool isSamplingCurrentThread;
|
||||
ThreadProfile* threadProfile;
|
||||
mozilla::TimeStamp timestamp;
|
||||
int64_t rssMemory;
|
||||
int64_t ussMemory;
|
||||
};
|
||||
|
||||
class ThreadInfo;
|
||||
class PlatformData;
|
||||
class GeckoSampler;
|
||||
class SyncProfile;
|
||||
class Sampler {
|
||||
public:
|
||||
// Initialize sampler.
|
||||
explicit Sampler(double interval, bool profiling, int entrySize);
|
||||
virtual ~Sampler();
|
||||
|
||||
double interval() const { return interval_; }
|
||||
|
||||
// This method is called for each sampling period with the current
|
||||
// program counter.
|
||||
virtual void Tick(TickSample* sample) = 0;
|
||||
|
||||
// Immediately captures the calling thread's call stack and returns it.
|
||||
virtual SyncProfile* GetBacktrace() = 0;
|
||||
|
||||
// Request a save from a signal handler
|
||||
virtual void RequestSave() = 0;
|
||||
// Process any outstanding request outside a signal handler.
|
||||
virtual void HandleSaveRequest() = 0;
|
||||
// Delete markers which are no longer part of the profile due to buffer wraparound.
|
||||
virtual void DeleteExpiredMarkers() = 0;
|
||||
|
||||
// Start and stop sampler.
|
||||
void Start();
|
||||
void Stop();
|
||||
|
||||
// Is the sampler used for profiling?
|
||||
bool IsProfiling() const { return profiling_; }
|
||||
|
||||
// Whether the sampler is running (that is, consumes resources).
|
||||
bool IsActive() const { return active_; }
|
||||
|
||||
// Low overhead way to stop the sampler from ticking
|
||||
bool IsPaused() const { return paused_; }
|
||||
void SetPaused(bool value) { NoBarrier_Store(&paused_, value); }
|
||||
|
||||
virtual bool ProfileThreads() const = 0;
|
||||
|
||||
int EntrySize() { return entrySize_; }
|
||||
|
||||
// We can't new/delete the type safely without defining it
|
||||
// (-Wdelete-incomplete). Use these Alloc/Free functions instead.
|
||||
static PlatformData* AllocPlatformData(int aThreadId);
|
||||
static void FreePlatformData(PlatformData*);
|
||||
|
||||
// If we move the backtracing code into the platform files we won't
|
||||
// need to have these hacks
|
||||
#ifdef XP_WIN
|
||||
// xxxehsan sucky hack :(
|
||||
static uintptr_t GetThreadHandle(PlatformData*);
|
||||
#endif
|
||||
#ifdef XP_MACOSX
|
||||
static pthread_t GetProfiledThread(PlatformData*);
|
||||
#endif
|
||||
|
||||
static std::vector<ThreadInfo*> GetRegisteredThreads() {
|
||||
return *sRegisteredThreads;
|
||||
}
|
||||
|
||||
static bool RegisterCurrentThread(const char* aName,
|
||||
PseudoStack* aPseudoStack,
|
||||
bool aIsMainThread, void* stackTop);
|
||||
static void UnregisterCurrentThread();
|
||||
|
||||
static void Startup();
|
||||
// Should only be called on shutdown
|
||||
static void Shutdown();
|
||||
|
||||
static GeckoSampler* GetActiveSampler() { return sActiveSampler; }
|
||||
static void SetActiveSampler(GeckoSampler* sampler) { sActiveSampler = sampler; }
|
||||
|
||||
static mozilla::UniquePtr<Mutex> sRegisteredThreadsMutex;
|
||||
|
||||
static bool CanNotifyObservers() {
|
||||
#ifdef MOZ_WIDGET_GONK
|
||||
// We use profile.sh on b2g to manually select threads and options per process.
|
||||
return false;
|
||||
#elif defined(SPS_OS_android) && !defined(MOZ_WIDGET_GONK)
|
||||
// Android ANR reporter uses the profiler off the main thread
|
||||
return NS_IsMainThread();
|
||||
#else
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
return true;
|
||||
#endif
|
||||
}
|
||||
|
||||
protected:
|
||||
static std::vector<ThreadInfo*>* sRegisteredThreads;
|
||||
static GeckoSampler* sActiveSampler;
|
||||
|
||||
private:
|
||||
void SetActive(bool value) { NoBarrier_Store(&active_, value); }
|
||||
|
||||
const double interval_;
|
||||
const bool profiling_;
|
||||
Atomic32 paused_;
|
||||
Atomic32 active_;
|
||||
const int entrySize_;
|
||||
|
||||
// Refactor me!
|
||||
#if defined(SPS_OS_linux) || defined(SPS_OS_android)
|
||||
bool signal_handler_installed_;
|
||||
struct sigaction old_sigprof_signal_handler_;
|
||||
struct sigaction old_sigsave_signal_handler_;
|
||||
bool signal_sender_launched_;
|
||||
pthread_t signal_sender_thread_;
|
||||
#endif
|
||||
};
|
||||
|
||||
#endif /* ndef TOOLS_PLATFORM_H_ */
|
||||
159
tools/profiler/core/shared-libraries-linux.cc
Normal file
159
tools/profiler/core/shared-libraries-linux.cc
Normal file
|
|
@ -0,0 +1,159 @@
|
|||
/* 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 "shared-libraries.h"
|
||||
|
||||
#define PATH_MAX_TOSTRING(x) #x
|
||||
#define PATH_MAX_STRING(x) PATH_MAX_TOSTRING(x)
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <limits.h>
|
||||
#include <unistd.h>
|
||||
#include <fstream>
|
||||
#include "platform.h"
|
||||
#include "shared-libraries.h"
|
||||
|
||||
#include "common/linux/file_id.h"
|
||||
#include <algorithm>
|
||||
|
||||
#define ARRAY_SIZE(a) (sizeof(a)/sizeof(a[0]))
|
||||
|
||||
// Get the breakpad Id for the binary file pointed by bin_name
|
||||
static std::string getId(const char *bin_name)
|
||||
{
|
||||
using namespace google_breakpad;
|
||||
using namespace std;
|
||||
|
||||
PageAllocator allocator;
|
||||
auto_wasteful_vector<uint8_t, sizeof(MDGUID)> identifier(&allocator);
|
||||
|
||||
FileID file_id(bin_name);
|
||||
if (file_id.ElfFileIdentifier(identifier)) {
|
||||
return FileID::ConvertIdentifierToUUIDString(identifier) + "0";
|
||||
}
|
||||
|
||||
return "";
|
||||
}
|
||||
|
||||
#if !defined(MOZ_WIDGET_GONK)
|
||||
// TODO fix me with proper include
|
||||
#include "nsDebug.h"
|
||||
#ifdef ANDROID
|
||||
#include "ElfLoader.h" // dl_phdr_info
|
||||
#else
|
||||
#include <link.h> // dl_phdr_info
|
||||
#endif
|
||||
#include <features.h>
|
||||
#include <dlfcn.h>
|
||||
#include <sys/types.h>
|
||||
|
||||
#ifdef ANDROID
|
||||
extern "C" MOZ_EXPORT __attribute__((weak))
|
||||
int dl_iterate_phdr(
|
||||
int (*callback) (struct dl_phdr_info *info,
|
||||
size_t size, void *data),
|
||||
void *data);
|
||||
#endif
|
||||
|
||||
static int
|
||||
dl_iterate_callback(struct dl_phdr_info *dl_info, size_t size, void *data)
|
||||
{
|
||||
SharedLibraryInfo& info = *reinterpret_cast<SharedLibraryInfo*>(data);
|
||||
|
||||
if (dl_info->dlpi_phnum <= 0)
|
||||
return 0;
|
||||
|
||||
unsigned long libStart = -1;
|
||||
unsigned long libEnd = 0;
|
||||
|
||||
for (size_t i = 0; i < dl_info->dlpi_phnum; i++) {
|
||||
if (dl_info->dlpi_phdr[i].p_type != PT_LOAD) {
|
||||
continue;
|
||||
}
|
||||
unsigned long start = dl_info->dlpi_addr + dl_info->dlpi_phdr[i].p_vaddr;
|
||||
unsigned long end = start + dl_info->dlpi_phdr[i].p_memsz;
|
||||
if (start < libStart)
|
||||
libStart = start;
|
||||
if (end > libEnd)
|
||||
libEnd = end;
|
||||
}
|
||||
const char *name = dl_info->dlpi_name;
|
||||
SharedLibrary shlib(libStart, libEnd, 0, getId(name), name);
|
||||
info.AddSharedLibrary(shlib);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
#endif // !MOZ_WIDGET_GONK
|
||||
|
||||
SharedLibraryInfo SharedLibraryInfo::GetInfoForSelf()
|
||||
{
|
||||
SharedLibraryInfo info;
|
||||
|
||||
#if !defined(MOZ_WIDGET_GONK)
|
||||
#ifdef ANDROID
|
||||
if (!dl_iterate_phdr) {
|
||||
// On ARM Android, dl_iterate_phdr is provided by the custom linker.
|
||||
// So if libxul was loaded by the system linker (e.g. as part of
|
||||
// xpcshell when running tests), it won't be available and we should
|
||||
// not call it.
|
||||
return info;
|
||||
}
|
||||
#endif // ANDROID
|
||||
|
||||
dl_iterate_phdr(dl_iterate_callback, &info);
|
||||
#endif // !MOZ_WIDGET_GONK
|
||||
|
||||
#if defined(ANDROID) || defined(MOZ_WIDGET_GONK)
|
||||
pid_t pid = getpid();
|
||||
char path[PATH_MAX];
|
||||
snprintf(path, PATH_MAX, "/proc/%d/maps", pid);
|
||||
std::ifstream maps(path);
|
||||
std::string line;
|
||||
int count = 0;
|
||||
while (std::getline(maps, line)) {
|
||||
int ret;
|
||||
//XXX: needs input sanitizing
|
||||
unsigned long start;
|
||||
unsigned long end;
|
||||
char perm[6] = "";
|
||||
unsigned long offset;
|
||||
char name[PATH_MAX] = "";
|
||||
ret = sscanf(line.c_str(),
|
||||
"%lx-%lx %6s %lx %*s %*x %" PATH_MAX_STRING(PATH_MAX) "s\n",
|
||||
&start, &end, perm, &offset, name);
|
||||
if (!strchr(perm, 'x')) {
|
||||
// Ignore non executable entries
|
||||
continue;
|
||||
}
|
||||
if (ret != 5 && ret != 4) {
|
||||
LOG("Get maps line failed");
|
||||
continue;
|
||||
}
|
||||
#if defined(ANDROID) && !defined(MOZ_WIDGET_GONK)
|
||||
// Use proc/pid/maps to get the dalvik-jit section since it has
|
||||
// no associated phdrs
|
||||
if (strcmp(name, "/dev/ashmem/dalvik-jit-code-cache") != 0)
|
||||
continue;
|
||||
#else
|
||||
if (strcmp(perm, "r-xp") != 0) {
|
||||
// Ignore entries that are writable and/or shared.
|
||||
// At least one graphics driver uses short-lived "rwxs" mappings
|
||||
// (see bug 926734 comment 5), so just checking for 'x' isn't enough.
|
||||
continue;
|
||||
}
|
||||
#endif
|
||||
SharedLibrary shlib(start, end, offset, getId(name), name);
|
||||
info.AddSharedLibrary(shlib);
|
||||
if (count > 10000) {
|
||||
LOG("Get maps failed");
|
||||
break;
|
||||
}
|
||||
count++;
|
||||
}
|
||||
#endif // ANDROID || MOZ_WIDGET_GONK
|
||||
|
||||
return info;
|
||||
}
|
||||
132
tools/profiler/core/shared-libraries-macos.cc
Normal file
132
tools/profiler/core/shared-libraries-macos.cc
Normal file
|
|
@ -0,0 +1,132 @@
|
|||
/* -*- 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 <AvailabilityMacros.h>
|
||||
#include <mach-o/loader.h>
|
||||
#include <mach-o/dyld_images.h>
|
||||
#include <mach/task_info.h>
|
||||
#include <mach/task.h>
|
||||
#include <mach/mach_init.h>
|
||||
#include <mach/mach_traps.h>
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
#include <vector>
|
||||
#include <sstream>
|
||||
|
||||
#include "shared-libraries.h"
|
||||
|
||||
#ifndef MAC_OS_X_VERSION_10_6
|
||||
#define MAC_OS_X_VERSION_10_6 1060
|
||||
#endif
|
||||
|
||||
#if MAC_OS_X_VERSION_MAX_ALLOWED < MAC_OS_X_VERSION_10_6
|
||||
// borrowed from Breakpad
|
||||
// Fallback declarations for TASK_DYLD_INFO and friends, introduced in
|
||||
// <mach/task_info.h> in the Mac OS X 10.6 SDK.
|
||||
#define TASK_DYLD_INFO 17
|
||||
struct task_dyld_info {
|
||||
mach_vm_address_t all_image_info_addr;
|
||||
mach_vm_size_t all_image_info_size;
|
||||
};
|
||||
typedef struct task_dyld_info task_dyld_info_data_t;
|
||||
typedef struct task_dyld_info *task_dyld_info_t;
|
||||
#define TASK_DYLD_INFO_COUNT (sizeof(task_dyld_info_data_t) / sizeof(natural_t))
|
||||
|
||||
#endif
|
||||
|
||||
// Architecture specific abstraction.
|
||||
#ifdef __i386__
|
||||
typedef mach_header platform_mach_header;
|
||||
typedef segment_command mach_segment_command_type;
|
||||
#define MACHO_MAGIC_NUMBER MH_MAGIC
|
||||
#define CMD_SEGMENT LC_SEGMENT
|
||||
#define seg_size uint32_t
|
||||
#else
|
||||
typedef mach_header_64 platform_mach_header;
|
||||
typedef segment_command_64 mach_segment_command_type;
|
||||
#define MACHO_MAGIC_NUMBER MH_MAGIC_64
|
||||
#define CMD_SEGMENT LC_SEGMENT_64
|
||||
#define seg_size uint64_t
|
||||
#endif
|
||||
|
||||
static
|
||||
void addSharedLibrary(const platform_mach_header* header, char *name, SharedLibraryInfo &info) {
|
||||
const struct load_command *cmd =
|
||||
reinterpret_cast<const struct load_command *>(header + 1);
|
||||
|
||||
seg_size size = 0;
|
||||
unsigned long long start = reinterpret_cast<unsigned long long>(header);
|
||||
// Find the cmd segment in the macho image. It will contain the offset we care about.
|
||||
const uint8_t *uuid_bytes = nullptr;
|
||||
for (unsigned int i = 0;
|
||||
cmd && (i < header->ncmds) && (uuid_bytes == nullptr || size == 0);
|
||||
++i) {
|
||||
if (cmd->cmd == CMD_SEGMENT) {
|
||||
const mach_segment_command_type *seg =
|
||||
reinterpret_cast<const mach_segment_command_type *>(cmd);
|
||||
|
||||
if (!strcmp(seg->segname, "__TEXT")) {
|
||||
size = seg->vmsize;
|
||||
}
|
||||
} else if (cmd->cmd == LC_UUID) {
|
||||
const uuid_command *ucmd = reinterpret_cast<const uuid_command *>(cmd);
|
||||
uuid_bytes = ucmd->uuid;
|
||||
}
|
||||
|
||||
cmd = reinterpret_cast<const struct load_command *>
|
||||
(reinterpret_cast<const char *>(cmd) + cmd->cmdsize);
|
||||
}
|
||||
|
||||
std::stringstream uuid;
|
||||
uuid << std::hex << std::uppercase;
|
||||
if (uuid_bytes != nullptr) {
|
||||
for (int i = 0; i < 16; ++i) {
|
||||
uuid << ((uuid_bytes[i] & 0xf0) >> 4);
|
||||
uuid << (uuid_bytes[i] & 0xf);
|
||||
}
|
||||
uuid << '0';
|
||||
}
|
||||
|
||||
info.AddSharedLibrary(SharedLibrary(start, start + size, 0, uuid.str(),
|
||||
name));
|
||||
}
|
||||
|
||||
// Use dyld to inspect the macho image information. We can build the SharedLibraryEntry structure
|
||||
// giving us roughtly the same info as /proc/PID/maps in Linux.
|
||||
SharedLibraryInfo SharedLibraryInfo::GetInfoForSelf()
|
||||
{
|
||||
SharedLibraryInfo sharedLibraryInfo;
|
||||
|
||||
task_dyld_info_data_t task_dyld_info;
|
||||
mach_msg_type_number_t count = TASK_DYLD_INFO_COUNT;
|
||||
if (task_info(mach_task_self (), TASK_DYLD_INFO, (task_info_t)&task_dyld_info,
|
||||
&count) != KERN_SUCCESS) {
|
||||
return sharedLibraryInfo;
|
||||
}
|
||||
|
||||
struct dyld_all_image_infos* aii = (struct dyld_all_image_infos*)task_dyld_info.all_image_info_addr;
|
||||
size_t infoCount = aii->infoArrayCount;
|
||||
|
||||
// Iterate through all dyld images (loaded libraries) to get their names
|
||||
// and offests.
|
||||
for (size_t i = 0; i < infoCount; ++i) {
|
||||
const dyld_image_info *info = &aii->infoArray[i];
|
||||
|
||||
// If the magic number doesn't match then go no further
|
||||
// since we're not pointing to where we think we are.
|
||||
if (info->imageLoadAddress->magic != MACHO_MAGIC_NUMBER) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const platform_mach_header* header =
|
||||
reinterpret_cast<const platform_mach_header*>(info->imageLoadAddress);
|
||||
|
||||
// Add the entry for this image.
|
||||
addSharedLibrary(header, (char*)info->imageFilePath, sharedLibraryInfo);
|
||||
|
||||
}
|
||||
return sharedLibraryInfo;
|
||||
}
|
||||
|
||||
137
tools/profiler/core/shared-libraries-win32.cc
Normal file
137
tools/profiler/core/shared-libraries-win32.cc
Normal file
|
|
@ -0,0 +1,137 @@
|
|||
/* -*- 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 <windows.h>
|
||||
#include <tlhelp32.h>
|
||||
#include <dbghelp.h>
|
||||
#include <sstream>
|
||||
|
||||
#include "shared-libraries.h"
|
||||
#include "nsWindowsHelpers.h"
|
||||
|
||||
#define CV_SIGNATURE 0x53445352 // 'SDSR'
|
||||
|
||||
struct CodeViewRecord70
|
||||
{
|
||||
uint32_t signature;
|
||||
GUID pdbSignature;
|
||||
uint32_t pdbAge;
|
||||
char pdbFileName[1];
|
||||
};
|
||||
|
||||
static bool GetPdbInfo(uintptr_t aStart, nsID& aSignature, uint32_t& aAge, char** aPdbName)
|
||||
{
|
||||
if (!aStart) {
|
||||
return false;
|
||||
}
|
||||
|
||||
PIMAGE_DOS_HEADER dosHeader = reinterpret_cast<PIMAGE_DOS_HEADER>(aStart);
|
||||
if (dosHeader->e_magic != IMAGE_DOS_SIGNATURE) {
|
||||
return false;
|
||||
}
|
||||
|
||||
PIMAGE_NT_HEADERS ntHeaders = reinterpret_cast<PIMAGE_NT_HEADERS>(
|
||||
aStart + dosHeader->e_lfanew);
|
||||
if (ntHeaders->Signature != IMAGE_NT_SIGNATURE) {
|
||||
return false;
|
||||
}
|
||||
|
||||
uint32_t relativeVirtualAddress =
|
||||
ntHeaders->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_DEBUG].VirtualAddress;
|
||||
if (!relativeVirtualAddress) {
|
||||
return false;
|
||||
}
|
||||
|
||||
PIMAGE_DEBUG_DIRECTORY debugDirectory =
|
||||
reinterpret_cast<PIMAGE_DEBUG_DIRECTORY>(aStart + relativeVirtualAddress);
|
||||
if (!debugDirectory || debugDirectory->Type != IMAGE_DEBUG_TYPE_CODEVIEW) {
|
||||
return false;
|
||||
}
|
||||
|
||||
CodeViewRecord70 *debugInfo = reinterpret_cast<CodeViewRecord70 *>(
|
||||
aStart + debugDirectory->AddressOfRawData);
|
||||
if (!debugInfo || debugInfo->signature != CV_SIGNATURE) {
|
||||
return false;
|
||||
}
|
||||
|
||||
aAge = debugInfo->pdbAge;
|
||||
GUID& pdbSignature = debugInfo->pdbSignature;
|
||||
aSignature.m0 = pdbSignature.Data1;
|
||||
aSignature.m1 = pdbSignature.Data2;
|
||||
aSignature.m2 = pdbSignature.Data3;
|
||||
memcpy(aSignature.m3, pdbSignature.Data4, sizeof(pdbSignature.Data4));
|
||||
|
||||
// The PDB file name could be different from module filename, so report both
|
||||
// e.g. The PDB for C:\Windows\SysWOW64\ntdll.dll is wntdll.pdb
|
||||
char * leafName = strrchr(debugInfo->pdbFileName, '\\');
|
||||
if (leafName) {
|
||||
// Only report the file portion of the path
|
||||
*aPdbName = leafName + 1;
|
||||
} else {
|
||||
*aPdbName = debugInfo->pdbFileName;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool IsDashOrBraces(char c)
|
||||
{
|
||||
return c == '-' || c == '{' || c == '}';
|
||||
}
|
||||
|
||||
SharedLibraryInfo SharedLibraryInfo::GetInfoForSelf()
|
||||
{
|
||||
SharedLibraryInfo sharedLibraryInfo;
|
||||
|
||||
nsAutoHandle snap(CreateToolhelp32Snapshot(TH32CS_SNAPMODULE, GetCurrentProcessId()));
|
||||
|
||||
MODULEENTRY32 module = {0};
|
||||
module.dwSize = sizeof(MODULEENTRY32);
|
||||
if (Module32First(snap, &module)) {
|
||||
do {
|
||||
nsID pdbSig;
|
||||
uint32_t pdbAge;
|
||||
char *pdbName = NULL;
|
||||
|
||||
// Load the module again to make sure that its handle will remain remain
|
||||
// valid as we attempt to read the PDB information from it. We load the
|
||||
// DLL as a datafile so that if the module actually gets unloaded between
|
||||
// the call to Module32Next and the following LoadLibraryEx, we don't end
|
||||
// up running the now newly loaded module's DllMain function. If the
|
||||
// module is already loaded, LoadLibraryEx just increments its refcount.
|
||||
//
|
||||
// Note that because of the race condition above, merely loading the DLL
|
||||
// again is not safe enough, therefore we also need to make sure that we
|
||||
// can read the memory mapped at the base address before we can safely
|
||||
// proceed to actually access those pages.
|
||||
HMODULE handleLock = LoadLibraryEx(module.szExePath, NULL, LOAD_LIBRARY_AS_DATAFILE);
|
||||
MEMORY_BASIC_INFORMATION vmemInfo = {0};
|
||||
if (handleLock &&
|
||||
sizeof(vmemInfo) == VirtualQuery(module.modBaseAddr, &vmemInfo, sizeof(vmemInfo)) &&
|
||||
vmemInfo.State == MEM_COMMIT &&
|
||||
GetPdbInfo((uintptr_t)module.modBaseAddr, pdbSig, pdbAge, &pdbName)) {
|
||||
std::ostringstream stream;
|
||||
stream << pdbSig.ToString() << std::hex << pdbAge;
|
||||
std::string breakpadId = stream.str();
|
||||
std::string::iterator end =
|
||||
std::remove_if(breakpadId.begin(), breakpadId.end(), IsDashOrBraces);
|
||||
breakpadId.erase(end, breakpadId.end());
|
||||
std::transform(breakpadId.begin(), breakpadId.end(),
|
||||
breakpadId.begin(), toupper);
|
||||
|
||||
SharedLibrary shlib((uintptr_t)module.modBaseAddr,
|
||||
(uintptr_t)module.modBaseAddr+module.modBaseSize,
|
||||
0, // DLLs are always mapped at offset 0 on Windows
|
||||
breakpadId,
|
||||
pdbName);
|
||||
sharedLibraryInfo.AddSharedLibrary(shlib);
|
||||
}
|
||||
FreeLibrary(handleLock); // ok to free null handles
|
||||
} while (Module32Next(snap, &module));
|
||||
}
|
||||
|
||||
return sharedLibraryInfo;
|
||||
}
|
||||
|
||||
48
tools/profiler/core/v8-support.h
Normal file
48
tools/profiler/core/v8-support.h
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
/* -*- 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/. */
|
||||
|
||||
/* This contains stubs and infrastructure to support code from v8 */
|
||||
|
||||
#ifndef V8_SUPPORT_H_
|
||||
#define V8_SUPPORT_H_
|
||||
|
||||
#if defined(_M_X64) || defined(__x86_64__)
|
||||
#define V8_HOST_ARCH_X64 1
|
||||
#elif defined(_M_IX86) || defined(__i386__) || defined(__i386)
|
||||
#define V8_HOST_ARCH_IA32 1
|
||||
#elif defined(__ARMEL__)
|
||||
#define V8_HOST_ARCH_ARM 1
|
||||
#else
|
||||
#warning Please add support for your architecture in chromium_types.h
|
||||
#endif
|
||||
|
||||
typedef int32_t Atomic32;
|
||||
|
||||
#if defined(V8_HOST_ARCH_X64) || defined(V8_HOST_ARCH_IA32) || defined(V8_HOST_ARCH_ARM)
|
||||
inline void NoBarrier_Store(volatile Atomic32* ptr, Atomic32 value) {
|
||||
*ptr = value;
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
const int kMaxInt = 0x7FFFFFFF;
|
||||
const int kMinInt = -kMaxInt - 1;
|
||||
|
||||
// A macro to disallow the evil copy constructor and operator= functions
|
||||
// This should be used in the private: declarations for a class
|
||||
#define DISALLOW_COPY_AND_ASSIGN(TypeName) \
|
||||
TypeName(const TypeName&); \
|
||||
void operator=(const TypeName&)
|
||||
|
||||
|
||||
// The USE(x) template is used to silence C++ compiler warnings
|
||||
// issued for (yet) unused variables (typically parameters).
|
||||
template <typename T>
|
||||
static inline void USE(T) { }
|
||||
|
||||
class Malloced {
|
||||
};
|
||||
|
||||
#endif // V8_SUPPORT_H_
|
||||
207
tools/profiler/gecko/ProfileGatherer.cpp
Normal file
207
tools/profiler/gecko/ProfileGatherer.cpp
Normal file
|
|
@ -0,0 +1,207 @@
|
|||
/* 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 "mozilla/ProfileGatherer.h"
|
||||
#include "mozilla/Services.h"
|
||||
#include "nsIObserverService.h"
|
||||
#include "GeckoSampler.h"
|
||||
|
||||
using mozilla::dom::AutoJSAPI;
|
||||
using mozilla::dom::Promise;
|
||||
|
||||
namespace mozilla {
|
||||
|
||||
/**
|
||||
* When a subprocess exits before we've gathered profiles, we'll
|
||||
* store profiles for those processes until gathering starts. We'll
|
||||
* only store up to MAX_SUBPROCESS_EXIT_PROFILES. The buffer is
|
||||
* circular, so as soon as we receive another exit profile, we'll
|
||||
* bump the oldest one out of the buffer.
|
||||
*/
|
||||
static const uint32_t MAX_SUBPROCESS_EXIT_PROFILES = 5;
|
||||
|
||||
NS_IMPL_ISUPPORTS(ProfileGatherer, nsIObserver)
|
||||
|
||||
ProfileGatherer::ProfileGatherer(GeckoSampler* aTicker)
|
||||
: mTicker(aTicker)
|
||||
, mSinceTime(0)
|
||||
, mPendingProfiles(0)
|
||||
, mGathering(false)
|
||||
{
|
||||
}
|
||||
|
||||
void
|
||||
ProfileGatherer::GatheredOOPProfile()
|
||||
{
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
if (!mGathering) {
|
||||
// If we're not actively gathering, then we don't actually
|
||||
// care that we gathered a profile here. This can happen for
|
||||
// processes that exit while profiling.
|
||||
return;
|
||||
}
|
||||
|
||||
if (NS_WARN_IF(!mPromise)) {
|
||||
// If we're not holding on to a Promise, then someone is
|
||||
// calling us erroneously.
|
||||
return;
|
||||
}
|
||||
|
||||
mPendingProfiles--;
|
||||
|
||||
if (mPendingProfiles == 0) {
|
||||
// We've got all of the async profiles now. Let's
|
||||
// finish off the profile and resolve the Promise.
|
||||
Finish();
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
ProfileGatherer::WillGatherOOPProfile()
|
||||
{
|
||||
mPendingProfiles++;
|
||||
}
|
||||
|
||||
void
|
||||
ProfileGatherer::Start(double aSinceTime,
|
||||
Promise* aPromise)
|
||||
{
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
if (mGathering) {
|
||||
// If we're already gathering, reject the promise - this isn't going
|
||||
// to end well.
|
||||
if (aPromise) {
|
||||
aPromise->MaybeReject(NS_ERROR_NOT_AVAILABLE);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
mSinceTime = aSinceTime;
|
||||
mPromise = aPromise;
|
||||
mGathering = true;
|
||||
mPendingProfiles = 0;
|
||||
|
||||
nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
|
||||
if (os) {
|
||||
DebugOnly<nsresult> rv =
|
||||
os->AddObserver(this, "profiler-subprocess", false);
|
||||
NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "AddObserver failed");
|
||||
rv = os->NotifyObservers(this, "profiler-subprocess-gather", nullptr);
|
||||
NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "NotifyObservers failed");
|
||||
}
|
||||
|
||||
if (!mPendingProfiles) {
|
||||
Finish();
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
ProfileGatherer::Finish()
|
||||
{
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
|
||||
if (!mTicker) {
|
||||
// We somehow got called after we were cancelled! This shouldn't
|
||||
// be possible, but doing a belt-and-suspenders check to be sure.
|
||||
return;
|
||||
}
|
||||
|
||||
UniquePtr<char[]> buf = mTicker->ToJSON(mSinceTime);
|
||||
|
||||
nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
|
||||
if (os) {
|
||||
DebugOnly<nsresult> rv = os->RemoveObserver(this, "profiler-subprocess");
|
||||
NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "RemoveObserver failed");
|
||||
}
|
||||
|
||||
AutoJSAPI jsapi;
|
||||
if (NS_WARN_IF(!jsapi.Init(mPromise->GlobalJSObject()))) {
|
||||
// We're really hosed if we can't get a JS context for some reason.
|
||||
Reset();
|
||||
return;
|
||||
}
|
||||
|
||||
JSContext* cx = jsapi.cx();
|
||||
|
||||
// Now parse the JSON so that we resolve with a JS Object.
|
||||
JS::RootedValue val(cx);
|
||||
{
|
||||
NS_ConvertUTF8toUTF16 js_string(nsDependentCString(buf.get()));
|
||||
if (!JS_ParseJSON(cx, static_cast<const char16_t*>(js_string.get()),
|
||||
js_string.Length(), &val)) {
|
||||
if (!jsapi.HasException()) {
|
||||
mPromise->MaybeReject(NS_ERROR_DOM_UNKNOWN_ERR);
|
||||
} else {
|
||||
JS::RootedValue exn(cx);
|
||||
DebugOnly<bool> gotException = jsapi.StealException(&exn);
|
||||
MOZ_ASSERT(gotException);
|
||||
|
||||
jsapi.ClearException();
|
||||
mPromise->MaybeReject(cx, exn);
|
||||
}
|
||||
} else {
|
||||
mPromise->MaybeResolve(val);
|
||||
}
|
||||
}
|
||||
|
||||
Reset();
|
||||
}
|
||||
|
||||
void
|
||||
ProfileGatherer::Reset()
|
||||
{
|
||||
mSinceTime = 0;
|
||||
mPromise = nullptr;
|
||||
mPendingProfiles = 0;
|
||||
mGathering = false;
|
||||
}
|
||||
|
||||
void
|
||||
ProfileGatherer::Cancel()
|
||||
{
|
||||
// The GeckoSampler is going away. If we have a Promise in flight, we
|
||||
// should reject it.
|
||||
if (mPromise) {
|
||||
mPromise->MaybeReject(NS_ERROR_DOM_ABORT_ERR);
|
||||
}
|
||||
|
||||
// Clear out the GeckoSampler reference, since it's being destroyed.
|
||||
mTicker = nullptr;
|
||||
}
|
||||
|
||||
void
|
||||
ProfileGatherer::OOPExitProfile(const nsCString& aProfile)
|
||||
{
|
||||
if (mExitProfiles.Length() >= MAX_SUBPROCESS_EXIT_PROFILES) {
|
||||
mExitProfiles.RemoveElementAt(0);
|
||||
}
|
||||
mExitProfiles.AppendElement(aProfile);
|
||||
|
||||
// If a process exited while gathering, we need to make
|
||||
// sure we decrement the counter.
|
||||
if (mGathering) {
|
||||
GatheredOOPProfile();
|
||||
}
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
ProfileGatherer::Observe(nsISupports* aSubject,
|
||||
const char* aTopic,
|
||||
const char16_t *someData)
|
||||
{
|
||||
if (!strcmp(aTopic, "profiler-subprocess")) {
|
||||
nsCOMPtr<nsIProfileSaveEvent> pse = do_QueryInterface(aSubject);
|
||||
if (pse) {
|
||||
for (size_t i = 0; i < mExitProfiles.Length(); ++i) {
|
||||
if (!mExitProfiles[i].IsEmpty()) {
|
||||
pse->AddSubProfile(mExitProfiles[i].get());
|
||||
}
|
||||
}
|
||||
mExitProfiles.Clear();
|
||||
}
|
||||
}
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
} // namespace mozilla
|
||||
16
tools/profiler/gecko/Profiler.jsm
Normal file
16
tools/profiler/gecko/Profiler.jsm
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
/* 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/. */
|
||||
|
||||
"use strict";
|
||||
|
||||
const Cc = Components.classes;
|
||||
const Ci = Components.interfaces;
|
||||
const Cr = Components.results;
|
||||
|
||||
this.EXPORTED_SYMBOLS = ["Profiler"];
|
||||
|
||||
this.Profiler = {
|
||||
|
||||
};
|
||||
|
||||
30
tools/profiler/gecko/ProfilerIOInterposeObserver.cpp
Normal file
30
tools/profiler/gecko/ProfilerIOInterposeObserver.cpp
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
/* 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 "GeckoProfiler.h"
|
||||
#include "ProfilerIOInterposeObserver.h"
|
||||
#include "ProfilerMarkers.h"
|
||||
|
||||
using namespace mozilla;
|
||||
|
||||
void ProfilerIOInterposeObserver::Observe(Observation& aObservation)
|
||||
{
|
||||
if (!IsMainThread()) {
|
||||
return;
|
||||
}
|
||||
|
||||
ProfilerBacktrace* stack = profiler_get_backtrace();
|
||||
|
||||
nsCString filename;
|
||||
if (aObservation.Filename()) {
|
||||
filename = NS_ConvertUTF16toUTF8(aObservation.Filename());
|
||||
}
|
||||
|
||||
IOMarkerPayload* markerPayload = new IOMarkerPayload(aObservation.Reference(),
|
||||
filename.get(),
|
||||
aObservation.Start(),
|
||||
aObservation.End(),
|
||||
stack);
|
||||
PROFILER_MARKER_PAYLOAD(aObservation.ObservedOperationString(), markerPayload);
|
||||
}
|
||||
28
tools/profiler/gecko/ProfilerIOInterposeObserver.h
Normal file
28
tools/profiler/gecko/ProfilerIOInterposeObserver.h
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
/* 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 PROFILERIOINTERPOSEOBSERVER_H
|
||||
#define PROFILERIOINTERPOSEOBSERVER_H
|
||||
|
||||
#ifdef MOZ_ENABLE_PROFILER_SPS
|
||||
|
||||
#include "mozilla/IOInterposer.h"
|
||||
|
||||
namespace mozilla {
|
||||
|
||||
/**
|
||||
* This class is the observer that calls into the profiler whenever
|
||||
* main thread I/O occurs.
|
||||
*/
|
||||
class ProfilerIOInterposeObserver final : public IOInterposeObserver
|
||||
{
|
||||
public:
|
||||
virtual void Observe(Observation& aObservation);
|
||||
};
|
||||
|
||||
} // namespace mozilla
|
||||
|
||||
#endif // MOZ_ENABLE_PROFILER_SPS
|
||||
|
||||
#endif // PROFILERIOINTERPOSEOBSERVER_H
|
||||
16
tools/profiler/gecko/ProfilerTypes.ipdlh
Normal file
16
tools/profiler/gecko/ProfilerTypes.ipdlh
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
/* -*- Mode: C++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 8 -*- */
|
||||
/* 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/. */
|
||||
|
||||
namespace mozilla {
|
||||
|
||||
struct ProfilerInitParams {
|
||||
bool enabled;
|
||||
uint32_t entries;
|
||||
double interval;
|
||||
nsCString[] threadFilters;
|
||||
nsCString[] features;
|
||||
};
|
||||
|
||||
} // namespace mozilla
|
||||
45
tools/profiler/gecko/SaveProfileTask.cpp
Normal file
45
tools/profiler/gecko/SaveProfileTask.cpp
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
/* -*- 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 "SaveProfileTask.h"
|
||||
#include "GeckoProfiler.h"
|
||||
|
||||
nsresult
|
||||
SaveProfileTask::Run() {
|
||||
// Get file path
|
||||
#if defined(SPS_PLAT_arm_android) && !defined(MOZ_WIDGET_GONK)
|
||||
nsCString tmpPath;
|
||||
tmpPath.AppendPrintf("/sdcard/profile_%i_%i.txt", XRE_GetProcessType(), getpid());
|
||||
#else
|
||||
nsCOMPtr<nsIFile> tmpFile;
|
||||
nsAutoCString tmpPath;
|
||||
if (NS_FAILED(NS_GetSpecialDirectory(NS_OS_TEMP_DIR, getter_AddRefs(tmpFile)))) {
|
||||
LOG("Failed to find temporary directory.");
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
tmpPath.AppendPrintf("profile_%i_%i.txt", XRE_GetProcessType(), getpid());
|
||||
|
||||
nsresult rv = tmpFile->AppendNative(tmpPath);
|
||||
if (NS_FAILED(rv))
|
||||
return rv;
|
||||
|
||||
rv = tmpFile->GetNativePath(tmpPath);
|
||||
if (NS_FAILED(rv))
|
||||
return rv;
|
||||
#endif
|
||||
|
||||
profiler_save_profile_to_file(tmpPath.get());
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMPL_ISUPPORTS(ProfileSaveEvent, nsIProfileSaveEvent)
|
||||
|
||||
nsresult
|
||||
ProfileSaveEvent::AddSubProfile(const char* aProfile) {
|
||||
mFunc(aProfile, mClosure);
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
54
tools/profiler/gecko/SaveProfileTask.h
Normal file
54
tools/profiler/gecko/SaveProfileTask.h
Normal file
|
|
@ -0,0 +1,54 @@
|
|||
/* -*- 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 PROFILER_SAVETASK_H_
|
||||
#define PROFILER_SAVETASK_H_
|
||||
|
||||
#include "platform.h"
|
||||
#include "nsThreadUtils.h"
|
||||
#include "nsIXULRuntime.h"
|
||||
#include "nsDirectoryServiceUtils.h"
|
||||
#include "nsDirectoryServiceDefs.h"
|
||||
#include "nsXULAppAPI.h"
|
||||
#include "nsIProfileSaveEvent.h"
|
||||
|
||||
#ifdef XP_WIN
|
||||
#include <windows.h>
|
||||
#define getpid GetCurrentProcessId
|
||||
#else
|
||||
#include <unistd.h>
|
||||
#endif
|
||||
|
||||
/**
|
||||
* This is an event used to save the profile on the main thread
|
||||
* to be sure that it is not being modified while saving.
|
||||
*/
|
||||
class SaveProfileTask : public mozilla::Runnable {
|
||||
public:
|
||||
SaveProfileTask() {}
|
||||
|
||||
NS_IMETHOD Run();
|
||||
};
|
||||
|
||||
class ProfileSaveEvent final : public nsIProfileSaveEvent {
|
||||
public:
|
||||
typedef void (*AddSubProfileFunc)(const char* aProfile, void* aClosure);
|
||||
NS_DECL_ISUPPORTS
|
||||
|
||||
ProfileSaveEvent(AddSubProfileFunc aFunc, void* aClosure)
|
||||
: mFunc(aFunc)
|
||||
, mClosure(aClosure)
|
||||
{}
|
||||
|
||||
NS_IMETHOD AddSubProfile(const char* aProfile) override;
|
||||
private:
|
||||
~ProfileSaveEvent() {}
|
||||
|
||||
AddSubProfileFunc mFunc;
|
||||
void* mClosure;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
118
tools/profiler/gecko/ThreadResponsiveness.cpp
Normal file
118
tools/profiler/gecko/ThreadResponsiveness.cpp
Normal file
|
|
@ -0,0 +1,118 @@
|
|||
/* -*- 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 "ThreadResponsiveness.h"
|
||||
#include "platform.h"
|
||||
#include "nsComponentManagerUtils.h"
|
||||
#include "nsThreadUtils.h"
|
||||
#include "nsITimer.h"
|
||||
#include "mozilla/Monitor.h"
|
||||
#include "ProfileEntry.h"
|
||||
#include "ThreadProfile.h"
|
||||
|
||||
using mozilla::Monitor;
|
||||
using mozilla::MonitorAutoLock;
|
||||
using mozilla::TimeStamp;
|
||||
|
||||
class CheckResponsivenessTask : public mozilla::Runnable,
|
||||
public nsITimerCallback {
|
||||
public:
|
||||
CheckResponsivenessTask()
|
||||
: mLastTracerTime(TimeStamp::Now())
|
||||
, mMonitor("CheckResponsivenessTask")
|
||||
, mTimer(nullptr)
|
||||
, mStop(false)
|
||||
{
|
||||
MOZ_COUNT_CTOR(CheckResponsivenessTask);
|
||||
}
|
||||
|
||||
protected:
|
||||
~CheckResponsivenessTask()
|
||||
{
|
||||
MOZ_COUNT_DTOR(CheckResponsivenessTask);
|
||||
}
|
||||
|
||||
public:
|
||||
NS_IMETHOD Run() override
|
||||
{
|
||||
MonitorAutoLock mon(mMonitor);
|
||||
if (mStop)
|
||||
return NS_OK;
|
||||
|
||||
// This is raced on because we might pause the thread here
|
||||
// for profiling so if we tried to use a monitor to protect
|
||||
// mLastTracerTime we could deadlock. We're risking seeing
|
||||
// a partial write which will show up as an outlier in our
|
||||
// performance data.
|
||||
mLastTracerTime = TimeStamp::Now();
|
||||
if (!mTimer) {
|
||||
mTimer = do_CreateInstance("@mozilla.org/timer;1");
|
||||
}
|
||||
mTimer->InitWithCallback(this, 16, nsITimer::TYPE_ONE_SHOT);
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHOD Notify(nsITimer* aTimer) final
|
||||
{
|
||||
NS_DispatchToMainThread(this);
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
void Terminate() {
|
||||
MonitorAutoLock mon(mMonitor);
|
||||
mStop = true;
|
||||
}
|
||||
|
||||
const TimeStamp& GetLastTracerTime() const {
|
||||
return mLastTracerTime;
|
||||
}
|
||||
|
||||
NS_DECL_ISUPPORTS_INHERITED
|
||||
|
||||
private:
|
||||
TimeStamp mLastTracerTime;
|
||||
Monitor mMonitor;
|
||||
nsCOMPtr<nsITimer> mTimer;
|
||||
bool mStop;
|
||||
};
|
||||
|
||||
NS_IMPL_ISUPPORTS_INHERITED(CheckResponsivenessTask, mozilla::Runnable,
|
||||
nsITimerCallback)
|
||||
|
||||
ThreadResponsiveness::ThreadResponsiveness(ThreadProfile *aThreadProfile)
|
||||
: mThreadProfile(aThreadProfile)
|
||||
, mActiveTracerEvent(nullptr)
|
||||
{
|
||||
MOZ_COUNT_CTOR(ThreadResponsiveness);
|
||||
}
|
||||
|
||||
ThreadResponsiveness::~ThreadResponsiveness()
|
||||
{
|
||||
MOZ_COUNT_DTOR(ThreadResponsiveness);
|
||||
if (mActiveTracerEvent) {
|
||||
mActiveTracerEvent->Terminate();
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
ThreadResponsiveness::Update()
|
||||
{
|
||||
if (!mActiveTracerEvent) {
|
||||
if (mThreadProfile->GetThreadInfo()->IsMainThread()) {
|
||||
mActiveTracerEvent = new CheckResponsivenessTask();
|
||||
NS_DispatchToMainThread(mActiveTracerEvent);
|
||||
} else if (mThreadProfile->GetThreadInfo()->GetThread()) {
|
||||
mActiveTracerEvent = new CheckResponsivenessTask();
|
||||
mThreadProfile->GetThreadInfo()->
|
||||
GetThread()->Dispatch(mActiveTracerEvent, NS_DISPATCH_NORMAL);
|
||||
}
|
||||
}
|
||||
|
||||
if (mActiveTracerEvent) {
|
||||
mLastTracerTime = mActiveTracerEvent->GetLastTracerTime();
|
||||
}
|
||||
}
|
||||
|
||||
38
tools/profiler/gecko/ThreadResponsiveness.h
Normal file
38
tools/profiler/gecko/ThreadResponsiveness.h
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
/* -*- 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 ThreadResponsiveness_h
|
||||
#define ThreadResponsiveness_h
|
||||
|
||||
#include "nsISupports.h"
|
||||
#include "mozilla/RefPtr.h"
|
||||
#include "mozilla/TimeStamp.h"
|
||||
|
||||
class ThreadProfile;
|
||||
class CheckResponsivenessTask;
|
||||
|
||||
class ThreadResponsiveness {
|
||||
public:
|
||||
explicit ThreadResponsiveness(ThreadProfile *aThreadProfile);
|
||||
|
||||
~ThreadResponsiveness();
|
||||
|
||||
void Update();
|
||||
|
||||
mozilla::TimeDuration GetUnresponsiveDuration(const mozilla::TimeStamp& now) const {
|
||||
return now - mLastTracerTime;
|
||||
}
|
||||
|
||||
bool HasData() const {
|
||||
return !mLastTracerTime.IsNull();
|
||||
}
|
||||
private:
|
||||
ThreadProfile* mThreadProfile;
|
||||
RefPtr<CheckResponsivenessTask> mActiveTracerEvent;
|
||||
mozilla::TimeStamp mLastTracerTime;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
19
tools/profiler/gecko/nsIProfileSaveEvent.idl
Normal file
19
tools/profiler/gecko/nsIProfileSaveEvent.idl
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
/* -*- Mode: IDL; 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 "nsISupports.idl"
|
||||
|
||||
[uuid(f5ad0830-e178-41f9-b253-db9b4fae4cb3)]
|
||||
interface nsIProfileSaveEvent : nsISupports
|
||||
{
|
||||
/**
|
||||
* Call this method when observing this event to include
|
||||
* a sub profile origining from an external source such
|
||||
* as a non native thread or another process.
|
||||
*/
|
||||
void AddSubProfile(in string aMarker);
|
||||
};
|
||||
|
||||
|
||||
101
tools/profiler/gecko/nsIProfiler.idl
Normal file
101
tools/profiler/gecko/nsIProfiler.idl
Normal file
|
|
@ -0,0 +1,101 @@
|
|||
/* -*- Mode: IDL; 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 "nsISupports.idl"
|
||||
|
||||
%{C++
|
||||
#include "nsTArrayForwardDeclare.h"
|
||||
class nsCString;
|
||||
%}
|
||||
|
||||
[ref] native StringArrayRef(const nsTArray<nsCString>);
|
||||
|
||||
/**
|
||||
* Start-up parameters for subprocesses are passed through nsIObserverService,
|
||||
* which, unfortunately, means we need to implement nsISupports in order to
|
||||
* go through it.
|
||||
*/
|
||||
[uuid(0a175ba7-8fcf-4ce9-9c4b-ccc6272f4425)]
|
||||
interface nsIProfilerStartParams : nsISupports
|
||||
{
|
||||
attribute uint32_t entries;
|
||||
attribute double interval;
|
||||
|
||||
[noscript, notxpcom, nostdcall] StringArrayRef getFeatures();
|
||||
[noscript, notxpcom, nostdcall] StringArrayRef getThreadFilterNames();
|
||||
};
|
||||
|
||||
[scriptable, uuid(ead3f75c-0e0e-4fbb-901c-1e5392ef5b2a)]
|
||||
interface nsIProfiler : nsISupports
|
||||
{
|
||||
boolean CanProfile();
|
||||
void StartProfiler(in uint32_t aEntries, in double aInterval,
|
||||
[array, size_is(aFeatureCount)] in string aFeatures,
|
||||
in uint32_t aFeatureCount,
|
||||
[array, size_is(aFilterCount), optional] in string aThreadNameFilters,
|
||||
[optional] in uint32_t aFilterCount);
|
||||
void StopProfiler();
|
||||
boolean IsPaused();
|
||||
void PauseSampling();
|
||||
void ResumeSampling();
|
||||
void AddMarker(in string aMarker);
|
||||
/*
|
||||
* Returns the JSON string of the profile. If aSinceTime is passed, only
|
||||
* report samples taken at >= aSinceTime.
|
||||
*/
|
||||
string GetProfile([optional] in double aSinceTime);
|
||||
|
||||
/*
|
||||
* Returns a JS object of the profile. If aSinceTime is passed, only report
|
||||
* samples taken at >= aSinceTime.
|
||||
*/
|
||||
[implicit_jscontext]
|
||||
jsval getProfileData([optional] in double aSinceTime);
|
||||
|
||||
[implicit_jscontext]
|
||||
nsISupports getProfileDataAsync([optional] in double aSinceTime);
|
||||
|
||||
boolean IsActive();
|
||||
void GetFeatures(out uint32_t aCount, [retval, array, size_is(aCount)] out string aFeatures);
|
||||
|
||||
/**
|
||||
* The starting parameters that were sent to the profiler for sampling.
|
||||
* If the profiler is not currently sampling, this will return null.
|
||||
*/
|
||||
readonly attribute nsIProfilerStartParams startParams;
|
||||
|
||||
/**
|
||||
* The profileGatherer will be null if the profiler is not currently
|
||||
* active.
|
||||
*/
|
||||
readonly attribute nsISupports profileGatherer;
|
||||
|
||||
void GetBufferInfo(out uint32_t aCurrentPosition, out uint32_t aTotalSize,
|
||||
out uint32_t aGeneration);
|
||||
|
||||
/**
|
||||
* Returns the elapsed time, in milliseconds, since the profiler's epoch.
|
||||
* The epoch is guaranteed to be constant for the duration of the
|
||||
* process, but is otherwise arbitrary.
|
||||
*/
|
||||
double getElapsedTime();
|
||||
|
||||
/**
|
||||
* Returns a JSON string of an array of shared library objects.
|
||||
* Every object has three properties: start, end, and name.
|
||||
* start and end are integers describing the address range that the library
|
||||
* occupies in memory. name is the path of the library as a string.
|
||||
*
|
||||
* On Windows profiling builds, the shared library objects will have
|
||||
* additional pdbSignature and pdbAge properties for uniquely identifying
|
||||
* shared library versions for stack symbolication.
|
||||
*/
|
||||
AString getSharedLibraryInformation();
|
||||
|
||||
/**
|
||||
* Dump the collected profile to a file.
|
||||
*/
|
||||
void dumpProfileToFile(in string aFilename);
|
||||
};
|
||||
308
tools/profiler/gecko/nsProfiler.cpp
Normal file
308
tools/profiler/gecko/nsProfiler.cpp
Normal file
|
|
@ -0,0 +1,308 @@
|
|||
/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
|
||||
/* 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 <string>
|
||||
#include <sstream>
|
||||
#include "GeckoProfiler.h"
|
||||
#include "nsProfiler.h"
|
||||
#include "nsProfilerStartParams.h"
|
||||
#include "nsMemory.h"
|
||||
#include "nsString.h"
|
||||
#include "mozilla/Services.h"
|
||||
#include "nsIObserverService.h"
|
||||
#include "nsIInterfaceRequestor.h"
|
||||
#include "nsILoadContext.h"
|
||||
#include "nsIWebNavigation.h"
|
||||
#include "nsIInterfaceRequestorUtils.h"
|
||||
#include "shared-libraries.h"
|
||||
#include "js/Value.h"
|
||||
#include "mozilla/ErrorResult.h"
|
||||
#include "mozilla/dom/Promise.h"
|
||||
|
||||
using mozilla::ErrorResult;
|
||||
using mozilla::dom::Promise;
|
||||
using std::string;
|
||||
|
||||
NS_IMPL_ISUPPORTS(nsProfiler, nsIProfiler)
|
||||
|
||||
nsProfiler::nsProfiler()
|
||||
: mLockedForPrivateBrowsing(false)
|
||||
{
|
||||
}
|
||||
|
||||
nsProfiler::~nsProfiler()
|
||||
{
|
||||
nsCOMPtr<nsIObserverService> observerService = mozilla::services::GetObserverService();
|
||||
if (observerService) {
|
||||
observerService->RemoveObserver(this, "chrome-document-global-created");
|
||||
observerService->RemoveObserver(this, "last-pb-context-exited");
|
||||
}
|
||||
}
|
||||
|
||||
nsresult
|
||||
nsProfiler::Init() {
|
||||
nsCOMPtr<nsIObserverService> observerService = mozilla::services::GetObserverService();
|
||||
if (observerService) {
|
||||
observerService->AddObserver(this, "chrome-document-global-created", false);
|
||||
observerService->AddObserver(this, "last-pb-context-exited", false);
|
||||
}
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsProfiler::Observe(nsISupports *aSubject,
|
||||
const char *aTopic,
|
||||
const char16_t *aData)
|
||||
{
|
||||
if (strcmp(aTopic, "chrome-document-global-created") == 0) {
|
||||
nsCOMPtr<nsIInterfaceRequestor> requestor = do_QueryInterface(aSubject);
|
||||
nsCOMPtr<nsIWebNavigation> parentWebNav = do_GetInterface(requestor);
|
||||
nsCOMPtr<nsILoadContext> loadContext = do_QueryInterface(parentWebNav);
|
||||
if (loadContext && loadContext->UsePrivateBrowsing() && !mLockedForPrivateBrowsing) {
|
||||
mLockedForPrivateBrowsing = true;
|
||||
profiler_lock();
|
||||
}
|
||||
} else if (strcmp(aTopic, "last-pb-context-exited") == 0) {
|
||||
mLockedForPrivateBrowsing = false;
|
||||
profiler_unlock();
|
||||
}
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsProfiler::CanProfile(bool *aCanProfile)
|
||||
{
|
||||
*aCanProfile = !mLockedForPrivateBrowsing;
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsProfiler::StartProfiler(uint32_t aEntries, double aInterval,
|
||||
const char** aFeatures, uint32_t aFeatureCount,
|
||||
const char** aThreadNameFilters, uint32_t aFilterCount)
|
||||
{
|
||||
if (mLockedForPrivateBrowsing) {
|
||||
return NS_ERROR_NOT_AVAILABLE;
|
||||
}
|
||||
|
||||
profiler_start(aEntries, aInterval,
|
||||
aFeatures, aFeatureCount,
|
||||
aThreadNameFilters, aFilterCount);
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsProfiler::StopProfiler()
|
||||
{
|
||||
profiler_stop();
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsProfiler::IsPaused(bool *aIsPaused)
|
||||
{
|
||||
*aIsPaused = profiler_is_paused();
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsProfiler::PauseSampling()
|
||||
{
|
||||
profiler_pause();
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsProfiler::ResumeSampling()
|
||||
{
|
||||
profiler_resume();
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsProfiler::AddMarker(const char *aMarker)
|
||||
{
|
||||
PROFILER_MARKER(aMarker);
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsProfiler::GetProfile(double aSinceTime, char** aProfile)
|
||||
{
|
||||
mozilla::UniquePtr<char[]> profile = profiler_get_profile(aSinceTime);
|
||||
if (profile) {
|
||||
size_t len = strlen(profile.get());
|
||||
char *profileStr = static_cast<char *>
|
||||
(nsMemory::Clone(profile.get(), (len + 1) * sizeof(char)));
|
||||
profileStr[len] = '\0';
|
||||
*aProfile = profileStr;
|
||||
}
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
std::string GetSharedLibraryInfoStringInternal();
|
||||
|
||||
std::string
|
||||
GetSharedLibraryInfoString()
|
||||
{
|
||||
return GetSharedLibraryInfoStringInternal();
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsProfiler::GetSharedLibraryInformation(nsAString& aOutString)
|
||||
{
|
||||
aOutString.Assign(NS_ConvertUTF8toUTF16(GetSharedLibraryInfoString().c_str()));
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsProfiler::DumpProfileToFile(const char* aFilename)
|
||||
{
|
||||
profiler_save_profile_to_file(aFilename);
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsProfiler::GetProfileData(double aSinceTime, JSContext* aCx,
|
||||
JS::MutableHandle<JS::Value> aResult)
|
||||
{
|
||||
JS::RootedObject obj(aCx, profiler_get_profile_jsobject(aCx, aSinceTime));
|
||||
if (!obj) {
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
aResult.setObject(*obj);
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsProfiler::GetProfileDataAsync(double aSinceTime, JSContext* aCx,
|
||||
nsISupports** aPromise)
|
||||
{
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
|
||||
if (NS_WARN_IF(!aCx)) {
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
|
||||
nsIGlobalObject* go = xpc::NativeGlobal(JS::CurrentGlobalOrNull(aCx));
|
||||
|
||||
if (NS_WARN_IF(!go)) {
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
|
||||
ErrorResult result;
|
||||
RefPtr<Promise> promise = Promise::Create(go, result);
|
||||
if (NS_WARN_IF(result.Failed())) {
|
||||
return result.StealNSResult();
|
||||
}
|
||||
|
||||
profiler_get_profile_jsobject_async(aSinceTime, promise);
|
||||
|
||||
promise.forget(aPromise);
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsProfiler::GetElapsedTime(double* aElapsedTime)
|
||||
{
|
||||
*aElapsedTime = profiler_time();
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsProfiler::IsActive(bool *aIsActive)
|
||||
{
|
||||
*aIsActive = profiler_is_active();
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsProfiler::GetFeatures(uint32_t *aCount, char ***aFeatures)
|
||||
{
|
||||
uint32_t len = 0;
|
||||
|
||||
const char **features = profiler_get_features();
|
||||
if (!features) {
|
||||
*aCount = 0;
|
||||
*aFeatures = nullptr;
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
while (features[len]) {
|
||||
len++;
|
||||
}
|
||||
|
||||
char **featureList = static_cast<char **>
|
||||
(moz_xmalloc(len * sizeof(char*)));
|
||||
|
||||
for (size_t i = 0; i < len; i++) {
|
||||
size_t strLen = strlen(features[i]);
|
||||
featureList[i] = static_cast<char *>
|
||||
(nsMemory::Clone(features[i], (strLen + 1) * sizeof(char)));
|
||||
}
|
||||
|
||||
*aFeatures = featureList;
|
||||
*aCount = len;
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsProfiler::GetStartParams(nsIProfilerStartParams** aRetVal)
|
||||
{
|
||||
if (!profiler_is_active()) {
|
||||
*aRetVal = nullptr;
|
||||
} else {
|
||||
int entrySize = 0;
|
||||
double interval = 0;
|
||||
mozilla::Vector<const char*> filters;
|
||||
mozilla::Vector<const char*> features;
|
||||
profiler_get_start_params(&entrySize, &interval, &filters, &features);
|
||||
|
||||
nsTArray<nsCString> filtersArray;
|
||||
for (uint32_t i = 0; i < filters.length(); ++i) {
|
||||
filtersArray.AppendElement(filters[i]);
|
||||
}
|
||||
|
||||
nsTArray<nsCString> featuresArray;
|
||||
for (size_t i = 0; i < features.length(); ++i) {
|
||||
featuresArray.AppendElement(features[i]);
|
||||
}
|
||||
|
||||
nsCOMPtr<nsIProfilerStartParams> startParams =
|
||||
new nsProfilerStartParams(entrySize, interval, featuresArray,
|
||||
filtersArray);
|
||||
|
||||
startParams.forget(aRetVal);
|
||||
}
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsProfiler::GetBufferInfo(uint32_t *aCurrentPosition, uint32_t *aTotalSize, uint32_t *aGeneration)
|
||||
{
|
||||
MOZ_ASSERT(aCurrentPosition);
|
||||
MOZ_ASSERT(aTotalSize);
|
||||
MOZ_ASSERT(aGeneration);
|
||||
profiler_get_buffer_info(aCurrentPosition, aTotalSize, aGeneration);
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsProfiler::GetProfileGatherer(nsISupports** aRetVal)
|
||||
{
|
||||
if (!aRetVal) {
|
||||
return NS_ERROR_INVALID_POINTER;
|
||||
}
|
||||
|
||||
// If we're not profiling, there will be no gatherer.
|
||||
if (!profiler_is_active()) {
|
||||
*aRetVal = nullptr;
|
||||
} else {
|
||||
nsCOMPtr<nsISupports> gatherer;
|
||||
profiler_get_gatherer(getter_AddRefs(gatherer));
|
||||
gatherer.forget(aRetVal);
|
||||
}
|
||||
return NS_OK;
|
||||
}
|
||||
29
tools/profiler/gecko/nsProfiler.h
Normal file
29
tools/profiler/gecko/nsProfiler.h
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
|
||||
/* 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 _NSPROFILER_H_
|
||||
#define _NSPROFILER_H_
|
||||
|
||||
#include "nsIProfiler.h"
|
||||
#include "nsIObserver.h"
|
||||
#include "mozilla/Attributes.h"
|
||||
|
||||
class nsProfiler final : public nsIProfiler, public nsIObserver
|
||||
{
|
||||
public:
|
||||
nsProfiler();
|
||||
|
||||
NS_DECL_ISUPPORTS
|
||||
NS_DECL_NSIOBSERVER
|
||||
NS_DECL_NSIPROFILER
|
||||
|
||||
nsresult Init();
|
||||
private:
|
||||
~nsProfiler();
|
||||
bool mLockedForPrivateBrowsing;
|
||||
};
|
||||
|
||||
#endif /* _NSPROFILER_H_ */
|
||||
|
||||
14
tools/profiler/gecko/nsProfilerCIID.h
Normal file
14
tools/profiler/gecko/nsProfilerCIID.h
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
/* -*- 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 nsProfilerCIID_h__
|
||||
#define nsProfilerCIID_h__
|
||||
|
||||
#define NS_PROFILER_CID \
|
||||
{ 0x25db9b8e, 0x8123, 0x4de1, \
|
||||
{ 0xb6, 0x6d, 0x8b, 0xbb, 0xed, 0xf2, 0xcd, 0xf4 } }
|
||||
|
||||
#endif
|
||||
|
||||
31
tools/profiler/gecko/nsProfilerFactory.cpp
Normal file
31
tools/profiler/gecko/nsProfilerFactory.cpp
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
|
||||
/* 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 "mozilla/ModuleUtils.h"
|
||||
#include "nsCOMPtr.h"
|
||||
#include "nsProfiler.h"
|
||||
#include "nsProfilerCIID.h"
|
||||
|
||||
NS_GENERIC_FACTORY_CONSTRUCTOR_INIT(nsProfiler, Init)
|
||||
|
||||
NS_DEFINE_NAMED_CID(NS_PROFILER_CID);
|
||||
|
||||
static const mozilla::Module::CIDEntry kProfilerCIDs[] = {
|
||||
{ &kNS_PROFILER_CID, false, nullptr, nsProfilerConstructor },
|
||||
{ nullptr }
|
||||
};
|
||||
|
||||
static const mozilla::Module::ContractIDEntry kProfilerContracts[] = {
|
||||
{ "@mozilla.org/tools/profiler;1", &kNS_PROFILER_CID },
|
||||
{ nullptr }
|
||||
};
|
||||
|
||||
static const mozilla::Module kProfilerModule = {
|
||||
mozilla::Module::kVersion,
|
||||
kProfilerCIDs,
|
||||
kProfilerContracts
|
||||
};
|
||||
|
||||
NSMODULE_DEFN(nsProfilerModule) = &kProfilerModule;
|
||||
67
tools/profiler/gecko/nsProfilerStartParams.cpp
Normal file
67
tools/profiler/gecko/nsProfilerStartParams.cpp
Normal file
|
|
@ -0,0 +1,67 @@
|
|||
/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
|
||||
/* 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 "nsProfilerStartParams.h"
|
||||
|
||||
NS_IMPL_ISUPPORTS(nsProfilerStartParams, nsIProfilerStartParams)
|
||||
|
||||
nsProfilerStartParams::nsProfilerStartParams(uint32_t aEntries,
|
||||
double aInterval,
|
||||
const nsTArray<nsCString>& aFeatures,
|
||||
const nsTArray<nsCString>& aThreadFilterNames) :
|
||||
mEntries(aEntries),
|
||||
mInterval(aInterval),
|
||||
mFeatures(aFeatures),
|
||||
mThreadFilterNames(aThreadFilterNames)
|
||||
{
|
||||
}
|
||||
|
||||
nsProfilerStartParams::~nsProfilerStartParams()
|
||||
{
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsProfilerStartParams::GetEntries(uint32_t* aEntries)
|
||||
{
|
||||
NS_ENSURE_ARG_POINTER(aEntries);
|
||||
*aEntries = mEntries;
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsProfilerStartParams::SetEntries(uint32_t aEntries)
|
||||
{
|
||||
NS_ENSURE_ARG(aEntries);
|
||||
mEntries = aEntries;
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsProfilerStartParams::GetInterval(double* aInterval)
|
||||
{
|
||||
NS_ENSURE_ARG_POINTER(aInterval);
|
||||
*aInterval = mInterval;
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsProfilerStartParams::SetInterval(double aInterval)
|
||||
{
|
||||
NS_ENSURE_ARG(aInterval);
|
||||
mInterval = aInterval;
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
const nsTArray<nsCString>&
|
||||
nsProfilerStartParams::GetFeatures()
|
||||
{
|
||||
return mFeatures;
|
||||
}
|
||||
|
||||
const nsTArray<nsCString>&
|
||||
nsProfilerStartParams::GetThreadFilterNames()
|
||||
{
|
||||
return mThreadFilterNames;
|
||||
}
|
||||
32
tools/profiler/gecko/nsProfilerStartParams.h
Normal file
32
tools/profiler/gecko/nsProfilerStartParams.h
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
|
||||
/* 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 _NSPROFILERSTARTPARAMS_H_
|
||||
#define _NSPROFILERSTARTPARAMS_H_
|
||||
|
||||
#include "nsIProfiler.h"
|
||||
#include "nsString.h"
|
||||
#include "nsTArray.h"
|
||||
|
||||
class nsProfilerStartParams : public nsIProfilerStartParams
|
||||
{
|
||||
public:
|
||||
NS_DECL_ISUPPORTS
|
||||
NS_DECL_NSIPROFILERSTARTPARAMS
|
||||
|
||||
nsProfilerStartParams(uint32_t aEntries,
|
||||
double aInterval,
|
||||
const nsTArray<nsCString>& aFeatures,
|
||||
const nsTArray<nsCString>& aThreadFilterNames);
|
||||
|
||||
private:
|
||||
virtual ~nsProfilerStartParams();
|
||||
uint32_t mEntries;
|
||||
double mInterval;
|
||||
nsTArray<nsCString> mFeatures;
|
||||
nsTArray<nsCString> mThreadFilterNames;
|
||||
};
|
||||
|
||||
#endif
|
||||
207
tools/profiler/lul/AutoObjectMapper.cpp
Normal file
207
tools/profiler/lul/AutoObjectMapper.cpp
Normal file
|
|
@ -0,0 +1,207 @@
|
|||
/* -*- 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 "PlatformMacros.h"
|
||||
#include "AutoObjectMapper.h"
|
||||
|
||||
#if defined(SPS_OS_android) && !defined(MOZ_WIDGET_GONK)
|
||||
# include <dlfcn.h>
|
||||
# include "mozilla/Types.h"
|
||||
// FIXME move these out of mozglue/linker/ElfLoader.h into their
|
||||
// own header, so as to avoid conflicts arising from two definitions
|
||||
// of Array
|
||||
extern "C" {
|
||||
MFBT_API size_t
|
||||
__dl_get_mappable_length(void *handle);
|
||||
MFBT_API void *
|
||||
__dl_mmap(void *handle, void *addr, size_t length, off_t offset);
|
||||
MFBT_API void
|
||||
__dl_munmap(void *handle, void *addr, size_t length);
|
||||
}
|
||||
// The following are for get_installation_lib_dir()
|
||||
# include "nsString.h"
|
||||
# include "nsDirectoryServiceUtils.h"
|
||||
# include "nsDirectoryServiceDefs.h"
|
||||
#endif
|
||||
|
||||
|
||||
// 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;
|
||||
}
|
||||
|
||||
|
||||
#if defined(SPS_OS_android) && !defined(MOZ_WIDGET_GONK)
|
||||
// A helper function for AutoObjectMapperFaultyLib::Map. Finds out
|
||||
// where the installation's lib directory is, since we'll have to look
|
||||
// in there to get hold of libmozglue.so. Returned C string is heap
|
||||
// allocated and the caller must deallocate it.
|
||||
static char*
|
||||
get_installation_lib_dir()
|
||||
{
|
||||
nsCOMPtr<nsIProperties>
|
||||
directoryService(do_GetService(NS_DIRECTORY_SERVICE_CONTRACTID));
|
||||
if (!directoryService) {
|
||||
return nullptr;
|
||||
}
|
||||
nsCOMPtr<nsIFile> greDir;
|
||||
nsresult rv = directoryService->Get(NS_GRE_DIR, NS_GET_IID(nsIFile),
|
||||
getter_AddRefs(greDir));
|
||||
if (NS_FAILED(rv)) return nullptr;
|
||||
nsCString path;
|
||||
rv = greDir->GetNativePath(path);
|
||||
if (NS_FAILED(rv)) {
|
||||
return nullptr;
|
||||
}
|
||||
return strdup(path.get());
|
||||
}
|
||||
|
||||
AutoObjectMapperFaultyLib::AutoObjectMapperFaultyLib(void(*aLog)(const char*))
|
||||
: AutoObjectMapperPOSIX(aLog)
|
||||
, mHdl(nullptr)
|
||||
{}
|
||||
|
||||
AutoObjectMapperFaultyLib::~AutoObjectMapperFaultyLib() {
|
||||
if (mHdl) {
|
||||
// We've got an object mapped by faulty.lib. Unmap it via faulty.lib.
|
||||
MOZ_ASSERT(mSize > 0);
|
||||
// Assert on the basis that no valid mapping would start at page zero.
|
||||
MOZ_ASSERT(mImage);
|
||||
__dl_munmap(mHdl, mImage, mSize);
|
||||
dlclose(mHdl);
|
||||
// Stop assertions in ~AutoObjectMapperPOSIX from failing.
|
||||
mImage = nullptr;
|
||||
mSize = 0;
|
||||
}
|
||||
// At this point the parent class destructor, ~AutoObjectMapperPOSIX,
|
||||
// gets called. If that has something mapped in the normal way, it
|
||||
// will unmap it in the normal way. Unfortunately there's no
|
||||
// obvious way to enforce the requirement that the object is mapped
|
||||
// either by faulty.lib or by the parent class, but not by both.
|
||||
}
|
||||
|
||||
bool AutoObjectMapperFaultyLib::Map(/*OUT*/void** start, /*OUT*/size_t* length,
|
||||
std::string fileName)
|
||||
{
|
||||
MOZ_ASSERT(!mHdl);
|
||||
|
||||
if (fileName == "libmozglue.so") {
|
||||
|
||||
// Do (2) in the comment above.
|
||||
char* libdir = get_installation_lib_dir();
|
||||
if (libdir) {
|
||||
fileName = std::string(libdir) + "/lib/" + fileName;
|
||||
free(libdir);
|
||||
}
|
||||
// Hand the problem off to the standard mapper.
|
||||
return AutoObjectMapperPOSIX::Map(start, length, fileName);
|
||||
|
||||
} else {
|
||||
|
||||
// Do cases (1) and (3) in the comment above. We have to
|
||||
// grapple with faulty.lib directly.
|
||||
void* hdl = dlopen(fileName.c_str(), RTLD_GLOBAL | RTLD_LAZY);
|
||||
if (!hdl) {
|
||||
failedToMessage(mLog, "get handle for ELF file", fileName);
|
||||
return false;
|
||||
}
|
||||
|
||||
size_t sz = __dl_get_mappable_length(hdl);
|
||||
if (sz == 0) {
|
||||
dlclose(hdl);
|
||||
failedToMessage(mLog, "get size for ELF file", fileName);
|
||||
return false;
|
||||
}
|
||||
|
||||
void* image = __dl_mmap(hdl, nullptr, sz, 0);
|
||||
if (image == MAP_FAILED) {
|
||||
dlclose(hdl);
|
||||
failedToMessage(mLog, "mmap ELF file", fileName);
|
||||
return false;
|
||||
}
|
||||
|
||||
mHdl = hdl;
|
||||
mImage = *start = image;
|
||||
mSize = *length = sz;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
#endif // defined(SPS_OS_android) && !defined(MOZ_WIDGET_GONK)
|
||||
115
tools/profiler/lul/AutoObjectMapper.h
Normal file
115
tools/profiler/lul/AutoObjectMapper.h
Normal file
|
|
@ -0,0 +1,115 @@
|
|||
/* -*- 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*);
|
||||
};
|
||||
|
||||
|
||||
#if defined(SPS_OS_android) && !defined(MOZ_WIDGET_GONK)
|
||||
// This is a variant of AutoObjectMapperPOSIX suitable for use in
|
||||
// conjunction with faulty.lib on Android. How it behaves depends on
|
||||
// the name of the file to be mapped. There are three possible cases:
|
||||
//
|
||||
// (1) /foo/bar/xyzzy/blah.apk!/libwurble.so
|
||||
// We hand it as-is to faulty.lib and let it fish the relevant
|
||||
// bits out of the APK.
|
||||
//
|
||||
// (2) libmozglue.so
|
||||
// This is part of the Fennec installation, but is not in the
|
||||
// APK. Instead we have to figure out the installation path
|
||||
// and look for it there. Because of faulty.lib limitations,
|
||||
// we have to use regular open/mmap instead of faulty.lib.
|
||||
//
|
||||
// (3) libanythingelse.so
|
||||
// faulty.lib assumes this is a system library, and prepends
|
||||
// "/system/lib/" to the path. So as in (1), we can give it
|
||||
// as-is to faulty.lib.
|
||||
//
|
||||
// Hence (1) and (3) require special-casing here. Case (2) simply
|
||||
// hands the problem to the parent class.
|
||||
|
||||
class MOZ_STACK_CLASS AutoObjectMapperFaultyLib : public AutoObjectMapperPOSIX {
|
||||
public:
|
||||
AutoObjectMapperFaultyLib(void(*aLog)(const char*));
|
||||
|
||||
~AutoObjectMapperFaultyLib();
|
||||
|
||||
bool Map(/*OUT*/void** start, /*OUT*/size_t* length, std::string fileName);
|
||||
|
||||
private:
|
||||
// faulty.lib requires us to maintain an abstract handle that can be
|
||||
// used later to unmap the area. If this is non-NULL, it is assumed
|
||||
// that unmapping is to be done by faulty.lib. Otherwise it goes
|
||||
// via the normal mechanism.
|
||||
void* mHdl;
|
||||
|
||||
// Disable copying and assignment.
|
||||
AutoObjectMapperFaultyLib(const AutoObjectMapperFaultyLib&);
|
||||
AutoObjectMapperFaultyLib& operator=(const AutoObjectMapperFaultyLib&);
|
||||
// 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 // defined(SPS_OS_android) && !defined(MOZ_WIDGET_GONK)
|
||||
|
||||
#endif // AutoObjectMapper_h
|
||||
114
tools/profiler/lul/LulCommon.cpp
Normal file
114
tools/profiler/lul/LulCommon.cpp
Normal file
|
|
@ -0,0 +1,114 @@
|
|||
/* -*- 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>
|
||||
|
||||
|
||||
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
|
||||
554
tools/profiler/lul/LulCommonExt.h
Normal file
554
tools/profiler/lul/LulCommonExt.h
Normal file
|
|
@ -0,0 +1,554 @@
|
|||
/* -*- 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::string;
|
||||
using std::map;
|
||||
|
||||
|
||||
////////////////////////////////////////////////////////////////
|
||||
// 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>
|
||||
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
|
||||
2180
tools/profiler/lul/LulDwarf.cpp
Normal file
2180
tools/profiler/lul/LulDwarf.cpp
Normal file
File diff suppressed because it is too large
Load diff
1287
tools/profiler/lul/LulDwarfExt.h
Normal file
1287
tools/profiler/lul/LulDwarfExt.h
Normal file
File diff suppressed because it is too large
Load diff
194
tools/profiler/lul/LulDwarfInt.h
Normal file
194
tools/profiler/lul/LulDwarfInt.h
Normal file
|
|
@ -0,0 +1,194 @@
|
|||
/* -*- 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
|
||||
359
tools/profiler/lul/LulDwarfSummariser.cpp
Normal file
359
tools/profiler/lul/LulDwarfSummariser.cpp
Normal file
|
|
@ -0,0 +1,359 @@
|
|||
/* -*- 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"
|
||||
|
||||
// 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(LUL_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(LUL_ARCH_x64) || defined(LUL_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 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_INTEL_XSP && oldReg != DW_REG_INTEL_XBP) {
|
||||
reason1 = "rule for DW_REG_CFA: invalid |oldReg|";
|
||||
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);
|
||||
}
|
||||
|
||||
#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
|
||||
65
tools/profiler/lul/LulDwarfSummariser.h
Normal file
65
tools/profiler/lul/LulDwarfSummariser.h
Normal file
|
|
@ -0,0 +1,65 @@
|
|||
/* -*- 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
|
||||
915
tools/profiler/lul/LulElf.cpp
Normal file
915
tools/profiler/lul/LulElf.cpp
Normal file
|
|
@ -0,0 +1,915 @@
|
|||
/* -*- 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 <stdio.h>
|
||||
#include <string.h>
|
||||
#include <sys/mman.h>
|
||||
#include <sys/stat.h>
|
||||
#include <unistd.h>
|
||||
#include <arpa/inet.h>
|
||||
|
||||
#include <set>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "mozilla/Assertions.h"
|
||||
#include "mozilla/Sprintf.h"
|
||||
|
||||
#include "LulPlatformMacros.h"
|
||||
#include "LulCommonExt.h"
|
||||
#include "LulDwarfExt.h"
|
||||
#include "LulElfInt.h"
|
||||
#include "LulMainInt.h"
|
||||
|
||||
|
||||
#if defined(LUL_PLAT_arm_android) && !defined(SHT_ARM_EXIDX)
|
||||
// bionic and older glibsc don't define it
|
||||
# define SHT_ARM_EXIDX (SHT_LOPROC + 1)
|
||||
#endif
|
||||
|
||||
|
||||
// This namespace contains helper functions.
|
||||
namespace {
|
||||
|
||||
using lul::DwarfCFIToModule;
|
||||
using lul::FindElfSectionByName;
|
||||
using lul::GetOffset;
|
||||
using lul::IsValidElf;
|
||||
using lul::Module;
|
||||
using lul::UniqueStringUniverse;
|
||||
using lul::scoped_ptr;
|
||||
using lul::Summariser;
|
||||
using std::string;
|
||||
using std::vector;
|
||||
using std::set;
|
||||
|
||||
//
|
||||
// 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;
|
||||
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 §ion) {
|
||||
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_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 (anon)
|
||||
|
||||
|
||||
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 (anon)
|
||||
|
||||
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
|
||||
68
tools/profiler/lul/LulElfExt.h
Normal file
68
tools/profiler/lul/LulElfExt.h
Normal file
|
|
@ -0,0 +1,68 @@
|
|||
/* -*- 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 {
|
||||
|
||||
// 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,
|
||||
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,
|
||||
void (*log)(const char*));
|
||||
|
||||
} // namespace lul
|
||||
|
||||
#endif // LulElfExt_h
|
||||
234
tools/profiler/lul/LulElfInt.h
Normal file
234
tools/profiler/lul/LulElfInt.h
Normal file
|
|
@ -0,0 +1,234 @@
|
|||
/* -*- 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 "LulPlatformMacros.h"
|
||||
|
||||
|
||||
// (derived from)
|
||||
// elfutils.h: Utilities for dealing with ELF files.
|
||||
//
|
||||
|
||||
#if defined(LUL_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
|
||||
|
||||
#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
|
||||
|
||||
//FIXME
|
||||
extern "C" {
|
||||
extern char* basename(const char* path);
|
||||
};
|
||||
#else
|
||||
|
||||
# include <link.h>
|
||||
#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
|
||||
1963
tools/profiler/lul/LulMain.cpp
Normal file
1963
tools/profiler/lul/LulMain.cpp
Normal file
File diff suppressed because it is too large
Load diff
397
tools/profiler/lul/LulMain.h
Normal file
397
tools/profiler/lul/LulMain.h
Normal file
|
|
@ -0,0 +1,397 @@
|
|||
/* -*- 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 "LulPlatformMacros.h"
|
||||
#include "mozilla/Atomics.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(LUL_ARCH_arm)
|
||||
TaggedUWord r7;
|
||||
TaggedUWord r11;
|
||||
TaggedUWord r12;
|
||||
TaggedUWord r13;
|
||||
TaggedUWord r14;
|
||||
TaggedUWord r15;
|
||||
#elif defined(LUL_ARCH_x64) || defined(LUL_ARCH_x86)
|
||||
TaggedUWord xbp;
|
||||
TaggedUWord xsp;
|
||||
TaggedUWord xip;
|
||||
#else
|
||||
# error "Unknown plat"
|
||||
#endif
|
||||
};
|
||||
|
||||
|
||||
// The maximum number of bytes in a stack snapshot. This can be
|
||||
// increased if necessary, but larger values cost performance, since a
|
||||
// stack snapshot needs to be copied between sampling and worker
|
||||
// threads for each snapshot. In practice 32k seems to be enough
|
||||
// to get good backtraces.
|
||||
static const size_t N_STACK_BYTES = 32768;
|
||||
|
||||
// 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)
|
||||
, mScanned(0)
|
||||
{}
|
||||
|
||||
template <typename S>
|
||||
explicit LULStats(const LULStats<S>& aOther)
|
||||
: mContext(aOther.mContext)
|
||||
, mCFI(aOther.mCFI)
|
||||
, mScanned(aOther.mScanned)
|
||||
{}
|
||||
|
||||
template <typename S>
|
||||
LULStats<T>& operator=(const LULStats<S>& aOther)
|
||||
{
|
||||
mContext = aOther.mContext;
|
||||
mCFI = aOther.mCFI;
|
||||
mScanned = aOther.mScanned;
|
||||
return *this;
|
||||
}
|
||||
|
||||
template <typename S>
|
||||
uint32_t operator-(const LULStats<S>& aOther) {
|
||||
return (mContext - aOther.mContext) +
|
||||
(mCFI - aOther.mCFI) + (mScanned - aOther.mScanned);
|
||||
}
|
||||
|
||||
T mContext; // Number of context frames
|
||||
T mCFI; // Number of CFI/EXIDX frames
|
||||
T mScanned; // Number of scanned 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.
|
||||
//
|
||||
// Unwinding may optionally use stack scanning. The maximum number
|
||||
// of frames that may be recovered by stack scanning is
|
||||
// |aScannedFramesAllowed| and the actual number recovered is
|
||||
// written into *aScannedFramesAcquired. |aScannedFramesAllowed|
|
||||
// must be less than or equal to |aFramesAvail|.
|
||||
//
|
||||
// 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.
|
||||
//
|
||||
// Up to aScannedFramesAllowed stack-scanned frames may be recovered.
|
||||
//
|
||||
// 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* aScannedFramesAcquired,
|
||||
size_t aFramesAvail,
|
||||
size_t aScannedFramesAllowed,
|
||||
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();
|
||||
|
||||
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.
|
||||
int 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
|
||||
393
tools/profiler/lul/LulMainInt.h
Normal file
393
tools/profiler/lul/LulMainInt.h
Normal file
|
|
@ -0,0 +1,393 @@
|
|||
/* -*- 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 "LulPlatformMacros.h"
|
||||
#include "LulMain.h" // for TaggedUWord
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include "mozilla/Assertions.h"
|
||||
|
||||
// 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(LUL_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(LUL_ARCH_x64)
|
||||
// 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(LUL_ARCH_x86)
|
||||
DW_REG_INTEL_XBP = 5,
|
||||
DW_REG_INTEL_XSP = 4,
|
||||
DW_REG_INTEL_XIP = 8,
|
||||
#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) {
|
||||
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.
|
||||
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(LUL_ARCH_x64) || defined(LUL_ARCH_x86)
|
||||
LExpr mXipExpr; // return address
|
||||
LExpr mXspExpr;
|
||||
LExpr mXbpExpr;
|
||||
#elif defined(LUL_ARCH_arm)
|
||||
LExpr mR15expr; // return address
|
||||
LExpr mR14expr;
|
||||
LExpr mR13expr;
|
||||
LExpr mR12expr;
|
||||
LExpr mR11expr;
|
||||
LExpr mR7expr;
|
||||
#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(LUL_ARCH_x64) || defined(LUL_ARCH_x86)
|
||||
case DW_REG_INTEL_XBP: case DW_REG_INTEL_XSP: case DW_REG_INTEL_XIP:
|
||||
return true;
|
||||
# elif defined(LUL_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;
|
||||
# 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(); }
|
||||
|
||||
// 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
|
||||
53
tools/profiler/lul/LulPlatformMacros.h
Normal file
53
tools/profiler/lul/LulPlatformMacros.h
Normal file
|
|
@ -0,0 +1,53 @@
|
|||
/* -*- 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 LulPlatformMacros_h
|
||||
#define LulPlatformMacros_h
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
// Define platform selection macros in a consistent way. The primary
|
||||
// factorisation is on (ARCH,OS) pairs ("PLATforms") but ARCH_ and OS_
|
||||
// macros are defined too, since they are sometimes convenient.
|
||||
|
||||
#undef LUL_PLAT_x64_linux
|
||||
#undef LUL_PLAT_x86_linux
|
||||
#undef LUL_PLAT_arm_android
|
||||
#undef LUL_PLAT_x86_android
|
||||
|
||||
#undef LUL_ARCH_arm
|
||||
#undef LUL_ARCH_x86
|
||||
#undef LUL_ARCH_x64
|
||||
|
||||
#undef LUL_OS_android
|
||||
#undef LUL_OS_linux
|
||||
|
||||
#if defined(__linux__) && defined(__x86_64__)
|
||||
# define LUL_PLAT_x64_linux 1
|
||||
# define LUL_ARCH_x64 1
|
||||
# define LUL_OS_linux 1
|
||||
|
||||
#elif defined(__linux__) && defined(__i386__) && !defined(__ANDROID__)
|
||||
# define LUL_PLAT_x86_linux 1
|
||||
# define LUL_ARCH_x86 1
|
||||
# define LUL_OS_linux 1
|
||||
|
||||
#elif defined(__ANDROID__) && defined(__arm__)
|
||||
# define LUL_PLAT_arm_android 1
|
||||
# define LUL_ARCH_arm 1
|
||||
# define LUL_OS_android 1
|
||||
|
||||
#elif defined(__ANDROID__) && defined(__i386__)
|
||||
# define LUL_PLAT_x86_android 1
|
||||
# define LUL_ARCH_x86 1
|
||||
# define LUL_OS_android 1
|
||||
|
||||
#else
|
||||
# error "Unsupported platform"
|
||||
#endif
|
||||
|
||||
#endif // LulPlatformMacros_h
|
||||
88
tools/profiler/lul/platform-linux-lul.cpp
Normal file
88
tools/profiler/lul/platform-linux-lul.cpp
Normal file
|
|
@ -0,0 +1,88 @@
|
|||
/* -*- 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 "platform.h"
|
||||
#include "PlatformMacros.h"
|
||||
#include "LulMain.h"
|
||||
#include "shared-libraries.h"
|
||||
#include "AutoObjectMapper.h"
|
||||
|
||||
// Contains miscellaneous helpers that are used to connect SPS 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(SPS_OS_linux) || defined(SPS_OS_android) || defined(SPS_OS_darwin)
|
||||
SharedLibraryInfo info = SharedLibraryInfo::GetInfoForSelf();
|
||||
|
||||
for (size_t i = 0; i < info.GetSize(); i++) {
|
||||
const SharedLibrary& lib = info.GetEntry(i);
|
||||
|
||||
# if defined(SPS_OS_android) && !defined(MOZ_WIDGET_GONK)
|
||||
// We're using faulty.lib. Use a special-case object mapper.
|
||||
AutoObjectMapperFaultyLib mapper(aLUL->mLog);
|
||||
# else
|
||||
// We can use the standard POSIX-based mapper.
|
||||
AutoObjectMapperPOSIX mapper(aLUL->mLog);
|
||||
# endif
|
||||
|
||||
// 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, lib.GetName());
|
||||
if (ok && image && size > 0) {
|
||||
aLUL->NotifyAfterMap(lib.GetStart(), lib.GetEnd()-lib.GetStart(),
|
||||
lib.GetName().c_str(), image);
|
||||
} else if (!ok && lib.GetName() == "") {
|
||||
// The object has no name and (as a consequence) the mapper
|
||||
// failed to map it. This happens on Linux, where
|
||||
// GetInfoForSelf() produces two such mappings: one for the
|
||||
// executable and one for the VDSO. The executable one isn't a
|
||||
// big deal since there's not much interesting code in there,
|
||||
// but the VDSO one 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) {
|
||||
// Ignore any trailing \n, since LOG will add one anyway.
|
||||
size_t n = strlen(str);
|
||||
if (n > 0 && str[n-1] == '\n') {
|
||||
char* tmp = strdup(str);
|
||||
tmp[n-1] = 0;
|
||||
LOG(tmp);
|
||||
free(tmp);
|
||||
} else {
|
||||
LOG(str);
|
||||
}
|
||||
}
|
||||
24
tools/profiler/lul/platform-linux-lul.h
Normal file
24
tools/profiler/lul/platform-linux-lul.h
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
/* -*- 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"
|
||||
|
||||
// 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);
|
||||
|
||||
// A singleton instance of the library.
|
||||
extern lul::LUL* sLUL;
|
||||
|
||||
#endif /* ndef MOZ_PLATFORM_LINUX_LUL_H */
|
||||
113
tools/profiler/merge-profiles.py
Normal file
113
tools/profiler/merge-profiles.py
Normal file
|
|
@ -0,0 +1,113 @@
|
|||
#!/usr/bin/env python
|
||||
#
|
||||
# This script takes b2g process profiles and merged them into a single profile.
|
||||
# The meta data is taken from the first profile. The startTime for each profile
|
||||
# is used to syncronized the samples. Each thread is moved into the merged
|
||||
# profile.
|
||||
#
|
||||
import json
|
||||
import re
|
||||
import sys
|
||||
|
||||
def MergeProfiles(files):
|
||||
threads = []
|
||||
fileData = []
|
||||
symTable = dict()
|
||||
meta = None
|
||||
libs = None
|
||||
videoUrl = None
|
||||
minStartTime = None
|
||||
|
||||
for fname in files:
|
||||
if fname.startswith("--video="):
|
||||
videoUrl = fname[8:]
|
||||
continue
|
||||
|
||||
match = re.match('profile_([0-9]+)_(.+)\.sym', fname)
|
||||
if match is None:
|
||||
raise Exception("Filename '" + fname + "' doesn't match expected pattern")
|
||||
pid = match.groups(0)[0]
|
||||
pname = match.groups(0)[1]
|
||||
|
||||
fp = open(fname, "r")
|
||||
fileData = json.load(fp)
|
||||
fp.close()
|
||||
|
||||
if meta is None:
|
||||
meta = fileData['profileJSON']['meta'].copy()
|
||||
libs = fileData['profileJSON']['libs']
|
||||
minStartTime = meta['startTime']
|
||||
else:
|
||||
minStartTime = min(minStartTime, fileData['profileJSON']['meta']['startTime'])
|
||||
meta['startTime'] = minStartTime
|
||||
|
||||
for thread in fileData['profileJSON']['threads']:
|
||||
thread['name'] = thread['name'] + " (" + pname + ":" + pid + ")"
|
||||
threads.append(thread)
|
||||
|
||||
# Note that pid + sym, pid + location could be ambigious
|
||||
# if we had pid=11 sym=1 && pid=1 sym=11.
|
||||
pidStr = pid + ":"
|
||||
|
||||
thread['startTime'] = fileData['profileJSON']['meta']['startTime']
|
||||
if meta['version'] >= 3:
|
||||
stringTable = thread['stringTable']
|
||||
for i, str in enumerate(stringTable):
|
||||
if str[:2] == '0x':
|
||||
newLoc = pidStr + str
|
||||
stringTable[i] = newLoc
|
||||
symTable[newLoc] = str
|
||||
else:
|
||||
samples = thread['samples']
|
||||
for sample in thread['samples']:
|
||||
for frame in sample['frames']:
|
||||
if "location" in frame and frame['location'][0:2] == '0x':
|
||||
oldLoc = frame['location']
|
||||
newLoc = pidStr + oldLoc
|
||||
frame['location'] = newLoc
|
||||
# Default to the unprefixed symbol if no translation is
|
||||
symTable[newLoc] = oldLoc
|
||||
|
||||
filesyms = fileData['symbolicationTable']
|
||||
for sym in filesyms.keys():
|
||||
symTable[pidStr + sym] = filesyms[sym]
|
||||
|
||||
# For each thread, make the time offsets line up based on the
|
||||
# earliest start
|
||||
for thread in threads:
|
||||
delta = thread['startTime'] - minStartTime
|
||||
if meta['version'] >= 3:
|
||||
idxTime = thread['samples']['schema']['time']
|
||||
for sample in thread['samples']['data']:
|
||||
sample[idxTime] += delta
|
||||
idxTime = thread['markers']['schema']['time']
|
||||
for marker in thread['markers']['data']:
|
||||
marker[idxTime] += delta
|
||||
else:
|
||||
for sample in thread['samples']:
|
||||
if "time" in sample:
|
||||
sample['time'] += delta
|
||||
for marker in thread['markers']:
|
||||
marker['time'] += delta
|
||||
|
||||
result = dict()
|
||||
result['profileJSON'] = dict()
|
||||
result['profileJSON']['meta'] = meta
|
||||
result['profileJSON']['libs'] = libs
|
||||
result['profileJSON']['threads'] = threads
|
||||
result['symbolicationTable'] = symTable
|
||||
result['format'] = "profileJSONWithSymbolicationTable,1"
|
||||
if videoUrl:
|
||||
result['profileJSON']['meta']['videoCapture'] = {"src": videoUrl}
|
||||
|
||||
json.dump(result, sys.stdout)
|
||||
|
||||
|
||||
if len(sys.argv) > 1:
|
||||
MergeProfiles(sys.argv[1:])
|
||||
sys.exit(0)
|
||||
|
||||
print "Usage: merge-profile.py profile_<pid1>_<pname1>.sym profile_<pid2>_<pname2>.sym > merged.sym"
|
||||
|
||||
|
||||
|
||||
147
tools/profiler/moz.build
Normal file
147
tools/profiler/moz.build
Normal file
|
|
@ -0,0 +1,147 @@
|
|||
# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
|
||||
# vim: set filetype=python:
|
||||
# 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/.
|
||||
|
||||
if CONFIG['MOZ_ENABLE_PROFILER_SPS']:
|
||||
XPIDL_MODULE = 'profiler'
|
||||
XPIDL_SOURCES += [
|
||||
'gecko/nsIProfiler.idl',
|
||||
'gecko/nsIProfileSaveEvent.idl',
|
||||
]
|
||||
EXPORTS += [
|
||||
'public/GeckoProfilerFunc.h',
|
||||
'public/GeckoProfilerImpl.h',
|
||||
'public/ProfilerBacktrace.h',
|
||||
'public/ProfilerMarkers.h',
|
||||
'public/PseudoStack.h',
|
||||
'public/shared-libraries.h',
|
||||
]
|
||||
EXPORTS.mozilla += [
|
||||
'public/ProfileGatherer.h',
|
||||
]
|
||||
EXTRA_JS_MODULES += [
|
||||
'gecko/Profiler.jsm',
|
||||
]
|
||||
UNIFIED_SOURCES += [
|
||||
'core/GeckoSampler.cpp',
|
||||
'core/platform.cpp',
|
||||
'core/ProfileBuffer.cpp',
|
||||
'core/ProfileEntry.cpp',
|
||||
'core/ProfileJSONWriter.cpp',
|
||||
'core/ProfilerBacktrace.cpp',
|
||||
'core/ProfilerMarkers.cpp',
|
||||
'core/StackTop.cpp',
|
||||
'core/SyncProfile.cpp',
|
||||
'core/ThreadInfo.cpp',
|
||||
'core/ThreadProfile.cpp',
|
||||
'gecko/nsProfiler.cpp',
|
||||
'gecko/nsProfilerFactory.cpp',
|
||||
'gecko/nsProfilerStartParams.cpp',
|
||||
'gecko/ProfileGatherer.cpp',
|
||||
'gecko/ProfilerIOInterposeObserver.cpp',
|
||||
'gecko/SaveProfileTask.cpp',
|
||||
'gecko/ThreadResponsiveness.cpp',
|
||||
]
|
||||
|
||||
if CONFIG['OS_TARGET'] in ('Android', 'Linux'):
|
||||
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/platform-linux.cc',
|
||||
'core/shared-libraries-linux.cc',
|
||||
]
|
||||
if not CONFIG['MOZ_CRASHREPORTER']:
|
||||
SOURCES += [
|
||||
'/toolkit/crashreporter/google-breakpad/src/common/linux/elfutils.cc',
|
||||
'/toolkit/crashreporter/google-breakpad/src/common/linux/file_id.cc',
|
||||
'/toolkit/crashreporter/google-breakpad/src/common/linux/guid_creator.cc',
|
||||
'/toolkit/crashreporter/google-breakpad/src/common/linux/linux_libc_support.cc',
|
||||
'/toolkit/crashreporter/google-breakpad/src/common/linux/memory_mapped_file.cc',
|
||||
]
|
||||
if CONFIG['CPU_ARCH'] == 'arm':
|
||||
SOURCES += [
|
||||
'core/EHABIStackWalk.cpp',
|
||||
]
|
||||
elif CONFIG['OS_TARGET'] == 'Darwin':
|
||||
UNIFIED_SOURCES += [
|
||||
'core/platform-macos.cc',
|
||||
'core/shared-libraries-macos.cc',
|
||||
]
|
||||
elif CONFIG['OS_TARGET'] == 'WINNT':
|
||||
SOURCES += [
|
||||
'core/IntelPowerGadget.cpp',
|
||||
'core/platform-win32.cc',
|
||||
'core/shared-libraries-win32.cc',
|
||||
]
|
||||
|
||||
LOCAL_INCLUDES += [
|
||||
'/docshell/base',
|
||||
'/ipc/chromium/src',
|
||||
'/mozglue/linker',
|
||||
'/toolkit/crashreporter/google-breakpad/src',
|
||||
'/tools/profiler/core/',
|
||||
'/tools/profiler/gecko/',
|
||||
'/xpcom/base',
|
||||
]
|
||||
|
||||
if CONFIG['OS_TARGET'] == 'Android':
|
||||
LOCAL_INCLUDES += [
|
||||
# We need access to Breakpad's getcontext(3) which is suitable for Android
|
||||
'/toolkit/crashreporter/google-breakpad/src/common/android/include',
|
||||
]
|
||||
|
||||
if not CONFIG['MOZ_CRASHREPORTER'] and CONFIG['OS_TARGET'] == 'Android':
|
||||
SOURCES += ['/toolkit/crashreporter/google-breakpad/src/common/android/breakpad_getcontext.S']
|
||||
|
||||
if CONFIG['ANDROID_CPU_ARCH'] == 'armeabi':
|
||||
DEFINES['ARCH_ARMV6'] = True
|
||||
|
||||
if CONFIG['ENABLE_TESTS']:
|
||||
DIRS += ['tests/gtest']
|
||||
|
||||
if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'gonk' and (CONFIG['ANDROID_VERSION'] <= '17' or CONFIG['ANDROID_VERSION'] >= '21'):
|
||||
DEFINES['ELFSIZE'] = 32
|
||||
|
||||
FINAL_LIBRARY = 'xul'
|
||||
|
||||
IPDL_SOURCES += [
|
||||
'gecko/ProfilerTypes.ipdlh',
|
||||
]
|
||||
|
||||
include('/ipc/chromium/chromium-config.mozbuild')
|
||||
|
||||
EXPORTS += [
|
||||
'public/GeckoProfiler.h',
|
||||
]
|
||||
|
||||
if CONFIG['MOZ_TASK_TRACER']:
|
||||
EXPORTS += [
|
||||
'tasktracer/GeckoTaskTracer.h',
|
||||
'tasktracer/GeckoTaskTracerImpl.h',
|
||||
'tasktracer/TracedTaskCommon.h',
|
||||
]
|
||||
UNIFIED_SOURCES += [
|
||||
'tasktracer/GeckoTaskTracer.cpp',
|
||||
'tasktracer/TracedTaskCommon.cpp',
|
||||
]
|
||||
|
||||
XPCSHELL_TESTS_MANIFESTS += ['tests/xpcshell.ini']
|
||||
|
||||
if CONFIG['GNU_CXX']:
|
||||
CXXFLAGS += [
|
||||
'-Wno-error=shadow',
|
||||
'-Wno-ignored-qualifiers', # due to use of breakpad headers
|
||||
]
|
||||
|
||||
with Files('**'):
|
||||
BUG_COMPONENT = ('Core', 'Gecko Profiler')
|
||||
48
tools/profiler/nm-symbolicate.py
Normal file
48
tools/profiler/nm-symbolicate.py
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
#!/usr/bin/env python
|
||||
|
||||
# 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/.
|
||||
|
||||
import sys, subprocess, os
|
||||
|
||||
def NMSymbolicate(library, addresses):
|
||||
target_tools_prefix = os.environ.get("TARGET_TOOLS_PREFIX", "")
|
||||
args = [
|
||||
target_tools_prefix + "nm", "-D", "-S", library
|
||||
]
|
||||
nm_lines = subprocess.check_output(args).split("\n")
|
||||
symbol_table = []
|
||||
for line in nm_lines:
|
||||
pieces = line.split(" ", 4)
|
||||
if len(pieces) != 4 or pieces[2] != "T":
|
||||
continue
|
||||
start = int(pieces[0], 16)
|
||||
end = int(pieces[1], 16)
|
||||
symbol = pieces[3]
|
||||
symbol_table.append({
|
||||
"start": int(pieces[0], 16),
|
||||
"end": int(pieces[0], 16) + int(pieces[1], 16),
|
||||
"funcName": pieces[3]
|
||||
});
|
||||
|
||||
for addressStr in addresses:
|
||||
address = int(addressStr, 16)
|
||||
symbolForAddress = None
|
||||
for symbol in symbol_table:
|
||||
if address >= symbol["start"] and address <= symbol["end"]:
|
||||
symbolForAddress = symbol
|
||||
break
|
||||
if symbolForAddress:
|
||||
print symbolForAddress["funcName"]
|
||||
else:
|
||||
print "??" # match addr2line
|
||||
print ":0" # no line information from nm
|
||||
|
||||
if len(sys.argv) > 1:
|
||||
NMSymbolicate(sys.argv[1], sys.argv[2:])
|
||||
sys.exit(0)
|
||||
|
||||
print "Usage: nm-symbolicate.py <library> <addresses> > merged.sym"
|
||||
|
||||
|
||||
300
tools/profiler/public/GeckoProfiler.h
Normal file
300
tools/profiler/public/GeckoProfiler.h
Normal file
|
|
@ -0,0 +1,300 @@
|
|||
/* -*- 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/. */
|
||||
|
||||
/* *************** SPS Sampler Information ****************
|
||||
*
|
||||
* SPS is an always on profiler that takes fast and low overheads samples
|
||||
* of the program execution using only userspace functionity for portability.
|
||||
* The goal of this module is to provide performance data in a generic
|
||||
* cross platform way without requiring custom tools or kernel support.
|
||||
*
|
||||
* Non goals: Support features that are platform specific or replace
|
||||
* platform specific profilers.
|
||||
*
|
||||
* Samples are collected to form a timeline with optional timeline event (markers)
|
||||
* used for filtering.
|
||||
*
|
||||
* SPS collects samples in a platform independant way by using a speudo stack abstraction
|
||||
* of the real program stack by using 'sample stack frames'. When a sample is collected
|
||||
* all active sample stack frames and the program counter are recorded.
|
||||
*/
|
||||
|
||||
/* *************** SPS Sampler File Format ****************
|
||||
*
|
||||
* Simple new line seperated tag format:
|
||||
* S -> BOF tags EOF
|
||||
* tags -> tag tags
|
||||
* tag -> CHAR - STRING
|
||||
*
|
||||
* Tags:
|
||||
* 's' - Sample tag followed by the first stack frame followed by 0 or more 'c' tags.
|
||||
* 'c' - Continue Sample tag gives remaining tag element. If a 'c' tag is seen without
|
||||
* a preceding 's' tag it should be ignored. This is to support the behavior
|
||||
* of circular buffers.
|
||||
* If the 'stackwalk' feature is enabled this tag will have the format
|
||||
* 'l-<library name>@<hex address>' and will expect an external tool to translate
|
||||
* the tag into something readable through a symbolication processing step.
|
||||
* 'm' - Timeline marker. Zero or more may appear before a 's' tag.
|
||||
* 'l' - Information about the program counter library and address. Post processing
|
||||
* can include function and source line. If built with leaf data enabled
|
||||
* this tag will describe the last 'c' tag.
|
||||
* 'r' - Responsiveness tag following an 's' tag. Gives an indication on how well the
|
||||
* application is responding to the event loop. Lower is better.
|
||||
* 't' - Elapse time since recording started.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef SAMPLER_H
|
||||
#define SAMPLER_H
|
||||
|
||||
#include "mozilla/Assertions.h"
|
||||
#include "mozilla/Attributes.h"
|
||||
#ifndef SPS_STANDALONE
|
||||
#include "js/TypeDecls.h"
|
||||
#endif
|
||||
#include "mozilla/UniquePtr.h"
|
||||
#include "mozilla/Vector.h"
|
||||
|
||||
namespace mozilla {
|
||||
class TimeStamp;
|
||||
|
||||
namespace dom {
|
||||
class Promise;
|
||||
} // namespace dom
|
||||
|
||||
} // namespace mozilla
|
||||
|
||||
#ifndef SPS_STANDALONE
|
||||
class nsIProfilerStartParams;
|
||||
#endif
|
||||
|
||||
enum TracingMetadata {
|
||||
TRACING_DEFAULT,
|
||||
TRACING_INTERVAL_START,
|
||||
TRACING_INTERVAL_END,
|
||||
TRACING_EVENT,
|
||||
TRACING_EVENT_BACKTRACE,
|
||||
TRACING_TIMESTAMP
|
||||
};
|
||||
|
||||
#if !defined(MOZ_ENABLE_PROFILER_SPS)
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdarg.h>
|
||||
|
||||
// Insert a RAII in this scope to active a pseudo label. Any samples collected
|
||||
// in this scope will contain this annotation. For dynamic strings use
|
||||
// PROFILER_LABEL_PRINTF. Arguments must be string literals.
|
||||
#define PROFILER_LABEL(name_space, info, category) do {} while (0)
|
||||
|
||||
// Similar to PROFILER_LABEL, PROFILER_LABEL_FUNC will push/pop the enclosing
|
||||
// functon name as the pseudostack label.
|
||||
#define PROFILER_LABEL_FUNC(category) do {} while (0)
|
||||
|
||||
// Format a dynamic string as a pseudo label. These labels will a considerable
|
||||
// storage size in the circular buffer compared to regular labels. This function
|
||||
// can be used to annotate custom information such as URL for the resource being
|
||||
// decoded or the size of the paint.
|
||||
#define PROFILER_LABEL_PRINTF(name_space, info, category, format, ...) do {} while (0)
|
||||
|
||||
// Insert a marker in the profile timeline. This is useful to delimit something
|
||||
// important happening such as the first paint. Unlike profiler_label that are
|
||||
// only recorded if a sample is collected while it is active, marker will always
|
||||
// be collected.
|
||||
#define PROFILER_MARKER(info) do {} while (0)
|
||||
#define PROFILER_MARKER_PAYLOAD(info, payload) do { mozilla::UniquePtr<ProfilerMarkerPayload> payloadDeletor(payload); } while (0)
|
||||
|
||||
// Main thread specilization to avoid TLS lookup for performance critical use.
|
||||
#define PROFILER_MAIN_THREAD_LABEL(name_space, info, category) do {} while (0)
|
||||
#define PROFILER_MAIN_THREAD_LABEL_PRINTF(name_space, info, category, format, ...) do {} while (0)
|
||||
|
||||
static inline void profiler_tracing(const char* aCategory, const char* aInfo,
|
||||
TracingMetadata metaData = TRACING_DEFAULT) {}
|
||||
class ProfilerBacktrace;
|
||||
|
||||
static inline void profiler_tracing(const char* aCategory, const char* aInfo,
|
||||
ProfilerBacktrace* aCause,
|
||||
TracingMetadata metaData = TRACING_DEFAULT) {}
|
||||
|
||||
// Initilize the profiler TLS, signal handlers on linux. If MOZ_PROFILER_STARTUP
|
||||
// is set the profiler will be started. This call must happen before any other
|
||||
// sampler calls. Particularly sampler_label/sampler_marker.
|
||||
static inline void profiler_init(void* stackTop) {};
|
||||
|
||||
// Clean up the profiler module, stopping it if required. This function may
|
||||
// also save a shutdown profile if requested. No profiler calls should happen
|
||||
// after this point and all pseudo labels should have been popped.
|
||||
static inline void profiler_shutdown() {};
|
||||
|
||||
// Start the profiler with the selected options. The samples will be
|
||||
// recorded in a circular buffer.
|
||||
// "aProfileEntries" is an abstract size indication of how big
|
||||
// the profile's circular buffer should be. Multiply by 4
|
||||
// words to get the cost.
|
||||
// "aInterval" the sampling interval. The profiler will do its
|
||||
// best to sample at this interval. The profiler visualization
|
||||
// should represent the actual sampling accuracy.
|
||||
static inline void profiler_start(int aProfileEntries, double aInterval,
|
||||
const char** aFeatures, uint32_t aFeatureCount,
|
||||
const char** aThreadNameFilters, uint32_t aFilterCount) {}
|
||||
|
||||
// Stop the profiler and discard the profile. Call 'profiler_save' before this
|
||||
// to retrieve the profile.
|
||||
static inline void profiler_stop() {}
|
||||
|
||||
// These functions pause and resume the profiler. While paused the profile will not
|
||||
// take any samples and will not record any data into its buffers. The profiler
|
||||
// remains fully initialized in this state. Timeline markers will still be stored.
|
||||
// This feature will keep javascript profiling enabled, thus allowing toggling the
|
||||
// profiler without invalidating the JIT.
|
||||
static inline bool profiler_is_paused() { return false; }
|
||||
static inline void profiler_pause() {}
|
||||
static inline void profiler_resume() {}
|
||||
|
||||
|
||||
// Immediately capture the current thread's call stack and return it
|
||||
static inline ProfilerBacktrace* profiler_get_backtrace() { return nullptr; }
|
||||
static inline void profiler_get_backtrace_noalloc(char *output, size_t outputSize) { return; }
|
||||
|
||||
// Free a ProfilerBacktrace returned by profiler_get_backtrace()
|
||||
static inline void profiler_free_backtrace(ProfilerBacktrace* aBacktrace) {}
|
||||
|
||||
static inline bool profiler_is_active() { return false; }
|
||||
|
||||
// Check if an external profiler feature is active.
|
||||
// Supported:
|
||||
// * gpu
|
||||
static inline bool profiler_feature_active(const char*) { return false; }
|
||||
|
||||
// Internal-only. Used by the event tracer.
|
||||
static inline void profiler_responsiveness(const mozilla::TimeStamp& aTime) {}
|
||||
|
||||
// Internal-only.
|
||||
static inline void profiler_set_frame_number(int frameNumber) {}
|
||||
|
||||
// Get the profile encoded as a JSON string.
|
||||
static inline mozilla::UniquePtr<char[]> profiler_get_profile(double aSinceTime = 0) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// Get the profile encoded as a JSON object.
|
||||
static inline JSObject* profiler_get_profile_jsobject(JSContext* aCx,
|
||||
double aSinceTime = 0) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
#ifndef SPS_STANDALONE
|
||||
// Get the profile encoded as a JSON object.
|
||||
static inline void profiler_get_profile_jsobject_async(double aSinceTime = 0,
|
||||
mozilla::dom::Promise* = 0) {}
|
||||
static inline void profiler_get_start_params(int* aEntrySize,
|
||||
double* aInterval,
|
||||
mozilla::Vector<const char*>* aFilters,
|
||||
mozilla::Vector<const char*>* aFeatures) {}
|
||||
#endif
|
||||
|
||||
// Get the profile and write it into a file
|
||||
static inline void profiler_save_profile_to_file(char* aFilename) { }
|
||||
|
||||
// Get the features supported by the profiler that are accepted by profiler_init.
|
||||
// Returns a null terminated char* array.
|
||||
static inline char** profiler_get_features() { return nullptr; }
|
||||
|
||||
// Get information about the current buffer status.
|
||||
// Retursn (using outparams) the current write position in the buffer,
|
||||
// the total size of the buffer, and the generation of the buffer.
|
||||
// This information may be useful to a user-interface displaying the
|
||||
// current status of the profiler, allowing the user to get a sense
|
||||
// for how fast the buffer is being written to, and how much
|
||||
// data is visible.
|
||||
static inline void profiler_get_buffer_info(uint32_t *aCurrentPosition,
|
||||
uint32_t *aTotalSize,
|
||||
uint32_t *aGeneration)
|
||||
{
|
||||
*aCurrentPosition = 0;
|
||||
*aTotalSize = 0;
|
||||
*aGeneration = 0;
|
||||
}
|
||||
|
||||
// Discard the profile, throw away the profile and notify 'profiler-locked'.
|
||||
// This function is to be used when entering private browsing to prevent
|
||||
// the profiler from collecting sensitive data.
|
||||
static inline void profiler_lock() {}
|
||||
|
||||
// Re-enable the profiler and notify 'profiler-unlocked'.
|
||||
static inline void profiler_unlock() {}
|
||||
|
||||
static inline void profiler_register_thread(const char* name, void* guessStackTop) {}
|
||||
static inline void profiler_unregister_thread() {}
|
||||
|
||||
// These functions tell the profiler that a thread went to sleep so that we can avoid
|
||||
// sampling it while it's sleeping. Calling profiler_sleep_start() twice without
|
||||
// profiler_sleep_end() is an error.
|
||||
static inline void profiler_sleep_start() {}
|
||||
static inline void profiler_sleep_end() {}
|
||||
static inline bool profiler_is_sleeping() { return false; }
|
||||
|
||||
// Call by the JSRuntime's operation callback. This is used to enable
|
||||
// profiling on auxilerary threads.
|
||||
static inline void profiler_js_operation_callback() {}
|
||||
|
||||
static inline double profiler_time() { return 0; }
|
||||
static inline double profiler_time(const mozilla::TimeStamp& aTime) { return 0; }
|
||||
|
||||
static inline bool profiler_in_privacy_mode() { return false; }
|
||||
|
||||
static inline void profiler_log(const char *str) {}
|
||||
static inline void profiler_log(const char *fmt, va_list args) {}
|
||||
|
||||
#else
|
||||
|
||||
#include "GeckoProfilerImpl.h"
|
||||
|
||||
#endif
|
||||
|
||||
class MOZ_RAII GeckoProfilerInitRAII {
|
||||
public:
|
||||
explicit GeckoProfilerInitRAII(void* stackTop) {
|
||||
profiler_init(stackTop);
|
||||
}
|
||||
~GeckoProfilerInitRAII() {
|
||||
profiler_shutdown();
|
||||
}
|
||||
};
|
||||
|
||||
class MOZ_RAII GeckoProfilerSleepRAII {
|
||||
public:
|
||||
GeckoProfilerSleepRAII() {
|
||||
profiler_sleep_start();
|
||||
}
|
||||
~GeckoProfilerSleepRAII() {
|
||||
profiler_sleep_end();
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Temporarily wake up the profiler while servicing events such as
|
||||
* Asynchronous Procedure Calls (APCs).
|
||||
*/
|
||||
class MOZ_RAII GeckoProfilerWakeRAII {
|
||||
public:
|
||||
GeckoProfilerWakeRAII()
|
||||
: mIssuedWake(profiler_is_sleeping())
|
||||
{
|
||||
if (mIssuedWake) {
|
||||
profiler_sleep_end();
|
||||
}
|
||||
}
|
||||
~GeckoProfilerWakeRAII() {
|
||||
if (mIssuedWake) {
|
||||
MOZ_ASSERT(!profiler_is_sleeping());
|
||||
profiler_sleep_start();
|
||||
}
|
||||
}
|
||||
private:
|
||||
bool mIssuedWake;
|
||||
};
|
||||
|
||||
#endif // ifndef SAMPLER_H
|
||||
125
tools/profiler/public/GeckoProfilerFunc.h
Normal file
125
tools/profiler/public/GeckoProfilerFunc.h
Normal file
|
|
@ -0,0 +1,125 @@
|
|||
/* -*- 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 PROFILER_FUNCS_H
|
||||
#define PROFILER_FUNCS_H
|
||||
|
||||
#ifndef SPS_STANDALONE
|
||||
#include "js/TypeDecls.h"
|
||||
#endif
|
||||
#include "js/ProfilingStack.h"
|
||||
#include "mozilla/UniquePtr.h"
|
||||
#include "mozilla/Vector.h"
|
||||
#include <stdint.h>
|
||||
|
||||
class nsISupports;
|
||||
|
||||
namespace mozilla {
|
||||
class TimeStamp;
|
||||
|
||||
namespace dom {
|
||||
class Promise;
|
||||
} // namespace dom
|
||||
|
||||
} // namespace mozilla
|
||||
|
||||
class ProfilerBacktrace;
|
||||
class ProfilerMarkerPayload;
|
||||
|
||||
// Returns a handle to pass on exit. This can check that we are popping the
|
||||
// correct callstack.
|
||||
inline void* mozilla_sampler_call_enter(const char *aInfo, js::ProfileEntry::Category aCategory,
|
||||
void *aFrameAddress = nullptr, bool aCopy = false,
|
||||
uint32_t line = 0);
|
||||
|
||||
inline void mozilla_sampler_call_exit(void* handle);
|
||||
|
||||
void mozilla_sampler_add_marker(const char *aInfo,
|
||||
ProfilerMarkerPayload *aPayload = nullptr);
|
||||
|
||||
void mozilla_sampler_start(int aEntries, double aInterval,
|
||||
const char** aFeatures, uint32_t aFeatureCount,
|
||||
const char** aThreadNameFilters, uint32_t aFilterCount);
|
||||
|
||||
void mozilla_sampler_stop();
|
||||
|
||||
bool mozilla_sampler_is_paused();
|
||||
void mozilla_sampler_pause();
|
||||
void mozilla_sampler_resume();
|
||||
|
||||
ProfilerBacktrace* mozilla_sampler_get_backtrace();
|
||||
void mozilla_sampler_free_backtrace(ProfilerBacktrace* aBacktrace);
|
||||
void mozilla_sampler_get_backtrace_noalloc(char *output, size_t outputSize);
|
||||
|
||||
bool mozilla_sampler_is_active();
|
||||
|
||||
bool mozilla_sampler_feature_active(const char* aName);
|
||||
|
||||
void mozilla_sampler_responsiveness(const mozilla::TimeStamp& time);
|
||||
|
||||
void mozilla_sampler_frame_number(int frameNumber);
|
||||
|
||||
const double* mozilla_sampler_get_responsiveness();
|
||||
|
||||
void mozilla_sampler_save();
|
||||
|
||||
mozilla::UniquePtr<char[]> mozilla_sampler_get_profile(double aSinceTime);
|
||||
|
||||
#ifndef SPS_STANDALONE
|
||||
JSObject *mozilla_sampler_get_profile_data(JSContext* aCx, double aSinceTime);
|
||||
void mozilla_sampler_get_profile_data_async(double aSinceTime,
|
||||
mozilla::dom::Promise* aPromise);
|
||||
void mozilla_sampler_get_profiler_start_params(int* aEntrySize,
|
||||
double* aInterval,
|
||||
mozilla::Vector<const char*>* aFilters,
|
||||
mozilla::Vector<const char*>* aFeatures);
|
||||
void mozilla_sampler_get_gatherer(nsISupports** aRetVal);
|
||||
#endif
|
||||
|
||||
// Make this function easily callable from a debugger in a build without
|
||||
// debugging information (work around http://llvm.org/bugs/show_bug.cgi?id=22211)
|
||||
extern "C" {
|
||||
void mozilla_sampler_save_profile_to_file(const char* aFilename);
|
||||
}
|
||||
|
||||
const char** mozilla_sampler_get_features();
|
||||
|
||||
void mozilla_sampler_get_buffer_info(uint32_t *aCurrentPosition, uint32_t *aTotalSize,
|
||||
uint32_t *aGeneration);
|
||||
|
||||
void mozilla_sampler_init(void* stackTop);
|
||||
|
||||
void mozilla_sampler_shutdown();
|
||||
|
||||
// Lock the profiler. When locked the profiler is (1) stopped,
|
||||
// (2) profile data is cleared, (3) profiler-locked is fired.
|
||||
// This is used to lock down the profiler during private browsing
|
||||
void mozilla_sampler_lock();
|
||||
|
||||
// Unlock the profiler, leaving it stopped and fires profiler-unlocked.
|
||||
void mozilla_sampler_unlock();
|
||||
|
||||
// Register/unregister threads with the profiler
|
||||
bool mozilla_sampler_register_thread(const char* name, void* stackTop);
|
||||
void mozilla_sampler_unregister_thread();
|
||||
|
||||
void mozilla_sampler_sleep_start();
|
||||
void mozilla_sampler_sleep_end();
|
||||
bool mozilla_sampler_is_sleeping();
|
||||
|
||||
double mozilla_sampler_time();
|
||||
double mozilla_sampler_time(const mozilla::TimeStamp& aTime);
|
||||
|
||||
void mozilla_sampler_tracing(const char* aCategory, const char* aInfo,
|
||||
TracingMetadata aMetaData);
|
||||
|
||||
void mozilla_sampler_tracing(const char* aCategory, const char* aInfo,
|
||||
ProfilerBacktrace* aCause,
|
||||
TracingMetadata aMetaData);
|
||||
|
||||
void mozilla_sampler_log(const char *fmt, va_list args);
|
||||
|
||||
#endif
|
||||
|
||||
522
tools/profiler/public/GeckoProfilerImpl.h
Normal file
522
tools/profiler/public/GeckoProfilerImpl.h
Normal file
|
|
@ -0,0 +1,522 @@
|
|||
/* -*- 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/. */
|
||||
// IWYU pragma: private, include "GeckoProfiler.h"
|
||||
|
||||
#ifndef TOOLS_SPS_SAMPLER_H_
|
||||
#define TOOLS_SPS_SAMPLER_H_
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <signal.h>
|
||||
#include <stdarg.h>
|
||||
#include "mozilla/Assertions.h"
|
||||
#include "mozilla/GuardObjects.h"
|
||||
#include "mozilla/Sprintf.h"
|
||||
#include "mozilla/ThreadLocal.h"
|
||||
#include "mozilla/UniquePtr.h"
|
||||
#ifndef SPS_STANDALONE
|
||||
#include "nscore.h"
|
||||
#include "nsISupports.h"
|
||||
#endif
|
||||
#include "GeckoProfilerFunc.h"
|
||||
#include "PseudoStack.h"
|
||||
#include "ProfilerBacktrace.h"
|
||||
|
||||
// Make sure that we can use std::min here without the Windows headers messing with us.
|
||||
#ifdef min
|
||||
#undef min
|
||||
#endif
|
||||
|
||||
class GeckoSampler;
|
||||
|
||||
namespace mozilla {
|
||||
class TimeStamp;
|
||||
} // namespace mozilla
|
||||
|
||||
extern MOZ_THREAD_LOCAL(PseudoStack *) tlsPseudoStack;
|
||||
extern MOZ_THREAD_LOCAL(GeckoSampler *) tlsTicker;
|
||||
extern MOZ_THREAD_LOCAL(void *) tlsStackTop;
|
||||
extern bool stack_key_initialized;
|
||||
|
||||
#ifndef SAMPLE_FUNCTION_NAME
|
||||
# ifdef __GNUC__
|
||||
# define SAMPLE_FUNCTION_NAME __FUNCTION__
|
||||
# elif defined(_MSC_VER)
|
||||
# define SAMPLE_FUNCTION_NAME __FUNCTION__
|
||||
# else
|
||||
# define SAMPLE_FUNCTION_NAME __func__ // defined in C99, supported in various C++ compilers. Just raw function name.
|
||||
# endif
|
||||
#endif
|
||||
|
||||
static inline
|
||||
void profiler_init(void* stackTop)
|
||||
{
|
||||
mozilla_sampler_init(stackTop);
|
||||
}
|
||||
|
||||
static inline
|
||||
void profiler_shutdown()
|
||||
{
|
||||
mozilla_sampler_shutdown();
|
||||
}
|
||||
|
||||
static inline
|
||||
void profiler_start(int aProfileEntries, double aInterval,
|
||||
const char** aFeatures, uint32_t aFeatureCount,
|
||||
const char** aThreadNameFilters, uint32_t aFilterCount)
|
||||
{
|
||||
mozilla_sampler_start(aProfileEntries, aInterval, aFeatures, aFeatureCount, aThreadNameFilters, aFilterCount);
|
||||
}
|
||||
|
||||
static inline
|
||||
void profiler_stop()
|
||||
{
|
||||
mozilla_sampler_stop();
|
||||
}
|
||||
|
||||
static inline
|
||||
bool profiler_is_paused()
|
||||
{
|
||||
return mozilla_sampler_is_paused();
|
||||
}
|
||||
|
||||
static inline
|
||||
void profiler_pause()
|
||||
{
|
||||
mozilla_sampler_pause();
|
||||
}
|
||||
|
||||
static inline
|
||||
void profiler_resume()
|
||||
{
|
||||
mozilla_sampler_resume();
|
||||
}
|
||||
|
||||
static inline
|
||||
ProfilerBacktrace* profiler_get_backtrace()
|
||||
{
|
||||
return mozilla_sampler_get_backtrace();
|
||||
}
|
||||
|
||||
static inline
|
||||
void profiler_free_backtrace(ProfilerBacktrace* aBacktrace)
|
||||
{
|
||||
mozilla_sampler_free_backtrace(aBacktrace);
|
||||
}
|
||||
|
||||
static inline
|
||||
void profiler_get_backtrace_noalloc(char *output, size_t outputSize)
|
||||
{
|
||||
return mozilla_sampler_get_backtrace_noalloc(output, outputSize);
|
||||
}
|
||||
|
||||
static inline
|
||||
bool profiler_is_active()
|
||||
{
|
||||
return mozilla_sampler_is_active();
|
||||
}
|
||||
|
||||
static inline
|
||||
bool profiler_feature_active(const char* aName)
|
||||
{
|
||||
return mozilla_sampler_feature_active(aName);
|
||||
}
|
||||
|
||||
static inline
|
||||
void profiler_responsiveness(const mozilla::TimeStamp& aTime)
|
||||
{
|
||||
mozilla_sampler_responsiveness(aTime);
|
||||
}
|
||||
|
||||
static inline
|
||||
void profiler_set_frame_number(int frameNumber)
|
||||
{
|
||||
return mozilla_sampler_frame_number(frameNumber);
|
||||
}
|
||||
|
||||
static inline
|
||||
mozilla::UniquePtr<char[]> profiler_get_profile(double aSinceTime = 0)
|
||||
{
|
||||
return mozilla_sampler_get_profile(aSinceTime);
|
||||
}
|
||||
|
||||
#ifndef SPS_STANDALONE
|
||||
static inline
|
||||
JSObject* profiler_get_profile_jsobject(JSContext* aCx, double aSinceTime = 0)
|
||||
{
|
||||
return mozilla_sampler_get_profile_data(aCx, aSinceTime);
|
||||
}
|
||||
|
||||
static inline
|
||||
void profiler_get_profile_jsobject_async(double aSinceTime = 0,
|
||||
mozilla::dom::Promise* aPromise = 0)
|
||||
{
|
||||
mozilla_sampler_get_profile_data_async(aSinceTime, aPromise);
|
||||
}
|
||||
|
||||
static inline
|
||||
void profiler_get_start_params(int* aEntrySize,
|
||||
double* aInterval,
|
||||
mozilla::Vector<const char*>* aFilters,
|
||||
mozilla::Vector<const char*>* aFeatures)
|
||||
{
|
||||
mozilla_sampler_get_profiler_start_params(aEntrySize, aInterval, aFilters, aFeatures);
|
||||
}
|
||||
|
||||
static inline
|
||||
void profiler_get_gatherer(nsISupports** aRetVal)
|
||||
{
|
||||
mozilla_sampler_get_gatherer(aRetVal);
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
static inline
|
||||
void profiler_save_profile_to_file(const char* aFilename)
|
||||
{
|
||||
return mozilla_sampler_save_profile_to_file(aFilename);
|
||||
}
|
||||
|
||||
static inline
|
||||
const char** profiler_get_features()
|
||||
{
|
||||
return mozilla_sampler_get_features();
|
||||
}
|
||||
|
||||
static inline
|
||||
void profiler_get_buffer_info(uint32_t *aCurrentPosition, uint32_t *aTotalSize,
|
||||
uint32_t *aGeneration)
|
||||
{
|
||||
return mozilla_sampler_get_buffer_info(aCurrentPosition, aTotalSize, aGeneration);
|
||||
}
|
||||
|
||||
static inline
|
||||
void profiler_lock()
|
||||
{
|
||||
return mozilla_sampler_lock();
|
||||
}
|
||||
|
||||
static inline
|
||||
void profiler_unlock()
|
||||
{
|
||||
return mozilla_sampler_unlock();
|
||||
}
|
||||
|
||||
static inline
|
||||
void profiler_register_thread(const char* name, void* guessStackTop)
|
||||
{
|
||||
mozilla_sampler_register_thread(name, guessStackTop);
|
||||
}
|
||||
|
||||
static inline
|
||||
void profiler_unregister_thread()
|
||||
{
|
||||
mozilla_sampler_unregister_thread();
|
||||
}
|
||||
|
||||
static inline
|
||||
void profiler_sleep_start()
|
||||
{
|
||||
mozilla_sampler_sleep_start();
|
||||
}
|
||||
|
||||
static inline
|
||||
void profiler_sleep_end()
|
||||
{
|
||||
mozilla_sampler_sleep_end();
|
||||
}
|
||||
|
||||
static inline
|
||||
bool profiler_is_sleeping()
|
||||
{
|
||||
return mozilla_sampler_is_sleeping();
|
||||
}
|
||||
|
||||
#ifndef SPS_STANDALONE
|
||||
static inline
|
||||
void profiler_js_operation_callback()
|
||||
{
|
||||
PseudoStack *stack = tlsPseudoStack.get();
|
||||
if (!stack) {
|
||||
return;
|
||||
}
|
||||
|
||||
stack->jsOperationCallback();
|
||||
}
|
||||
#endif
|
||||
|
||||
static inline
|
||||
double profiler_time()
|
||||
{
|
||||
return mozilla_sampler_time();
|
||||
}
|
||||
|
||||
static inline
|
||||
double profiler_time(const mozilla::TimeStamp& aTime)
|
||||
{
|
||||
return mozilla_sampler_time(aTime);
|
||||
}
|
||||
|
||||
static inline
|
||||
bool profiler_in_privacy_mode()
|
||||
{
|
||||
PseudoStack *stack = tlsPseudoStack.get();
|
||||
if (!stack) {
|
||||
return false;
|
||||
}
|
||||
return stack->mPrivacyMode;
|
||||
}
|
||||
|
||||
static inline void profiler_tracing(const char* aCategory, const char* aInfo,
|
||||
ProfilerBacktrace* aCause,
|
||||
TracingMetadata aMetaData = TRACING_DEFAULT)
|
||||
{
|
||||
// Don't insert a marker if we're not profiling to avoid
|
||||
// the heap copy (malloc).
|
||||
if (!stack_key_initialized || !profiler_is_active()) {
|
||||
delete aCause;
|
||||
return;
|
||||
}
|
||||
|
||||
mozilla_sampler_tracing(aCategory, aInfo, aCause, aMetaData);
|
||||
}
|
||||
|
||||
static inline void profiler_tracing(const char* aCategory, const char* aInfo,
|
||||
TracingMetadata aMetaData = TRACING_DEFAULT)
|
||||
{
|
||||
if (!stack_key_initialized)
|
||||
return;
|
||||
|
||||
// Don't insert a marker if we're not profiling to avoid
|
||||
// the heap copy (malloc).
|
||||
if (!profiler_is_active()) {
|
||||
return;
|
||||
}
|
||||
|
||||
mozilla_sampler_tracing(aCategory, aInfo, aMetaData);
|
||||
}
|
||||
|
||||
#define SAMPLER_APPEND_LINE_NUMBER_PASTE(id, line) id ## line
|
||||
#define SAMPLER_APPEND_LINE_NUMBER_EXPAND(id, line) SAMPLER_APPEND_LINE_NUMBER_PASTE(id, line)
|
||||
#define SAMPLER_APPEND_LINE_NUMBER(id) SAMPLER_APPEND_LINE_NUMBER_EXPAND(id, __LINE__)
|
||||
|
||||
// Uncomment this to turn on systrace or build with
|
||||
// ac_add_options --enable-systace
|
||||
//#define MOZ_USE_SYSTRACE
|
||||
#ifdef MOZ_USE_SYSTRACE
|
||||
#ifndef ATRACE_TAG
|
||||
# define ATRACE_TAG ATRACE_TAG_ALWAYS
|
||||
#endif
|
||||
// We need HAVE_ANDROID_OS to be defined for Trace.h.
|
||||
// If its not set we will set it temporary and remove it.
|
||||
# ifndef HAVE_ANDROID_OS
|
||||
# define HAVE_ANDROID_OS
|
||||
# define REMOVE_HAVE_ANDROID_OS
|
||||
# endif
|
||||
// Android source code will include <cutils/trace.h> before this. There is no
|
||||
// HAVE_ANDROID_OS defined in Firefox OS build at that time. Enabled it globally
|
||||
// will cause other build break. So atrace_begin and atrace_end are not defined.
|
||||
// It will cause a build-break when we include <utils/Trace.h>. Use undef
|
||||
// _LIBS_CUTILS_TRACE_H will force <cutils/trace.h> to define atrace_begin and
|
||||
// atrace_end with defined HAVE_ANDROID_OS again. Then there is no build-break.
|
||||
# undef _LIBS_CUTILS_TRACE_H
|
||||
# include <utils/Trace.h>
|
||||
# define MOZ_PLATFORM_TRACING(name) android::ScopedTrace SAMPLER_APPEND_LINE_NUMBER(scopedTrace)(ATRACE_TAG, name);
|
||||
# ifdef REMOVE_HAVE_ANDROID_OS
|
||||
# undef HAVE_ANDROID_OS
|
||||
# undef REMOVE_HAVE_ANDROID_OS
|
||||
# endif
|
||||
#else
|
||||
# define MOZ_PLATFORM_TRACING(name)
|
||||
#endif
|
||||
|
||||
// we want the class and function name but can't easily get that using preprocessor macros
|
||||
// __func__ doesn't have the class name and __PRETTY_FUNCTION__ has the parameters
|
||||
|
||||
#define PROFILER_LABEL(name_space, info, category) MOZ_PLATFORM_TRACING(name_space "::" info) mozilla::SamplerStackFrameRAII SAMPLER_APPEND_LINE_NUMBER(sampler_raii)(name_space "::" info, category, __LINE__)
|
||||
#define PROFILER_LABEL_FUNC(category) MOZ_PLATFORM_TRACING(SAMPLE_FUNCTION_NAME) mozilla::SamplerStackFrameRAII SAMPLER_APPEND_LINE_NUMBER(sampler_raii)(SAMPLE_FUNCTION_NAME, category, __LINE__)
|
||||
#define PROFILER_LABEL_PRINTF(name_space, info, category, ...) MOZ_PLATFORM_TRACING(name_space "::" info) mozilla::SamplerStackFramePrintfRAII SAMPLER_APPEND_LINE_NUMBER(sampler_raii)(name_space "::" info, category, __LINE__, __VA_ARGS__)
|
||||
|
||||
#define PROFILER_MARKER(info) mozilla_sampler_add_marker(info)
|
||||
#define PROFILER_MARKER_PAYLOAD(info, payload) mozilla_sampler_add_marker(info, payload)
|
||||
#define PROFILER_MAIN_THREAD_MARKER(info) MOZ_ASSERT(NS_IsMainThread(), "This can only be called on the main thread"); mozilla_sampler_add_marker(info)
|
||||
|
||||
#define PROFILER_MAIN_THREAD_LABEL(name_space, info, category) MOZ_ASSERT(NS_IsMainThread(), "This can only be called on the main thread"); mozilla::SamplerStackFrameRAII SAMPLER_APPEND_LINE_NUMBER(sampler_raii)(name_space "::" info, category, __LINE__)
|
||||
#define PROFILER_MAIN_THREAD_LABEL_PRINTF(name_space, info, category, ...) MOZ_ASSERT(NS_IsMainThread(), "This can only be called on the main thread"); mozilla::SamplerStackFramePrintfRAII SAMPLER_APPEND_LINE_NUMBER(sampler_raii)(name_space "::" info, category, __LINE__, __VA_ARGS__)
|
||||
|
||||
|
||||
/* FIXME/bug 789667: memory constraints wouldn't much of a problem for
|
||||
* this small a sample buffer size, except that serializing the
|
||||
* profile data is extremely, unnecessarily memory intensive. */
|
||||
#ifdef MOZ_WIDGET_GONK
|
||||
# define PLATFORM_LIKELY_MEMORY_CONSTRAINED
|
||||
#endif
|
||||
|
||||
#if !defined(PLATFORM_LIKELY_MEMORY_CONSTRAINED) && !defined(ARCH_ARMV6)
|
||||
# define PROFILE_DEFAULT_ENTRY 1000000
|
||||
#else
|
||||
# define PROFILE_DEFAULT_ENTRY 100000
|
||||
#endif
|
||||
|
||||
// In the case of profiler_get_backtrace we know that we only need enough space
|
||||
// for a single backtrace.
|
||||
#define GET_BACKTRACE_DEFAULT_ENTRY 1000
|
||||
|
||||
#if defined(PLATFORM_LIKELY_MEMORY_CONSTRAINED)
|
||||
/* A 1ms sampling interval has been shown to be a large perf hit
|
||||
* (10fps) on memory-contrained (low-end) platforms, and additionally
|
||||
* to yield different results from the profiler. Where this is the
|
||||
* important case, b2g, there are also many gecko processes which
|
||||
* magnify these effects. */
|
||||
# define PROFILE_DEFAULT_INTERVAL 10
|
||||
#elif defined(ANDROID)
|
||||
// We use a lower frequency on Android, in order to make things work
|
||||
// more smoothly on phones. This value can be adjusted later with
|
||||
// some libunwind optimizations.
|
||||
// In one sample measurement on Galaxy Nexus, out of about 700 backtraces,
|
||||
// 60 of them took more than 25ms, and the average and standard deviation
|
||||
// were 6.17ms and 9.71ms respectively.
|
||||
|
||||
// For now since we don't support stackwalking let's use 1ms since it's fast
|
||||
// enough.
|
||||
#define PROFILE_DEFAULT_INTERVAL 1
|
||||
#else
|
||||
#define PROFILE_DEFAULT_INTERVAL 1
|
||||
#endif
|
||||
#define PROFILE_DEFAULT_FEATURES NULL
|
||||
#define PROFILE_DEFAULT_FEATURE_COUNT 0
|
||||
|
||||
namespace mozilla {
|
||||
|
||||
class MOZ_RAII GeckoProfilerTracingRAII {
|
||||
public:
|
||||
GeckoProfilerTracingRAII(const char* aCategory, const char* aInfo,
|
||||
mozilla::UniquePtr<ProfilerBacktrace> aBacktrace
|
||||
MOZ_GUARD_OBJECT_NOTIFIER_PARAM)
|
||||
: mCategory(aCategory)
|
||||
, mInfo(aInfo)
|
||||
{
|
||||
MOZ_GUARD_OBJECT_NOTIFIER_INIT;
|
||||
profiler_tracing(mCategory, mInfo, aBacktrace.release(), TRACING_INTERVAL_START);
|
||||
}
|
||||
|
||||
~GeckoProfilerTracingRAII() {
|
||||
profiler_tracing(mCategory, mInfo, TRACING_INTERVAL_END);
|
||||
}
|
||||
|
||||
protected:
|
||||
MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER
|
||||
const char* mCategory;
|
||||
const char* mInfo;
|
||||
};
|
||||
|
||||
class MOZ_RAII SamplerStackFrameRAII {
|
||||
public:
|
||||
// we only copy the strings at save time, so to take multiple parameters we'd need to copy them then.
|
||||
SamplerStackFrameRAII(const char *aInfo,
|
||||
js::ProfileEntry::Category aCategory, uint32_t line
|
||||
MOZ_GUARD_OBJECT_NOTIFIER_PARAM)
|
||||
{
|
||||
MOZ_GUARD_OBJECT_NOTIFIER_INIT;
|
||||
mHandle = mozilla_sampler_call_enter(aInfo, aCategory, this, false, line);
|
||||
}
|
||||
~SamplerStackFrameRAII() {
|
||||
mozilla_sampler_call_exit(mHandle);
|
||||
}
|
||||
private:
|
||||
MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER
|
||||
void* mHandle;
|
||||
};
|
||||
|
||||
static const int SAMPLER_MAX_STRING = 128;
|
||||
class MOZ_RAII SamplerStackFramePrintfRAII {
|
||||
public:
|
||||
// we only copy the strings at save time, so to take multiple parameters we'd need to copy them then.
|
||||
SamplerStackFramePrintfRAII(const char *aInfo,
|
||||
js::ProfileEntry::Category aCategory, uint32_t line, const char *aFormat, ...)
|
||||
: mHandle(nullptr)
|
||||
{
|
||||
if (profiler_is_active() && !profiler_in_privacy_mode()) {
|
||||
va_list args;
|
||||
va_start(args, aFormat);
|
||||
char buff[SAMPLER_MAX_STRING];
|
||||
|
||||
// We have to use seperate printf's because we're using
|
||||
// the vargs.
|
||||
VsprintfLiteral(buff, aFormat, args);
|
||||
SprintfLiteral(mDest, "%s %s", aInfo, buff);
|
||||
|
||||
mHandle = mozilla_sampler_call_enter(mDest, aCategory, this, true, line);
|
||||
va_end(args);
|
||||
} else {
|
||||
mHandle = mozilla_sampler_call_enter(aInfo, aCategory, this, false, line);
|
||||
}
|
||||
}
|
||||
~SamplerStackFramePrintfRAII() {
|
||||
mozilla_sampler_call_exit(mHandle);
|
||||
}
|
||||
private:
|
||||
char mDest[SAMPLER_MAX_STRING];
|
||||
void* mHandle;
|
||||
};
|
||||
|
||||
} // namespace mozilla
|
||||
|
||||
inline PseudoStack* mozilla_get_pseudo_stack(void)
|
||||
{
|
||||
if (!stack_key_initialized)
|
||||
return nullptr;
|
||||
return tlsPseudoStack.get();
|
||||
}
|
||||
|
||||
inline void* mozilla_sampler_call_enter(const char *aInfo,
|
||||
js::ProfileEntry::Category aCategory, void *aFrameAddress, bool aCopy, uint32_t line)
|
||||
{
|
||||
// check if we've been initialized to avoid calling pthread_getspecific
|
||||
// with a null tlsStack which will return undefined results.
|
||||
if (!stack_key_initialized)
|
||||
return nullptr;
|
||||
|
||||
PseudoStack *stack = tlsPseudoStack.get();
|
||||
// we can't infer whether 'stack' has been initialized
|
||||
// based on the value of stack_key_intiailized because
|
||||
// 'stack' is only intialized when a thread is being
|
||||
// profiled.
|
||||
if (!stack) {
|
||||
return stack;
|
||||
}
|
||||
stack->push(aInfo, aCategory, aFrameAddress, aCopy, line);
|
||||
|
||||
// The handle is meant to support future changes
|
||||
// but for now it is simply use to save a call to
|
||||
// pthread_getspecific on exit. It also supports the
|
||||
// case where the sampler is initialized between
|
||||
// enter and exit.
|
||||
return stack;
|
||||
}
|
||||
|
||||
inline void mozilla_sampler_call_exit(void *aHandle)
|
||||
{
|
||||
if (!aHandle)
|
||||
return;
|
||||
|
||||
PseudoStack *stack = (PseudoStack*)aHandle;
|
||||
stack->popAndMaybeDelete();
|
||||
}
|
||||
|
||||
void mozilla_sampler_add_marker(const char *aMarker, ProfilerMarkerPayload *aPayload);
|
||||
|
||||
static inline
|
||||
void profiler_log(const char *str)
|
||||
{
|
||||
profiler_tracing("log", str, TRACING_EVENT);
|
||||
}
|
||||
|
||||
static inline
|
||||
void profiler_log(const char *fmt, va_list args)
|
||||
{
|
||||
mozilla_sampler_log(fmt, args);
|
||||
}
|
||||
|
||||
#endif /* ndef TOOLS_SPS_SAMPLER_H_ */
|
||||
42
tools/profiler/public/ProfileGatherer.h
Normal file
42
tools/profiler/public/ProfileGatherer.h
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
/* 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_PROFILE_GATHERER_H
|
||||
#define MOZ_PROFILE_GATHERER_H
|
||||
|
||||
#include "mozilla/dom/Promise.h"
|
||||
|
||||
class GeckoSampler;
|
||||
|
||||
namespace mozilla {
|
||||
|
||||
class ProfileGatherer final : public nsIObserver
|
||||
{
|
||||
public:
|
||||
NS_DECL_ISUPPORTS
|
||||
NS_DECL_NSIOBSERVER
|
||||
|
||||
explicit ProfileGatherer(GeckoSampler* aTicker);
|
||||
void WillGatherOOPProfile();
|
||||
void GatheredOOPProfile();
|
||||
void Start(double aSinceTime, mozilla::dom::Promise* aPromise);
|
||||
void Cancel();
|
||||
void OOPExitProfile(const nsCString& aProfile);
|
||||
|
||||
private:
|
||||
~ProfileGatherer() {};
|
||||
void Finish();
|
||||
void Reset();
|
||||
|
||||
nsTArray<nsCString> mExitProfiles;
|
||||
RefPtr<mozilla::dom::Promise> mPromise;
|
||||
GeckoSampler* mTicker;
|
||||
double mSinceTime;
|
||||
uint32_t mPendingProfiles;
|
||||
bool mGathering;
|
||||
};
|
||||
|
||||
} // namespace mozilla
|
||||
|
||||
#endif
|
||||
36
tools/profiler/public/ProfilerBacktrace.h
Normal file
36
tools/profiler/public/ProfilerBacktrace.h
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
/* -*- Mode: C++; tab-width: 2; 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 __PROFILER_BACKTRACE_H
|
||||
#define __PROFILER_BACKTRACE_H
|
||||
|
||||
class SyncProfile;
|
||||
class SpliceableJSONWriter;
|
||||
class UniqueStacks;
|
||||
|
||||
class ProfilerBacktrace
|
||||
{
|
||||
public:
|
||||
explicit ProfilerBacktrace(SyncProfile* aProfile);
|
||||
~ProfilerBacktrace();
|
||||
|
||||
// ProfilerBacktraces' stacks are deduplicated in the context of the
|
||||
// profile that contains the backtrace as a marker payload.
|
||||
//
|
||||
// That is, markers that contain backtraces should not need their own stack,
|
||||
// frame, and string tables. They should instead reuse their parent
|
||||
// profile's tables.
|
||||
void StreamJSON(SpliceableJSONWriter& aWriter, UniqueStacks& aUniqueStacks);
|
||||
|
||||
private:
|
||||
ProfilerBacktrace(const ProfilerBacktrace&);
|
||||
ProfilerBacktrace& operator=(const ProfilerBacktrace&);
|
||||
|
||||
SyncProfile* mProfile;
|
||||
};
|
||||
|
||||
#endif // __PROFILER_BACKTRACE_H
|
||||
|
||||
193
tools/profiler/public/ProfilerMarkers.h
Normal file
193
tools/profiler/public/ProfilerMarkers.h
Normal file
|
|
@ -0,0 +1,193 @@
|
|||
/* -*- 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 PROFILER_MARKERS_H
|
||||
#define PROFILER_MARKERS_H
|
||||
|
||||
#include "mozilla/TimeStamp.h"
|
||||
#include "mozilla/Attributes.h"
|
||||
|
||||
namespace mozilla {
|
||||
namespace layers {
|
||||
class Layer;
|
||||
} // namespace layers
|
||||
} // namespace mozilla
|
||||
|
||||
class SpliceableJSONWriter;
|
||||
class UniqueStacks;
|
||||
|
||||
/**
|
||||
* This is an abstract object that can be implied to supply
|
||||
* data to be attached with a profiler marker. Most data inserted
|
||||
* into a profile is stored in a circular buffer. This buffer
|
||||
* typically wraps around and overwrites most entries. Because
|
||||
* of this, this structure is designed to defer the work of
|
||||
* prepare the payload only when 'preparePayload' is called.
|
||||
*
|
||||
* Note when implementing that this object is typically constructed
|
||||
* on a particular thread but 'preparePayload' and the destructor
|
||||
* is called from the main thread.
|
||||
*/
|
||||
class ProfilerMarkerPayload
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* ProfilerMarkerPayload takes ownership of aStack
|
||||
*/
|
||||
explicit ProfilerMarkerPayload(ProfilerBacktrace* aStack = nullptr);
|
||||
ProfilerMarkerPayload(const mozilla::TimeStamp& aStartTime,
|
||||
const mozilla::TimeStamp& aEndTime,
|
||||
ProfilerBacktrace* aStack = nullptr);
|
||||
|
||||
/**
|
||||
* Called from the main thread
|
||||
*/
|
||||
virtual ~ProfilerMarkerPayload();
|
||||
|
||||
/**
|
||||
* Called from the main thread
|
||||
*/
|
||||
virtual void StreamPayload(SpliceableJSONWriter& aWriter,
|
||||
UniqueStacks& aUniqueStacks) = 0;
|
||||
|
||||
mozilla::TimeStamp GetStartTime() const { return mStartTime; }
|
||||
|
||||
protected:
|
||||
/**
|
||||
* Called from the main thread
|
||||
*/
|
||||
void streamCommonProps(const char* aMarkerType, SpliceableJSONWriter& aWriter,
|
||||
UniqueStacks& aUniqueStacks);
|
||||
|
||||
void SetStack(ProfilerBacktrace* aStack) { mStack = aStack; }
|
||||
|
||||
private:
|
||||
mozilla::TimeStamp mStartTime;
|
||||
mozilla::TimeStamp mEndTime;
|
||||
ProfilerBacktrace* mStack;
|
||||
};
|
||||
|
||||
class ProfilerMarkerTracing : public ProfilerMarkerPayload
|
||||
{
|
||||
public:
|
||||
ProfilerMarkerTracing(const char* aCategory, TracingMetadata aMetaData);
|
||||
ProfilerMarkerTracing(const char* aCategory, TracingMetadata aMetaData, ProfilerBacktrace* aCause);
|
||||
|
||||
const char *GetCategory() const { return mCategory; }
|
||||
TracingMetadata GetMetaData() const { return mMetaData; }
|
||||
|
||||
virtual void StreamPayload(SpliceableJSONWriter& aWriter,
|
||||
UniqueStacks& aUniqueStacks) override;
|
||||
|
||||
private:
|
||||
const char *mCategory;
|
||||
TracingMetadata mMetaData;
|
||||
};
|
||||
|
||||
|
||||
#ifndef SPS_STANDALONE
|
||||
#include "gfxASurface.h"
|
||||
class ProfilerMarkerImagePayload : public ProfilerMarkerPayload
|
||||
{
|
||||
public:
|
||||
explicit ProfilerMarkerImagePayload(gfxASurface *aImg);
|
||||
|
||||
virtual void StreamPayload(SpliceableJSONWriter& aWriter,
|
||||
UniqueStacks& aUniqueStacks) override;
|
||||
|
||||
private:
|
||||
RefPtr<gfxASurface> mImg;
|
||||
};
|
||||
|
||||
class IOMarkerPayload : public ProfilerMarkerPayload
|
||||
{
|
||||
public:
|
||||
IOMarkerPayload(const char* aSource, const char* aFilename, const mozilla::TimeStamp& aStartTime,
|
||||
const mozilla::TimeStamp& aEndTime,
|
||||
ProfilerBacktrace* aStack);
|
||||
~IOMarkerPayload();
|
||||
|
||||
virtual void StreamPayload(SpliceableJSONWriter& aWriter,
|
||||
UniqueStacks& aUniqueStacks) override;
|
||||
|
||||
private:
|
||||
const char* mSource;
|
||||
char* mFilename;
|
||||
};
|
||||
|
||||
/**
|
||||
* Contains the translation applied to a 2d layer so we can
|
||||
* track the layer position at each frame.
|
||||
*/
|
||||
class LayerTranslationPayload : public ProfilerMarkerPayload
|
||||
{
|
||||
public:
|
||||
LayerTranslationPayload(mozilla::layers::Layer* aLayer,
|
||||
mozilla::gfx::Point aPoint);
|
||||
|
||||
virtual void StreamPayload(SpliceableJSONWriter& aWriter,
|
||||
UniqueStacks& aUniqueStacks) override;
|
||||
|
||||
private:
|
||||
mozilla::layers::Layer* mLayer;
|
||||
mozilla::gfx::Point mPoint;
|
||||
};
|
||||
|
||||
#include "Units.h" // For ScreenIntPoint
|
||||
|
||||
/**
|
||||
* Tracks when touch events are processed by gecko, not when
|
||||
* the touch actually occured in gonk/android.
|
||||
*/
|
||||
class TouchDataPayload : public ProfilerMarkerPayload
|
||||
{
|
||||
public:
|
||||
explicit TouchDataPayload(const mozilla::ScreenIntPoint& aPoint);
|
||||
virtual ~TouchDataPayload() {}
|
||||
|
||||
virtual void StreamPayload(SpliceableJSONWriter& aWriter,
|
||||
UniqueStacks& aUniqueStacks) override;
|
||||
|
||||
private:
|
||||
mozilla::ScreenIntPoint mPoint;
|
||||
};
|
||||
|
||||
/**
|
||||
* Tracks when a vsync occurs according to the HardwareComposer.
|
||||
*/
|
||||
class VsyncPayload : public ProfilerMarkerPayload
|
||||
{
|
||||
public:
|
||||
explicit VsyncPayload(mozilla::TimeStamp aVsyncTimestamp);
|
||||
virtual ~VsyncPayload() {}
|
||||
|
||||
virtual void StreamPayload(SpliceableJSONWriter& aWriter,
|
||||
UniqueStacks& aUniqueStacks) override;
|
||||
|
||||
private:
|
||||
mozilla::TimeStamp mVsyncTimestamp;
|
||||
};
|
||||
|
||||
class GPUMarkerPayload : public ProfilerMarkerPayload
|
||||
{
|
||||
public:
|
||||
GPUMarkerPayload(const mozilla::TimeStamp& aCpuTimeStart,
|
||||
const mozilla::TimeStamp& aCpuTimeEnd,
|
||||
uint64_t aGpuTimeStart,
|
||||
uint64_t aGpuTimeEnd);
|
||||
~GPUMarkerPayload() {}
|
||||
|
||||
virtual void StreamPayload(SpliceableJSONWriter& aWriter,
|
||||
UniqueStacks& aUniqueStacks) override;
|
||||
|
||||
private:
|
||||
mozilla::TimeStamp mCpuTimeStart;
|
||||
mozilla::TimeStamp mCpuTimeEnd;
|
||||
uint64_t mGpuTimeStart;
|
||||
uint64_t mGpuTimeEnd;
|
||||
};
|
||||
#endif
|
||||
|
||||
#endif // PROFILER_MARKERS_H
|
||||
469
tools/profiler/public/PseudoStack.h
Normal file
469
tools/profiler/public/PseudoStack.h
Normal file
|
|
@ -0,0 +1,469 @@
|
|||
/* -*- 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 PROFILER_PSEUDO_STACK_H_
|
||||
#define PROFILER_PSEUDO_STACK_H_
|
||||
|
||||
#include "mozilla/ArrayUtils.h"
|
||||
#include <stdint.h>
|
||||
#include "js/ProfilingStack.h"
|
||||
#include <stdlib.h>
|
||||
#include "mozilla/Atomics.h"
|
||||
#ifndef SPS_STANDALONE
|
||||
#include "nsISupportsImpl.h"
|
||||
#endif
|
||||
|
||||
/* we duplicate this code here to avoid header dependencies
|
||||
* which make it more difficult to include in other places */
|
||||
#if defined(_M_X64) || defined(__x86_64__)
|
||||
#define V8_HOST_ARCH_X64 1
|
||||
#elif defined(_M_IX86) || defined(__i386__) || defined(__i386)
|
||||
#define V8_HOST_ARCH_IA32 1
|
||||
#elif defined(__ARMEL__)
|
||||
#define V8_HOST_ARCH_ARM 1
|
||||
#else
|
||||
#warning Please add support for your architecture in chromium_types.h
|
||||
#endif
|
||||
|
||||
// STORE_SEQUENCER: Because signals can interrupt our profile modification
|
||||
// we need to make stores are not re-ordered by the compiler
|
||||
// or hardware to make sure the profile is consistent at
|
||||
// every point the signal can fire.
|
||||
#ifdef V8_HOST_ARCH_ARM
|
||||
// TODO Is there something cheaper that will prevent
|
||||
// memory stores from being reordered
|
||||
|
||||
typedef void (*LinuxKernelMemoryBarrierFunc)(void);
|
||||
LinuxKernelMemoryBarrierFunc pLinuxKernelMemoryBarrier __attribute__((weak)) =
|
||||
(LinuxKernelMemoryBarrierFunc) 0xffff0fa0;
|
||||
|
||||
# define STORE_SEQUENCER() pLinuxKernelMemoryBarrier()
|
||||
#elif defined(V8_HOST_ARCH_IA32) || defined(V8_HOST_ARCH_X64)
|
||||
# if defined(_MSC_VER)
|
||||
# include <intrin.h>
|
||||
# define STORE_SEQUENCER() _ReadWriteBarrier();
|
||||
# elif defined(__INTEL_COMPILER)
|
||||
# define STORE_SEQUENCER() __memory_barrier();
|
||||
# elif __GNUC__
|
||||
# define STORE_SEQUENCER() asm volatile("" ::: "memory");
|
||||
# else
|
||||
# error "Memory clobber not supported for your compiler."
|
||||
# endif
|
||||
#else
|
||||
# error "Memory clobber not supported for your platform."
|
||||
#endif
|
||||
|
||||
// We can't include <algorithm> because it causes issues on OS X, so we use
|
||||
// our own min function.
|
||||
static inline uint32_t sMin(uint32_t l, uint32_t r) {
|
||||
return l < r ? l : r;
|
||||
}
|
||||
|
||||
// A stack entry exists to allow the JS engine to inform SPS of the current
|
||||
// backtrace, but also to instrument particular points in C++ in case stack
|
||||
// walking is not available on the platform we are running on.
|
||||
//
|
||||
// Each entry has a descriptive string, a relevant stack address, and some extra
|
||||
// information the JS engine might want to inform SPS of. This class inherits
|
||||
// from the JS engine's version of the entry to ensure that the size and layout
|
||||
// of the two representations are consistent.
|
||||
class StackEntry : public js::ProfileEntry
|
||||
{
|
||||
};
|
||||
|
||||
class ProfilerMarkerPayload;
|
||||
template<typename T>
|
||||
class ProfilerLinkedList;
|
||||
class SpliceableJSONWriter;
|
||||
class UniqueStacks;
|
||||
|
||||
class ProfilerMarker {
|
||||
friend class ProfilerLinkedList<ProfilerMarker>;
|
||||
public:
|
||||
explicit ProfilerMarker(const char* aMarkerName,
|
||||
ProfilerMarkerPayload* aPayload = nullptr,
|
||||
double aTime = 0);
|
||||
|
||||
~ProfilerMarker();
|
||||
|
||||
const char* GetMarkerName() const {
|
||||
return mMarkerName;
|
||||
}
|
||||
|
||||
void StreamJSON(SpliceableJSONWriter& aWriter, UniqueStacks& aUniqueStacks) const;
|
||||
|
||||
void SetGeneration(uint32_t aGenID);
|
||||
|
||||
bool HasExpired(uint32_t aGenID) const {
|
||||
return mGenID + 2 <= aGenID;
|
||||
}
|
||||
|
||||
double GetTime() const;
|
||||
|
||||
private:
|
||||
char* mMarkerName;
|
||||
ProfilerMarkerPayload* mPayload;
|
||||
ProfilerMarker* mNext;
|
||||
double mTime;
|
||||
uint32_t mGenID;
|
||||
};
|
||||
|
||||
template<typename T>
|
||||
class ProfilerLinkedList {
|
||||
public:
|
||||
ProfilerLinkedList()
|
||||
: mHead(nullptr)
|
||||
, mTail(nullptr)
|
||||
{}
|
||||
|
||||
void insert(T* elem)
|
||||
{
|
||||
if (!mTail) {
|
||||
mHead = elem;
|
||||
mTail = elem;
|
||||
} else {
|
||||
mTail->mNext = elem;
|
||||
mTail = elem;
|
||||
}
|
||||
elem->mNext = nullptr;
|
||||
}
|
||||
|
||||
T* popHead()
|
||||
{
|
||||
if (!mHead) {
|
||||
MOZ_ASSERT(false);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
T* head = mHead;
|
||||
|
||||
mHead = head->mNext;
|
||||
if (!mHead) {
|
||||
mTail = nullptr;
|
||||
}
|
||||
|
||||
return head;
|
||||
}
|
||||
|
||||
const T* peek() {
|
||||
return mHead;
|
||||
}
|
||||
|
||||
private:
|
||||
T* mHead;
|
||||
T* mTail;
|
||||
};
|
||||
|
||||
typedef ProfilerLinkedList<ProfilerMarker> ProfilerMarkerLinkedList;
|
||||
|
||||
template<typename T>
|
||||
class ProfilerSignalSafeLinkedList {
|
||||
public:
|
||||
ProfilerSignalSafeLinkedList()
|
||||
: mSignalLock(false)
|
||||
{}
|
||||
|
||||
~ProfilerSignalSafeLinkedList()
|
||||
{
|
||||
if (mSignalLock) {
|
||||
// Some thread is modifying the list. We should only be released on that
|
||||
// thread.
|
||||
abort();
|
||||
}
|
||||
|
||||
while (mList.peek()) {
|
||||
delete mList.popHead();
|
||||
}
|
||||
}
|
||||
|
||||
// Insert an item into the list.
|
||||
// Must only be called from the owning thread.
|
||||
// Must not be called while the list from accessList() is being accessed.
|
||||
// In the profiler, we ensure that by interrupting the profiled thread
|
||||
// (which is the one that owns this list and calls insert() on it) until
|
||||
// we're done reading the list from the signal handler.
|
||||
void insert(T* aElement) {
|
||||
MOZ_ASSERT(aElement);
|
||||
|
||||
mSignalLock = true;
|
||||
STORE_SEQUENCER();
|
||||
|
||||
mList.insert(aElement);
|
||||
|
||||
STORE_SEQUENCER();
|
||||
mSignalLock = false;
|
||||
}
|
||||
|
||||
// Called within signal, from any thread, possibly while insert() is in the
|
||||
// middle of modifying the list (on the owning thread). Will return null if
|
||||
// that is the case.
|
||||
// Function must be reentrant.
|
||||
ProfilerLinkedList<T>* accessList()
|
||||
{
|
||||
if (mSignalLock) {
|
||||
return nullptr;
|
||||
}
|
||||
return &mList;
|
||||
}
|
||||
|
||||
private:
|
||||
ProfilerLinkedList<T> mList;
|
||||
|
||||
// If this is set, then it's not safe to read the list because its contents
|
||||
// are being changed.
|
||||
volatile bool mSignalLock;
|
||||
};
|
||||
|
||||
// Stub eventMarker function for js-engine event generation.
|
||||
void ProfilerJSEventMarker(const char *event);
|
||||
|
||||
// the PseudoStack members are read by signal
|
||||
// handlers, so the mutation of them needs to be signal-safe.
|
||||
struct PseudoStack
|
||||
{
|
||||
public:
|
||||
// Create a new PseudoStack and acquire a reference to it.
|
||||
static PseudoStack *create()
|
||||
{
|
||||
return new PseudoStack();
|
||||
}
|
||||
|
||||
// This is called on every profiler restart. Put things that should happen at that time here.
|
||||
void reinitializeOnResume() {
|
||||
// This is needed to cause an initial sample to be taken from sleeping threads. Otherwise sleeping
|
||||
// threads would not have any samples to copy forward while sleeping.
|
||||
mSleepId++;
|
||||
}
|
||||
|
||||
void addMarker(const char* aMarkerStr, ProfilerMarkerPayload* aPayload, double aTime)
|
||||
{
|
||||
ProfilerMarker* marker = new ProfilerMarker(aMarkerStr, aPayload, aTime);
|
||||
mPendingMarkers.insert(marker);
|
||||
}
|
||||
|
||||
// called within signal. Function must be reentrant
|
||||
ProfilerMarkerLinkedList* getPendingMarkers()
|
||||
{
|
||||
// The profiled thread is interrupted, so we can access the list safely.
|
||||
// Unless the profiled thread was in the middle of changing the list when
|
||||
// we interrupted it - in that case, accessList() will return null.
|
||||
return mPendingMarkers.accessList();
|
||||
}
|
||||
|
||||
void push(const char *aName, js::ProfileEntry::Category aCategory, uint32_t line)
|
||||
{
|
||||
push(aName, aCategory, nullptr, false, line);
|
||||
}
|
||||
|
||||
void push(const char *aName, js::ProfileEntry::Category aCategory,
|
||||
void *aStackAddress, bool aCopy, uint32_t line)
|
||||
{
|
||||
if (size_t(mStackPointer) >= mozilla::ArrayLength(mStack)) {
|
||||
mStackPointer++;
|
||||
return;
|
||||
}
|
||||
|
||||
// In order to ensure this object is kept alive while it is
|
||||
// active, we acquire a reference at the outermost push. This is
|
||||
// released by the corresponding pop.
|
||||
if (mStackPointer == 0) {
|
||||
ref();
|
||||
}
|
||||
|
||||
volatile StackEntry &entry = mStack[mStackPointer];
|
||||
|
||||
// Make sure we increment the pointer after the name has
|
||||
// been written such that mStack is always consistent.
|
||||
entry.initCppFrame(aStackAddress, line);
|
||||
entry.setLabel(aName);
|
||||
MOZ_ASSERT(entry.flags() == js::ProfileEntry::IS_CPP_ENTRY);
|
||||
entry.setCategory(aCategory);
|
||||
|
||||
// Track if mLabel needs a copy.
|
||||
if (aCopy)
|
||||
entry.setFlag(js::ProfileEntry::FRAME_LABEL_COPY);
|
||||
else
|
||||
entry.unsetFlag(js::ProfileEntry::FRAME_LABEL_COPY);
|
||||
|
||||
// Prevent the optimizer from re-ordering these instructions
|
||||
STORE_SEQUENCER();
|
||||
mStackPointer++;
|
||||
}
|
||||
|
||||
// Pop the stack. If the stack is empty and all other references to
|
||||
// this PseudoStack have been dropped, then the PseudoStack is
|
||||
// deleted and "false" is returned. Otherwise "true" is returned.
|
||||
bool popAndMaybeDelete()
|
||||
{
|
||||
mStackPointer--;
|
||||
if (mStackPointer == 0) {
|
||||
// Release our self-owned reference count. See 'push'.
|
||||
deref();
|
||||
return false;
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
bool isEmpty()
|
||||
{
|
||||
return mStackPointer == 0;
|
||||
}
|
||||
uint32_t stackSize() const
|
||||
{
|
||||
return sMin(mStackPointer, mozilla::sig_safe_t(mozilla::ArrayLength(mStack)));
|
||||
}
|
||||
|
||||
void sampleContext(JSContext* context) {
|
||||
#ifndef SPS_STANDALONE
|
||||
if (mContext && !context) {
|
||||
// On JS shut down, flush the current buffer as stringifying JIT samples
|
||||
// requires a live JSContext.
|
||||
flushSamplerOnJSShutdown();
|
||||
}
|
||||
|
||||
mContext = context;
|
||||
|
||||
if (!context) {
|
||||
return;
|
||||
}
|
||||
|
||||
static_assert(sizeof(mStack[0]) == sizeof(js::ProfileEntry),
|
||||
"mStack must be binary compatible with js::ProfileEntry.");
|
||||
js::SetContextProfilingStack(context,
|
||||
(js::ProfileEntry*) mStack,
|
||||
(uint32_t*) &mStackPointer,
|
||||
(uint32_t) mozilla::ArrayLength(mStack));
|
||||
if (mStartJSSampling)
|
||||
enableJSSampling();
|
||||
#endif
|
||||
}
|
||||
#ifndef SPS_STANDALONE
|
||||
void enableJSSampling() {
|
||||
if (mContext) {
|
||||
js::EnableContextProfilingStack(mContext, true);
|
||||
js::RegisterContextProfilingEventMarker(mContext, &ProfilerJSEventMarker);
|
||||
mStartJSSampling = false;
|
||||
} else {
|
||||
mStartJSSampling = true;
|
||||
}
|
||||
}
|
||||
void jsOperationCallback() {
|
||||
if (mStartJSSampling)
|
||||
enableJSSampling();
|
||||
}
|
||||
void disableJSSampling() {
|
||||
mStartJSSampling = false;
|
||||
if (mContext)
|
||||
js::EnableContextProfilingStack(mContext, false);
|
||||
}
|
||||
#endif
|
||||
|
||||
// Keep a list of active checkpoints
|
||||
StackEntry volatile mStack[1024];
|
||||
private:
|
||||
|
||||
// A PseudoStack can only be created via the "create" method.
|
||||
PseudoStack()
|
||||
: mStackPointer(0)
|
||||
, mSleepId(0)
|
||||
, mSleepIdObserved(0)
|
||||
, mSleeping(false)
|
||||
, mRefCnt(1)
|
||||
#ifndef SPS_STANDALONE
|
||||
, mContext(nullptr)
|
||||
#endif
|
||||
, mStartJSSampling(false)
|
||||
, mPrivacyMode(false)
|
||||
{
|
||||
MOZ_COUNT_CTOR(PseudoStack);
|
||||
}
|
||||
|
||||
// A PseudoStack can only be deleted via deref.
|
||||
~PseudoStack() {
|
||||
MOZ_COUNT_DTOR(PseudoStack);
|
||||
if (mStackPointer != 0) {
|
||||
// We're releasing the pseudostack while it's still in use.
|
||||
// The label macros keep a non ref counted reference to the
|
||||
// stack to avoid a TLS. If these are not all cleared we will
|
||||
// get a use-after-free so better to crash now.
|
||||
abort();
|
||||
}
|
||||
}
|
||||
|
||||
// No copying.
|
||||
PseudoStack(const PseudoStack&) = delete;
|
||||
void operator=(const PseudoStack&) = delete;
|
||||
|
||||
void flushSamplerOnJSShutdown();
|
||||
|
||||
// Keep a list of pending markers that must be moved
|
||||
// to the circular buffer
|
||||
ProfilerSignalSafeLinkedList<ProfilerMarker> mPendingMarkers;
|
||||
// This may exceed the length of mStack, so instead use the stackSize() method
|
||||
// to determine the number of valid samples in mStack
|
||||
mozilla::sig_safe_t mStackPointer;
|
||||
// Incremented at every sleep/wake up of the thread
|
||||
int mSleepId;
|
||||
// Previous id observed. If this is not the same as mSleepId, this thread is not sleeping in the same place any more
|
||||
mozilla::Atomic<int> mSleepIdObserved;
|
||||
// Keeps tack of whether the thread is sleeping or not (1 when sleeping 0 when awake)
|
||||
mozilla::Atomic<int> mSleeping;
|
||||
// This class is reference counted because it must be kept alive by
|
||||
// the ThreadInfo, by the reference from tlsPseudoStack, and by the
|
||||
// current thread when callbacks are in progress.
|
||||
mozilla::Atomic<int> mRefCnt;
|
||||
|
||||
public:
|
||||
#ifndef SPS_STANDALONE
|
||||
// The context which is being sampled
|
||||
JSContext *mContext;
|
||||
#endif
|
||||
// Start JS Profiling when possible
|
||||
bool mStartJSSampling;
|
||||
bool mPrivacyMode;
|
||||
|
||||
enum SleepState {NOT_SLEEPING, SLEEPING_FIRST, SLEEPING_AGAIN};
|
||||
|
||||
// The first time this is called per sleep cycle we return SLEEPING_FIRST
|
||||
// and any other subsequent call within the same sleep cycle we return SLEEPING_AGAIN
|
||||
SleepState observeSleeping() {
|
||||
if (mSleeping != 0) {
|
||||
if (mSleepIdObserved == mSleepId) {
|
||||
return SLEEPING_AGAIN;
|
||||
} else {
|
||||
mSleepIdObserved = mSleepId;
|
||||
return SLEEPING_FIRST;
|
||||
}
|
||||
} else {
|
||||
return NOT_SLEEPING;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Call this whenever the current thread sleeps or wakes up
|
||||
// Calling setSleeping with the same value twice in a row is an error
|
||||
void setSleeping(int sleeping) {
|
||||
MOZ_ASSERT(mSleeping != sleeping);
|
||||
mSleepId++;
|
||||
mSleeping = sleeping;
|
||||
}
|
||||
|
||||
bool isSleeping() {
|
||||
return !!mSleeping;
|
||||
}
|
||||
|
||||
void ref() {
|
||||
++mRefCnt;
|
||||
}
|
||||
|
||||
void deref() {
|
||||
int newValue = --mRefCnt;
|
||||
if (newValue == 0) {
|
||||
delete this;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
#endif
|
||||
137
tools/profiler/public/shared-libraries.h
Normal file
137
tools/profiler/public/shared-libraries.h
Normal file
|
|
@ -0,0 +1,137 @@
|
|||
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* vim:set ts=2 sw=2 sts=2 et cindent: */
|
||||
/* 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 SHARED_LIBRARIES_H_
|
||||
#define SHARED_LIBRARIES_H_
|
||||
|
||||
#ifndef MOZ_ENABLE_PROFILER_SPS
|
||||
#error This header does not have a useful implementation on your platform!
|
||||
#endif
|
||||
|
||||
#include <algorithm>
|
||||
#include <vector>
|
||||
#include <string>
|
||||
#include <stdlib.h>
|
||||
#include <stdint.h>
|
||||
#ifndef SPS_STANDALONE
|
||||
#include <nsID.h>
|
||||
#endif
|
||||
|
||||
class SharedLibrary {
|
||||
public:
|
||||
|
||||
SharedLibrary(uintptr_t aStart,
|
||||
uintptr_t aEnd,
|
||||
uintptr_t aOffset,
|
||||
const std::string& aBreakpadId,
|
||||
const std::string& aName)
|
||||
: mStart(aStart)
|
||||
, mEnd(aEnd)
|
||||
, mOffset(aOffset)
|
||||
, mBreakpadId(aBreakpadId)
|
||||
, mName(aName)
|
||||
{}
|
||||
|
||||
SharedLibrary(const SharedLibrary& aEntry)
|
||||
: mStart(aEntry.mStart)
|
||||
, mEnd(aEntry.mEnd)
|
||||
, mOffset(aEntry.mOffset)
|
||||
, mBreakpadId(aEntry.mBreakpadId)
|
||||
, mName(aEntry.mName)
|
||||
{}
|
||||
|
||||
SharedLibrary& operator=(const SharedLibrary& aEntry)
|
||||
{
|
||||
// Gracefully handle self assignment
|
||||
if (this == &aEntry) return *this;
|
||||
|
||||
mStart = aEntry.mStart;
|
||||
mEnd = aEntry.mEnd;
|
||||
mOffset = aEntry.mOffset;
|
||||
mBreakpadId = aEntry.mBreakpadId;
|
||||
mName = aEntry.mName;
|
||||
return *this;
|
||||
}
|
||||
|
||||
bool operator==(const SharedLibrary& other) const
|
||||
{
|
||||
return (mStart == other.mStart) &&
|
||||
(mEnd == other.mEnd) &&
|
||||
(mOffset == other.mOffset) &&
|
||||
(mName == other.mName) &&
|
||||
(mBreakpadId == other.mBreakpadId);
|
||||
}
|
||||
|
||||
uintptr_t GetStart() const { return mStart; }
|
||||
uintptr_t GetEnd() const { return mEnd; }
|
||||
uintptr_t GetOffset() const { return mOffset; }
|
||||
const std::string &GetBreakpadId() const { return mBreakpadId; }
|
||||
const std::string &GetName() const { return mName; }
|
||||
|
||||
private:
|
||||
SharedLibrary() {}
|
||||
|
||||
uintptr_t mStart;
|
||||
uintptr_t mEnd;
|
||||
uintptr_t mOffset;
|
||||
std::string mBreakpadId;
|
||||
std::string mName;
|
||||
};
|
||||
|
||||
static bool
|
||||
CompareAddresses(const SharedLibrary& first, const SharedLibrary& second)
|
||||
{
|
||||
return first.GetStart() < second.GetStart();
|
||||
}
|
||||
|
||||
class SharedLibraryInfo {
|
||||
public:
|
||||
static SharedLibraryInfo GetInfoForSelf();
|
||||
SharedLibraryInfo() {}
|
||||
|
||||
void AddSharedLibrary(SharedLibrary entry)
|
||||
{
|
||||
mEntries.push_back(entry);
|
||||
}
|
||||
|
||||
const SharedLibrary& GetEntry(size_t i) const
|
||||
{
|
||||
return mEntries[i];
|
||||
}
|
||||
|
||||
// Removes items in the range [first, last)
|
||||
// i.e. element at the "last" index is not removed
|
||||
void RemoveEntries(size_t first, size_t last)
|
||||
{
|
||||
mEntries.erase(mEntries.begin() + first, mEntries.begin() + last);
|
||||
}
|
||||
|
||||
bool Contains(const SharedLibrary& searchItem) const
|
||||
{
|
||||
return (mEntries.end() !=
|
||||
std::find(mEntries.begin(), mEntries.end(), searchItem));
|
||||
}
|
||||
|
||||
size_t GetSize() const
|
||||
{
|
||||
return mEntries.size();
|
||||
}
|
||||
|
||||
void SortByAddress()
|
||||
{
|
||||
std::sort(mEntries.begin(), mEntries.end(), CompareAddresses);
|
||||
}
|
||||
|
||||
void Clear()
|
||||
{
|
||||
mEntries.clear();
|
||||
}
|
||||
|
||||
private:
|
||||
std::vector<SharedLibrary> mEntries;
|
||||
};
|
||||
|
||||
#endif
|
||||
472
tools/profiler/tasktracer/GeckoTaskTracer.cpp
Normal file
472
tools/profiler/tasktracer/GeckoTaskTracer.cpp
Normal file
|
|
@ -0,0 +1,472 @@
|
|||
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* vim:set ts=2 sw=2 sts=2 et cindent: */
|
||||
/* 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 "GeckoTaskTracer.h"
|
||||
#include "GeckoTaskTracerImpl.h"
|
||||
|
||||
#include "mozilla/MathAlgorithms.h"
|
||||
#include "mozilla/StaticMutex.h"
|
||||
#include "mozilla/ThreadLocal.h"
|
||||
#include "mozilla/TimeStamp.h"
|
||||
#include "mozilla/UniquePtr.h"
|
||||
#include "mozilla/Unused.h"
|
||||
|
||||
#include "nsString.h"
|
||||
#include "nsThreadUtils.h"
|
||||
#include "prtime.h"
|
||||
|
||||
#include <stdarg.h>
|
||||
|
||||
// We need a definition of gettid(), but glibc doesn't provide a
|
||||
// wrapper for it.
|
||||
#if defined(__GLIBC__)
|
||||
#include <unistd.h>
|
||||
#include <sys/syscall.h>
|
||||
static inline pid_t gettid()
|
||||
{
|
||||
return (pid_t) syscall(SYS_gettid);
|
||||
}
|
||||
#elif defined(XP_MACOSX)
|
||||
#include <unistd.h>
|
||||
#include <sys/syscall.h>
|
||||
static inline pid_t gettid()
|
||||
{
|
||||
return (pid_t) syscall(SYS_thread_selfid);
|
||||
}
|
||||
#elif defined(LINUX)
|
||||
#include <sys/types.h>
|
||||
pid_t gettid();
|
||||
#endif
|
||||
|
||||
// NS_ENSURE_TRUE_VOID() without the warning on the debug build.
|
||||
#define ENSURE_TRUE_VOID(x) \
|
||||
do { \
|
||||
if (MOZ_UNLIKELY(!(x))) { \
|
||||
return; \
|
||||
} \
|
||||
} while(0)
|
||||
|
||||
// NS_ENSURE_TRUE() without the warning on the debug build.
|
||||
#define ENSURE_TRUE(x, ret) \
|
||||
do { \
|
||||
if (MOZ_UNLIKELY(!(x))) { \
|
||||
return ret; \
|
||||
} \
|
||||
} while(0)
|
||||
|
||||
namespace mozilla {
|
||||
namespace tasktracer {
|
||||
|
||||
static MOZ_THREAD_LOCAL(TraceInfo*) sTraceInfoTLS;
|
||||
static mozilla::StaticMutex sMutex;
|
||||
|
||||
// The generation of TraceInfo. It will be > 0 if the Task Tracer is started and
|
||||
// <= 0 if stopped.
|
||||
static mozilla::Atomic<bool> sStarted;
|
||||
static nsTArray<UniquePtr<TraceInfo>>* sTraceInfos = nullptr;
|
||||
static PRTime sStartTime;
|
||||
|
||||
static const char sJSLabelPrefix[] = "#tt#";
|
||||
|
||||
namespace {
|
||||
|
||||
static PRTime
|
||||
GetTimestamp()
|
||||
{
|
||||
return PR_Now() / 1000;
|
||||
}
|
||||
|
||||
static TraceInfo*
|
||||
AllocTraceInfo(int aTid)
|
||||
{
|
||||
StaticMutexAutoLock lock(sMutex);
|
||||
|
||||
auto* info = sTraceInfos->AppendElement(MakeUnique<TraceInfo>(aTid));
|
||||
|
||||
return info->get();
|
||||
}
|
||||
|
||||
static void
|
||||
SaveCurTraceInfo()
|
||||
{
|
||||
TraceInfo* info = GetOrCreateTraceInfo();
|
||||
ENSURE_TRUE_VOID(info);
|
||||
|
||||
info->mSavedCurTraceSourceId = info->mCurTraceSourceId;
|
||||
info->mSavedCurTraceSourceType = info->mCurTraceSourceType;
|
||||
info->mSavedCurTaskId = info->mCurTaskId;
|
||||
}
|
||||
|
||||
static void
|
||||
RestoreCurTraceInfo()
|
||||
{
|
||||
TraceInfo* info = GetOrCreateTraceInfo();
|
||||
ENSURE_TRUE_VOID(info);
|
||||
|
||||
info->mCurTraceSourceId = info->mSavedCurTraceSourceId;
|
||||
info->mCurTraceSourceType = info->mSavedCurTraceSourceType;
|
||||
info->mCurTaskId = info->mSavedCurTaskId;
|
||||
}
|
||||
|
||||
static void
|
||||
CreateSourceEvent(SourceEventType aType)
|
||||
{
|
||||
// Save the currently traced source event info.
|
||||
SaveCurTraceInfo();
|
||||
|
||||
// Create a new unique task id.
|
||||
uint64_t newId = GenNewUniqueTaskId();
|
||||
TraceInfo* info = GetOrCreateTraceInfo();
|
||||
ENSURE_TRUE_VOID(info);
|
||||
|
||||
info->mCurTraceSourceId = newId;
|
||||
info->mCurTraceSourceType = aType;
|
||||
info->mCurTaskId = newId;
|
||||
|
||||
uintptr_t* namePtr;
|
||||
#define SOURCE_EVENT_NAME(type) \
|
||||
case SourceEventType::type: \
|
||||
{ \
|
||||
static int CreateSourceEvent##type; \
|
||||
namePtr = (uintptr_t*)&CreateSourceEvent##type; \
|
||||
break; \
|
||||
}
|
||||
|
||||
switch (aType) {
|
||||
#include "SourceEventTypeMap.h"
|
||||
default:
|
||||
MOZ_CRASH("Unknown SourceEvent.");
|
||||
}
|
||||
#undef CREATE_SOURCE_EVENT_NAME
|
||||
|
||||
// Log a fake dispatch and start for this source event.
|
||||
LogDispatch(newId, newId, newId, aType);
|
||||
LogVirtualTablePtr(newId, newId, namePtr);
|
||||
LogBegin(newId, newId);
|
||||
}
|
||||
|
||||
static void
|
||||
DestroySourceEvent()
|
||||
{
|
||||
// Log a fake end for this source event.
|
||||
TraceInfo* info = GetOrCreateTraceInfo();
|
||||
ENSURE_TRUE_VOID(info);
|
||||
|
||||
LogEnd(info->mCurTraceSourceId, info->mCurTraceSourceId);
|
||||
|
||||
// Restore the previously saved source event info.
|
||||
RestoreCurTraceInfo();
|
||||
}
|
||||
|
||||
inline static bool
|
||||
IsStartLogging()
|
||||
{
|
||||
return sStarted;
|
||||
}
|
||||
|
||||
static void
|
||||
SetLogStarted(bool aIsStartLogging)
|
||||
{
|
||||
MOZ_ASSERT(aIsStartLogging != IsStartLogging());
|
||||
sStarted = aIsStartLogging;
|
||||
|
||||
StaticMutexAutoLock lock(sMutex);
|
||||
if (!aIsStartLogging) {
|
||||
for (uint32_t i = 0; i < sTraceInfos->Length(); ++i) {
|
||||
(*sTraceInfos)[i]->mObsolete = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
CleanUp()
|
||||
{
|
||||
SetLogStarted(false);
|
||||
StaticMutexAutoLock lock(sMutex);
|
||||
|
||||
if (sTraceInfos) {
|
||||
delete sTraceInfos;
|
||||
sTraceInfos = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
inline static void
|
||||
ObsoleteCurrentTraceInfos()
|
||||
{
|
||||
// Note that we can't and don't need to acquire sMutex here because this
|
||||
// function is called before the other threads are recreated.
|
||||
for (uint32_t i = 0; i < sTraceInfos->Length(); ++i) {
|
||||
(*sTraceInfos)[i]->mObsolete = true;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace anonymous
|
||||
|
||||
nsCString*
|
||||
TraceInfo::AppendLog()
|
||||
{
|
||||
MutexAutoLock lock(mLogsMutex);
|
||||
return mLogs.AppendElement();
|
||||
}
|
||||
|
||||
void
|
||||
TraceInfo::MoveLogsInto(TraceInfoLogsType& aResult)
|
||||
{
|
||||
MutexAutoLock lock(mLogsMutex);
|
||||
aResult.AppendElements(Move(mLogs));
|
||||
}
|
||||
|
||||
void
|
||||
InitTaskTracer(uint32_t aFlags)
|
||||
{
|
||||
if (aFlags & FORKED_AFTER_NUWA) {
|
||||
ObsoleteCurrentTraceInfos();
|
||||
return;
|
||||
}
|
||||
|
||||
MOZ_ASSERT(!sTraceInfos);
|
||||
sTraceInfos = new nsTArray<UniquePtr<TraceInfo>>();
|
||||
|
||||
if (!sTraceInfoTLS.initialized()) {
|
||||
Unused << sTraceInfoTLS.init();
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
ShutdownTaskTracer()
|
||||
{
|
||||
CleanUp();
|
||||
}
|
||||
|
||||
static void
|
||||
FreeTraceInfo(TraceInfo* aTraceInfo)
|
||||
{
|
||||
StaticMutexAutoLock lock(sMutex);
|
||||
if (aTraceInfo) {
|
||||
sTraceInfos->RemoveElement(aTraceInfo);
|
||||
}
|
||||
}
|
||||
|
||||
void FreeTraceInfo()
|
||||
{
|
||||
FreeTraceInfo(sTraceInfoTLS.get());
|
||||
}
|
||||
|
||||
TraceInfo*
|
||||
GetOrCreateTraceInfo()
|
||||
{
|
||||
ENSURE_TRUE(sTraceInfoTLS.initialized(), nullptr);
|
||||
ENSURE_TRUE(IsStartLogging(), nullptr);
|
||||
|
||||
TraceInfo* info = sTraceInfoTLS.get();
|
||||
if (info && info->mObsolete) {
|
||||
// TraceInfo is obsolete: remove it.
|
||||
FreeTraceInfo(info);
|
||||
info = nullptr;
|
||||
}
|
||||
|
||||
if (!info) {
|
||||
info = AllocTraceInfo(gettid());
|
||||
sTraceInfoTLS.set(info);
|
||||
}
|
||||
|
||||
return info;
|
||||
}
|
||||
|
||||
uint64_t
|
||||
GenNewUniqueTaskId()
|
||||
{
|
||||
TraceInfo* info = GetOrCreateTraceInfo();
|
||||
ENSURE_TRUE(info, 0);
|
||||
|
||||
pid_t tid = gettid();
|
||||
uint64_t taskid = ((uint64_t)tid << 32) | ++info->mLastUniqueTaskId;
|
||||
return taskid;
|
||||
}
|
||||
|
||||
AutoSaveCurTraceInfo::AutoSaveCurTraceInfo()
|
||||
{
|
||||
SaveCurTraceInfo();
|
||||
}
|
||||
|
||||
AutoSaveCurTraceInfo::~AutoSaveCurTraceInfo()
|
||||
{
|
||||
RestoreCurTraceInfo();
|
||||
}
|
||||
|
||||
void
|
||||
SetCurTraceInfo(uint64_t aSourceEventId, uint64_t aParentTaskId,
|
||||
SourceEventType aSourceEventType)
|
||||
{
|
||||
TraceInfo* info = GetOrCreateTraceInfo();
|
||||
ENSURE_TRUE_VOID(info);
|
||||
|
||||
info->mCurTraceSourceId = aSourceEventId;
|
||||
info->mCurTaskId = aParentTaskId;
|
||||
info->mCurTraceSourceType = aSourceEventType;
|
||||
}
|
||||
|
||||
void
|
||||
GetCurTraceInfo(uint64_t* aOutSourceEventId, uint64_t* aOutParentTaskId,
|
||||
SourceEventType* aOutSourceEventType)
|
||||
{
|
||||
TraceInfo* info = GetOrCreateTraceInfo();
|
||||
ENSURE_TRUE_VOID(info);
|
||||
|
||||
*aOutSourceEventId = info->mCurTraceSourceId;
|
||||
*aOutParentTaskId = info->mCurTaskId;
|
||||
*aOutSourceEventType = info->mCurTraceSourceType;
|
||||
}
|
||||
|
||||
void
|
||||
LogDispatch(uint64_t aTaskId, uint64_t aParentTaskId, uint64_t aSourceEventId,
|
||||
SourceEventType aSourceEventType)
|
||||
{
|
||||
LogDispatch(aTaskId, aParentTaskId, aSourceEventId, aSourceEventType, 0);
|
||||
}
|
||||
|
||||
void
|
||||
LogDispatch(uint64_t aTaskId, uint64_t aParentTaskId, uint64_t aSourceEventId,
|
||||
SourceEventType aSourceEventType, int aDelayTimeMs)
|
||||
{
|
||||
TraceInfo* info = GetOrCreateTraceInfo();
|
||||
ENSURE_TRUE_VOID(info);
|
||||
|
||||
// aDelayTimeMs is the expected delay time in milliseconds, thus the dispatch
|
||||
// time calculated of it might be slightly off in the real world.
|
||||
uint64_t time = (aDelayTimeMs <= 0) ? GetTimestamp() :
|
||||
GetTimestamp() + aDelayTimeMs;
|
||||
|
||||
// Log format:
|
||||
// [0 taskId dispatchTime sourceEventId sourceEventType parentTaskId]
|
||||
nsCString* log = info->AppendLog();
|
||||
if (log) {
|
||||
log->AppendPrintf("%d %lld %lld %lld %d %lld",
|
||||
ACTION_DISPATCH, aTaskId, time, aSourceEventId,
|
||||
aSourceEventType, aParentTaskId);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
LogBegin(uint64_t aTaskId, uint64_t aSourceEventId)
|
||||
{
|
||||
TraceInfo* info = GetOrCreateTraceInfo();
|
||||
ENSURE_TRUE_VOID(info);
|
||||
|
||||
// Log format:
|
||||
// [1 taskId beginTime processId threadId]
|
||||
nsCString* log = info->AppendLog();
|
||||
if (log) {
|
||||
log->AppendPrintf("%d %lld %lld %d %d",
|
||||
ACTION_BEGIN, aTaskId, GetTimestamp(), getpid(), gettid());
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
LogEnd(uint64_t aTaskId, uint64_t aSourceEventId)
|
||||
{
|
||||
TraceInfo* info = GetOrCreateTraceInfo();
|
||||
ENSURE_TRUE_VOID(info);
|
||||
|
||||
// Log format:
|
||||
// [2 taskId endTime]
|
||||
nsCString* log = info->AppendLog();
|
||||
if (log) {
|
||||
log->AppendPrintf("%d %lld %lld", ACTION_END, aTaskId, GetTimestamp());
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
LogVirtualTablePtr(uint64_t aTaskId, uint64_t aSourceEventId, uintptr_t* aVptr)
|
||||
{
|
||||
TraceInfo* info = GetOrCreateTraceInfo();
|
||||
ENSURE_TRUE_VOID(info);
|
||||
|
||||
// Log format:
|
||||
// [4 taskId address]
|
||||
nsCString* log = info->AppendLog();
|
||||
if (log) {
|
||||
log->AppendPrintf("%d %lld %p", ACTION_GET_VTABLE, aTaskId, aVptr);
|
||||
}
|
||||
}
|
||||
|
||||
AutoSourceEvent::AutoSourceEvent(SourceEventType aType)
|
||||
{
|
||||
CreateSourceEvent(aType);
|
||||
}
|
||||
|
||||
AutoSourceEvent::~AutoSourceEvent()
|
||||
{
|
||||
DestroySourceEvent();
|
||||
}
|
||||
|
||||
void AddLabel(const char* aFormat, ...)
|
||||
{
|
||||
TraceInfo* info = GetOrCreateTraceInfo();
|
||||
ENSURE_TRUE_VOID(info);
|
||||
|
||||
va_list args;
|
||||
va_start(args, aFormat);
|
||||
nsAutoCString buffer;
|
||||
buffer.AppendPrintf(aFormat, args);
|
||||
va_end(args);
|
||||
|
||||
// Log format:
|
||||
// [3 taskId "label"]
|
||||
nsCString* log = info->AppendLog();
|
||||
if (log) {
|
||||
log->AppendPrintf("%d %lld %lld \"%s\"", ACTION_ADD_LABEL, info->mCurTaskId,
|
||||
GetTimestamp(), buffer.get());
|
||||
}
|
||||
}
|
||||
|
||||
// Functions used by GeckoProfiler.
|
||||
|
||||
void
|
||||
StartLogging()
|
||||
{
|
||||
sStartTime = GetTimestamp();
|
||||
SetLogStarted(true);
|
||||
}
|
||||
|
||||
void
|
||||
StopLogging()
|
||||
{
|
||||
SetLogStarted(false);
|
||||
}
|
||||
|
||||
UniquePtr<TraceInfoLogsType>
|
||||
GetLoggedData(TimeStamp aTimeStamp)
|
||||
{
|
||||
auto result = MakeUnique<TraceInfoLogsType>();
|
||||
|
||||
// TODO: This is called from a signal handler. Use semaphore instead.
|
||||
StaticMutexAutoLock lock(sMutex);
|
||||
|
||||
for (uint32_t i = 0; i < sTraceInfos->Length(); ++i) {
|
||||
(*sTraceInfos)[i]->MoveLogsInto(*result);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
const PRTime
|
||||
GetStartTime()
|
||||
{
|
||||
return sStartTime;
|
||||
}
|
||||
|
||||
const char*
|
||||
GetJSLabelPrefix()
|
||||
{
|
||||
return sJSLabelPrefix;
|
||||
}
|
||||
|
||||
#undef ENSURE_TRUE_VOID
|
||||
#undef ENSURE_TRUE
|
||||
|
||||
} // namespace tasktracer
|
||||
} // namespace mozilla
|
||||
92
tools/profiler/tasktracer/GeckoTaskTracer.h
Normal file
92
tools/profiler/tasktracer/GeckoTaskTracer.h
Normal file
|
|
@ -0,0 +1,92 @@
|
|||
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* vim:set ts=2 sw=2 sts=2 et cindent: */
|
||||
/* 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 GECKO_TASK_TRACER_H
|
||||
#define GECKO_TASK_TRACER_H
|
||||
|
||||
#include "mozilla/UniquePtr.h"
|
||||
#include "nsCOMPtr.h"
|
||||
#include "nsTArrayForwardDeclare.h"
|
||||
|
||||
/**
|
||||
* TaskTracer provides a way to trace the correlation between different tasks
|
||||
* across threads and processes. Unlike sampling based profilers, TaskTracer can
|
||||
* tell you where a task is dispatched from, what its original source was, how
|
||||
* long it waited in the event queue, and how long it took to execute.
|
||||
*
|
||||
* Source Events are usually some kinds of I/O events we're interested in, such
|
||||
* as touch events, timer events, network events, etc. When a source event is
|
||||
* created, TaskTracer records the entire chain of Tasks and nsRunnables as they
|
||||
* are dispatched to different threads and processes. It records latency,
|
||||
* execution time, etc. for each Task and nsRunnable that chains back to the
|
||||
* original source event.
|
||||
*/
|
||||
|
||||
class Task;
|
||||
class nsIRunnable;
|
||||
class nsCString;
|
||||
|
||||
namespace mozilla {
|
||||
|
||||
class TimeStamp;
|
||||
|
||||
namespace tasktracer {
|
||||
|
||||
enum {
|
||||
FORKED_AFTER_NUWA = 1 << 0
|
||||
};
|
||||
|
||||
enum SourceEventType {
|
||||
Unknown = 0,
|
||||
Touch,
|
||||
Mouse,
|
||||
Key,
|
||||
Bluetooth,
|
||||
Unixsocket,
|
||||
Wifi
|
||||
};
|
||||
|
||||
class AutoSourceEvent
|
||||
{
|
||||
public:
|
||||
AutoSourceEvent(SourceEventType aType);
|
||||
~AutoSourceEvent();
|
||||
};
|
||||
|
||||
void InitTaskTracer(uint32_t aFlags = 0);
|
||||
void ShutdownTaskTracer();
|
||||
|
||||
// Add a label to the currently running task, aFormat is the message to log,
|
||||
// followed by corresponding parameters.
|
||||
void AddLabel(const char* aFormat, ...);
|
||||
|
||||
void StartLogging();
|
||||
void StopLogging();
|
||||
UniquePtr<nsTArray<nsCString>> GetLoggedData(TimeStamp aStartTime);
|
||||
|
||||
// Returns the timestamp when Task Tracer is enabled in this process.
|
||||
const PRTime GetStartTime();
|
||||
|
||||
/**
|
||||
* Internal functions.
|
||||
*/
|
||||
|
||||
Task* CreateTracedTask(Task* aTask);
|
||||
|
||||
already_AddRefed<nsIRunnable>
|
||||
CreateTracedRunnable(already_AddRefed<nsIRunnable>&& aRunnable);
|
||||
|
||||
// Free the TraceInfo allocated on a thread's TLS. Currently we are wrapping
|
||||
// tasks running on nsThreads and base::thread, so FreeTraceInfo is called at
|
||||
// where nsThread and base::thread release themselves.
|
||||
void FreeTraceInfo();
|
||||
|
||||
const char* GetJSLabelPrefix();
|
||||
|
||||
} // namespace tasktracer
|
||||
} // namespace mozilla.
|
||||
|
||||
#endif
|
||||
102
tools/profiler/tasktracer/GeckoTaskTracerImpl.h
Normal file
102
tools/profiler/tasktracer/GeckoTaskTracerImpl.h
Normal file
|
|
@ -0,0 +1,102 @@
|
|||
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* vim:set ts=2 sw=2 sts=2 et cindent: */
|
||||
/* 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 GECKO_TASK_TRACER_IMPL_H
|
||||
#define GECKO_TASK_TRACER_IMPL_H
|
||||
|
||||
#include "GeckoTaskTracer.h"
|
||||
#include "mozilla/Mutex.h"
|
||||
#include "nsTArray.h"
|
||||
|
||||
namespace mozilla {
|
||||
namespace tasktracer {
|
||||
|
||||
typedef nsTArray<nsCString> TraceInfoLogsType;
|
||||
|
||||
struct TraceInfo
|
||||
{
|
||||
TraceInfo(uint32_t aThreadId)
|
||||
: mCurTraceSourceId(0)
|
||||
, mCurTaskId(0)
|
||||
, mSavedCurTraceSourceId(0)
|
||||
, mSavedCurTaskId(0)
|
||||
, mCurTraceSourceType(Unknown)
|
||||
, mSavedCurTraceSourceType(Unknown)
|
||||
, mThreadId(aThreadId)
|
||||
, mLastUniqueTaskId(0)
|
||||
, mObsolete(false)
|
||||
, mLogsMutex("TraceInfoMutex")
|
||||
{
|
||||
MOZ_COUNT_CTOR(TraceInfo);
|
||||
}
|
||||
|
||||
~TraceInfo() { MOZ_COUNT_DTOR(TraceInfo); }
|
||||
|
||||
nsCString* AppendLog();
|
||||
void MoveLogsInto(TraceInfoLogsType& aResult);
|
||||
|
||||
uint64_t mCurTraceSourceId;
|
||||
uint64_t mCurTaskId;
|
||||
uint64_t mSavedCurTraceSourceId;
|
||||
uint64_t mSavedCurTaskId;
|
||||
SourceEventType mCurTraceSourceType;
|
||||
SourceEventType mSavedCurTraceSourceType;
|
||||
uint32_t mThreadId;
|
||||
uint32_t mLastUniqueTaskId;
|
||||
mozilla::Atomic<bool> mObsolete;
|
||||
|
||||
// This mutex protects the following log array because MoveLogsInto() might
|
||||
// be called on another thread.
|
||||
mozilla::Mutex mLogsMutex;
|
||||
TraceInfoLogsType mLogs;
|
||||
};
|
||||
|
||||
// Return the TraceInfo of current thread, allocate a new one if not exit.
|
||||
TraceInfo* GetOrCreateTraceInfo();
|
||||
|
||||
uint64_t GenNewUniqueTaskId();
|
||||
|
||||
class AutoSaveCurTraceInfo
|
||||
{
|
||||
public:
|
||||
AutoSaveCurTraceInfo();
|
||||
~AutoSaveCurTraceInfo();
|
||||
};
|
||||
|
||||
void SetCurTraceInfo(uint64_t aSourceEventId, uint64_t aParentTaskId,
|
||||
SourceEventType aSourceEventType);
|
||||
|
||||
void GetCurTraceInfo(uint64_t* aOutSourceEventId, uint64_t* aOutParentTaskId,
|
||||
SourceEventType* aOutSourceEventType);
|
||||
|
||||
/**
|
||||
* Logging functions of different trace actions.
|
||||
*/
|
||||
enum ActionType {
|
||||
ACTION_DISPATCH = 0,
|
||||
ACTION_BEGIN,
|
||||
ACTION_END,
|
||||
ACTION_ADD_LABEL,
|
||||
ACTION_GET_VTABLE
|
||||
};
|
||||
|
||||
void LogDispatch(uint64_t aTaskId, uint64_t aParentTaskId,
|
||||
uint64_t aSourceEventId, SourceEventType aSourceEventType);
|
||||
|
||||
void LogDispatch(uint64_t aTaskId, uint64_t aParentTaskId,
|
||||
uint64_t aSourceEventId, SourceEventType aSourceEventType,
|
||||
int aDelayTimeMs);
|
||||
|
||||
void LogBegin(uint64_t aTaskId, uint64_t aSourceEventId);
|
||||
|
||||
void LogEnd(uint64_t aTaskId, uint64_t aSourceEventId);
|
||||
|
||||
void LogVirtualTablePtr(uint64_t aTaskId, uint64_t aSourceEventId, uintptr_t* aVptr);
|
||||
|
||||
} // namespace mozilla
|
||||
} // namespace tasktracer
|
||||
|
||||
#endif
|
||||
11
tools/profiler/tasktracer/SourceEventTypeMap.h
Normal file
11
tools/profiler/tasktracer/SourceEventTypeMap.h
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
/* 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/. */
|
||||
|
||||
SOURCE_EVENT_NAME(Unknown)
|
||||
SOURCE_EVENT_NAME(Touch)
|
||||
SOURCE_EVENT_NAME(Mouse)
|
||||
SOURCE_EVENT_NAME(Key)
|
||||
SOURCE_EVENT_NAME(Bluetooth)
|
||||
SOURCE_EVENT_NAME(Unixsocket)
|
||||
SOURCE_EVENT_NAME(Wifi)
|
||||
169
tools/profiler/tasktracer/TracedTaskCommon.cpp
Normal file
169
tools/profiler/tasktracer/TracedTaskCommon.cpp
Normal file
|
|
@ -0,0 +1,169 @@
|
|||
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* vim:set ts=2 sw=2 sts=2 et cindent: */
|
||||
/* 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 "GeckoTaskTracerImpl.h"
|
||||
#include "TracedTaskCommon.h"
|
||||
|
||||
// NS_ENSURE_TRUE_VOID() without the warning on the debug build.
|
||||
#define ENSURE_TRUE_VOID(x) \
|
||||
do { \
|
||||
if (MOZ_UNLIKELY(!(x))) { \
|
||||
return; \
|
||||
} \
|
||||
} while(0)
|
||||
|
||||
namespace mozilla {
|
||||
namespace tasktracer {
|
||||
|
||||
TracedTaskCommon::TracedTaskCommon()
|
||||
: mSourceEventType(SourceEventType::Unknown)
|
||||
, mSourceEventId(0)
|
||||
, mParentTaskId(0)
|
||||
, mTaskId(0)
|
||||
, mIsTraceInfoInit(false)
|
||||
{
|
||||
}
|
||||
|
||||
TracedTaskCommon::~TracedTaskCommon()
|
||||
{
|
||||
}
|
||||
|
||||
void
|
||||
TracedTaskCommon::Init()
|
||||
{
|
||||
TraceInfo* info = GetOrCreateTraceInfo();
|
||||
ENSURE_TRUE_VOID(info);
|
||||
|
||||
mTaskId = GenNewUniqueTaskId();
|
||||
mSourceEventId = info->mCurTraceSourceId;
|
||||
mSourceEventType = info->mCurTraceSourceType;
|
||||
mParentTaskId = info->mCurTaskId;
|
||||
mIsTraceInfoInit = true;
|
||||
}
|
||||
|
||||
void
|
||||
TracedTaskCommon::DispatchTask(int aDelayTimeMs)
|
||||
{
|
||||
LogDispatch(mTaskId, mParentTaskId, mSourceEventId, mSourceEventType,
|
||||
aDelayTimeMs);
|
||||
}
|
||||
|
||||
void
|
||||
TracedTaskCommon::GetTLSTraceInfo()
|
||||
{
|
||||
TraceInfo* info = GetOrCreateTraceInfo();
|
||||
ENSURE_TRUE_VOID(info);
|
||||
|
||||
mSourceEventType = info->mCurTraceSourceType;
|
||||
mSourceEventId = info->mCurTraceSourceId;
|
||||
mTaskId = info->mCurTaskId;
|
||||
mIsTraceInfoInit = true;
|
||||
}
|
||||
|
||||
void
|
||||
TracedTaskCommon::SetTLSTraceInfo()
|
||||
{
|
||||
TraceInfo* info = GetOrCreateTraceInfo();
|
||||
ENSURE_TRUE_VOID(info);
|
||||
|
||||
if (mIsTraceInfoInit) {
|
||||
info->mCurTraceSourceId = mSourceEventId;
|
||||
info->mCurTraceSourceType = mSourceEventType;
|
||||
info->mCurTaskId = mTaskId;
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
TracedTaskCommon::ClearTLSTraceInfo()
|
||||
{
|
||||
TraceInfo* info = GetOrCreateTraceInfo();
|
||||
ENSURE_TRUE_VOID(info);
|
||||
|
||||
info->mCurTraceSourceId = 0;
|
||||
info->mCurTraceSourceType = SourceEventType::Unknown;
|
||||
info->mCurTaskId = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implementation of class TracedRunnable.
|
||||
*/
|
||||
TracedRunnable::TracedRunnable(already_AddRefed<nsIRunnable>&& aOriginalObj)
|
||||
: TracedTaskCommon()
|
||||
, mOriginalObj(Move(aOriginalObj))
|
||||
{
|
||||
Init();
|
||||
LogVirtualTablePtr(mTaskId, mSourceEventId, reinterpret_cast<uintptr_t*>(mOriginalObj.get()));
|
||||
}
|
||||
|
||||
TracedRunnable::~TracedRunnable()
|
||||
{
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
TracedRunnable::Run()
|
||||
{
|
||||
SetTLSTraceInfo();
|
||||
LogBegin(mTaskId, mSourceEventId);
|
||||
nsresult rv = mOriginalObj->Run();
|
||||
LogEnd(mTaskId, mSourceEventId);
|
||||
ClearTLSTraceInfo();
|
||||
|
||||
return rv;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implementation of class TracedTask.
|
||||
*/
|
||||
TracedTask::TracedTask(Task* aOriginalObj)
|
||||
: TracedTaskCommon()
|
||||
, mOriginalObj(aOriginalObj)
|
||||
{
|
||||
Init();
|
||||
LogVirtualTablePtr(mTaskId, mSourceEventId, reinterpret_cast<uintptr_t*>(aOriginalObj));
|
||||
}
|
||||
|
||||
TracedTask::~TracedTask()
|
||||
{
|
||||
if (mOriginalObj) {
|
||||
delete mOriginalObj;
|
||||
mOriginalObj = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
TracedTask::Run()
|
||||
{
|
||||
SetTLSTraceInfo();
|
||||
LogBegin(mTaskId, mSourceEventId);
|
||||
mOriginalObj->Run();
|
||||
LogEnd(mTaskId, mSourceEventId);
|
||||
ClearTLSTraceInfo();
|
||||
}
|
||||
|
||||
/**
|
||||
* CreateTracedRunnable() returns a TracedRunnable wrapping the original
|
||||
* nsIRunnable object, aRunnable.
|
||||
*/
|
||||
already_AddRefed<nsIRunnable>
|
||||
CreateTracedRunnable(already_AddRefed<nsIRunnable>&& aRunnable)
|
||||
{
|
||||
nsCOMPtr<nsIRunnable> runnable = new TracedRunnable(Move(aRunnable));
|
||||
return runnable.forget();
|
||||
}
|
||||
|
||||
/**
|
||||
* CreateTracedTask() returns a TracedTask wrapping the original Task object,
|
||||
* aTask.
|
||||
*/
|
||||
Task*
|
||||
CreateTracedTask(Task* aTask)
|
||||
{
|
||||
Task* task = new TracedTask(aTask);
|
||||
return task;
|
||||
}
|
||||
|
||||
} // namespace tasktracer
|
||||
} // namespace mozilla
|
||||
73
tools/profiler/tasktracer/TracedTaskCommon.h
Normal file
73
tools/profiler/tasktracer/TracedTaskCommon.h
Normal file
|
|
@ -0,0 +1,73 @@
|
|||
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* vim:set ts=2 sw=2 sts=2 et cindent: */
|
||||
/* 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 TRACED_TASK_COMMON_H
|
||||
#define TRACED_TASK_COMMON_H
|
||||
|
||||
#include "base/task.h"
|
||||
#include "GeckoTaskTracer.h"
|
||||
#include "nsCOMPtr.h"
|
||||
#include "nsThreadUtils.h"
|
||||
|
||||
namespace mozilla {
|
||||
namespace tasktracer {
|
||||
|
||||
class TracedTaskCommon
|
||||
{
|
||||
public:
|
||||
TracedTaskCommon();
|
||||
virtual ~TracedTaskCommon();
|
||||
|
||||
void DispatchTask(int aDelayTimeMs = 0);
|
||||
|
||||
void SetTLSTraceInfo();
|
||||
void GetTLSTraceInfo();
|
||||
void ClearTLSTraceInfo();
|
||||
|
||||
protected:
|
||||
void Init();
|
||||
|
||||
// TraceInfo of TLS will be set by the following parameters, including source
|
||||
// event type, source event ID, parent task ID, and task ID of this traced
|
||||
// task/runnable.
|
||||
SourceEventType mSourceEventType;
|
||||
uint64_t mSourceEventId;
|
||||
uint64_t mParentTaskId;
|
||||
uint64_t mTaskId;
|
||||
bool mIsTraceInfoInit;
|
||||
};
|
||||
|
||||
class TracedRunnable : public TracedTaskCommon
|
||||
, public nsRunnable
|
||||
{
|
||||
public:
|
||||
NS_DECL_NSIRUNNABLE
|
||||
|
||||
TracedRunnable(already_AddRefed<nsIRunnable>&& aOriginalObj);
|
||||
|
||||
private:
|
||||
virtual ~TracedRunnable();
|
||||
|
||||
nsCOMPtr<nsIRunnable> mOriginalObj;
|
||||
};
|
||||
|
||||
class TracedTask : public TracedTaskCommon
|
||||
, public Task
|
||||
{
|
||||
public:
|
||||
TracedTask(Task* aOriginalObj);
|
||||
~TracedTask();
|
||||
|
||||
virtual void Run();
|
||||
|
||||
private:
|
||||
Task* mOriginalObj;
|
||||
};
|
||||
|
||||
} // namespace tasktracer
|
||||
} // namespace mozilla
|
||||
|
||||
#endif
|
||||
51
tools/profiler/tests/gtest/LulTest.cpp
Normal file
51
tools/profiler/tests/gtest/LulTest.cpp
Normal file
|
|
@ -0,0 +1,51 @@
|
|||
/* -*- 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 "gtest/gtest.h"
|
||||
#include "mozilla/Atomics.h"
|
||||
#include "LulMain.h"
|
||||
#include "GeckoProfiler.h" // for TracingMetadata
|
||||
#include "platform-linux-lul.h" // for read_procmaps
|
||||
|
||||
// Set this to 0 to make LUL be completely silent during tests.
|
||||
// Set it to 1 to get logging output from LUL, presumably for
|
||||
// the purpose of debugging it.
|
||||
#define DEBUG_LUL_TEST 0
|
||||
|
||||
// LUL needs a callback for its logging sink.
|
||||
static void
|
||||
gtest_logging_sink_for_LulIntegration(const char* str) {
|
||||
if (DEBUG_LUL_TEST == 0) {
|
||||
return;
|
||||
}
|
||||
// Ignore any trailing \n, since LOG will add one anyway.
|
||||
size_t n = strlen(str);
|
||||
if (n > 0 && str[n-1] == '\n') {
|
||||
char* tmp = strdup(str);
|
||||
tmp[n-1] = 0;
|
||||
fprintf(stderr, "LUL-in-gtest: %s\n", tmp);
|
||||
free(tmp);
|
||||
} else {
|
||||
fprintf(stderr, "LUL-in-gtest: %s\n", str);
|
||||
}
|
||||
}
|
||||
|
||||
TEST(LulIntegration, unwind_consistency) {
|
||||
// Set up LUL and get it to read unwind info for libxul.so, which is
|
||||
// all we care about here, plus (incidentally) practically every
|
||||
// other object in the process too.
|
||||
lul::LUL* lul = new lul::LUL(gtest_logging_sink_for_LulIntegration);
|
||||
read_procmaps(lul);
|
||||
|
||||
// Run unwind tests and receive information about how many there
|
||||
// were and how many were successful.
|
||||
lul->EnableUnwinding();
|
||||
int nTests = 0, nTestsPassed = 0;
|
||||
RunLulUnitTests(&nTests, &nTestsPassed, lul);
|
||||
EXPECT_TRUE(nTests == 6) << "Unexpected number of tests";
|
||||
EXPECT_TRUE(nTestsPassed == nTests) << "Not all tests passed";
|
||||
|
||||
delete lul;
|
||||
}
|
||||
2597
tools/profiler/tests/gtest/LulTestDwarf.cpp
Normal file
2597
tools/profiler/tests/gtest/LulTestDwarf.cpp
Normal file
File diff suppressed because it is too large
Load diff
491
tools/profiler/tests/gtest/LulTestInfrastructure.cpp
Normal file
491
tools/profiler/tests/gtest/LulTestInfrastructure.cpp
Normal file
|
|
@ -0,0 +1,491 @@
|
|||
// Copyright (c) 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.
|
||||
|
||||
// Original author: Jim Blandy <jimb@mozilla.com> <jimb@red-bean.com>
|
||||
|
||||
// Derived from:
|
||||
// test_assembler.cc: Implementation of google_breakpad::TestAssembler.
|
||||
// See test_assembler.h for details.
|
||||
|
||||
// Derived from:
|
||||
// cfi_assembler.cc: Implementation of google_breakpad::CFISection class.
|
||||
// See cfi_assembler.h for details.
|
||||
|
||||
#include "LulTestInfrastructure.h"
|
||||
|
||||
namespace lul_test {
|
||||
namespace test_assembler {
|
||||
|
||||
using std::back_insert_iterator;
|
||||
|
||||
Label::Label() : value_(new Binding()) { }
|
||||
Label::Label(uint64_t value) : value_(new Binding(value)) { }
|
||||
Label::Label(const Label &label) {
|
||||
value_ = label.value_;
|
||||
value_->Acquire();
|
||||
}
|
||||
Label::~Label() {
|
||||
if (value_->Release()) delete value_;
|
||||
}
|
||||
|
||||
Label &Label::operator=(uint64_t value) {
|
||||
value_->Set(NULL, value);
|
||||
return *this;
|
||||
}
|
||||
|
||||
Label &Label::operator=(const Label &label) {
|
||||
value_->Set(label.value_, 0);
|
||||
return *this;
|
||||
}
|
||||
|
||||
Label Label::operator+(uint64_t addend) const {
|
||||
Label l;
|
||||
l.value_->Set(this->value_, addend);
|
||||
return l;
|
||||
}
|
||||
|
||||
Label Label::operator-(uint64_t subtrahend) const {
|
||||
Label l;
|
||||
l.value_->Set(this->value_, -subtrahend);
|
||||
return l;
|
||||
}
|
||||
|
||||
// When NDEBUG is #defined, assert doesn't evaluate its argument. This
|
||||
// means you can't simply use assert to check the return value of a
|
||||
// function with necessary side effects.
|
||||
//
|
||||
// ALWAYS_EVALUATE_AND_ASSERT(x) evaluates x regardless of whether
|
||||
// NDEBUG is #defined; when NDEBUG is not #defined, it further asserts
|
||||
// that x is true.
|
||||
#ifdef NDEBUG
|
||||
#define ALWAYS_EVALUATE_AND_ASSERT(x) x
|
||||
#else
|
||||
#define ALWAYS_EVALUATE_AND_ASSERT(x) assert(x)
|
||||
#endif
|
||||
|
||||
uint64_t Label::operator-(const Label &label) const {
|
||||
uint64_t offset;
|
||||
ALWAYS_EVALUATE_AND_ASSERT(IsKnownOffsetFrom(label, &offset));
|
||||
return offset;
|
||||
}
|
||||
|
||||
bool Label::IsKnownConstant(uint64_t *value_p) const {
|
||||
Binding *base;
|
||||
uint64_t addend;
|
||||
value_->Get(&base, &addend);
|
||||
if (base != NULL) return false;
|
||||
if (value_p) *value_p = addend;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Label::IsKnownOffsetFrom(const Label &label, uint64_t *offset_p) const
|
||||
{
|
||||
Binding *label_base, *this_base;
|
||||
uint64_t label_addend, this_addend;
|
||||
label.value_->Get(&label_base, &label_addend);
|
||||
value_->Get(&this_base, &this_addend);
|
||||
// If this and label are related, Get will find their final
|
||||
// common ancestor, regardless of how indirect the relation is. This
|
||||
// comparison also handles the constant vs. constant case.
|
||||
if (this_base != label_base) return false;
|
||||
if (offset_p) *offset_p = this_addend - label_addend;
|
||||
return true;
|
||||
}
|
||||
|
||||
Label::Binding::Binding() : base_(this), addend_(), reference_count_(1) { }
|
||||
|
||||
Label::Binding::Binding(uint64_t addend)
|
||||
: base_(NULL), addend_(addend), reference_count_(1) { }
|
||||
|
||||
Label::Binding::~Binding() {
|
||||
assert(reference_count_ == 0);
|
||||
if (base_ && base_ != this && base_->Release())
|
||||
delete base_;
|
||||
}
|
||||
|
||||
void Label::Binding::Set(Binding *binding, uint64_t addend) {
|
||||
if (!base_ && !binding) {
|
||||
// We're equating two constants. This could be okay.
|
||||
assert(addend_ == addend);
|
||||
} else if (!base_) {
|
||||
// We are a known constant, but BINDING may not be, so turn the
|
||||
// tables and try to set BINDING's value instead.
|
||||
binding->Set(NULL, addend_ - addend);
|
||||
} else {
|
||||
if (binding) {
|
||||
// Find binding's final value. Since the final value is always either
|
||||
// completely unconstrained or a constant, never a reference to
|
||||
// another variable (otherwise, it wouldn't be final), this
|
||||
// guarantees we won't create cycles here, even for code like this:
|
||||
// l = m, m = n, n = l;
|
||||
uint64_t binding_addend;
|
||||
binding->Get(&binding, &binding_addend);
|
||||
addend += binding_addend;
|
||||
}
|
||||
|
||||
// It seems likely that setting a binding to itself is a bug
|
||||
// (although I can imagine this might turn out to be helpful to
|
||||
// permit).
|
||||
assert(binding != this);
|
||||
|
||||
if (base_ != this) {
|
||||
// Set the other bindings on our chain as well. Note that this
|
||||
// is sufficient even though binding relationships form trees:
|
||||
// All binding operations traverse their chains to the end, and
|
||||
// all bindings related to us share some tail of our chain, so
|
||||
// they will see the changes we make here.
|
||||
base_->Set(binding, addend - addend_);
|
||||
// We're not going to use base_ any more.
|
||||
if (base_->Release()) delete base_;
|
||||
}
|
||||
|
||||
// Adopt BINDING as our base. Note that it should be correct to
|
||||
// acquire here, after the release above, even though the usual
|
||||
// reference-counting rules call for acquiring first, and then
|
||||
// releasing: the self-reference assertion above should have
|
||||
// complained if BINDING were 'this' or anywhere along our chain,
|
||||
// so we didn't release BINDING.
|
||||
if (binding) binding->Acquire();
|
||||
base_ = binding;
|
||||
addend_ = addend;
|
||||
}
|
||||
}
|
||||
|
||||
void Label::Binding::Get(Binding **base, uint64_t *addend) {
|
||||
if (base_ && base_ != this) {
|
||||
// Recurse to find the end of our reference chain (the root of our
|
||||
// tree), and then rewrite every binding along the chain to refer
|
||||
// to it directly, adjusting addends appropriately. (This is why
|
||||
// this member function isn't this-const.)
|
||||
Binding *final_base;
|
||||
uint64_t final_addend;
|
||||
base_->Get(&final_base, &final_addend);
|
||||
if (final_base) final_base->Acquire();
|
||||
if (base_->Release()) delete base_;
|
||||
base_ = final_base;
|
||||
addend_ += final_addend;
|
||||
}
|
||||
*base = base_;
|
||||
*addend = addend_;
|
||||
}
|
||||
|
||||
template<typename Inserter>
|
||||
static inline void InsertEndian(test_assembler::Endianness endianness,
|
||||
size_t size, uint64_t number, Inserter dest) {
|
||||
assert(size > 0);
|
||||
if (endianness == kLittleEndian) {
|
||||
for (size_t i = 0; i < size; i++) {
|
||||
*dest++ = (char) (number & 0xff);
|
||||
number >>= 8;
|
||||
}
|
||||
} else {
|
||||
assert(endianness == kBigEndian);
|
||||
// The loop condition is odd, but it's correct for size_t.
|
||||
for (size_t i = size - 1; i < size; i--)
|
||||
*dest++ = (char) ((number >> (i * 8)) & 0xff);
|
||||
}
|
||||
}
|
||||
|
||||
Section &Section::Append(Endianness endianness, size_t size, uint64_t number) {
|
||||
InsertEndian(endianness, size, number,
|
||||
back_insert_iterator<string>(contents_));
|
||||
return *this;
|
||||
}
|
||||
|
||||
Section &Section::Append(Endianness endianness, size_t size,
|
||||
const Label &label) {
|
||||
// If this label's value is known, there's no reason to waste an
|
||||
// entry in references_ on it.
|
||||
uint64_t value;
|
||||
if (label.IsKnownConstant(&value))
|
||||
return Append(endianness, size, value);
|
||||
|
||||
// This will get caught when the references are resolved, but it's
|
||||
// nicer to find out earlier.
|
||||
assert(endianness != kUnsetEndian);
|
||||
|
||||
references_.push_back(Reference(contents_.size(), endianness, size, label));
|
||||
contents_.append(size, 0);
|
||||
return *this;
|
||||
}
|
||||
|
||||
#define ENDIANNESS_L kLittleEndian
|
||||
#define ENDIANNESS_B kBigEndian
|
||||
#define ENDIANNESS(e) ENDIANNESS_ ## e
|
||||
|
||||
#define DEFINE_SHORT_APPEND_NUMBER_ENDIAN(e, bits) \
|
||||
Section &Section::e ## bits(uint ## bits ## _t v) { \
|
||||
InsertEndian(ENDIANNESS(e), bits / 8, v, \
|
||||
back_insert_iterator<string>(contents_)); \
|
||||
return *this; \
|
||||
}
|
||||
|
||||
#define DEFINE_SHORT_APPEND_LABEL_ENDIAN(e, bits) \
|
||||
Section &Section::e ## bits(const Label &v) { \
|
||||
return Append(ENDIANNESS(e), bits / 8, v); \
|
||||
}
|
||||
|
||||
// Define L16, B32, and friends.
|
||||
#define DEFINE_SHORT_APPEND_ENDIAN(e, bits) \
|
||||
DEFINE_SHORT_APPEND_NUMBER_ENDIAN(e, bits) \
|
||||
DEFINE_SHORT_APPEND_LABEL_ENDIAN(e, bits)
|
||||
|
||||
DEFINE_SHORT_APPEND_LABEL_ENDIAN(L, 8);
|
||||
DEFINE_SHORT_APPEND_LABEL_ENDIAN(B, 8);
|
||||
DEFINE_SHORT_APPEND_ENDIAN(L, 16);
|
||||
DEFINE_SHORT_APPEND_ENDIAN(L, 32);
|
||||
DEFINE_SHORT_APPEND_ENDIAN(L, 64);
|
||||
DEFINE_SHORT_APPEND_ENDIAN(B, 16);
|
||||
DEFINE_SHORT_APPEND_ENDIAN(B, 32);
|
||||
DEFINE_SHORT_APPEND_ENDIAN(B, 64);
|
||||
|
||||
#define DEFINE_SHORT_APPEND_NUMBER_DEFAULT(bits) \
|
||||
Section &Section::D ## bits(uint ## bits ## _t v) { \
|
||||
InsertEndian(endianness_, bits / 8, v, \
|
||||
back_insert_iterator<string>(contents_)); \
|
||||
return *this; \
|
||||
}
|
||||
#define DEFINE_SHORT_APPEND_LABEL_DEFAULT(bits) \
|
||||
Section &Section::D ## bits(const Label &v) { \
|
||||
return Append(endianness_, bits / 8, v); \
|
||||
}
|
||||
#define DEFINE_SHORT_APPEND_DEFAULT(bits) \
|
||||
DEFINE_SHORT_APPEND_NUMBER_DEFAULT(bits) \
|
||||
DEFINE_SHORT_APPEND_LABEL_DEFAULT(bits)
|
||||
|
||||
DEFINE_SHORT_APPEND_LABEL_DEFAULT(8)
|
||||
DEFINE_SHORT_APPEND_DEFAULT(16);
|
||||
DEFINE_SHORT_APPEND_DEFAULT(32);
|
||||
DEFINE_SHORT_APPEND_DEFAULT(64);
|
||||
|
||||
Section &Section::LEB128(long long value) {
|
||||
while (value < -0x40 || 0x3f < value) {
|
||||
contents_ += (value & 0x7f) | 0x80;
|
||||
if (value < 0)
|
||||
value = (value >> 7) | ~(((unsigned long long) -1) >> 7);
|
||||
else
|
||||
value = (value >> 7);
|
||||
}
|
||||
contents_ += value & 0x7f;
|
||||
return *this;
|
||||
}
|
||||
|
||||
Section &Section::ULEB128(uint64_t value) {
|
||||
while (value > 0x7f) {
|
||||
contents_ += (value & 0x7f) | 0x80;
|
||||
value = (value >> 7);
|
||||
}
|
||||
contents_ += value;
|
||||
return *this;
|
||||
}
|
||||
|
||||
Section &Section::Align(size_t alignment, uint8_t pad_byte) {
|
||||
// ALIGNMENT must be a power of two.
|
||||
assert(((alignment - 1) & alignment) == 0);
|
||||
size_t new_size = (contents_.size() + alignment - 1) & ~(alignment - 1);
|
||||
contents_.append(new_size - contents_.size(), pad_byte);
|
||||
assert((contents_.size() & (alignment - 1)) == 0);
|
||||
return *this;
|
||||
}
|
||||
|
||||
bool Section::GetContents(string *contents) {
|
||||
// For each label reference, find the label's value, and patch it into
|
||||
// the section's contents.
|
||||
for (size_t i = 0; i < references_.size(); i++) {
|
||||
Reference &r = references_[i];
|
||||
uint64_t value;
|
||||
if (!r.label.IsKnownConstant(&value)) {
|
||||
fprintf(stderr, "Undefined label #%zu at offset 0x%zx\n", i, r.offset);
|
||||
return false;
|
||||
}
|
||||
assert(r.offset < contents_.size());
|
||||
assert(contents_.size() - r.offset >= r.size);
|
||||
InsertEndian(r.endianness, r.size, value, contents_.begin() + r.offset);
|
||||
}
|
||||
contents->clear();
|
||||
std::swap(contents_, *contents);
|
||||
references_.clear();
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace test_assembler
|
||||
} // namespace lul_test
|
||||
|
||||
|
||||
namespace lul_test {
|
||||
|
||||
CFISection &CFISection::CIEHeader(uint64_t code_alignment_factor,
|
||||
int data_alignment_factor,
|
||||
unsigned return_address_register,
|
||||
uint8_t version,
|
||||
const string &augmentation,
|
||||
bool dwarf64) {
|
||||
assert(!entry_length_);
|
||||
entry_length_ = new PendingLength();
|
||||
in_fde_ = false;
|
||||
|
||||
if (dwarf64) {
|
||||
D32(kDwarf64InitialLengthMarker);
|
||||
D64(entry_length_->length);
|
||||
entry_length_->start = Here();
|
||||
D64(eh_frame_ ? kEHFrame64CIEIdentifier : kDwarf64CIEIdentifier);
|
||||
} else {
|
||||
D32(entry_length_->length);
|
||||
entry_length_->start = Here();
|
||||
D32(eh_frame_ ? kEHFrame32CIEIdentifier : kDwarf32CIEIdentifier);
|
||||
}
|
||||
D8(version);
|
||||
AppendCString(augmentation);
|
||||
ULEB128(code_alignment_factor);
|
||||
LEB128(data_alignment_factor);
|
||||
if (version == 1)
|
||||
D8(return_address_register);
|
||||
else
|
||||
ULEB128(return_address_register);
|
||||
return *this;
|
||||
}
|
||||
|
||||
CFISection &CFISection::FDEHeader(Label cie_pointer,
|
||||
uint64_t initial_location,
|
||||
uint64_t address_range,
|
||||
bool dwarf64) {
|
||||
assert(!entry_length_);
|
||||
entry_length_ = new PendingLength();
|
||||
in_fde_ = true;
|
||||
fde_start_address_ = initial_location;
|
||||
|
||||
if (dwarf64) {
|
||||
D32(0xffffffff);
|
||||
D64(entry_length_->length);
|
||||
entry_length_->start = Here();
|
||||
if (eh_frame_)
|
||||
D64(Here() - cie_pointer);
|
||||
else
|
||||
D64(cie_pointer);
|
||||
} else {
|
||||
D32(entry_length_->length);
|
||||
entry_length_->start = Here();
|
||||
if (eh_frame_)
|
||||
D32(Here() - cie_pointer);
|
||||
else
|
||||
D32(cie_pointer);
|
||||
}
|
||||
EncodedPointer(initial_location);
|
||||
// The FDE length in an .eh_frame section uses the same encoding as the
|
||||
// initial location, but ignores the base address (selected by the upper
|
||||
// nybble of the encoding), as it's a length, not an address that can be
|
||||
// made relative.
|
||||
EncodedPointer(address_range,
|
||||
DwarfPointerEncoding(pointer_encoding_ & 0x0f));
|
||||
return *this;
|
||||
}
|
||||
|
||||
CFISection &CFISection::FinishEntry() {
|
||||
assert(entry_length_);
|
||||
Align(address_size_, lul::DW_CFA_nop);
|
||||
entry_length_->length = Here() - entry_length_->start;
|
||||
delete entry_length_;
|
||||
entry_length_ = NULL;
|
||||
in_fde_ = false;
|
||||
return *this;
|
||||
}
|
||||
|
||||
CFISection &CFISection::EncodedPointer(uint64_t address,
|
||||
DwarfPointerEncoding encoding,
|
||||
const EncodedPointerBases &bases) {
|
||||
// Omitted data is extremely easy to emit.
|
||||
if (encoding == lul::DW_EH_PE_omit)
|
||||
return *this;
|
||||
|
||||
// If (encoding & lul::DW_EH_PE_indirect) != 0, then we assume
|
||||
// that ADDRESS is the address at which the pointer is stored --- in
|
||||
// other words, that bit has no effect on how we write the pointer.
|
||||
encoding = DwarfPointerEncoding(encoding & ~lul::DW_EH_PE_indirect);
|
||||
|
||||
// Find the base address to which this pointer is relative. The upper
|
||||
// nybble of the encoding specifies this.
|
||||
uint64_t base;
|
||||
switch (encoding & 0xf0) {
|
||||
case lul::DW_EH_PE_absptr: base = 0; break;
|
||||
case lul::DW_EH_PE_pcrel: base = bases.cfi + Size(); break;
|
||||
case lul::DW_EH_PE_textrel: base = bases.text; break;
|
||||
case lul::DW_EH_PE_datarel: base = bases.data; break;
|
||||
case lul::DW_EH_PE_funcrel: base = fde_start_address_; break;
|
||||
case lul::DW_EH_PE_aligned: base = 0; break;
|
||||
default: abort();
|
||||
};
|
||||
|
||||
// Make ADDRESS relative. Yes, this is appropriate even for "absptr"
|
||||
// values; see gcc/unwind-pe.h.
|
||||
address -= base;
|
||||
|
||||
// Align the pointer, if required.
|
||||
if ((encoding & 0xf0) == lul::DW_EH_PE_aligned)
|
||||
Align(AddressSize());
|
||||
|
||||
// Append ADDRESS to this section in the appropriate form. For the
|
||||
// fixed-width forms, we don't need to differentiate between signed and
|
||||
// unsigned encodings, because ADDRESS has already been extended to 64
|
||||
// bits before it was passed to us.
|
||||
switch (encoding & 0x0f) {
|
||||
case lul::DW_EH_PE_absptr:
|
||||
Address(address);
|
||||
break;
|
||||
|
||||
case lul::DW_EH_PE_uleb128:
|
||||
ULEB128(address);
|
||||
break;
|
||||
|
||||
case lul::DW_EH_PE_sleb128:
|
||||
LEB128(address);
|
||||
break;
|
||||
|
||||
case lul::DW_EH_PE_udata2:
|
||||
case lul::DW_EH_PE_sdata2:
|
||||
D16(address);
|
||||
break;
|
||||
|
||||
case lul::DW_EH_PE_udata4:
|
||||
case lul::DW_EH_PE_sdata4:
|
||||
D32(address);
|
||||
break;
|
||||
|
||||
case lul::DW_EH_PE_udata8:
|
||||
case lul::DW_EH_PE_sdata8:
|
||||
D64(address);
|
||||
break;
|
||||
|
||||
default:
|
||||
abort();
|
||||
}
|
||||
|
||||
return *this;
|
||||
};
|
||||
|
||||
} // namespace lul_test
|
||||
666
tools/profiler/tests/gtest/LulTestInfrastructure.h
Normal file
666
tools/profiler/tests/gtest/LulTestInfrastructure.h
Normal file
|
|
@ -0,0 +1,666 @@
|
|||
// -*- mode: C++ -*-
|
||||
|
||||
// Copyright (c) 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.
|
||||
|
||||
// Original author: Jim Blandy <jimb@mozilla.com> <jimb@red-bean.com>
|
||||
|
||||
// Derived from:
|
||||
// cfi_assembler.h: Define CFISection, a class for creating properly
|
||||
// (and improperly) formatted DWARF CFI data for unit tests.
|
||||
|
||||
// Derived from:
|
||||
// test-assembler.h: interface to class for building complex binary streams.
|
||||
|
||||
// To test the Breakpad symbol dumper and processor thoroughly, for
|
||||
// all combinations of host system and minidump processor
|
||||
// architecture, we need to be able to easily generate complex test
|
||||
// data like debugging information and minidump files.
|
||||
//
|
||||
// For example, if we want our unit tests to provide full code
|
||||
// coverage for stack walking, it may be difficult to persuade the
|
||||
// compiler to generate every possible sort of stack walking
|
||||
// information that we want to support; there are probably DWARF CFI
|
||||
// opcodes that GCC never emits. Similarly, if we want to test our
|
||||
// error handling, we will need to generate damaged minidumps or
|
||||
// debugging information that (we hope) the client or compiler will
|
||||
// never produce on its own.
|
||||
//
|
||||
// google_breakpad::TestAssembler provides a predictable and
|
||||
// (relatively) simple way to generate complex formatted data streams
|
||||
// like minidumps and CFI. Furthermore, because TestAssembler is
|
||||
// portable, developers without access to (say) Visual Studio or a
|
||||
// SPARC assembler can still work on test data for those targets.
|
||||
|
||||
#ifndef LUL_TEST_INFRASTRUCTURE_H
|
||||
#define LUL_TEST_INFRASTRUCTURE_H
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
using std::string;
|
||||
using std::vector;
|
||||
|
||||
namespace lul_test {
|
||||
namespace test_assembler {
|
||||
|
||||
// A Label represents a value not yet known that we need to store in a
|
||||
// section. As long as all the labels a section refers to are defined
|
||||
// by the time we retrieve its contents as bytes, we can use undefined
|
||||
// labels freely in that section's construction.
|
||||
//
|
||||
// A label can be in one of three states:
|
||||
// - undefined,
|
||||
// - defined as the sum of some other label and a constant, or
|
||||
// - a constant.
|
||||
//
|
||||
// A label's value never changes, but it can accumulate constraints.
|
||||
// Adding labels and integers is permitted, and yields a label.
|
||||
// Subtracting a constant from a label is permitted, and also yields a
|
||||
// label. Subtracting two labels that have some relationship to each
|
||||
// other is permitted, and yields a constant.
|
||||
//
|
||||
// For example:
|
||||
//
|
||||
// Label a; // a's value is undefined
|
||||
// Label b; // b's value is undefined
|
||||
// {
|
||||
// Label c = a + 4; // okay, even though a's value is unknown
|
||||
// b = c + 4; // also okay; b is now a+8
|
||||
// }
|
||||
// Label d = b - 2; // okay; d == a+6, even though c is gone
|
||||
// d.Value(); // error: d's value is not yet known
|
||||
// d - a; // is 6, even though their values are not known
|
||||
// a = 12; // now b == 20, and d == 18
|
||||
// d.Value(); // 18: no longer an error
|
||||
// b.Value(); // 20
|
||||
// d = 10; // error: d is already defined.
|
||||
//
|
||||
// Label objects' lifetimes are unconstrained: notice that, in the
|
||||
// above example, even though a and b are only related through c, and
|
||||
// c goes out of scope, the assignment to a sets b's value as well. In
|
||||
// particular, it's not necessary to ensure that a Label lives beyond
|
||||
// Sections that refer to it.
|
||||
class Label {
|
||||
public:
|
||||
Label(); // An undefined label.
|
||||
explicit Label(uint64_t value); // A label with a fixed value
|
||||
Label(const Label &value); // A label equal to another.
|
||||
~Label();
|
||||
|
||||
Label &operator=(uint64_t value);
|
||||
Label &operator=(const Label &value);
|
||||
Label operator+(uint64_t addend) const;
|
||||
Label operator-(uint64_t subtrahend) const;
|
||||
uint64_t operator-(const Label &subtrahend) const;
|
||||
|
||||
// We could also provide == and != that work on undefined, but
|
||||
// related, labels.
|
||||
|
||||
// Return true if this label's value is known. If VALUE_P is given,
|
||||
// set *VALUE_P to the known value if returning true.
|
||||
bool IsKnownConstant(uint64_t *value_p = NULL) const;
|
||||
|
||||
// Return true if the offset from LABEL to this label is known. If
|
||||
// OFFSET_P is given, set *OFFSET_P to the offset when returning true.
|
||||
//
|
||||
// You can think of l.KnownOffsetFrom(m, &d) as being like 'd = l-m',
|
||||
// except that it also returns a value indicating whether the
|
||||
// subtraction is possible given what we currently know of l and m.
|
||||
// It can be possible even if we don't know l and m's values. For
|
||||
// example:
|
||||
//
|
||||
// Label l, m;
|
||||
// m = l + 10;
|
||||
// l.IsKnownConstant(); // false
|
||||
// m.IsKnownConstant(); // false
|
||||
// uint64_t d;
|
||||
// l.IsKnownOffsetFrom(m, &d); // true, and sets d to -10.
|
||||
// l-m // -10
|
||||
// m-l // 10
|
||||
// m.Value() // error: m's value is not known
|
||||
bool IsKnownOffsetFrom(const Label &label, uint64_t *offset_p = NULL) const;
|
||||
|
||||
private:
|
||||
// A label's value, or if that is not yet known, how the value is
|
||||
// related to other labels' values. A binding may be:
|
||||
// - a known constant,
|
||||
// - constrained to be equal to some other binding plus a constant, or
|
||||
// - unconstrained, and free to take on any value.
|
||||
//
|
||||
// Many labels may point to a single binding, and each binding may
|
||||
// refer to another, so bindings and labels form trees whose leaves
|
||||
// are labels, whose interior nodes (and roots) are bindings, and
|
||||
// where links point from children to parents. Bindings are
|
||||
// reference counted, allowing labels to be lightweight, copyable,
|
||||
// assignable, placed in containers, and so on.
|
||||
class Binding {
|
||||
public:
|
||||
Binding();
|
||||
explicit Binding(uint64_t addend);
|
||||
~Binding();
|
||||
|
||||
// Increment our reference count.
|
||||
void Acquire() { reference_count_++; };
|
||||
// Decrement our reference count, and return true if it is zero.
|
||||
bool Release() { return --reference_count_ == 0; }
|
||||
|
||||
// Set this binding to be equal to BINDING + ADDEND. If BINDING is
|
||||
// NULL, then set this binding to the known constant ADDEND.
|
||||
// Update every binding on this binding's chain to point directly
|
||||
// to BINDING, or to be a constant, with addends adjusted
|
||||
// appropriately.
|
||||
void Set(Binding *binding, uint64_t value);
|
||||
|
||||
// Return what we know about the value of this binding.
|
||||
// - If this binding's value is a known constant, set BASE to
|
||||
// NULL, and set ADDEND to its value.
|
||||
// - If this binding is not a known constant but related to other
|
||||
// bindings, set BASE to the binding at the end of the relation
|
||||
// chain (which will always be unconstrained), and set ADDEND to the
|
||||
// value to add to that binding's value to get this binding's
|
||||
// value.
|
||||
// - If this binding is unconstrained, set BASE to this, and leave
|
||||
// ADDEND unchanged.
|
||||
void Get(Binding **base, uint64_t *addend);
|
||||
|
||||
private:
|
||||
// There are three cases:
|
||||
//
|
||||
// - A binding representing a known constant value has base_ NULL,
|
||||
// and addend_ equal to the value.
|
||||
//
|
||||
// - A binding representing a completely unconstrained value has
|
||||
// base_ pointing to this; addend_ is unused.
|
||||
//
|
||||
// - A binding whose value is related to some other binding's
|
||||
// value has base_ pointing to that other binding, and addend_
|
||||
// set to the amount to add to that binding's value to get this
|
||||
// binding's value. We only represent relationships of the form
|
||||
// x = y+c.
|
||||
//
|
||||
// Thus, the bind_ links form a chain terminating in either a
|
||||
// known constant value or a completely unconstrained value. Most
|
||||
// operations on bindings do path compression: they change every
|
||||
// binding on the chain to point directly to the final value,
|
||||
// adjusting addends as appropriate.
|
||||
Binding *base_;
|
||||
uint64_t addend_;
|
||||
|
||||
// The number of Labels and Bindings pointing to this binding.
|
||||
// (When a binding points to itself, indicating a completely
|
||||
// unconstrained binding, that doesn't count as a reference.)
|
||||
int reference_count_;
|
||||
};
|
||||
|
||||
// This label's value.
|
||||
Binding *value_;
|
||||
};
|
||||
|
||||
// Conventions for representing larger numbers as sequences of bytes.
|
||||
enum Endianness {
|
||||
kBigEndian, // Big-endian: the most significant byte comes first.
|
||||
kLittleEndian, // Little-endian: the least significant byte comes first.
|
||||
kUnsetEndian, // used internally
|
||||
};
|
||||
|
||||
// A section is a sequence of bytes, constructed by appending bytes
|
||||
// to the end. Sections have a convenient and flexible set of member
|
||||
// functions for appending data in various formats: big-endian and
|
||||
// little-endian signed and unsigned values of different sizes;
|
||||
// LEB128 and ULEB128 values (see below), and raw blocks of bytes.
|
||||
//
|
||||
// If you need to append a value to a section that is not convenient
|
||||
// to compute immediately, you can create a label, append the
|
||||
// label's value to the section, and then set the label's value
|
||||
// later, when it's convenient to do so. Once a label's value is
|
||||
// known, the section class takes care of updating all previously
|
||||
// appended references to it.
|
||||
//
|
||||
// Once all the labels to which a section refers have had their
|
||||
// values determined, you can get a copy of the section's contents
|
||||
// as a string.
|
||||
//
|
||||
// Note that there is no specified "start of section" label. This is
|
||||
// because there are typically several different meanings for "the
|
||||
// start of a section": the offset of the section within an object
|
||||
// file, the address in memory at which the section's content appear,
|
||||
// and so on. It's up to the code that uses the Section class to
|
||||
// keep track of these explicitly, as they depend on the application.
|
||||
class Section {
|
||||
public:
|
||||
explicit Section(Endianness endianness = kUnsetEndian)
|
||||
: endianness_(endianness) { };
|
||||
|
||||
// A base class destructor should be either public and virtual,
|
||||
// or protected and nonvirtual.
|
||||
virtual ~Section() { };
|
||||
|
||||
// Return the default endianness of this section.
|
||||
Endianness endianness() const { return endianness_; }
|
||||
|
||||
// Append the SIZE bytes at DATA to the end of this section. Return
|
||||
// a reference to this section.
|
||||
Section &Append(const string &data) {
|
||||
contents_.append(data);
|
||||
return *this;
|
||||
};
|
||||
|
||||
// Append SIZE copies of BYTE to the end of this section. Return a
|
||||
// reference to this section.
|
||||
Section &Append(size_t size, uint8_t byte) {
|
||||
contents_.append(size, (char) byte);
|
||||
return *this;
|
||||
}
|
||||
|
||||
// Append NUMBER to this section. ENDIANNESS is the endianness to
|
||||
// use to write the number. SIZE is the length of the number in
|
||||
// bytes. Return a reference to this section.
|
||||
Section &Append(Endianness endianness, size_t size, uint64_t number);
|
||||
Section &Append(Endianness endianness, size_t size, const Label &label);
|
||||
|
||||
// Append SECTION to the end of this section. The labels SECTION
|
||||
// refers to need not be defined yet.
|
||||
//
|
||||
// Note that this has no effect on any Labels' values, or on
|
||||
// SECTION. If placing SECTION within 'this' provides new
|
||||
// constraints on existing labels' values, then it's up to the
|
||||
// caller to fiddle with those labels as needed.
|
||||
Section &Append(const Section §ion);
|
||||
|
||||
// Append the contents of DATA as a series of bytes terminated by
|
||||
// a NULL character.
|
||||
Section &AppendCString(const string &data) {
|
||||
Append(data);
|
||||
contents_ += '\0';
|
||||
return *this;
|
||||
}
|
||||
|
||||
// Append VALUE or LABEL to this section, with the given bit width and
|
||||
// endianness. Return a reference to this section.
|
||||
//
|
||||
// The names of these functions have the form <ENDIANNESS><BITWIDTH>:
|
||||
// <ENDIANNESS> is either 'L' (little-endian, least significant byte first),
|
||||
// 'B' (big-endian, most significant byte first), or
|
||||
// 'D' (default, the section's default endianness)
|
||||
// <BITWIDTH> is 8, 16, 32, or 64.
|
||||
//
|
||||
// Since endianness doesn't matter for a single byte, all the
|
||||
// <BITWIDTH>=8 functions are equivalent.
|
||||
//
|
||||
// These can be used to write both signed and unsigned values, as
|
||||
// the compiler will properly sign-extend a signed value before
|
||||
// passing it to the function, at which point the function's
|
||||
// behavior is the same either way.
|
||||
Section &L8(uint8_t value) { contents_ += value; return *this; }
|
||||
Section &B8(uint8_t value) { contents_ += value; return *this; }
|
||||
Section &D8(uint8_t value) { contents_ += value; return *this; }
|
||||
Section &L16(uint16_t), &L32(uint32_t), &L64(uint64_t),
|
||||
&B16(uint16_t), &B32(uint32_t), &B64(uint64_t),
|
||||
&D16(uint16_t), &D32(uint32_t), &D64(uint64_t);
|
||||
Section &L8(const Label &label), &L16(const Label &label),
|
||||
&L32(const Label &label), &L64(const Label &label),
|
||||
&B8(const Label &label), &B16(const Label &label),
|
||||
&B32(const Label &label), &B64(const Label &label),
|
||||
&D8(const Label &label), &D16(const Label &label),
|
||||
&D32(const Label &label), &D64(const Label &label);
|
||||
|
||||
// Append VALUE in a signed LEB128 (Little-Endian Base 128) form.
|
||||
//
|
||||
// The signed LEB128 representation of an integer N is a variable
|
||||
// number of bytes:
|
||||
//
|
||||
// - If N is between -0x40 and 0x3f, then its signed LEB128
|
||||
// representation is a single byte whose value is N.
|
||||
//
|
||||
// - Otherwise, its signed LEB128 representation is (N & 0x7f) |
|
||||
// 0x80, followed by the signed LEB128 representation of N / 128,
|
||||
// rounded towards negative infinity.
|
||||
//
|
||||
// In other words, we break VALUE into groups of seven bits, put
|
||||
// them in little-endian order, and then write them as eight-bit
|
||||
// bytes with the high bit on all but the last.
|
||||
//
|
||||
// Note that VALUE cannot be a Label (we would have to implement
|
||||
// relaxation).
|
||||
Section &LEB128(long long value);
|
||||
|
||||
// Append VALUE in unsigned LEB128 (Little-Endian Base 128) form.
|
||||
//
|
||||
// The unsigned LEB128 representation of an integer N is a variable
|
||||
// number of bytes:
|
||||
//
|
||||
// - If N is between 0 and 0x7f, then its unsigned LEB128
|
||||
// representation is a single byte whose value is N.
|
||||
//
|
||||
// - Otherwise, its unsigned LEB128 representation is (N & 0x7f) |
|
||||
// 0x80, followed by the unsigned LEB128 representation of N /
|
||||
// 128, rounded towards negative infinity.
|
||||
//
|
||||
// Note that VALUE cannot be a Label (we would have to implement
|
||||
// relaxation).
|
||||
Section &ULEB128(uint64_t value);
|
||||
|
||||
// Jump to the next location aligned on an ALIGNMENT-byte boundary,
|
||||
// relative to the start of the section. Fill the gap with PAD_BYTE.
|
||||
// ALIGNMENT must be a power of two. Return a reference to this
|
||||
// section.
|
||||
Section &Align(size_t alignment, uint8_t pad_byte = 0);
|
||||
|
||||
// Return the current size of the section.
|
||||
size_t Size() const { return contents_.size(); }
|
||||
|
||||
// Return a label representing the start of the section.
|
||||
//
|
||||
// It is up to the user whether this label represents the section's
|
||||
// position in an object file, the section's address in memory, or
|
||||
// what have you; some applications may need both, in which case
|
||||
// this simple-minded interface won't be enough. This class only
|
||||
// provides a single start label, for use with the Here and Mark
|
||||
// member functions.
|
||||
//
|
||||
// Ideally, we'd provide this in a subclass that actually knows more
|
||||
// about the application at hand and can provide an appropriate
|
||||
// collection of start labels. But then the appending member
|
||||
// functions like Append and D32 would return a reference to the
|
||||
// base class, not the derived class, and the chaining won't work.
|
||||
// Since the only value here is in pretty notation, that's a fatal
|
||||
// flaw.
|
||||
Label start() const { return start_; }
|
||||
|
||||
// Return a label representing the point at which the next Appended
|
||||
// item will appear in the section, relative to start().
|
||||
Label Here() const { return start_ + Size(); }
|
||||
|
||||
// Set *LABEL to Here, and return a reference to this section.
|
||||
Section &Mark(Label *label) { *label = Here(); return *this; }
|
||||
|
||||
// If there are no undefined label references left in this
|
||||
// section, set CONTENTS to the contents of this section, as a
|
||||
// string, and clear this section. Return true on success, or false
|
||||
// if there were still undefined labels.
|
||||
bool GetContents(string *contents);
|
||||
|
||||
private:
|
||||
// Used internally. A reference to a label's value.
|
||||
struct Reference {
|
||||
Reference(size_t set_offset, Endianness set_endianness, size_t set_size,
|
||||
const Label &set_label)
|
||||
: offset(set_offset), endianness(set_endianness), size(set_size),
|
||||
label(set_label) { }
|
||||
|
||||
// The offset of the reference within the section.
|
||||
size_t offset;
|
||||
|
||||
// The endianness of the reference.
|
||||
Endianness endianness;
|
||||
|
||||
// The size of the reference.
|
||||
size_t size;
|
||||
|
||||
// The label to which this is a reference.
|
||||
Label label;
|
||||
};
|
||||
|
||||
// The default endianness of this section.
|
||||
Endianness endianness_;
|
||||
|
||||
// The contents of the section.
|
||||
string contents_;
|
||||
|
||||
// References to labels within those contents.
|
||||
vector<Reference> references_;
|
||||
|
||||
// A label referring to the beginning of the section.
|
||||
Label start_;
|
||||
};
|
||||
|
||||
} // namespace test_assembler
|
||||
} // namespace lul_test
|
||||
|
||||
|
||||
namespace lul_test {
|
||||
|
||||
using lul::DwarfPointerEncoding;
|
||||
using lul_test::test_assembler::Endianness;
|
||||
using lul_test::test_assembler::Label;
|
||||
using lul_test::test_assembler::Section;
|
||||
|
||||
class CFISection: public Section {
|
||||
public:
|
||||
|
||||
// CFI augmentation strings beginning with 'z', defined by the
|
||||
// Linux/IA-64 C++ ABI, can specify interesting encodings for
|
||||
// addresses appearing in FDE headers and call frame instructions (and
|
||||
// for additional fields whose presence the augmentation string
|
||||
// specifies). In particular, pointers can be specified to be relative
|
||||
// to various base address: the start of the .text section, the
|
||||
// location holding the address itself, and so on. These allow the
|
||||
// frame data to be position-independent even when they live in
|
||||
// write-protected pages. These variants are specified at the
|
||||
// following two URLs:
|
||||
//
|
||||
// http://refspecs.linux-foundation.org/LSB_4.0.0/LSB-Core-generic/LSB-Core-generic/dwarfext.html
|
||||
// http://refspecs.linux-foundation.org/LSB_4.0.0/LSB-Core-generic/LSB-Core-generic/ehframechpt.html
|
||||
//
|
||||
// CFISection leaves the production of well-formed 'z'-augmented CIEs and
|
||||
// FDEs to the user, but does provide EncodedPointer, to emit
|
||||
// properly-encoded addresses for a given pointer encoding.
|
||||
// EncodedPointer uses an instance of this structure to find the base
|
||||
// addresses it should use; you can establish a default for all encoded
|
||||
// pointers appended to this section with SetEncodedPointerBases.
|
||||
struct EncodedPointerBases {
|
||||
EncodedPointerBases() : cfi(), text(), data() { }
|
||||
|
||||
// The starting address of this CFI section in memory, for
|
||||
// DW_EH_PE_pcrel. DW_EH_PE_pcrel pointers may only be used in data
|
||||
// that has is loaded into the program's address space.
|
||||
uint64_t cfi;
|
||||
|
||||
// The starting address of this file's .text section, for DW_EH_PE_textrel.
|
||||
uint64_t text;
|
||||
|
||||
// The starting address of this file's .got or .eh_frame_hdr section,
|
||||
// for DW_EH_PE_datarel.
|
||||
uint64_t data;
|
||||
};
|
||||
|
||||
// Create a CFISection whose endianness is ENDIANNESS, and where
|
||||
// machine addresses are ADDRESS_SIZE bytes long. If EH_FRAME is
|
||||
// true, use the .eh_frame format, as described by the Linux
|
||||
// Standards Base Core Specification, instead of the DWARF CFI
|
||||
// format.
|
||||
CFISection(Endianness endianness, size_t address_size,
|
||||
bool eh_frame = false)
|
||||
: Section(endianness), address_size_(address_size), eh_frame_(eh_frame),
|
||||
pointer_encoding_(lul::DW_EH_PE_absptr),
|
||||
encoded_pointer_bases_(), entry_length_(NULL), in_fde_(false) {
|
||||
// The 'start', 'Here', and 'Mark' members of a CFISection all refer
|
||||
// to section offsets.
|
||||
start() = 0;
|
||||
}
|
||||
|
||||
// Return this CFISection's address size.
|
||||
size_t AddressSize() const { return address_size_; }
|
||||
|
||||
// Return true if this CFISection uses the .eh_frame format, or
|
||||
// false if it contains ordinary DWARF CFI data.
|
||||
bool ContainsEHFrame() const { return eh_frame_; }
|
||||
|
||||
// Use ENCODING for pointers in calls to FDEHeader and EncodedPointer.
|
||||
void SetPointerEncoding(DwarfPointerEncoding encoding) {
|
||||
pointer_encoding_ = encoding;
|
||||
}
|
||||
|
||||
// Use the addresses in BASES as the base addresses for encoded
|
||||
// pointers in subsequent calls to FDEHeader or EncodedPointer.
|
||||
// This function makes a copy of BASES.
|
||||
void SetEncodedPointerBases(const EncodedPointerBases &bases) {
|
||||
encoded_pointer_bases_ = bases;
|
||||
}
|
||||
|
||||
// Append a Common Information Entry header to this section with the
|
||||
// given values. If dwarf64 is true, use the 64-bit DWARF initial
|
||||
// length format for the CIE's initial length. Return a reference to
|
||||
// this section. You should call FinishEntry after writing the last
|
||||
// instruction for the CIE.
|
||||
//
|
||||
// Before calling this function, you will typically want to use Mark
|
||||
// or Here to make a label to pass to FDEHeader that refers to this
|
||||
// CIE's position in the section.
|
||||
CFISection &CIEHeader(uint64_t code_alignment_factor,
|
||||
int data_alignment_factor,
|
||||
unsigned return_address_register,
|
||||
uint8_t version = 3,
|
||||
const string &augmentation = "",
|
||||
bool dwarf64 = false);
|
||||
|
||||
// Append a Frame Description Entry header to this section with the
|
||||
// given values. If dwarf64 is true, use the 64-bit DWARF initial
|
||||
// length format for the CIE's initial length. Return a reference to
|
||||
// this section. You should call FinishEntry after writing the last
|
||||
// instruction for the CIE.
|
||||
//
|
||||
// This function doesn't support entries that are longer than
|
||||
// 0xffffff00 bytes. (The "initial length" is always a 32-bit
|
||||
// value.) Nor does it support .debug_frame sections longer than
|
||||
// 0xffffff00 bytes.
|
||||
CFISection &FDEHeader(Label cie_pointer,
|
||||
uint64_t initial_location,
|
||||
uint64_t address_range,
|
||||
bool dwarf64 = false);
|
||||
|
||||
// Note the current position as the end of the last CIE or FDE we
|
||||
// started, after padding with DW_CFA_nops for alignment. This
|
||||
// defines the label representing the entry's length, cited in the
|
||||
// entry's header. Return a reference to this section.
|
||||
CFISection &FinishEntry();
|
||||
|
||||
// Append the contents of BLOCK as a DW_FORM_block value: an
|
||||
// unsigned LEB128 length, followed by that many bytes of data.
|
||||
CFISection &Block(const string &block) {
|
||||
ULEB128(block.size());
|
||||
Append(block);
|
||||
return *this;
|
||||
}
|
||||
|
||||
// Append ADDRESS to this section, in the appropriate size and
|
||||
// endianness. Return a reference to this section.
|
||||
CFISection &Address(uint64_t address) {
|
||||
Section::Append(endianness(), address_size_, address);
|
||||
return *this;
|
||||
}
|
||||
|
||||
// Append ADDRESS to this section, using ENCODING and BASES. ENCODING
|
||||
// defaults to this section's default encoding, established by
|
||||
// SetPointerEncoding. BASES defaults to this section's bases, set by
|
||||
// SetEncodedPointerBases. If the DW_EH_PE_indirect bit is set in the
|
||||
// encoding, assume that ADDRESS is where the true address is stored.
|
||||
// Return a reference to this section.
|
||||
//
|
||||
// (C++ doesn't let me use default arguments here, because I want to
|
||||
// refer to members of *this in the default argument expression.)
|
||||
CFISection &EncodedPointer(uint64_t address) {
|
||||
return EncodedPointer(address, pointer_encoding_, encoded_pointer_bases_);
|
||||
}
|
||||
CFISection &EncodedPointer(uint64_t address, DwarfPointerEncoding encoding) {
|
||||
return EncodedPointer(address, encoding, encoded_pointer_bases_);
|
||||
}
|
||||
CFISection &EncodedPointer(uint64_t address, DwarfPointerEncoding encoding,
|
||||
const EncodedPointerBases &bases);
|
||||
|
||||
// Restate some member functions, to keep chaining working nicely.
|
||||
CFISection &Mark(Label *label) { Section::Mark(label); return *this; }
|
||||
CFISection &D8(uint8_t v) { Section::D8(v); return *this; }
|
||||
CFISection &D16(uint16_t v) { Section::D16(v); return *this; }
|
||||
CFISection &D16(Label v) { Section::D16(v); return *this; }
|
||||
CFISection &D32(uint32_t v) { Section::D32(v); return *this; }
|
||||
CFISection &D32(const Label &v) { Section::D32(v); return *this; }
|
||||
CFISection &D64(uint64_t v) { Section::D64(v); return *this; }
|
||||
CFISection &D64(const Label &v) { Section::D64(v); return *this; }
|
||||
CFISection &LEB128(long long v) { Section::LEB128(v); return *this; }
|
||||
CFISection &ULEB128(uint64_t v) { Section::ULEB128(v); return *this; }
|
||||
|
||||
private:
|
||||
// A length value that we've appended to the section, but is not yet
|
||||
// known. LENGTH is the appended value; START is a label referring
|
||||
// to the start of the data whose length was cited.
|
||||
struct PendingLength {
|
||||
Label length;
|
||||
Label start;
|
||||
};
|
||||
|
||||
// Constants used in CFI/.eh_frame data:
|
||||
|
||||
// If the first four bytes of an "initial length" are this constant, then
|
||||
// the data uses the 64-bit DWARF format, and the length itself is the
|
||||
// subsequent eight bytes.
|
||||
static const uint32_t kDwarf64InitialLengthMarker = 0xffffffffU;
|
||||
|
||||
// The CIE identifier for 32- and 64-bit DWARF CFI and .eh_frame data.
|
||||
static const uint32_t kDwarf32CIEIdentifier = ~(uint32_t)0;
|
||||
static const uint64_t kDwarf64CIEIdentifier = ~(uint64_t)0;
|
||||
static const uint32_t kEHFrame32CIEIdentifier = 0;
|
||||
static const uint64_t kEHFrame64CIEIdentifier = 0;
|
||||
|
||||
// The size of a machine address for the data in this section.
|
||||
size_t address_size_;
|
||||
|
||||
// If true, we are generating a Linux .eh_frame section, instead of
|
||||
// a standard DWARF .debug_frame section.
|
||||
bool eh_frame_;
|
||||
|
||||
// The encoding to use for FDE pointers.
|
||||
DwarfPointerEncoding pointer_encoding_;
|
||||
|
||||
// The base addresses to use when emitting encoded pointers.
|
||||
EncodedPointerBases encoded_pointer_bases_;
|
||||
|
||||
// The length value for the current entry.
|
||||
//
|
||||
// Oddly, this must be dynamically allocated. Labels never get new
|
||||
// values; they only acquire constraints on the value they already
|
||||
// have, or assert if you assign them something incompatible. So
|
||||
// each header needs truly fresh Label objects to cite in their
|
||||
// headers and track their positions. The alternative is explicit
|
||||
// destructor invocation and a placement new. Ick.
|
||||
PendingLength *entry_length_;
|
||||
|
||||
// True if we are currently emitting an FDE --- that is, we have
|
||||
// called FDEHeader but have not yet called FinishEntry.
|
||||
bool in_fde_;
|
||||
|
||||
// If in_fde_ is true, this is its starting address. We use this for
|
||||
// emitting DW_EH_PE_funcrel pointers.
|
||||
uint64_t fde_start_address_;
|
||||
};
|
||||
|
||||
} // namespace lul_test
|
||||
|
||||
#endif // LUL_TEST_INFRASTRUCTURE_H
|
||||
75
tools/profiler/tests/gtest/ThreadProfileTest.cpp
Normal file
75
tools/profiler/tests/gtest/ThreadProfileTest.cpp
Normal file
|
|
@ -0,0 +1,75 @@
|
|||
/* -*- 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 "gtest/gtest.h"
|
||||
|
||||
#include "ProfileEntry.h"
|
||||
#include "ThreadProfile.h"
|
||||
|
||||
// Make sure we can initialize our ThreadProfile
|
||||
TEST(ThreadProfile, Initialization) {
|
||||
PseudoStack* stack = PseudoStack::create();
|
||||
Thread::tid_t tid = 1000;
|
||||
ThreadInfo info("testThread", tid, true, stack, nullptr);
|
||||
RefPtr<ProfileBuffer> pb = new ProfileBuffer(10);
|
||||
ThreadProfile tp(&info, pb);
|
||||
}
|
||||
|
||||
// Make sure we can record one tag and read it
|
||||
TEST(ThreadProfile, InsertOneTag) {
|
||||
PseudoStack* stack = PseudoStack::create();
|
||||
Thread::tid_t tid = 1000;
|
||||
ThreadInfo info("testThread", tid, true, stack, nullptr);
|
||||
RefPtr<ProfileBuffer> pb = new ProfileBuffer(10);
|
||||
pb->addTag(ProfileEntry('t', 123.1));
|
||||
ASSERT_TRUE(pb->mEntries != nullptr);
|
||||
ASSERT_TRUE(pb->mEntries[pb->mReadPos].mTagName == 't');
|
||||
ASSERT_TRUE(pb->mEntries[pb->mReadPos].mTagDouble == 123.1);
|
||||
}
|
||||
|
||||
// See if we can insert some tags
|
||||
TEST(ThreadProfile, InsertTagsNoWrap) {
|
||||
PseudoStack* stack = PseudoStack::create();
|
||||
Thread::tid_t tid = 1000;
|
||||
ThreadInfo info("testThread", tid, true, stack, nullptr);
|
||||
RefPtr<ProfileBuffer> pb = new ProfileBuffer(100);
|
||||
int test_size = 50;
|
||||
for (int i = 0; i < test_size; i++) {
|
||||
pb->addTag(ProfileEntry('t', i));
|
||||
}
|
||||
ASSERT_TRUE(pb->mEntries != nullptr);
|
||||
int readPos = pb->mReadPos;
|
||||
while (readPos != pb->mWritePos) {
|
||||
ASSERT_TRUE(pb->mEntries[readPos].mTagName == 't');
|
||||
ASSERT_TRUE(pb->mEntries[readPos].mTagInt == readPos);
|
||||
readPos = (readPos + 1) % pb->mEntrySize;
|
||||
}
|
||||
}
|
||||
|
||||
// See if wrapping works as it should in the basic case
|
||||
TEST(ThreadProfile, InsertTagsWrap) {
|
||||
PseudoStack* stack = PseudoStack::create();
|
||||
Thread::tid_t tid = 1000;
|
||||
// we can fit only 24 tags in this buffer because of the empty slot
|
||||
int tags = 24;
|
||||
int buffer_size = tags + 1;
|
||||
ThreadInfo info("testThread", tid, true, stack, nullptr);
|
||||
RefPtr<ProfileBuffer> pb = new ProfileBuffer(buffer_size);
|
||||
int test_size = 43;
|
||||
for (int i = 0; i < test_size; i++) {
|
||||
pb->addTag(ProfileEntry('t', i));
|
||||
}
|
||||
ASSERT_TRUE(pb->mEntries != nullptr);
|
||||
int readPos = pb->mReadPos;
|
||||
int ctr = 0;
|
||||
while (readPos != pb->mWritePos) {
|
||||
ASSERT_TRUE(pb->mEntries[readPos].mTagName == 't');
|
||||
// the first few tags were discarded when we wrapped
|
||||
ASSERT_TRUE(pb->mEntries[readPos].mTagInt == ctr + (test_size - tags));
|
||||
ctr++;
|
||||
readPos = (readPos + 1) % pb->mEntrySize;
|
||||
}
|
||||
}
|
||||
|
||||
30
tools/profiler/tests/gtest/moz.build
Normal file
30
tools/profiler/tests/gtest/moz.build
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
|
||||
# vim: set filetype=python:
|
||||
# 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/.
|
||||
|
||||
if CONFIG['OS_TARGET'] in ('Android', 'Linux'):
|
||||
UNIFIED_SOURCES += [
|
||||
'LulTestDwarf.cpp',
|
||||
'LulTestInfrastructure.cpp',
|
||||
]
|
||||
if CONFIG['CPU_ARCH'] != 'x86':
|
||||
UNIFIED_SOURCES += [
|
||||
'LulTest.cpp',
|
||||
]
|
||||
|
||||
LOCAL_INCLUDES += [
|
||||
'/tools/profiler/core',
|
||||
'/tools/profiler/gecko',
|
||||
'/tools/profiler/lul',
|
||||
]
|
||||
|
||||
UNIFIED_SOURCES += [
|
||||
'ThreadProfileTest.cpp',
|
||||
]
|
||||
|
||||
FINAL_LIBRARY = 'xul-gtest'
|
||||
|
||||
if CONFIG['GNU_CXX']:
|
||||
CXXFLAGS += ['-Wno-error=shadow']
|
||||
31
tools/profiler/tests/head_profiler.js
Normal file
31
tools/profiler/tests/head_profiler.js
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
/* 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/. */
|
||||
|
||||
const Cc = Components.classes;
|
||||
const Ci = Components.interfaces;
|
||||
const Cu = Components.utils;
|
||||
|
||||
function getInflatedStackLocations(thread, sample) {
|
||||
let stackTable = thread.stackTable;
|
||||
let frameTable = thread.frameTable;
|
||||
let stringTable = thread.stringTable;
|
||||
let SAMPLE_STACK_SLOT = thread.samples.schema.stack;
|
||||
let STACK_PREFIX_SLOT = stackTable.schema.prefix;
|
||||
let STACK_FRAME_SLOT = stackTable.schema.frame;
|
||||
let FRAME_LOCATION_SLOT = frameTable.schema.location;
|
||||
|
||||
// Build the stack from the raw data and accumulate the locations in
|
||||
// an array.
|
||||
let stackIndex = sample[SAMPLE_STACK_SLOT];
|
||||
let locations = [];
|
||||
while (stackIndex !== null) {
|
||||
let stackEntry = stackTable.data[stackIndex];
|
||||
let frame = frameTable.data[stackEntry[STACK_FRAME_SLOT]];
|
||||
locations.push(stringTable[frame[FRAME_LOCATION_SLOT]]);
|
||||
stackIndex = stackEntry[STACK_PREFIX_SLOT];
|
||||
}
|
||||
|
||||
// The profiler tree is inverted, so reverse the array.
|
||||
return locations.reverse();
|
||||
}
|
||||
79
tools/profiler/tests/test_asm.js
Normal file
79
tools/profiler/tests/test_asm.js
Normal file
|
|
@ -0,0 +1,79 @@
|
|||
// Check that asm.js code shows up on the stack.
|
||||
function run_test() {
|
||||
let p = Cc["@mozilla.org/tools/profiler;1"];
|
||||
|
||||
// Just skip the test if the profiler component isn't present.
|
||||
if (!p)
|
||||
return;
|
||||
p = p.getService(Ci.nsIProfiler);
|
||||
if (!p)
|
||||
return;
|
||||
|
||||
// This test assumes that it's starting on an empty SPS stack.
|
||||
// (Note that the other profiler tests also assume the profiler
|
||||
// isn't already started.)
|
||||
do_check_true(!p.IsActive());
|
||||
|
||||
let jsFuns = Cu.getJSTestingFunctions();
|
||||
if (!jsFuns.isAsmJSCompilationAvailable())
|
||||
return;
|
||||
|
||||
const ms = 10;
|
||||
p.StartProfiler(10000, ms, ["js"], 1);
|
||||
|
||||
let stack = null;
|
||||
function ffi_function(){
|
||||
var delayMS = 5;
|
||||
while (1) {
|
||||
let then = Date.now();
|
||||
do {} while (Date.now() - then < delayMS);
|
||||
|
||||
var thread0 = p.getProfileData().threads[0];
|
||||
|
||||
if (delayMS > 30000)
|
||||
return;
|
||||
|
||||
delayMS *= 2;
|
||||
|
||||
if (thread0.samples.data.length == 0)
|
||||
continue;
|
||||
|
||||
var lastSample = thread0.samples.data[thread0.samples.data.length - 1];
|
||||
stack = String(getInflatedStackLocations(thread0, lastSample));
|
||||
if (stack.indexOf("trampoline") !== -1)
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
function asmjs_module(global, ffis) {
|
||||
"use asm";
|
||||
var ffi = ffis.ffi;
|
||||
function asmjs_function() {
|
||||
ffi();
|
||||
}
|
||||
return asmjs_function;
|
||||
}
|
||||
|
||||
do_check_true(jsFuns.isAsmJSModule(asmjs_module));
|
||||
|
||||
var asmjs_function = asmjs_module(null, {ffi:ffi_function});
|
||||
do_check_true(jsFuns.isAsmJSFunction(asmjs_function));
|
||||
|
||||
asmjs_function();
|
||||
|
||||
do_check_neq(stack, null);
|
||||
|
||||
var i1 = stack.indexOf("entry trampoline");
|
||||
do_check_true(i1 !== -1);
|
||||
var i2 = stack.indexOf("asmjs_function");
|
||||
do_check_true(i2 !== -1);
|
||||
var i3 = stack.indexOf("FFI trampoline");
|
||||
do_check_true(i3 !== -1);
|
||||
var i4 = stack.indexOf("ffi_function");
|
||||
do_check_true(i4 !== -1);
|
||||
do_check_true(i1 < i2);
|
||||
do_check_true(i2 < i3);
|
||||
do_check_true(i3 < i4);
|
||||
|
||||
p.StopProfiler();
|
||||
}
|
||||
59
tools/profiler/tests/test_enterjit_osr.js
Normal file
59
tools/profiler/tests/test_enterjit_osr.js
Normal file
|
|
@ -0,0 +1,59 @@
|
|||
// Check that the EnterJIT frame, added by the JIT trampoline and
|
||||
// usable by a native unwinder to resume unwinding after encountering
|
||||
// JIT code, is pushed as expected.
|
||||
function run_test() {
|
||||
let p = Cc["@mozilla.org/tools/profiler;1"];
|
||||
// Just skip the test if the profiler component isn't present.
|
||||
if (!p)
|
||||
return;
|
||||
p = p.getService(Ci.nsIProfiler);
|
||||
if (!p)
|
||||
return;
|
||||
|
||||
// This test assumes that it's starting on an empty SPS stack.
|
||||
// (Note that the other profiler tests also assume the profiler
|
||||
// isn't already started.)
|
||||
do_check_true(!p.IsActive());
|
||||
|
||||
const ms = 5;
|
||||
p.StartProfiler(100, ms, ["js"], 1);
|
||||
|
||||
function arbitrary_name(){
|
||||
// A frame for |arbitrary_name| has been pushed. Do a sequence of
|
||||
// increasingly long spins until we get a sample.
|
||||
var delayMS = 5;
|
||||
while (1) {
|
||||
do_print("loop: ms = " + delayMS);
|
||||
let then = Date.now();
|
||||
do {
|
||||
let n = 10000;
|
||||
while (--n); // OSR happens here
|
||||
// Spin in the hope of getting a sample.
|
||||
} while (Date.now() - then < delayMS);
|
||||
let pr = p.getProfileData().threads[0];
|
||||
if (pr.samples.data.length > 0 || delayMS > 30000)
|
||||
return pr;
|
||||
delayMS *= 2;
|
||||
}
|
||||
};
|
||||
|
||||
var profile = arbitrary_name();
|
||||
|
||||
do_check_neq(profile.samples.data.length, 0);
|
||||
var lastSample = profile.samples.data[profile.samples.data.length - 1];
|
||||
var stack = getInflatedStackLocations(profile, lastSample);
|
||||
do_print(stack);
|
||||
|
||||
// All we can really check here is ensure that there is exactly
|
||||
// one arbitrary_name frame in the list.
|
||||
var gotName = false;
|
||||
for (var i = 0; i < stack.length; i++) {
|
||||
if (stack[i].match(/arbitrary_name/)) {
|
||||
do_check_eq(gotName, false);
|
||||
gotName = true;
|
||||
}
|
||||
}
|
||||
do_check_eq(gotName, true);
|
||||
|
||||
p.StopProfiler();
|
||||
}
|
||||
21
tools/profiler/tests/test_enterjit_osr_disabling.js
Normal file
21
tools/profiler/tests/test_enterjit_osr_disabling.js
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
function run_test() {
|
||||
let p = Cc["@mozilla.org/tools/profiler;1"];
|
||||
// Just skip the test if the profiler component isn't present.
|
||||
if (!p)
|
||||
return;
|
||||
p = p.getService(Ci.nsIProfiler);
|
||||
if (!p)
|
||||
return;
|
||||
|
||||
do_check_true(!p.IsActive());
|
||||
|
||||
p.StartProfiler(100, 10, ["js"], 1);
|
||||
// The function is entered with the profiler enabled
|
||||
(function (){
|
||||
p.StopProfiler();
|
||||
let n = 10000;
|
||||
while (--n); // OSR happens here with the profiler disabled.
|
||||
// An assertion will fail when this function returns, if the
|
||||
// SPS stack was misbalanced.
|
||||
})();
|
||||
}
|
||||
21
tools/profiler/tests/test_enterjit_osr_enabling.js
Normal file
21
tools/profiler/tests/test_enterjit_osr_enabling.js
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
function run_test() {
|
||||
let p = Cc["@mozilla.org/tools/profiler;1"];
|
||||
// Just skip the test if the profiler component isn't present.
|
||||
if (!p)
|
||||
return;
|
||||
p = p.getService(Ci.nsIProfiler);
|
||||
if (!p)
|
||||
return;
|
||||
|
||||
do_check_true(!p.IsActive());
|
||||
|
||||
// The function is entered with the profiler disabled.
|
||||
(function (){
|
||||
p.StartProfiler(100, 10, ["js"], 1);
|
||||
let n = 10000;
|
||||
while (--n); // OSR happens here with the profiler enabled.
|
||||
// An assertion will fail when this function returns, if the
|
||||
// SPS stack was misbalanced.
|
||||
})();
|
||||
p.StopProfiler();
|
||||
}
|
||||
18
tools/profiler/tests/test_get_features.js
Normal file
18
tools/profiler/tests/test_get_features.js
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
/* 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/. */
|
||||
|
||||
function run_test() {
|
||||
// If we can't get the profiler component then assume gecko was
|
||||
// built without it and pass all the tests
|
||||
var profilerCc = Cc["@mozilla.org/tools/profiler;1"];
|
||||
if (!profilerCc)
|
||||
return;
|
||||
|
||||
var profiler = Cc["@mozilla.org/tools/profiler;1"].getService(Ci.nsIProfiler);
|
||||
if (!profiler)
|
||||
return;
|
||||
|
||||
var profilerFeatures = profiler.GetFeatures([]);
|
||||
do_check_true(profilerFeatures != null);
|
||||
}
|
||||
35
tools/profiler/tests/test_pause.js
Normal file
35
tools/profiler/tests/test_pause.js
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
/* 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/. */
|
||||
|
||||
function run_test() {
|
||||
// If we can't get the profiler component then assume gecko was
|
||||
// built without it and pass all the tests
|
||||
var profilerCc = Cc["@mozilla.org/tools/profiler;1"];
|
||||
if (!profilerCc)
|
||||
return;
|
||||
|
||||
var profiler = profilerCc.getService(Ci.nsIProfiler);
|
||||
if (!profiler)
|
||||
return;
|
||||
|
||||
do_check_true(!profiler.IsActive());
|
||||
do_check_true(!profiler.IsPaused());
|
||||
|
||||
profiler.StartProfiler(1000, 10, [], 0);
|
||||
|
||||
do_check_true(profiler.IsActive());
|
||||
|
||||
profiler.PauseSampling();
|
||||
|
||||
do_check_true(profiler.IsPaused());
|
||||
|
||||
profiler.ResumeSampling();
|
||||
|
||||
do_check_true(!profiler.IsPaused());
|
||||
|
||||
profiler.StopProfiler();
|
||||
do_check_true(!profiler.IsActive());
|
||||
do_check_true(!profiler.IsPaused());
|
||||
do_test_finished();
|
||||
}
|
||||
44
tools/profiler/tests/test_run.js
Normal file
44
tools/profiler/tests/test_run.js
Normal file
|
|
@ -0,0 +1,44 @@
|
|||
/* 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/. */
|
||||
|
||||
function run_test() {
|
||||
// If we can't get the profiler component then assume gecko was
|
||||
// built without it and pass all the tests
|
||||
var profilerCc = Cc["@mozilla.org/tools/profiler;1"];
|
||||
if (!profilerCc)
|
||||
return;
|
||||
|
||||
var profiler = Cc["@mozilla.org/tools/profiler;1"].getService(Ci.nsIProfiler);
|
||||
if (!profiler)
|
||||
return;
|
||||
|
||||
do_check_true(!profiler.IsActive());
|
||||
|
||||
profiler.StartProfiler(1000, 10, [], 0);
|
||||
|
||||
do_check_true(profiler.IsActive());
|
||||
|
||||
do_test_pending();
|
||||
|
||||
do_timeout(1000, function wait() {
|
||||
// Check text profile format
|
||||
var profileStr = profiler.GetProfile();
|
||||
do_check_true(profileStr.length > 10);
|
||||
|
||||
// check json profile format
|
||||
var profileObj = profiler.getProfileData();
|
||||
do_check_neq(profileObj, null);
|
||||
do_check_neq(profileObj.threads, null);
|
||||
do_check_true(profileObj.threads.length >= 1);
|
||||
do_check_neq(profileObj.threads[0].samples, null);
|
||||
// NOTE: The number of samples will be empty since we
|
||||
// don't have any labels in the xpcshell code
|
||||
|
||||
profiler.StopProfiler();
|
||||
do_check_true(!profiler.IsActive());
|
||||
do_test_finished();
|
||||
});
|
||||
|
||||
|
||||
}
|
||||
23
tools/profiler/tests/test_shared_library.js
Normal file
23
tools/profiler/tests/test_shared_library.js
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
/* 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/. */
|
||||
|
||||
function run_test() {
|
||||
// If we can't get the profiler component then assume gecko was
|
||||
// built without it and pass all the tests
|
||||
var profilerCc = Cc["@mozilla.org/tools/profiler;1"];
|
||||
if (!profilerCc)
|
||||
return;
|
||||
|
||||
var profiler = Cc["@mozilla.org/tools/profiler;1"].getService(Ci.nsIProfiler);
|
||||
if (!profiler)
|
||||
return;
|
||||
|
||||
var sharedStr = profiler.getSharedLibraryInformation();
|
||||
sharedStr = sharedStr.toLowerCase();
|
||||
|
||||
// Let's not hardcode anything too specific
|
||||
// just some sanity checks.
|
||||
do_check_neq(sharedStr, null);
|
||||
do_check_neq(sharedStr, "");
|
||||
}
|
||||
25
tools/profiler/tests/test_start.js
Normal file
25
tools/profiler/tests/test_start.js
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
/* 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/. */
|
||||
|
||||
function run_test() {
|
||||
// If we can't get the profiler component then assume gecko was
|
||||
// built without it and pass all the tests
|
||||
var profilerCc = Cc["@mozilla.org/tools/profiler;1"];
|
||||
if (!profilerCc)
|
||||
return;
|
||||
|
||||
var profiler = Cc["@mozilla.org/tools/profiler;1"].getService(Ci.nsIProfiler);
|
||||
if (!profiler)
|
||||
return;
|
||||
|
||||
do_check_true(!profiler.IsActive());
|
||||
|
||||
profiler.StartProfiler(10, 100, [], 0);
|
||||
|
||||
do_check_true(profiler.IsActive());
|
||||
|
||||
profiler.StopProfiler();
|
||||
|
||||
do_check_true(!profiler.IsActive());
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue