Bug 938157 - Lightweight CFI/EXIDX unwinding library for SPS. r=n.nethercote, nfroyd, bgirard.

--HG--
extra : rebase_source : bd427749667ddd6641eff414879c3706a5cb5f5e
This commit is contained in:
Julian Seward 2013-12-18 13:02:34 +01:00
parent e8b418c5f5
commit 9bd60ea2d4
27 changed files with 10064 additions and 627 deletions

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/NullPtr.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];
snprintf(buf, sizeof(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|.
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,110 @@
/* -*- 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:
UniqueString(string str) { str_ = strdup(str.c_str()); }
~UniqueString() { free(reinterpret_cast<void*>(const_cast<char*>(str_))); }
const char* str_;
};
class UniqueStringUniverse {
public:
UniqueStringUniverse() {};
const UniqueString* FindOrCopy(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;
}
}
private:
std::map<string, UniqueString*> map_;
};
const UniqueString* ToUniqueString(string str) {
// For the initialisation of sUniverse to be threadsafe,
// this relies on C++11's semantics.
static UniqueStringUniverse sUniverse;
return sUniverse.FindOrCopy(str);
}
const char* const FromUniqueString(const UniqueString* ustr)
{
return ustr->str_;
}
} // namespace lul

View File

@ -0,0 +1,582 @@
/* -*- 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"
#include "mozilla/NullPtr.h"
namespace lul {
////////////////////////////////////////////////////////////////
// UniqueString
//
// Abstract type
class UniqueString;
// Unique-ify a string. |ToUniqueString| can never return nullptr.
const UniqueString* ToUniqueString(std::string);
// Get the contained C string (debugging only)
const char* const FromUniqueString(const UniqueString*);
// Some handy pre-uniqified strings. Z is an escape character:
// ZS '$'
// ZD '.'
// Zeq '='
// Zplus '+'
// Zstar '*'
// Zslash '/'
// Zpercent '%'
// Zat '@'
// Zcaret '^'
// Note that ustr__empty and (UniqueString*)nullptr are considered
// to be different.
//
// Unfortunately these have to be written as functions so as to
// make them safe to use in static initialisers.
// ""
inline static const UniqueString* ustr__empty() {
static const UniqueString* us = nullptr;
if (!us) us = ToUniqueString("");
return us;
}
// ".cfa"
inline static const UniqueString* ustr__ZDcfa() {
static const UniqueString* us = nullptr;
if (!us) us = ToUniqueString(".cfa");
return us;
}
// ".ra"
inline static const UniqueString* ustr__ZDra() {
static const UniqueString* us = nullptr;
if (!us) us = ToUniqueString(".ra");
return us;
}
////////////////////////////////////////////////////////////////
// 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() {
typedef char type_must_be_complete[sizeof(T)];
delete ptr;
}
void reset(T* p = 0) {
typedef char type_must_be_complete[sizeof(T)];
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() {
typedef char type_must_be_complete[sizeof(T)];
delete[] ptr;
}
void reset(T* p = 0) {
typedef char type_must_be_complete[sizeof(T)];
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() {
typedef char type_must_be_complete[sizeof(T)];
free_((void*) ptr);
}
void reset(T* p = 0) {
typedef char type_must_be_complete[sizeof(T)];
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 (ident == ustr__empty()) {
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

2010
tools/profiler/LulDwarf.cpp Normal file

File diff suppressed because it is too large Load Diff

1272
tools/profiler/LulDwarfExt.h Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,127 @@
/* -*- 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'
};
} // namespace lul
#endif // LulDwarfInt_h

View File

@ -0,0 +1,242 @@
/* -*- 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 {
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];
snprintf(buf, sizeof(buf), "LUL Entry(%llx, %llu)\n",
(unsigned long long int)aAddress,
(unsigned long long int)aLength);
buf[sizeof(buf)-1] = 0;
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, int aOldReg, intptr_t aOffset, bool aDeref)
{
aAddress += mTextBias;
if (DEBUG_SUMMARISER) {
char buf[100];
snprintf(buf, sizeof(buf),
"LUL 0x%llx old-r%d = %sr%d + %ld%s\n",
(unsigned long long int)aAddress, aNewReg,
aDeref ? "*(" : "", aOldReg, (long)aOffset, aDeref ? ")" : "");
buf[sizeof(buf)-1] = 0;
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;
}
// 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 (aDeref) {
goto cant_summarise;
}
switch (aOldReg) {
case DW_REG_ARM_R7: case DW_REG_ARM_R11:
case DW_REG_ARM_R12: case DW_REG_ARM_R13:
break;
default:
goto cant_summarise;
}
mCurrRules.mCfaExpr = LExpr(LExpr::NODEREF, aOldReg, aOffset);
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: {
// Check the aOldReg is valid.
switch (aOldReg) {
case DW_REG_CFA:
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:
break;
default:
goto cant_summarise;
}
// This is a new rule for one of r{7,11,12,13,14,15} and has a
// representable offset. In particular the new value of r15 is
// going to be the return address.
LExpr expr = LExpr(aDeref ? LExpr::DEREF : LExpr::NODEREF,
aOldReg, aOffset);
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:
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 == LExpr::UNKNOWN) {
mCurrRules.mR7expr = LExpr(LExpr::NODEREF, DW_REG_ARM_R7, 0);
}
if (mCurrRules.mR11expr.mHow == LExpr::UNKNOWN) {
mCurrRules.mR11expr = LExpr(LExpr::NODEREF, DW_REG_ARM_R11, 0);
}
if (mCurrRules.mR12expr.mHow == LExpr::UNKNOWN) {
mCurrRules.mR12expr = LExpr(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(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 == LExpr::UNKNOWN) {
mCurrRules.mR15expr = LExpr(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. In the 64 bit case
// we need to check that aOffset will fit into an int32_t. In the
// 32 bit case it is expected that the compiler will fold out the
// test since it always succeeds.
if (aNewReg == DW_REG_CFA) {
// This is a rule that defines the CFA. The only forms we can
// represent are: = SP+offset or = FP+offset.
if (!aDeref && aOffset == (intptr_t)(int32_t)aOffset &&
(aOldReg == DW_REG_INTEL_XSP || aOldReg == DW_REG_INTEL_XBP)) {
mCurrRules.mCfaExpr = LExpr(LExpr::NODEREF, aOldReg, aOffset);
} else {
goto cant_summarise;
}
}
else
if ((aNewReg == DW_REG_INTEL_XSP ||
aNewReg == DW_REG_INTEL_XBP || aNewReg == DW_REG_INTEL_XIP) &&
(aOldReg == DW_REG_CFA ||
aOldReg == DW_REG_INTEL_XSP ||
aOldReg == DW_REG_INTEL_XBP || aOldReg == DW_REG_INTEL_XIP) &&
aOffset == (intptr_t)(int32_t)aOffset) {
// This is a new rule for SP, BP or the return address
// respectively, and has a representable offset.
LExpr expr = LExpr(aDeref ? LExpr::DEREF : LExpr::NODEREF,
aOldReg, aOffset);
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");
}
}
else {
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 == LExpr::UNKNOWN) {
mCurrRules.mXspExpr = LExpr(LExpr::NODEREF, DW_REG_CFA, 0);
}
// Also, gcc says "Undef" for BP when it is unchanged.
if (mCurrRules.mXbpExpr.mHow == LExpr::UNKNOWN) {
mCurrRules.mXbpExpr = LExpr(LExpr::NODEREF, DW_REG_INTEL_XBP, 0);
}
#else
# error "Unsupported arch"
#endif
return;
cant_summarise:
if (0) {
mLog("LUL can't summarise\n");
}
}
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,52 @@
/* -*- 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*));
void Entry(uintptr_t aAddress, uintptr_t aLength);
void End();
void Rule(uintptr_t aAddress,
int aNewReg, int aOldReg, intptr_t aOffset, bool aDeref);
private:
// The SecMap in which we park the finished summaries (RuleSets).
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

1008
tools/profiler/LulElf.cpp Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,66 @@
/* -*- 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,
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,
void (*log)(const char*));
} // namespace lul
#endif // LulElfExt_h

234
tools/profiler/LulElfInt.h Normal file
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

690
tools/profiler/LulExidx.cpp Normal file
View File

@ -0,0 +1,690 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* libunwind - a platform-independent unwind library
Copyright 2011 Linaro Limited
This file is part of libunwind.
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
// 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.
// Derived from libunwind, with extensive modifications.
// This file translates EXIDX unwind information into the same format
// that LUL uses for CFI information. Hence LUL's CFI unwinding
// abilities also become usable for EXIDX.
//
// See: "Exception Handling ABI for the ARM Architecture", ARM IHI 0038A
// http://infocenter.arm.com/help/topic/com.arm.doc.ihi0038a/IHI0038A_ehabi.pdf
// EXIDX data is presented in two parts:
//
// * an index table. This contains two words per routine,
// the first of which identifies the routine, and the second
// of which is a reference to the unwind bytecode. If the
// bytecode is very compact -- 3 bytes or less -- it can be
// stored directly in the second word.
//
// * an area containing the unwind bytecodes.
//
// General flow is: ExceptionTableInfo::Start iterates over all
// of the index table entries (pairs). For each entry, it:
//
// * calls ExceptionTableInfo::ExtabEntryExtract to copy the bytecode
// out into an intermediate buffer.
// * uses ExceptionTableInfo::ExtabEntryDecode to parse the intermediate
// buffer. Each bytecode instruction is bundled into a
// arm_ex_to_module::extab_data structure, and handed to ..
//
// * .. ARMExToModule::ImproveStackFrame, which in turn hands it to
// ARMExToModule::TranslateCmd, and that generates the pseudo-CFI
// records that Breakpad stores.
// This file is derived from the following files in
// toolkit/crashreporter/google-breakpad:
// src/common/arm_ex_to_module.cc
// src/common/arm_ex_reader.cc
#include "mozilla/Assertions.h"
#include "mozilla/NullPtr.h"
#include "LulExidxExt.h"
#define ARM_EXBUF_START(x) (((x) >> 4) & 0x0f)
#define ARM_EXBUF_COUNT(x) ((x) & 0x0f)
#define ARM_EXBUF_END(x) (ARM_EXBUF_START(x) + ARM_EXBUF_COUNT(x))
namespace lul {
// Translate command from extab_data to command for Module.
int ARMExToModule::TranslateCmd(const struct extab_data* edata,
LExpr& vsp) {
int ret = 0;
switch (edata->cmd) {
case ARM_EXIDX_CMD_FINISH:
/* Copy LR to PC if there isn't currently a rule for PC in force. */
if (curr_rules_.mR15expr.mHow == LExpr::UNKNOWN) {
if (curr_rules_.mR14expr.mHow == LExpr::UNKNOWN) {
curr_rules_.mR15expr = LExpr(LExpr::NODEREF, DW_REG_ARM_R14, 0);
} else {
curr_rules_.mR15expr = curr_rules_.mR14expr;
}
}
break;
case ARM_EXIDX_CMD_SUB_FROM_VSP:
vsp = vsp.add_delta(- static_cast<long>(edata->data));
break;
case ARM_EXIDX_CMD_ADD_TO_VSP:
vsp = vsp.add_delta(static_cast<long>(edata->data));
break;
case ARM_EXIDX_CMD_REG_POP:
for (unsigned int i = 0; i < 16; i++) {
if (edata->data & (1 << i)) {
// See if we're summarising for int register |i|. If so,
// describe how to pull it off the stack. The cast of |i| is
// a bit of a kludge but works because DW_REG_ARM_Rn has the
// value |n|, for 0 <= |n| <= 15 -- that is, for the ARM
// general-purpose registers.
LExpr* regI_exprP = curr_rules_.ExprForRegno((DW_REG_NUMBER)i);
if (regI_exprP) {
*regI_exprP = vsp.deref();
}
vsp = vsp.add_delta(4);
}
}
/* Set cfa in case the SP got popped. */
if (edata->data & (1 << 13)) {
vsp = curr_rules_.mR13expr;
}
break;
case ARM_EXIDX_CMD_REG_TO_SP: {
MOZ_ASSERT (edata->data < 16);
int reg_no = edata->data;
// Same comment as above, re the casting of |reg_no|, applies.
LExpr* reg_exprP = curr_rules_.ExprForRegno((DW_REG_NUMBER)reg_no);
if (reg_exprP) {
if (reg_exprP->mHow == LExpr::UNKNOWN) {
curr_rules_.mR13expr = LExpr(LExpr::NODEREF, reg_no, 0);
} else {
curr_rules_.mR13expr = *reg_exprP;
}
vsp = curr_rules_.mR13expr;
}
break;
}
case ARM_EXIDX_CMD_VFP_POP:
/* Don't recover VFP registers, but be sure to adjust the stack
pointer. */
for (unsigned int i = ARM_EXBUF_START(edata->data);
i <= ARM_EXBUF_END(edata->data); i++) {
vsp = vsp.add_delta(8);
}
if (!(edata->data & ARM_EXIDX_VFP_FSTMD)) {
vsp = vsp.add_delta(4);
}
break;
case ARM_EXIDX_CMD_WREG_POP:
for (unsigned int i = ARM_EXBUF_START(edata->data);
i <= ARM_EXBUF_END(edata->data); i++) {
vsp = vsp.add_delta(8);
}
break;
case ARM_EXIDX_CMD_WCGR_POP:
// Pop wCGR registers under mask {wCGR3,2,1,0}, hence "i < 4"
for (unsigned int i = 0; i < 4; i++) {
if (edata->data & (1 << i)) {
vsp = vsp.add_delta(4);
}
}
break;
case ARM_EXIDX_CMD_REFUSED:
case ARM_EXIDX_CMD_RESERVED:
ret = -1;
break;
}
return ret;
}
void ARMExToModule::AddStackFrame(uintptr_t addr, size_t size) {
// Here we are effectively reinitialising the EXIDX summariser for a
// new code address range. smap_ stays unchanged. All other fields
// are reinitialised.
vsp_ = LExpr(LExpr::NODEREF, DW_REG_ARM_R13, 0);
(void) new (&curr_rules_) RuleSet();
curr_rules_.mAddr = (uintptr_t)addr;
curr_rules_.mLen = (uintptr_t)size;
if (0) {
char buf[100];
sprintf(buf, " AddStackFrame %llx .. %llx",
(uint64_t)addr, (uint64_t)(addr + size - 1));
log_(buf);
}
}
int ARMExToModule::ImproveStackFrame(const struct extab_data* edata) {
return TranslateCmd(edata, vsp_) ;
}
void ARMExToModule::DeleteStackFrame() {
}
void ARMExToModule::SubmitStackFrame() {
// JRS: I'm really not sure what this means, or if it is necessary
// return address always winds up in pc
//stack_frame_entry_->initial_rules[ustr__ZDra()] // ".ra"
// = stack_frame_entry_->initial_rules[ustr__pc()];
// maybe don't need to do anything here?
// the final value of vsp is the new value of sp
curr_rules_.mR13expr = vsp_;
// Finally, add the completed RuleSet to the SecMap
if (curr_rules_.mLen > 0) {
// Futz with the rules for r4 .. r11 in the same way as happens
// with the CFI summariser:
/* 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 (curr_rules_.mR7expr.mHow == LExpr::UNKNOWN) {
curr_rules_.mR7expr = LExpr(LExpr::NODEREF, DW_REG_ARM_R7, 0);
}
if (curr_rules_.mR11expr.mHow == LExpr::UNKNOWN) {
curr_rules_.mR11expr = LExpr(LExpr::NODEREF, DW_REG_ARM_R11, 0);
}
if (curr_rules_.mR12expr.mHow == LExpr::UNKNOWN) {
curr_rules_.mR12expr = LExpr(LExpr::NODEREF, DW_REG_ARM_R12, 0);
}
if (curr_rules_.mR14expr.mHow == LExpr::UNKNOWN) {
curr_rules_.mR14expr = LExpr(LExpr::NODEREF, DW_REG_ARM_R14, 0);
}
// And add them
smap_->AddRuleSet(&curr_rules_);
if (0) {
curr_rules_.Print(log_);
}
if (0) {
char buf[100];
sprintf(buf, " SubmitStackFrame %llx .. %llx",
(uint64_t)curr_rules_.mAddr,
(uint64_t)(curr_rules_.mAddr + curr_rules_.mLen - 1));
log_(buf);
}
}
}
#define ARM_EXIDX_CANT_UNWIND 0x00000001
#define ARM_EXIDX_COMPACT 0x80000000
#define ARM_EXTBL_OP_FINISH 0xb0
#define ARM_EXIDX_TABLE_LIMIT (255*4)
using lul::ARM_EXIDX_CMD_FINISH;
using lul::ARM_EXIDX_CMD_SUB_FROM_VSP;
using lul::ARM_EXIDX_CMD_ADD_TO_VSP;
using lul::ARM_EXIDX_CMD_REG_POP;
using lul::ARM_EXIDX_CMD_REG_TO_SP;
using lul::ARM_EXIDX_CMD_VFP_POP;
using lul::ARM_EXIDX_CMD_WREG_POP;
using lul::ARM_EXIDX_CMD_WCGR_POP;
using lul::ARM_EXIDX_CMD_RESERVED;
using lul::ARM_EXIDX_CMD_REFUSED;
using lul::exidx_entry;
using lul::ARM_EXIDX_VFP_SHIFT_16;
using lul::ARM_EXIDX_VFP_FSTMD;
using lul::MemoryRange;
static void* Prel31ToAddr(const void* addr)
{
uint32_t offset32 = *reinterpret_cast<const uint32_t*>(addr);
// sign extend offset32[30:0] to 64 bits -- copy bit 30 to positions
// 63:31 inclusive.
uint64_t offset64 = offset32;
if (offset64 & (1ULL << 30))
offset64 |= 0xFFFFFFFF80000000ULL;
else
offset64 &= 0x000000007FFFFFFFULL;
return ((char*)addr) + (uintptr_t)offset64;
}
// Extract unwind bytecode for the function denoted by |entry| into |buf|,
// and return the number of bytes of |buf| written, along with a code
// indicating the outcome.
ExceptionTableInfo::ExExtractResult
ExceptionTableInfo::ExtabEntryExtract(const struct exidx_entry* entry,
uint8_t* buf, size_t buf_size,
/*OUT*/size_t* buf_used)
{
MemoryRange mr_out(buf, buf_size);
*buf_used = 0;
# define PUT_BUF_U8(_byte) \
do { if (!mr_out.Covers(*buf_used, 1)) return ExOutBufOverflow; \
buf[(*buf_used)++] = (_byte); } while (0)
# define GET_EX_U32(_lval, _addr, _sec_mr) \
do { if (!(_sec_mr).Covers(reinterpret_cast<const uint8_t*>(_addr) \
- (_sec_mr).data(), 4)) \
return ExInBufOverflow; \
(_lval) = *(reinterpret_cast<const uint32_t*>(_addr)); } while (0)
# define GET_EXIDX_U32(_lval, _addr) \
GET_EX_U32(_lval, _addr, mr_exidx_)
# define GET_EXTAB_U32(_lval, _addr) \
GET_EX_U32(_lval, _addr, mr_extab_)
uint32_t data;
GET_EXIDX_U32(data, &entry->data);
// A function can be marked CANT_UNWIND if (eg) it is known to be
// at the bottom of the stack.
if (data == ARM_EXIDX_CANT_UNWIND)
return ExCantUnwind;
uint32_t pers; // personality number
uint32_t extra; // number of extra data words required
uint32_t extra_allowed; // number of extra data words allowed
uint32_t* extbl_data; // the handler entry, if not inlined
if (data & ARM_EXIDX_COMPACT) {
// The handler table entry has been inlined into the index table entry.
// In this case it can only be an ARM-defined compact model, since
// bit 31 is 1. Only personalities 0, 1 and 2 are defined for the
// ARM compact model, but 1 and 2 are "Long format" and may require
// extra data words. Hence the allowable personalities here are:
// personality 0, in which case 'extra' has no meaning
// personality 1, with zero extra words
// personality 2, with zero extra words
extbl_data = nullptr;
pers = (data >> 24) & 0x0F;
extra = (data >> 16) & 0xFF;
extra_allowed = 0;
}
else {
// The index table entry is a pointer to the handler entry. Note
// that Prel31ToAddr will read the given address, but we already
// range-checked above.
extbl_data = reinterpret_cast<uint32_t*>(Prel31ToAddr(&entry->data));
GET_EXTAB_U32(data, extbl_data);
if (!(data & ARM_EXIDX_COMPACT)) {
// This denotes a "generic model" handler. That will involve
// executing arbitary machine code, which is something we
// can't represent here; hence reject it.
return ExCantRepresent;
}
// So we have a compact model representation. Again, 3 possible
// personalities, but this time up to 255 allowable extra words.
pers = (data >> 24) & 0x0F;
extra = (data >> 16) & 0xFF;
extra_allowed = 255;
extbl_data++;
}
// Now look at the the handler table entry. The first word is
// |data| and subsequent words start at |*extbl_data|. The number
// of extra words to use is |extra|, provided that the personality
// allows extra words. Even if it does, none may be available --
// extra_allowed is the maximum number of extra words allowed. */
if (pers == 0) {
// "Su16" in the documentation -- 3 unwinding insn bytes
// |extra| has no meaning here; instead that byte is an unwind-info byte
PUT_BUF_U8(data >> 16);
PUT_BUF_U8(data >> 8);
PUT_BUF_U8(data);
}
else if ((pers == 1 || pers == 2) && extra <= extra_allowed) {
// "Lu16" or "Lu32" respectively -- 2 unwinding insn bytes,
// and up to 255 extra words.
PUT_BUF_U8(data >> 8);
PUT_BUF_U8(data);
for (uint32_t j = 0; j < extra; j++) {
GET_EXTAB_U32(data, extbl_data);
extbl_data++;
PUT_BUF_U8(data >> 24);
PUT_BUF_U8(data >> 16);
PUT_BUF_U8(data >> 8);
PUT_BUF_U8(data >> 0);
}
}
else {
// The entry is invalid.
return ExInvalid;
}
// Make sure the entry is terminated with "FINISH"
if (*buf_used > 0 && buf[(*buf_used) - 1] != ARM_EXTBL_OP_FINISH)
PUT_BUF_U8(ARM_EXTBL_OP_FINISH);
return ExSuccess;
# undef GET_EXTAB_U32
# undef GET_EXIDX_U32
# undef GET_U32
# undef PUT_BUF_U8
}
// Take the unwind information extracted by ExtabEntryExtract
// and parse it into frame-unwind instructions. These are as
// specified in "Table 4, ARM-defined frame-unwinding instructions"
// in the specification document detailed in comments at the top
// of this file.
//
// This reads from |buf[0, +data_size)|. It checks for overruns of
// the input buffer and returns a negative value if that happens, or
// for any other failure cases. It returns zero in case of success.
int ExceptionTableInfo::ExtabEntryDecode(const uint8_t* buf, size_t buf_size)
{
if (buf == nullptr || buf_size == 0)
return -1;
MemoryRange mr_in(buf, buf_size);
const uint8_t* buf_initially = buf;
# define GET_BUF_U8(_lval) \
do { if (!mr_in.Covers(buf - buf_initially, 1)) return -1; \
(_lval) = *(buf++); } while (0)
const uint8_t* end = buf + buf_size;
while (buf < end) {
struct lul::extab_data edata;
memset(&edata, 0, sizeof(edata));
uint8_t op;
GET_BUF_U8(op);
if ((op & 0xc0) == 0x00) {
// vsp = vsp + (xxxxxx << 2) + 4
edata.cmd = ARM_EXIDX_CMD_ADD_TO_VSP;
edata.data = (((int)op & 0x3f) << 2) + 4;
}
else if ((op & 0xc0) == 0x40) {
// vsp = vsp - (xxxxxx << 2) - 4
edata.cmd = ARM_EXIDX_CMD_SUB_FROM_VSP;
edata.data = (((int)op & 0x3f) << 2) + 4;
}
else if ((op & 0xf0) == 0x80) {
uint8_t op2;
GET_BUF_U8(op2);
if (op == 0x80 && op2 == 0x00) {
// Refuse to unwind
edata.cmd = ARM_EXIDX_CMD_REFUSED;
} else {
// Pop up to 12 integer registers under masks {r15-r12},{r11-r4}
edata.cmd = ARM_EXIDX_CMD_REG_POP;
edata.data = ((op & 0xf) << 8) | op2;
edata.data = edata.data << 4;
}
}
else if ((op & 0xf0) == 0x90) {
if (op == 0x9d || op == 0x9f) {
// 9d: Reserved as prefix for ARM register to register moves
// 9f: Reserved as perfix for Intel Wireless MMX reg to reg moves
edata.cmd = ARM_EXIDX_CMD_RESERVED;
} else {
// Set vsp = r[nnnn]
edata.cmd = ARM_EXIDX_CMD_REG_TO_SP;
edata.data = op & 0x0f;
}
}
else if ((op & 0xf0) == 0xa0) {
// Pop r4 to r[4+nnn], or
// Pop r4 to r[4+nnn] and r14 or
unsigned end = (op & 0x07);
edata.data = (1 << (end + 1)) - 1;
edata.data = edata.data << 4;
if (op & 0x08) edata.data |= 1 << 14;
edata.cmd = ARM_EXIDX_CMD_REG_POP;
}
else if (op == ARM_EXTBL_OP_FINISH) {
// Finish
edata.cmd = ARM_EXIDX_CMD_FINISH;
buf = end;
}
else if (op == 0xb1) {
uint8_t op2;
GET_BUF_U8(op2);
if (op2 == 0 || (op2 & 0xf0)) {
// Spare
edata.cmd = ARM_EXIDX_CMD_RESERVED;
} else {
// Pop integer registers under mask {r3,r2,r1,r0}
edata.cmd = ARM_EXIDX_CMD_REG_POP;
edata.data = op2 & 0x0f;
}
}
else if (op == 0xb2) {
// vsp = vsp + 0x204 + (uleb128 << 2)
uint64_t offset = 0;
uint8_t byte, shift = 0;
do {
GET_BUF_U8(byte);
offset |= (byte & 0x7f) << shift;
shift += 7;
} while ((byte & 0x80) && buf < end);
edata.data = offset * 4 + 0x204;
edata.cmd = ARM_EXIDX_CMD_ADD_TO_VSP;
}
else if (op == 0xb3 || op == 0xc8 || op == 0xc9) {
// b3: Pop VFP regs D[ssss] to D[ssss+cccc], FSTMFDX-ishly
// c8: Pop VFP regs D[16+ssss] to D[16+ssss+cccc], FSTMFDD-ishly
// c9: Pop VFP regs D[ssss] to D[ssss+cccc], FSTMFDD-ishly
edata.cmd = ARM_EXIDX_CMD_VFP_POP;
GET_BUF_U8(edata.data);
if (op == 0xc8) edata.data |= ARM_EXIDX_VFP_SHIFT_16;
if (op != 0xb3) edata.data |= ARM_EXIDX_VFP_FSTMD;
}
else if ((op & 0xf8) == 0xb8 || (op & 0xf8) == 0xd0) {
// b8: Pop VFP regs D[8] to D[8+nnn], FSTMFDX-ishly
// d0: Pop VFP regs D[8] to D[8+nnn], FSTMFDD-ishly
edata.cmd = ARM_EXIDX_CMD_VFP_POP;
edata.data = 0x80 | (op & 0x07);
if ((op & 0xf8) == 0xd0) edata.data |= ARM_EXIDX_VFP_FSTMD;
}
else if (op >= 0xc0 && op <= 0xc5) {
// Intel Wireless MMX pop wR[10]-wr[10+nnn], nnn != 6,7
edata.cmd = ARM_EXIDX_CMD_WREG_POP;
edata.data = 0xa0 | (op & 0x07);
}
else if (op == 0xc6) {
// Intel Wireless MMX pop wR[ssss] to wR[ssss+cccc]
edata.cmd = ARM_EXIDX_CMD_WREG_POP;
GET_BUF_U8(edata.data);
}
else if (op == 0xc7) {
uint8_t op2;
GET_BUF_U8(op2);
if (op2 == 0 || (op2 & 0xf0)) {
// Spare
edata.cmd = ARM_EXIDX_CMD_RESERVED;
} else {
// Intel Wireless MMX pop wCGR registers under mask {wCGR3,2,1,0}
edata.cmd = ARM_EXIDX_CMD_WCGR_POP;
edata.data = op2 & 0x0f;
}
}
else {
// Spare
edata.cmd = ARM_EXIDX_CMD_RESERVED;
}
int ret = handler_->ImproveStackFrame(&edata);
if (ret < 0) return ret;
}
return 0;
# undef GET_BUF_U8
}
void ExceptionTableInfo::Start()
{
const struct exidx_entry* start
= reinterpret_cast<const struct exidx_entry*>(mr_exidx_.data());
const struct exidx_entry* end
= reinterpret_cast<const struct exidx_entry*>(mr_exidx_.data()
+ mr_exidx_.length());
// Iterate over each of the EXIDX entries (pairs of 32-bit words).
// These occupy the entire .exidx section.
for (const struct exidx_entry* entry = start; entry < end; ++entry) {
// Figure out the code address range that this table entry is
// associated with.
//
// I don't claim to understand the biasing here. It appears that
// (Prel31ToAddr(&entry->addr))
// - mapping_addr_ + loading_addr_) & 0x7fffffff
// produces a SVMA. Adding the text_bias_ gives plausible AVMAs.
uint32_t svma = (reinterpret_cast<char*>(Prel31ToAddr(&entry->addr))
- mapping_addr_ + loading_addr_) & 0x7fffffff;
uint32_t next_svma;
if (entry < end - 1) {
next_svma = (reinterpret_cast<char*>(Prel31ToAddr(&((entry + 1)->addr)))
- mapping_addr_ + loading_addr_) & 0x7fffffff;
} else {
// This is the last EXIDX entry in the sequence, so we don't
// have an address for the start of the next function, to limit
// this one. Instead use the address of the last byte of the
// text section associated with this .exidx section, that we
// have been given. So as to avoid junking up the CFI unwind
// tables with absurdly large address ranges in the case where
// text_last_svma_ is wrong, only use the value if it is nonzero
// and within one page of |svma|. Otherwise assume a length of 1.
//
// In some cases, gcc has been observed to finish the exidx
// section with an entry of length 1 marked CANT_UNWIND,
// presumably exactly for the purpose of giving a definite
// length for the last real entry, without having to look at
// text segment boundaries.
bool plausible = false;
next_svma = svma + 1;
if (text_last_svma_ != 0) {
uint32_t maybe_next_svma = text_last_svma_ + 1;
if (maybe_next_svma > svma && maybe_next_svma - svma <= 4096) {
next_svma = maybe_next_svma;
plausible = true;
}
}
if (!plausible) {
char buf[100];
snprintf(buf, sizeof(buf),
"ExceptionTableInfo: implausible EXIDX last entry size %d"
"; using 1 instead.", (int32_t)(text_last_svma_ - svma));
buf[sizeof(buf)-1] = 0;
log_(buf);
}
}
// Extract the unwind info into |buf|. This might fail for
// various reasons. It involves reading both the .exidx and
// .extab sections. All accesses to those sections are
// bounds-checked.
uint8_t buf[ARM_EXIDX_TABLE_LIMIT];
size_t buf_used = 0;
ExExtractResult res = ExtabEntryExtract(entry, buf, sizeof(buf), &buf_used);
if (res != ExSuccess) {
// Couldn't extract the unwind info, for some reason. Move on.
switch (res) {
case ExInBufOverflow:
log_("ExtabEntryExtract: .exidx/.extab section overrun");
break;
case ExOutBufOverflow:
log_("ExtabEntryExtract: bytecode buffer overflow");
break;
case ExCantUnwind:
log_("ExtabEntryExtract: function is marked CANT_UNWIND");
break;
case ExCantRepresent:
log_("ExtabEntryExtract: bytecode can't be represented");
break;
case ExInvalid:
log_("ExtabEntryExtract: index table entry is invalid");
break;
default: {
char buf[100];
snprintf(buf, sizeof(buf),
"ExtabEntryExtract: unknown error: %d", (int)res);
buf[sizeof(buf)-1] = 0;
log_(buf);
break;
}
}
continue;
}
// Finally, work through the unwind instructions in |buf| and
// create CFI entries that Breakpad can use. This can also fail.
// First, add a new stack frame entry, into which ExtabEntryDecode
// will write the CFI entries.
handler_->AddStackFrame(svma + text_bias_, next_svma - svma);
int ret = ExtabEntryDecode(buf, buf_used);
if (ret < 0) {
handler_->DeleteStackFrame();
char buf[100];
snprintf(buf, sizeof(buf),
"ExtabEntryDecode: failed with error code: %d", ret);
buf[sizeof(buf)-1] = 0;
log_(buf);
continue;
}
handler_->SubmitStackFrame();
} /* iterating over .exidx */
}
} // namespace lul

