import FIREFOX_52_6_0esr_RELEASE from mozilla-esr52 hg repo

This commit is contained in:
Roy Tam 2018-01-19 03:59:58 +08:00
commit dcd9973243
150858 changed files with 23884658 additions and 0 deletions

View 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

View 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

File diff suppressed because it is too large Load diff

View 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

View 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;
}

View 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

View 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 */

View 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;
}

View 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

View 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
////////////////////////////////////////////////////////////////////////

View 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 */

View 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;
}

View 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

View 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);
}

View 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

View 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
}

View 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

View 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);
}

View 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

View 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
}

View 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

View 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);
}

View 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

View 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");
}

View 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)));
}

View 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
}

File diff suppressed because it is too large Load diff

View 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_ */

View 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;
}

View 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;
}

View 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;
}

View 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_

View 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

View 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 = {
};

View 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);
}

View 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

View 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

View 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;
}

View 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

View 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();
}
}

View 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

View 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);
};

View 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);
};

View 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;
}

View 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_ */

View 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

View 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;

View 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;
}

View 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

View 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)

View 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

View 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

View 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

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View 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

View 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

View 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

View 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 &section) {
if (loaded_sections_.count(section) == 0) {
loaded_sections_.insert(section);
} else {
fprintf(stderr, "Section %s has already been loaded.\n",
section.c_str());
}
}
string debuglink_file() const {
return debuglink_file_;
}
private:
const vector<string>& debug_dirs_; // Directories in which to
// search for the debug ELF file.
string debuglink_file_; // Full path to the debug ELF file.
bool has_loading_addr_; // Indicate if LOADING_ADDR_ is valid.
set<string> loaded_sections_; // Tracks the Loaded ELF sections
// between calls to LoadSymbols().
};
// Find the preferred loading address of the binary.
template<typename ElfClass>
typename ElfClass::Addr GetLoadingAddress(
const typename ElfClass::Phdr* program_headers,
int nheader) {
typedef typename ElfClass::Phdr Phdr;
// For non-PIC executables (e_type == ET_EXEC), the load address is
// the start address of the first PT_LOAD segment. (ELF requires
// the segments to be sorted by load address.) For PIC executables
// and dynamic libraries (e_type == ET_DYN), this address will
// normally be zero.
for (int i = 0; i < nheader; ++i) {
const Phdr& header = program_headers[i];
if (header.p_type == PT_LOAD)
return header.p_vaddr;
}
return 0;
}
template<typename ElfClass>
bool LoadSymbols(const string& obj_file,
const bool big_endian,
const typename ElfClass::Ehdr* elf_header,
const bool read_gnu_debug_link,
LoadSymbolsInfo<ElfClass>* info,
SecMap* smap,
void* rx_avma, size_t rx_size,
UniqueStringUniverse* usu,
void (*log)(const char*)) {
typedef typename ElfClass::Phdr Phdr;
typedef typename ElfClass::Shdr Shdr;
char buf[500];
SprintfLiteral(buf, "LoadSymbols: BEGIN %s\n", obj_file.c_str());
buf[sizeof(buf)-1] = 0;
log(buf);
// This is how the text bias is calculated.
// BEGIN CALCULATE BIAS
uintptr_t loading_addr = GetLoadingAddress<ElfClass>(
GetOffset<ElfClass, Phdr>(elf_header, elf_header->e_phoff),
elf_header->e_phnum);
uintptr_t text_bias = ((uintptr_t)rx_avma) - loading_addr;
SprintfLiteral(buf,
"LoadSymbols: rx_avma=%llx, text_bias=%llx",
(unsigned long long int)(uintptr_t)rx_avma,
(unsigned long long int)text_bias);
buf[sizeof(buf)-1] = 0;
log(buf);
// END CALCULATE BIAS
const Shdr* sections =
GetOffset<ElfClass, Shdr>(elf_header, elf_header->e_shoff);
const Shdr* section_names = sections + elf_header->e_shstrndx;
const char* names =
GetOffset<ElfClass, char>(elf_header, section_names->sh_offset);
const char *names_end = names + section_names->sh_size;
bool found_usable_info = false;
// Dwarf Call Frame Information (CFI) is actually independent from
// the other DWARF debugging information, and can be used alone.
const Shdr* dwarf_cfi_section =
FindElfSectionByName<ElfClass>(".debug_frame", SHT_PROGBITS,
sections, names, names_end,
elf_header->e_shnum);
if (dwarf_cfi_section) {
// Ignore the return value of this function; even without call frame
// information, the other debugging information could be perfectly
// useful.
info->LoadedSection(".debug_frame");
bool result =
LoadDwarfCFI<ElfClass>(obj_file, elf_header, ".debug_frame",
dwarf_cfi_section, false, 0, 0, big_endian,
smap, text_bias, usu, log);
found_usable_info = found_usable_info || result;
if (result)
log("LoadSymbols: read CFI from .debug_frame");
}
// Linux C++ exception handling information can also provide
// unwinding data.
const Shdr* eh_frame_section =
FindElfSectionByName<ElfClass>(".eh_frame", SHT_PROGBITS,
sections, names, names_end,
elf_header->e_shnum);
if (eh_frame_section) {
// Pointers in .eh_frame data may be relative to the base addresses of
// certain sections. Provide those sections if present.
const Shdr* got_section =
FindElfSectionByName<ElfClass>(".got", SHT_PROGBITS,
sections, names, names_end,
elf_header->e_shnum);
const Shdr* text_section =
FindElfSectionByName<ElfClass>(".text", SHT_PROGBITS,
sections, names, names_end,
elf_header->e_shnum);
info->LoadedSection(".eh_frame");
// As above, ignore the return value of this function.
bool result =
LoadDwarfCFI<ElfClass>(obj_file, elf_header, ".eh_frame",
eh_frame_section, true,
got_section, text_section, big_endian,
smap, text_bias, usu, log);
found_usable_info = found_usable_info || result;
if (result)
log("LoadSymbols: read CFI from .eh_frame");
}
SprintfLiteral(buf, "LoadSymbols: END %s\n", obj_file.c_str());
buf[sizeof(buf)-1] = 0;
log(buf);
return found_usable_info;
}
// Return the breakpad symbol file identifier for the architecture of
// ELF_HEADER.
template<typename ElfClass>
const char* ElfArchitecture(const typename ElfClass::Ehdr* elf_header) {
typedef typename ElfClass::Half Half;
Half arch = elf_header->e_machine;
switch (arch) {
case EM_386: return "x86";
case EM_ARM: return "arm";
case EM_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**)&note_section, &note_size, &elfclass) ||
note_size == 0) &&
(!FindElfSection(elf_mapped_base, ".note.gnu.build-id", SHT_NOTE,
(const void**)&note_section, &note_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

View 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

View 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

File diff suppressed because it is too large Load diff

View 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

View 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

View 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

View 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);
}
}

View 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 */

View 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
View 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')

View 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"

View 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

View 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

View 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_ */

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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)

View 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

View 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

View 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;
}

File diff suppressed because it is too large Load diff

View 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

View 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 &section);
// 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

View 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;
}
}

View 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']

View 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();
}

View 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();
}

View 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();
}

View 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.
})();
}

View 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();
}

View 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);
}

View 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();
}

View 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();
});
}

View 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, "");
}

View 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