michael@0: /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ michael@0: /* vim: set ts=8 sts=2 et sw=2 tw=80: */ michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: #ifndef LulMain_h michael@0: #define LulMain_h michael@0: michael@0: #include // pthread_t michael@0: michael@0: #include michael@0: michael@0: #include "LulPlatformMacros.h" michael@0: #include "LulRWLock.h" michael@0: michael@0: // LUL: A Lightweight Unwind Library. michael@0: // This file provides the end-user (external) interface for LUL. michael@0: michael@0: // Some comments about naming in the implementation. These are safe michael@0: // to ignore if you are merely using LUL, but are important if you michael@0: // hack on its internals. michael@0: // michael@0: // Debuginfo readers in general have tended to use the word "address" michael@0: // to mean several different things. This sometimes makes them michael@0: // difficult to understand and maintain. LUL tries hard to avoid michael@0: // using the word "address" and instead uses the following more michael@0: // precise terms: michael@0: // michael@0: // * SVMA ("Stated Virtual Memory Address"): this is an address of a michael@0: // symbol (etc) as it is stated in the symbol table, or other michael@0: // metadata, of an object. Such values are typically small and michael@0: // start from zero or thereabouts, unless the object has been michael@0: // prelinked. michael@0: // michael@0: // * AVMA ("Actual Virtual Memory Address"): this is the address of a michael@0: // symbol (etc) in a running process, that is, once the associated michael@0: // object has been mapped into a process. Such values are typically michael@0: // much larger than SVMAs, since objects can get mapped arbitrarily michael@0: // far along the address space. michael@0: // michael@0: // * "Bias": the difference between AVMA and SVMA for a given symbol michael@0: // (specifically, AVMA - SVMA). The bias is always an integral michael@0: // number of pages. Once we know the bias for a given object's michael@0: // text section (for example), we can compute the AVMAs of all of michael@0: // its text symbols by adding the bias to their SVMAs. michael@0: // michael@0: // * "Image address": typically, to read debuginfo from an object we michael@0: // will temporarily mmap in the file so as to read symbol tables michael@0: // etc. Addresses in this temporary mapping are called "Image michael@0: // addresses". Note that the temporary mapping is entirely michael@0: // unrelated to the mappings of the file that the dynamic linker michael@0: // must perform merely in order to get the program to run. Hence michael@0: // image addresses are unrelated to either SVMAs or AVMAs. michael@0: michael@0: michael@0: namespace lul { michael@0: michael@0: // A machine word plus validity tag. michael@0: class TaggedUWord { michael@0: public: michael@0: // Construct a valid one. michael@0: TaggedUWord(uintptr_t w) michael@0: : mValue(w) michael@0: , mValid(true) michael@0: {} michael@0: michael@0: // Construct an invalid one. michael@0: TaggedUWord() michael@0: : mValue(0) michael@0: , mValid(false) michael@0: {} michael@0: michael@0: // Add in a second one. michael@0: void Add(TaggedUWord other) { michael@0: if (mValid && other.Valid()) { michael@0: mValue += other.Value(); michael@0: } else { michael@0: mValue = 0; michael@0: mValid = false; michael@0: } michael@0: } michael@0: michael@0: // Is it word-aligned? michael@0: bool IsAligned() const { michael@0: return mValid && (mValue & (sizeof(uintptr_t)-1)) == 0; michael@0: } michael@0: michael@0: uintptr_t Value() const { return mValue; } michael@0: bool Valid() const { return mValid; } michael@0: michael@0: private: michael@0: uintptr_t mValue; michael@0: bool mValid; michael@0: }; michael@0: michael@0: michael@0: // The registers, with validity tags, that will be unwound. michael@0: michael@0: struct UnwindRegs { michael@0: #if defined(LUL_ARCH_arm) michael@0: TaggedUWord r7; michael@0: TaggedUWord r11; michael@0: TaggedUWord r12; michael@0: TaggedUWord r13; michael@0: TaggedUWord r14; michael@0: TaggedUWord r15; michael@0: #elif defined(LUL_ARCH_x64) || defined(LUL_ARCH_x86) michael@0: TaggedUWord xbp; michael@0: TaggedUWord xsp; michael@0: TaggedUWord xip; michael@0: #else michael@0: # error "Unknown plat" michael@0: #endif michael@0: }; michael@0: michael@0: michael@0: // The maximum number of bytes in a stack snapshot. This can be michael@0: // increased if necessary, but larger values cost performance, since a michael@0: // stack snapshot needs to be copied between sampling and worker michael@0: // threads for each snapshot. In practice 32k seems to be enough michael@0: // to get good backtraces. michael@0: static const size_t N_STACK_BYTES = 32768; michael@0: michael@0: // The stack chunk image that will be unwound. michael@0: struct StackImage { michael@0: // [start_avma, +len) specify the address range in the buffer. michael@0: // Obviously we require 0 <= len <= N_STACK_BYTES. michael@0: uintptr_t mStartAvma; michael@0: size_t mLen; michael@0: uint8_t mContents[N_STACK_BYTES]; michael@0: }; michael@0: michael@0: michael@0: // The core unwinder library class. Just one of these is needed, and michael@0: // it can be shared by multiple unwinder threads. michael@0: // michael@0: // Access to the library is mediated by a single reader-writer lock. michael@0: // All attempts to change the library's internal shared state -- that michael@0: // is, loading or unloading unwind info -- are forced single-threaded michael@0: // by causing the called routine to acquire a write-lock. Unwind michael@0: // requests do not change the library's internal shared state and michael@0: // therefore require only a read-lock. Hence multiple threads can michael@0: // unwind in parallel. michael@0: // michael@0: // The library needs to maintain state which is private to each michael@0: // unwinder thread -- the CFI (Dwarf Call Frame Information) fast michael@0: // cache. Hence unwinder threads first need to register with the michael@0: // library, so their identities are known. Also, for maximum michael@0: // effectiveness of the CFI caching, it is preferable to have a small michael@0: // number of very-busy unwinder threads rather than a large number of michael@0: // mostly-idle unwinder threads. michael@0: // michael@0: // None of the methods may be safely called from within a signal michael@0: // handler, since this risks deadlock. In particular this means michael@0: // a thread may not unwind itself from within a signal handler michael@0: // frame. It might be safe to call Unwind() on its own stack michael@0: // from not-inside a signal frame, although even that cannot be michael@0: // guaranteed deadlock free. michael@0: michael@0: class PriMap; michael@0: class SegArray; michael@0: class CFICache; michael@0: michael@0: class LUL { michael@0: public: michael@0: // Create; supply a logging sink. Initialises the rw-lock. michael@0: LUL(void (*aLog)(const char*)); michael@0: michael@0: // Destroy. This acquires mRWlock for writing. By doing that, waits michael@0: // for all unwinder threads to finish any Unwind() calls they may be michael@0: // in. All resources are freed and all registered unwinder threads michael@0: // are deregistered. michael@0: ~LUL(); michael@0: michael@0: // Notify of a new r-x mapping, and load the associated unwind info. michael@0: // The filename is strdup'd and used for debug printing. If michael@0: // aMappedImage is NULL, this function will mmap/munmap the file michael@0: // itself, so as to be able to read the unwind info. If michael@0: // aMappedImage is non-NULL then it is assumed to point to a michael@0: // called-supplied and caller-managed mapped image of the file. michael@0: // michael@0: // Acquires mRWlock for writing. This must be called only after the michael@0: // code area in question really has been mapped. michael@0: void NotifyAfterMap(uintptr_t aRXavma, size_t aSize, michael@0: const char* aFileName, const void* aMappedImage); michael@0: michael@0: // In rare cases we know an executable area exists but don't know michael@0: // what the associated file is. This call notifies LUL of such michael@0: // areas. This is important for correct functioning of stack michael@0: // scanning and of the x86-{linux,android} special-case michael@0: // __kernel_syscall function handling. Acquires mRWlock for michael@0: // writing. This must be called only after the code area in michael@0: // question really has been mapped. michael@0: void NotifyExecutableArea(uintptr_t aRXavma, size_t aSize); michael@0: michael@0: // Notify that a mapped area has been unmapped; discard any michael@0: // associated unwind info. Acquires mRWlock for writing. Note that michael@0: // to avoid segfaulting the stack-scan unwinder, which inspects code michael@0: // areas, this must be called before the code area in question is michael@0: // really unmapped. Note that, unlike NotifyAfterMap(), this michael@0: // function takes the start and end addresses of the range to be michael@0: // unmapped, rather than a start and a length parameter. This is so michael@0: // as to make it possible to notify an unmap for the entire address michael@0: // space using a single call. michael@0: void NotifyBeforeUnmap(uintptr_t aAvmaMin, uintptr_t aAvmaMax); michael@0: michael@0: // Apply NotifyBeforeUnmap to the entire address space. This causes michael@0: // LUL to discard all unwind and executable-area information for the michael@0: // entire address space. michael@0: void NotifyBeforeUnmapAll() { michael@0: NotifyBeforeUnmap(0, UINTPTR_MAX); michael@0: } michael@0: michael@0: // Returns the number of mappings currently registered. Acquires michael@0: // mRWlock for writing. michael@0: size_t CountMappings(); michael@0: michael@0: // Register the calling thread for unwinding. Acquires mRWlock for michael@0: // writing. michael@0: void RegisterUnwinderThread(); michael@0: michael@0: // Unwind |aStackImg| starting with the context in |aStartRegs|. michael@0: // Write the number of frames recovered in *aFramesUsed. Put michael@0: // the PC values in aFramePCs[0 .. *aFramesUsed-1] and michael@0: // the SP values in aFrameSPs[0 .. *aFramesUsed-1]. michael@0: // |aFramesAvail| is the size of the two output arrays and hence the michael@0: // largest possible value of *aFramesUsed. PC values are always michael@0: // valid, and the unwind will stop when the PC becomes invalid, but michael@0: // the SP values might be invalid, in which case the value zero will michael@0: // be written in the relevant frameSPs[] slot. michael@0: // michael@0: // Unwinding may optionally use stack scanning. The maximum number michael@0: // of frames that may be recovered by stack scanning is michael@0: // |aScannedFramesAllowed| and the actual number recovered is michael@0: // written into *aScannedFramesAcquired. |aScannedFramesAllowed| michael@0: // must be less than or equal to |aFramesAvail|. michael@0: // michael@0: // This function assumes that the SP values increase as it unwinds michael@0: // away from the innermost frame -- that is, that the stack grows michael@0: // down. It monitors SP values as it unwinds to check they michael@0: // decrease, so as to avoid looping on corrupted stacks. michael@0: // michael@0: // Acquires mRWlock for reading. Hence multiple threads may unwind michael@0: // at once, but no thread may be unwinding whilst the library loads michael@0: // or discards unwind information. Returns false if the calling michael@0: // thread is not registered for unwinding. michael@0: // michael@0: // Up to aScannedFramesAllowed stack-scanned frames may be recovered. michael@0: // michael@0: // The calling thread must previously have registered itself via michael@0: // RegisterUnwinderThread. michael@0: void Unwind(/*OUT*/uintptr_t* aFramePCs, michael@0: /*OUT*/uintptr_t* aFrameSPs, michael@0: /*OUT*/size_t* aFramesUsed, michael@0: /*OUT*/size_t* aScannedFramesAcquired, michael@0: size_t aFramesAvail, michael@0: size_t aScannedFramesAllowed, michael@0: UnwindRegs* aStartRegs, StackImage* aStackImg); michael@0: michael@0: // The logging sink. Call to send debug strings to the caller- michael@0: // specified destination. michael@0: void (*mLog)(const char*); michael@0: michael@0: private: michael@0: // Invalidate the caches. Requires mRWlock to be held for writing; michael@0: // does not acquire it itself. michael@0: void InvalidateCFICaches(); michael@0: michael@0: // The one-and-only lock, a reader-writer lock, for the library. michael@0: LulRWLock* mRWlock; michael@0: michael@0: // The top level mapping from code address ranges to postprocessed michael@0: // unwind info. Basically a sorted array of (addr, len, info) michael@0: // records. Threads wishing to query this field must hold mRWlock michael@0: // for reading. Threads wishing to modify this field must hold michael@0: // mRWlock for writing. This field is updated by NotifyAfterMap and michael@0: // NotifyBeforeUnmap. michael@0: PriMap* mPriMap; michael@0: michael@0: // An auxiliary structure that records which address ranges are michael@0: // mapped r-x, for the benefit of the stack scanner. Threads michael@0: // wishing to query this field must hold mRWlock for reading. michael@0: // Threads wishing to modify this field must hold mRWlock for michael@0: // writing. michael@0: SegArray* mSegArray; michael@0: michael@0: // The thread-local data: a mapping from threads to CFI-fast-caches. michael@0: // Threads wishing to query this field must hold mRWlock for michael@0: // reading. Threads wishing to modify this field must hold mRWlock michael@0: // for writing. michael@0: // michael@0: // The CFICaches themselves are thread-local and can be both read michael@0: // and written when mRWlock is held for reading. It would probably michael@0: // be faster to use the pthread_{set,get}specific functions, but michael@0: // also more difficult. This map is queried once per unwind, in michael@0: // order to get hold of the CFI cache for a given thread. michael@0: std::map mCaches; michael@0: }; michael@0: michael@0: michael@0: // Run unit tests on an initialised, loaded-up LUL instance, and print michael@0: // summary results on |aLUL|'s logging sink. Also return the number michael@0: // of tests run in *aNTests and the number that passed in michael@0: // *aNTestsPassed. michael@0: void michael@0: RunLulUnitTests(/*OUT*/int* aNTests, /*OUT*/int*aNTestsPassed, LUL* aLUL); michael@0: michael@0: } // namespace lul michael@0: michael@0: #endif // LulMain_h