mozglue/baseprofiler/lul/LulMain.h
author Masayuki Nakano <masayuki@d-toybox.com>
Sun, 16 Jan 2022 06:21:17 +0000
changeset 604634 9ef0614a59629916c1e182eb8eda055b0b0e8b32
parent 586273 c70e8287ac4ad49fe2578c0f96cecae993afab54
permissions -rw-r--r--
Bug 1749299 - Make `HTMLEditor::HandleInsertLinefeed()` stop handling it if insertion point cannot have text nodes r=m_kato Ideally, it should not be called when the editor cannot insert new text node. However, the callers are complicated. Therefore, let's check in it for avoiding making the callers more complicated. Fortunately, this is not realistic path for normal web apps. Therefore, the compatibility of the behavior is not matter. That's the reason why this patch does not have a test comparing the result. Differential Revision: https://phabricator.services.mozilla.com/D135826

/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

#ifndef LulMain_h
#define LulMain_h

#include "PlatformMacros.h"
#include "mozilla/Atomics.h"
#include "mozilla/MemoryReporting.h"
#include "mozilla/BaseProfilerUtils.h"

// LUL: A Lightweight Unwind Library.
// This file provides the end-user (external) interface for LUL.

// Some comments about naming in the implementation.  These are safe
// to ignore if you are merely using LUL, but are important if you
// hack on its internals.
//
// Debuginfo readers in general have tended to use the word "address"
// to mean several different things.  This sometimes makes them
// difficult to understand and maintain.  LUL tries hard to avoid
// using the word "address" and instead uses the following more
// precise terms:
//
// * SVMA ("Stated Virtual Memory Address"): this is an address of a
//   symbol (etc) as it is stated in the symbol table, or other
//   metadata, of an object.  Such values are typically small and
//   start from zero or thereabouts, unless the object has been
//   prelinked.
//
// * AVMA ("Actual Virtual Memory Address"): this is the address of a
//   symbol (etc) in a running process, that is, once the associated
//   object has been mapped into a process.  Such values are typically
//   much larger than SVMAs, since objects can get mapped arbitrarily
//   far along the address space.
//
// * "Bias": the difference between AVMA and SVMA for a given symbol
//   (specifically, AVMA - SVMA).  The bias is always an integral
//   number of pages.  Once we know the bias for a given object's
//   text section (for example), we can compute the AVMAs of all of
//   its text symbols by adding the bias to their SVMAs.
//
// * "Image address": typically, to read debuginfo from an object we
//   will temporarily mmap in the file so as to read symbol tables
//   etc.  Addresses in this temporary mapping are called "Image
//   addresses".  Note that the temporary mapping is entirely
//   unrelated to the mappings of the file that the dynamic linker
//   must perform merely in order to get the program to run.  Hence
//   image addresses are unrelated to either SVMAs or AVMAs.

namespace lul {

// A machine word plus validity tag.
class TaggedUWord {
 public:
  // RUNS IN NO-MALLOC CONTEXT
  // Construct a valid one.
  explicit TaggedUWord(uintptr_t w) : mValue(w), mValid(true) {}

  // RUNS IN NO-MALLOC CONTEXT
  // Construct an invalid one.
  TaggedUWord() : mValue(0), mValid(false) {}

  // RUNS IN NO-MALLOC CONTEXT
  TaggedUWord operator+(TaggedUWord rhs) const {
    return (Valid() && rhs.Valid()) ? TaggedUWord(Value() + rhs.Value())
                                    : TaggedUWord();
  }

  // RUNS IN NO-MALLOC CONTEXT
  TaggedUWord operator-(TaggedUWord rhs) const {
    return (Valid() && rhs.Valid()) ? TaggedUWord(Value() - rhs.Value())
                                    : TaggedUWord();
  }

  // RUNS IN NO-MALLOC CONTEXT
  TaggedUWord operator&(TaggedUWord rhs) const {
    return (Valid() && rhs.Valid()) ? TaggedUWord(Value() & rhs.Value())
                                    : TaggedUWord();
  }