View File

@ -0,0 +1,233 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* libunwind - a platform-independent unwind library
Copyright 2011 Linaro Limited
This file is part of libunwind.
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
// Copyright (c) 2010, 2011 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.
// Derived from libunwind, with extensive modifications.
// This file is derived from the following files in
// toolkit/crashreporter/google-breakpad:
// src/common/arm_ex_to_module.h
// src/common/memory_range.h
// src/common/arm_ex_reader.h
#ifndef LulExidxExt_h
#define LulExidxExt_h
#include "LulMainInt.h"
using lul::LExpr;
using lul::RuleSet;
using lul::SecMap;
namespace lul {
typedef enum extab_cmd {
ARM_EXIDX_CMD_FINISH,
ARM_EXIDX_CMD_SUB_FROM_VSP,
ARM_EXIDX_CMD_ADD_TO_VSP,
ARM_EXIDX_CMD_REG_POP,
ARM_EXIDX_CMD_REG_TO_SP,
ARM_EXIDX_CMD_VFP_POP,
ARM_EXIDX_CMD_WREG_POP,
ARM_EXIDX_CMD_WCGR_POP,
ARM_EXIDX_CMD_RESERVED,
ARM_EXIDX_CMD_REFUSED,
} extab_cmd_t;
struct exidx_entry {
uint32_t addr;
uint32_t data;
};
struct extab_data {
extab_cmd_t cmd;
uint32_t data;
};
enum extab_cmd_flags {
ARM_EXIDX_VFP_SHIFT_16 = 1 << 16,
ARM_EXIDX_VFP_FSTMD = 1 << 17, // distinguishes FSTMxxD from FSTMxxX
};
// Receives information from arm_ex_reader::ExceptionTableInfo
// and adds it to the SecMap object
// This is in effect the EXIDX summariser.
class ARMExToModule {
public:
ARMExToModule(SecMap* smap, void(*log)(const char*)) : smap_(smap)
, log_(log) { }
~ARMExToModule() { }
void AddStackFrame(uintptr_t addr, size_t size);
int ImproveStackFrame(const struct extab_data* edata);
void DeleteStackFrame();
void SubmitStackFrame();
private:
SecMap* smap_;
LExpr vsp_; // Always appears to be of the form "sp + offset"
RuleSet curr_rules_; // Also holds the address range being summarised
// debugging message sink
void (*log_)(const char*);
int TranslateCmd(const struct extab_data* edata, LExpr& vsp);
};
// (derived from)
// memory_range.h: Define the google_breakpad::MemoryRange class, which
// is a lightweight wrapper with a pointer and a length to encapsulate
// a contiguous range of memory.
// A lightweight wrapper with a pointer and a length to encapsulate a
// contiguous range of memory. It provides helper methods for checked
// access of a subrange of the memory. Its implemementation does not
// allocate memory or call into libc functions, and is thus safer to use
// in a crashed environment.
class MemoryRange {
public:
MemoryRange(const void* data, size_t length) {
Set(data, length);
}
// Sets this memory range to point to |data| and its length to |length|.
void Set(const void* data, size_t length) {
data_ = reinterpret_cast<const uint8_t*>(data);
// Always set |length_| to zero if |data_| is NULL.
length_ = data ? length : 0;
}
// Returns true if this range covers a subrange of |sub_length| bytes
// at |sub_offset| bytes of this memory range, or false otherwise.
bool Covers(size_t sub_offset, size_t sub_length) const {
// The following checks verify that:
// 1. sub_offset is within [ 0 .. length_ - 1 ]
// 2. sub_offset + sub_length is within
// [ sub_offset .. length_ ]
return sub_offset < length_ &&
sub_offset + sub_length >= sub_offset &&
sub_offset + sub_length <= length_;
}
// Returns a pointer to the beginning of this memory range.
const uint8_t* data() const { return data_; }
// Returns the length, in bytes, of this memory range.
size_t length() const { return length_; }
private:
// Pointer to the beginning of this memory range.
const uint8_t* data_;
// Length, in bytes, of this memory range.
size_t length_;
};
// This class is a reader for ARM unwind information
// from .ARM.exidx and .ARM.extab sections.
class ExceptionTableInfo {
public:
ExceptionTableInfo(const char* exidx, size_t exidx_size,
const char* extab, size_t extab_size,
uint32_t text_last_svma,
lul::ARMExToModule* handler,
const char* mapping_addr,
uint32_t loading_addr,
uintptr_t text_bias,
void (*log)(const char*))
: mr_exidx_(lul::MemoryRange(exidx, exidx_size)),
mr_extab_(lul::MemoryRange(extab, extab_size)),
text_last_svma_(text_last_svma),
handler_(handler), mapping_addr_(mapping_addr),
loading_addr_(loading_addr),
text_bias_(text_bias),
log_(log) { }
~ExceptionTableInfo() { }
// Parses the entries in .ARM.exidx and possibly
// in .ARM.extab tables, reports what we find to
// arm_ex_to_module::ARMExToModule.
void Start();
private:
lul::MemoryRange mr_exidx_;
lul::MemoryRange mr_extab_;
uint32_t text_last_svma_;
lul::ARMExToModule* handler_;
const char* mapping_addr_;
uint32_t loading_addr_;
uintptr_t text_bias_;
// debugging message sink
void (*log_)(const char*);
enum ExExtractResult {
ExSuccess, // success
ExInBufOverflow, // out-of-range while reading .exidx
ExOutBufOverflow, // output buffer is too small
ExCantUnwind, // this function is marked CANT_UNWIND
ExCantRepresent, // entry valid, but we can't represent it
ExInvalid // entry is invalid
};
ExExtractResult
ExtabEntryExtract(const struct lul::exidx_entry* entry,
uint8_t* buf, size_t buf_size,
/*OUT*/size_t* buf_used);
int ExtabEntryDecode(const uint8_t* buf, size_t buf_size);
};
} // namespace lul
#endif // LulExidxExt_h