  // RUNS IN NO-MALLOC CONTEXT
  TaggedUWord operator|(TaggedUWord rhs) const {
    return (Valid() && rhs.Valid()) ? TaggedUWord(Value() | rhs.Value())
                                    : TaggedUWord();
  }

  // RUNS IN NO-MALLOC CONTEXT
  TaggedUWord CmpGEs(TaggedUWord rhs) const {
    if (Valid() && rhs.Valid()) {
      intptr_t s1 = (intptr_t)Value();
      intptr_t s2 = (intptr_t)rhs.Value();
      return TaggedUWord(s1 >= s2 ? 1 : 0);
    }
    return TaggedUWord();
  }

  // RUNS IN NO-MALLOC CONTEXT
  TaggedUWord operator<<(TaggedUWord rhs) const {
    if (Valid() && rhs.Valid()) {
      uintptr_t shift = rhs.Value();
      if (shift < 8 * sizeof(uintptr_t)) return TaggedUWord(Value() << shift);
    }
    return TaggedUWord();
  }

  // RUNS IN NO-MALLOC CONTEXT
  // Is equal?  Note: non-validity on either side gives non-equality.
  bool operator==(TaggedUWord other) const {
    return (mValid && other.Valid()) ? (mValue == other.Value()) : false;
  }

  // RUNS IN NO-MALLOC CONTEXT
  // Is it word-aligned?
  bool IsAligned() const {
    return mValid && (mValue & (sizeof(uintptr_t) - 1)) == 0;
  }

  // RUNS IN NO-MALLOC CONTEXT
  uintptr_t Value() const { return mValue; }

  // RUNS IN NO-MALLOC CONTEXT
  bool Valid() const { return mValid; }

 private:
  uintptr_t mValue;
  bool mValid;
};

// The registers, with validity tags, that will be unwound.

struct UnwindRegs {
#if defined(GP_ARCH_arm)
  TaggedUWord r7;
  TaggedUWord r11;
  TaggedUWord r12;
  TaggedUWord r13;
  TaggedUWord r14;
  TaggedUWord r15;
#elif defined(GP_ARCH_arm64)
  TaggedUWord x29;
  TaggedUWord x30;
  TaggedUWord sp;
  TaggedUWord pc;
#elif defined(GP_ARCH_amd64) || defined(GP_ARCH_x86)
  TaggedUWord xbp;
  TaggedUWord xsp;
  TaggedUWord xip;
#elif defined(GP_ARCH_mips64)
  TaggedUWord sp;
  TaggedUWord fp;
  TaggedUWord pc;
#else
#  error "Unknown plat"
#endif
};

// The maximum number of bytes in a stack snapshot.  This value can be increased
// if necessary, but testing showed that 160k is enough to obtain good
// backtraces on x86_64 Linux.  Most backtraces fit comfortably into 4-8k of
// stack space, but we do have some very deep stacks occasionally.  Please see
// the comments in DoNativeBacktrace as to why it's OK to have this value be so
// large.
static const size_t N_STACK_BYTES = 160 * 1024;

// The stack chunk image that will be unwound.
struct StackImage {
  // [start_avma, +len) specify the address range in the buffer.
  // Obviously we require 0 <= len <= N_STACK_BYTES.
  uintptr_t mStartAvma;
  size_t mLen;
  uint8_t mContents[N_STACK_BYTES];
};

// Statistics collection for the unwinder.
template <typename T>
class LULStats {
 public:
  LULStats() : mContext(0), mCFI(0), mFP(0) {}

  template <typename S>
  explicit LULStats(const LULStats<S>& aOther)
      : mContext(aOther.mContext), mCFI(aOther.mCFI), mFP(aOther.mFP) {}

  template <typename S>
  LULStats<T>& operator=(const LULStats<S>& aOther) {
    mContext = aOther.mContext;
    mCFI = aOther.mCFI;
    mFP = aOther.mFP;
    return *this;
  }