1878
tools/profiler/LulMain.cpp Normal file

File diff suppressed because it is too large Load Diff

311
tools/profiler/LulMain.h Normal file
View File

@ -0,0 +1,311 @@
/* -*- 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 <pthread.h> // pthread_t
#include <map>
#include "LulPlatformMacros.h"
#include "LulRWLock.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:
// Construct a valid one.
TaggedUWord(uintptr_t w)
: mValue(w)
, mValid(true)
{}
// Construct an invalid one.
TaggedUWord()
: mValue(0)
, mValid(false)
{}
// Add in a second one.
void Add(TaggedUWord other) {
if (mValid && other.Valid()) {
mValue += other.Value();
} else {
mValue = 0;
mValid = false;
}
}
// Is it word-aligned?
bool IsAligned() const {
return mValid && (mValue & (sizeof(uintptr_t)-1)) == 0;
}
uintptr_t Value() const { return mValue; }
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];
};
// The core unwinder library class. Just one of these is needed, and
// it can be shared by multiple unwinder threads.
//
// Access to the library is mediated by a single reader-writer lock.
// All attempts to change the library's internal shared state -- that
// is, loading or unloading unwind info -- are forced single-threaded
// by causing the called routine to acquire a write-lock. Unwind
// requests do not change the library's internal shared state and
// therefore require only a read-lock. Hence multiple threads can
// unwind in parallel.
//
// The library needs to maintain state which is private to each
// unwinder thread -- the CFI (Dwarf Call Frame Information) fast
// cache. Hence unwinder threads first need to register with the
// library, so their identities are known. Also, for maximum
// effectiveness of the CFI caching, it is preferable to have a small
// number of very-busy unwinder threads rather than a large number of
// mostly-idle unwinder threads.
//
// None of the methods may be safely called from within a signal
// handler, since this risks deadlock. In particular this means
// a thread may not unwind itself from within a signal handler
// frame. It might be safe to call Unwind() on its own stack
// from not-inside a signal frame, although even that cannot be
// guaranteed deadlock free.
class PriMap;
class SegArray;
class CFICache;
class LUL {
public:
// Create; supply a logging sink. Initialises the rw-lock.
LUL(void (*aLog)(const char*));
// Destroy. This acquires mRWlock for writing. By doing that, waits
// for all unwinder threads to finish any Unwind() calls they may be
// in. All resources are freed and all registered unwinder threads
// are deregistered.
~LUL();
// 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.
//
// Acquires mRWlock for writing. This must be called only after the
// code area in question really has been mapped.
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. Acquires mRWlock for
// writing. This must be called only after the code area in
// question really has been mapped.
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.
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.
void NotifyBeforeUnmapAll() {
NotifyBeforeUnmap(0, UINTPTR_MAX);
}
// Returns the number of mappings currently registered. Acquires
// mRWlock for writing.
size_t CountMappings();
// Register the calling thread for unwinding. Acquires mRWlock for
// writing.
void RegisterUnwinderThread();
// 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.
//
// Acquires mRWlock for reading. Hence multiple threads may unwind
// at once, but no thread may be unwinding whilst the library loads
// or discards unwind information. Returns false if the calling
// thread is not registered for unwinding.
//
// Up to aScannedFramesAllowed stack-scanned frames may be recovered.
//
// The calling thread must previously have registered itself via
// RegisterUnwinderThread.
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.
void (*mLog)(const char*);
private:
// Invalidate the caches. Requires mRWlock to be held for writing;
// does not acquire it itself.
void InvalidateCFICaches();
// The one-and-only lock, a reader-writer lock, for the library.
LulRWLock* mRWlock;
// The top level mapping from code address ranges to postprocessed
// unwind info. Basically a sorted array of (addr, len, info)
// records. Threads wishing to query this field must hold mRWlock
// for reading. Threads wishing to modify this field must hold
// mRWlock for writing. 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. Threads
// wishing to query this field must hold mRWlock for reading.
// Threads wishing to modify this field must hold mRWlock for
// writing.
SegArray* mSegArray;
// The thread-local data: a mapping from threads to CFI-fast-caches.
// Threads wishing to query this field must hold mRWlock for
// reading. Threads wishing to modify this field must hold mRWlock
// for writing.
//
// The CFICaches themselves are thread-local and can be both read
// and written when mRWlock is held for reading. It would probably
// be faster to use the pthread_{set,get}specific functions, but
// also more difficult. This map is queried once per unwind, in
// order to get hold of the CFI cache for a given thread.
std::map<pthread_t, CFICache*> mCaches;
};
// 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

266
tools/profiler/LulMainInt.h Normal file
View File

@ -0,0 +1,266 @@
/* -*- 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 <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 {
////////////////////////////////////////////////////////////////
// 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
};
////////////////////////////////////////////////////////////////
// LExpr //
////////////////////////////////////////////////////////////////
// An expression -- very primitive. Denotes either "register +
// offset" or a dereferenced version of the same. 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.
struct LExpr {
// Denotes an expression with no value.
LExpr()
: mHow(UNKNOWN)
, mReg(0)
, mOffset(0)
{}
// Denotes any expressible expression.
LExpr(uint8_t how, int16_t reg, int32_t offset)
: mHow(how)
, mReg(reg)
, mOffset(offset)
{}
// 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
}
// 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.
enum { UNKNOWN=0, // This LExpr denotes no value.
NODEREF, // Value is (mReg + mOffset).
DEREF }; // Value is *(mReg + mOffset).
uint8_t mHow; // UNKNOWN, NODEREF or DEREF
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*));
// 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
};
////////////////////////////////////////////////////////////////
// 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.
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(RuleSet* rs);
// 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()).
std::vector<RuleSet> mRuleSets;
// 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,93 @@
/* -*- 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 "mozilla/Assertions.h"
#include "mozilla/DebugOnly.h"
#include "mozilla/NullPtr.h"
#include "LulRWLock.h"
namespace lul {
// An implementation for targets where libpthread does provide
// pthread_rwlock_t. These are straight wrappers around the
// equivalent pthread functions.
#if defined(LUL_OS_linux)
LulRWLock::LulRWLock() {
mozilla::DebugOnly<int> r = pthread_rwlock_init(&mLock, nullptr);
MOZ_ASSERT(!r);
}
LulRWLock::~LulRWLock() {
mozilla::DebugOnly<int>r = pthread_rwlock_destroy(&mLock);
MOZ_ASSERT(!r);
}
void
LulRWLock::WrLock() {
mozilla::DebugOnly<int>r = pthread_rwlock_wrlock(&mLock);
MOZ_ASSERT(!r);
}
void
LulRWLock::RdLock() {
mozilla::DebugOnly<int>r = pthread_rwlock_rdlock(&mLock);
MOZ_ASSERT(!r);
}
void
LulRWLock::Unlock() {
mozilla::DebugOnly<int>r = pthread_rwlock_unlock(&mLock);
MOZ_ASSERT(!r);
}
// An implementation for cases where libpthread does not provide
// pthread_rwlock_t. Currently this is a kludge in that it uses
// normal mutexes, resulting in the following limitations: (1) at most
// one reader is allowed at once, and (2) any thread that tries to
// read-acquire the lock more than once will deadlock. (2) could be
// avoided if it were possible to use recursive pthread_mutex_t's.
#elif defined(LUL_OS_android)
LulRWLock::LulRWLock() {
mozilla::DebugOnly<int> r = pthread_mutex_init(&mLock, nullptr);
MOZ_ASSERT(!r);
}
LulRWLock::~LulRWLock() {
mozilla::DebugOnly<int>r = pthread_mutex_destroy(&mLock);
MOZ_ASSERT(!r);
}
void
LulRWLock::WrLock() {
mozilla::DebugOnly<int>r = pthread_mutex_lock(&mLock);
MOZ_ASSERT(!r);
}
void
LulRWLock::RdLock() {
mozilla::DebugOnly<int>r = pthread_mutex_lock(&mLock);
MOZ_ASSERT(!r);
}
void
LulRWLock::Unlock() {
mozilla::DebugOnly<int>r = pthread_mutex_unlock(&mLock);
MOZ_ASSERT(!r);
}
#else
# error "Unsupported OS"
#endif
} // namespace lul

View File

@ -0,0 +1,42 @@
/* -*- 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 LulRWLock_h
#define LulRWLock_h
#include "LulPlatformMacros.h"
#include <pthread.h>
// This class provides a simple wrapper around reader-writer locks.
// This is necessary because Android 2.2's libpthread does not
// provide such functionality, so there has to be a way to provide
// an alternative with an equivalent interface.
namespace lul {
class LulRWLock {
public:
LulRWLock();
~LulRWLock();
void WrLock();
void RdLock();
void Unlock();
private:
#if defined(LUL_OS_android)
pthread_mutex_t mLock;
#elif defined(LUL_OS_linux)
pthread_rwlock_t mLock;
#else
# error "Unsupported OS"
#endif
};
} // namespace lul
#endif // LulRWLock_h

File diff suppressed because it is too large Load Diff

View File

@ -9,6 +9,11 @@
#include "GeckoProfilerImpl.h"
#include "ProfileEntry.h"
#include "PlatformMacros.h"
#if defined(SPS_OS_android) || defined(SPS_OS_linux)
# include "LulMain.h"
#endif
/* Top level exports of UnwinderThread.cpp. */
// Abstract type. A buffer which is used to transfer information between
@ -89,4 +94,11 @@ void utb__release_sync_buffer(LinkedUWTBuffer* utb);
// This typedef must match uwt__release_full_buffer and uwt__finish_sync_buffer
typedef void (*UTB_RELEASE_FUNC)(ThreadProfile*,UnwinderThreadBuffer*,void*);
#if defined(SPS_OS_android) || defined(SPS_OS_linux)
// Notify |aLUL| of the objects in the current process, so as to get
// it to read unwind data for them. This has to be externally visible
// so it can be used in LUL unit tests, tools/profiler/tests/gtest/LulTest.cpp.
void read_procmaps(lul::LUL* aLUL);
#endif
#endif /* ndef MOZ_UNWINDER_THREAD_2_H */

View File

@ -45,12 +45,24 @@ if CONFIG['MOZ_ENABLE_PROFILER_SPS']:
]
if CONFIG['OS_TARGET'] in ('Android', 'Linux'):
# These files cannot be built in unified mode because of name clashes with mozglue headers on Android.
UNIFIED_SOURCES += [
'AutoObjectMapper.cpp',
'LulCommon.cpp',
'LulDwarf.cpp',
'LulDwarfSummariser.cpp',
'LulElf.cpp',
'LulMain.cpp',
'LulRWLock.cpp',
]
# These files cannot be built in unified mode because of name clashes with mozglue headers on Android.
SOURCES += [
'platform-linux.cc',
'shared-libraries-linux.cc',
]
if CONFIG['CPU_ARCH'] == 'arm':
UNIFIED_SOURCES += [
'LulExidx.cpp',
]
SOURCES += [
'EHABIStackWalk.cpp',
]
@ -82,6 +94,9 @@ if CONFIG['MOZ_ENABLE_PROFILER_SPS']:
if CONFIG['ANDROID_CPU_ARCH'] == 'armeabi':
DEFINES['ARCH_ARMV6'] = True
if CONFIG['ENABLE_TESTS']:
DIRS += ['tests/gtest']
FINAL_LIBRARY = 'xul'
EXPORTS += [

View File

@ -237,7 +237,6 @@ bool sps_version2()
return version == 2;
}
#if !defined(ANDROID)
/* Has MOZ_PROFILER_VERBOSE been set? */
bool moz_profiler_verbose()
{
@ -253,7 +252,6 @@ bool moz_profiler_verbose()
return status == 2;
}
#endif
static inline const char* name_UnwMode(UnwMode m)
{
@ -367,6 +365,7 @@ void read_profiler_env_vars()
LOGF("SPS: UnwindStackScan = %d (max dubious frames per unwind).",
(int)sUnwindStackScan);
LOG( "SPS: Use env var MOZ_PROFILER_MODE=help for further information.");
LOG( "SPS: Note that MOZ_PROFILER_MODE=help sets all values to defaults.");
LOG( "SPS:");
}
}
@ -394,7 +393,11 @@ void profiler_usage() {
LOG( "SPS: The number of dubious (stack-scanned) frames allowed");
LOG( "SPS: ");
LOG( "SPS: MOZ_PROFILER_NEW");
LOG( "SPS: Needs to be set to use Breakpad-based unwinding.");
LOG( "SPS: Needs to be set to use LUL-based unwinding.");
LOG( "SPS: ");
LOG( "SPS: MOZ_PROFILER_LUL_TEST");
LOG( "SPS: If set to any value, runs LUL unit tests at startup of");
LOG( "SPS: the unwinder thread, and prints a short summary of results.");
LOG( "SPS: ");
LOGF("SPS: This platform %s native unwinding.",
is_native_unwinding_avail() ? "supports" : "does not support");
@ -415,6 +418,7 @@ void profiler_usage() {
LOGF("SPS: UnwindStackScan = %d (max dubious frames per unwind).",
(int)sUnwindStackScan);
LOG( "SPS: Use env var MOZ_PROFILER_MODE=help for further information.");
LOG( "SPS: Note that MOZ_PROFILER_MODE=help sets all values to defaults.");
LOG( "SPS:");
return;
@ -614,6 +618,8 @@ void mozilla_sampler_start(int aProfileEntries, double aInterval,
const char** aThreadNameFilters, uint32_t aFilterCount)
{
LOG("BEGIN mozilla_sampler_start");
if (!stack_key_initialized)
profiler_init(nullptr);
@ -686,15 +692,20 @@ void mozilla_sampler_start(int aProfileEntries, double aInterval,
nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
if (os)
os->NotifyObservers(nullptr, "profiler-started", nullptr);
LOG("END mozilla_sampler_start");
}
void mozilla_sampler_stop()
{
LOG("BEGIN mozilla_sampler_stop");
if (!stack_key_initialized)
profiler_init(nullptr);
TableTicker *t = tlsTicker.get();
if (!t) {
LOG("END mozilla_sampler_stop-early");
return;
}
@ -732,6 +743,8 @@ void mozilla_sampler_stop()
nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
if (os)
os->NotifyObservers(nullptr, "profiler-stopped", nullptr);
LOG("END mozilla_sampler_stop");
}
bool mozilla_sampler_is_paused() {

View File

@ -54,18 +54,24 @@
#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) \
__android_log_write(ANDROID_LOG_ERROR, "Profiler", text)
do { if (moz_profiler_verbose()) \
__android_log_write(ANDROID_LOG_ERROR, "Profiler", text); \
} while (0)
# define LOGF(format, ...) \
__android_log_print(ANDROID_LOG_ERROR, "Profiler", format, __VA_ARGS__)
do { if (moz_profiler_verbose()) \
__android_log_print(ANDROID_LOG_ERROR, "Profiler", format, \
__VA_ARGS__); \
} while (0)
#else
extern bool moz_profiler_verbose();
# define LOG(text) \
do { if (moz_profiler_verbose()) fprintf(stderr, "Profiler: %s\n", text); \
} while (0)

View File

@ -0,0 +1,50 @@
/* -*- 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 "LulMain.h"
#include "GeckoProfiler.h" // for TracingMetadata
#include "UnwinderThread2.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
logging_sink_for_LUL(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(LUL, 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(logging_sink_for_LUL);
lul->RegisterUnwinderThread();
read_procmaps(lul);
// Run unwind tests and receive information about how many there
// were and how many were successful.
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;
}

View File

@ -0,0 +1,20 @@
# -*- Mode: python; c-basic-offset: 4; 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/.
LIBRARY_NAME = 'sps_test'
if CONFIG['OS_TARGET'] in ('Android', 'Linux'):
UNIFIED_SOURCES += [
'LulTest.cpp',
]
EXPORT_LIBRARY = True
LOCAL_INCLUDES += [
'/tools/profiler',
]
FINAL_LIBRARY = 'xul-gtest'