  template <typename S>
  uint32_t operator-(const LULStats<S>& aOther) {
    return (mContext - aOther.mContext) + (mCFI - aOther.mCFI) +
           (mFP - aOther.mFP);
  }

  T mContext;  // Number of context frames
  T mCFI;      // Number of CFI/EXIDX frames
  T mFP;       // Number of frame-pointer recovered frames
};

// The core unwinder library class.  Just one of these is needed, and
// it can be shared by multiple unwinder threads.
//
// The library operates in one of two modes.
//
// * Admin mode.  The library is this state after creation.  In Admin
//   mode, no unwinding may be performed.  It is however allowable to
//   perform administrative tasks -- primarily, loading of unwind info
//   -- in this mode.  In particular, it is safe for the library to
//   perform dynamic memory allocation in this mode.  Safe in the
//   sense that there is no risk of deadlock against unwinding threads
//   that might -- because of where they have been sampled -- hold the
//   system's malloc lock.
//
// * Unwind mode.  In this mode, calls to ::Unwind may be made, but
//   nothing else.  ::Unwind guarantees not to make any dynamic memory
//   requests, so as to guarantee that the calling thread won't
//   deadlock in the case where it already holds the system's malloc lock.
//
// The library is created in Admin mode.  After debuginfo is loaded,
// the caller must switch it into Unwind mode by calling
// ::EnableUnwinding.  There is no way to switch it back to Admin mode
// after that.  To safely switch back to Admin mode would require the
// caller (or other external agent) to guarantee that there are no
// pending ::Unwind calls.

class PriMap;
class SegArray;
class UniqueStringUniverse;

class LUL {
 public:
  // Create; supply a logging sink.  Sets the object in Admin mode.
  explicit LUL(void (*aLog)(const char*));

  // Destroy.  Caller is responsible for ensuring that no other
  // threads are in Unwind calls.  All resources are freed and all
  // registered unwinder threads are deregistered.  Can be called
  // either in Admin or Unwind mode.
  ~LUL();

  // Notify the library that unwinding is now allowed and so
  // admin-mode calls are no longer allowed.  The object is initially
  // created in admin mode.  The only possible transition is
  // admin->unwinding, therefore.
  void EnableUnwinding();

  // Notify of a new r-x mapping, and load the associated unwind info.
  // The filename is strdup'd and used for debug printing.  If
  // aMappedImage is NULL, this function will mmap/munmap the file
  // itself, so as to be able to read the unwind info.  If
  // aMappedImage is non-NULL then it is assumed to point to a
  // called-supplied and caller-managed mapped image of the file.
  // May only be called in Admin mode.
  void NotifyAfterMap(uintptr_t aRXavma, size_t aSize, const char* aFileName,
                      const void* aMappedImage);

  // In rare cases we know an executable area exists but don't know
  // what the associated file is.  This call notifies LUL of such
  // areas.  This is important for correct functioning of stack
  // scanning and of the x86-{linux,android} special-case
  // __kernel_syscall function handling.
  // This must be called only after the code area in
  // question really has been mapped.
  // May only be called in Admin mode.
  void NotifyExecutableArea(uintptr_t aRXavma, size_t aSize);

  // Notify that a mapped area has been unmapped; discard any
  // associated unwind info.  Acquires mRWlock for writing.  Note that
  // to avoid segfaulting the stack-scan unwinder, which inspects code
  // areas, this must be called before the code area in question is
  // really unmapped.  Note that, unlike NotifyAfterMap(), this
  // function takes the start and end addresses of the range to be
  // unmapped, rather than a start and a length parameter.  This is so
  // as to make it possible to notify an unmap for the entire address
  // space using a single call.
  // May only be called in Admin mode.
  void NotifyBeforeUnmap(uintptr_t aAvmaMin, uintptr_t aAvmaMax);

  // Apply NotifyBeforeUnmap to the entire address space.  This causes
  // LUL to discard all unwind and executable-area information for the
  // entire address space.
  // May only be called in Admin mode.
  void NotifyBeforeUnmapAll() { NotifyBeforeUnmap(0, UINTPTR_MAX); }

  // Returns the number of mappings currently registered.
  // May only be called in Admin mode.
  size_t CountMappings();

  // Unwind |aStackImg| starting with the context in |aStartRegs|.
  // Write the number of frames recovered in *aFramesUsed.  Put
  // the PC values in aFramePCs[0 .. *aFramesUsed-1] and
  // the SP values in aFrameSPs[0 .. *aFramesUsed-1].
  // |aFramesAvail| is the size of the two output arrays and hence the
  // largest possible value of *aFramesUsed.  PC values are always
  // valid, and the unwind will stop when the PC becomes invalid, but
  // the SP values might be invalid, in which case the value zero will
  // be written in the relevant frameSPs[] slot.
  //
  // This function assumes that the SP values increase as it unwinds
  // away from the innermost frame -- that is, that the stack grows
  // down.  It monitors SP values as it unwinds to check they
  // decrease, so as to avoid looping on corrupted stacks.
  //
  // May only be called in Unwind mode.  Multiple threads may unwind
  // at once.  LUL user is responsible for ensuring that no thread makes
  // any Admin calls whilst in Unwind mode.
  // MOZ_CRASHes if the calling thread is not registered for unwinding.
  //
  // The calling thread must previously have been registered via a call to
  // RegisterSampledThread.
  void Unwind(/*OUT*/ uintptr_t* aFramePCs,
              /*OUT*/ uintptr_t* aFrameSPs,
              /*OUT*/ size_t* aFramesUsed,
              /*OUT*/ size_t* aFramePointerFramesAcquired, size_t aFramesAvail,
              UnwindRegs* aStartRegs, StackImage* aStackImg);

  // The logging sink.  Call to send debug strings to the caller-
  // specified destination.  Can only be called by the Admin thread.
  void (*mLog)(const char*);

  // Statistics relating to unwinding.  These have to be atomic since
  // unwinding can occur on different threads simultaneously.
  LULStats<mozilla::Atomic<uint32_t>> mStats;

  // Possibly show the statistics.  This may not be called from any
  // registered sampling thread, since it involves I/O.
  void MaybeShowStats();

  size_t SizeOfIncludingThis(mozilla::MallocSizeOf) const;

 private:
  // The statistics counters at the point where they were last printed.
  LULStats<uint32_t> mStatsPrevious;

  // Are we in admin mode?  Initially |true| but changes to |false|
  // once unwinding begins.
  bool mAdminMode;

  // The thread ID associated with admin mode.  This is the only thread
  // that is allowed do perform non-Unwind calls on this object.  Conversely,
  // no registered Unwinding thread may be the admin thread.  This is so
  // as to clearly partition the one thread that may do dynamic memory
  // allocation from the threads that are being sampled, since the latter
  // absolutely may not do dynamic memory allocation.
  mozilla::baseprofiler::BaseProfilerThreadId mAdminThreadId;

  // The top level mapping from code address ranges to postprocessed
  // unwind info.  Basically a sorted array of (addr, len, info)
  // records.  This field is updated by NotifyAfterMap and NotifyBeforeUnmap.
  PriMap* mPriMap;

  // An auxiliary structure that records which address ranges are
  // mapped r-x, for the benefit of the stack scanner.
  SegArray* mSegArray;

  // A UniqueStringUniverse that holds all the strdup'd strings created
  // whilst reading unwind information.  This is included so as to make
  // it possible to free them in ~LUL.
  UniqueStringUniverse* mUSU;
};

// Run unit tests on an initialised, loaded-up LUL instance, and print
// summary results on |aLUL|'s logging sink.  Also return the number
// of tests run in *aNTests and the number that passed in
// *aNTestsPassed.
void RunLulUnitTests(/*OUT*/ int* aNTests, /*OUT*/ int* aNTestsPassed,
                     LUL* aLUL);

}  // namespace lul

#endif  // LulMain_h