Просмотр исходного кода

enhance TraceRing, enable xplat STACK_BACK_TRACE

Make diagnostic helper TraceRing (renamed from StackBackTraceRing)
thread safe and strongly typed so it is easier to navigate to any frame
and easier to read frame data in debugger (windbg/gdb).

Use backtrace()/backtrace_symbols() to support STACK_BACK_TRACE on xplat.
Jianchun Xu 9 лет назад
Родитель
Сommit
a9822845ba

+ 2 - 3
lib/Common/CommonDefines.h

@@ -663,12 +663,9 @@
 #define ENABLE_TRACE
 #endif
 
-// xplat-todo: Capture stack backtrace on non-win32 platforms
-#ifdef _WIN32
 #if DBG || defined(CHECK_MEMORY_LEAK) || defined(LEAK_REPORT) || defined(TRACK_DISPATCH) || defined(ENABLE_TRACE) || defined(RECYCLER_PAGE_HEAP)
 #define STACK_BACK_TRACE
 #endif
-#endif
 
 // ENABLE_DEBUG_STACK_BACK_TRACE is for capturing stack back trace for debug only.
 // (STACK_BACK_TRACE is enabled on release build, used by RECYCLER_PAGE_HEAP.)
@@ -677,8 +674,10 @@
 #endif
 
 #if defined(STACK_BACK_TRACE) || defined(CONTROL_FLOW_GUARD_LOGGER)
+#ifdef _WIN32
 #define DBGHELP_SYMBOL_MANAGER
 #endif
+#endif
 
 #if defined(TRACK_DISPATCH) || defined(CHECK_MEMORY_LEAK) || defined(LEAK_REPORT)
 #define TRACK_JS_DISPATCH

+ 23 - 2
lib/Common/Core/StackBackTrace.cpp

@@ -34,10 +34,11 @@ ULONG StackBackTrace::Capture(ULONG framesToSkip)
 size_t
 StackBackTrace::Print()
 {
-    DbgHelpSymbolManager::EnsureInitialized();
-
     size_t retValue = 0;
 
+#ifdef DBGHELP_SYMBOL_MANAGER
+    DbgHelpSymbolManager::EnsureInitialized();
+
     for(ULONG i = 0; i < this->framesCount; i++)
     {
         PVOID address = this->stackBackTrace[i];
@@ -45,8 +46,28 @@ StackBackTrace::Print()
         retValue += DbgHelpSymbolManager::PrintSymbol(address);
         retValue += Output::Print(_u("\n"));
     }
+#else
+    char** f = backtrace_symbols(this->stackBackTrace, this->framesCount);
+    if (f)
+    {
+        for (ULONG i = 0; i < this->framesCount; i++)
+        {
+            retValue += Output::Print(_u(" %S\n"), f[i]);
+        }
+        free(f);
+    }
+#endif
 
     retValue += Output::Print(_u("\n"));
     return retValue;
 }
+
+#if ENABLE_DEBUG_CONFIG_OPTIONS
+static uint _s_trace_ring_id = 0;
+uint _trace_ring_next_id()
+{
+    return InterlockedIncrement(&_s_trace_ring_id);
+}
+#endif  // ENABLE_DEBUG_CONFIG_OPTIONS
+
 #endif

+ 93 - 96
lib/Common/Core/StackBackTrace.h

@@ -5,6 +5,13 @@
 #pragma once
 
 #ifdef STACK_BACK_TRACE
+
+#ifndef _WIN32
+#include <execinfo.h>
+#define CaptureStackBackTrace(FramesToSkip, FramesToCapture, buffer, hash) \
+    backtrace(buffer, FramesToCapture)
+#endif  // !_WIN32
+
 class StackBackTrace
 {
 public:
@@ -119,136 +126,126 @@ private:
     StackBackTraceNode * next;
 };
 
+
+//
+// In memory TraceRing
+//
+
+uint _trace_ring_next_id();
+
 //
-// A buffer of requested "size", dynamically allocated or statically embedded.
+// A buffer of size "T[count]", dynamically allocated (!useStatic) or
+// statically embedded (useStatic).
 //
-template <ULONG size, bool useStatic>
-struct _SimpleBuffer
+template <class T, LONG count, bool useStatic>
+struct _TraceRingBuffer
+{
+    T* buf;
+    _TraceRingBuffer() { buf = new T[count]; }
+    ~_TraceRingBuffer() { delete[] buf; }
+};
+template <class T, LONG count>
+struct _TraceRingBuffer<T, count, true>
 {
-    BYTE* _buf;
-    _SimpleBuffer() { _buf = new BYTE[size]; }
-    ~_SimpleBuffer() { delete[] _buf; }
+    T buf[count];
 };
-template <ULONG size>
-struct _SimpleBuffer<size, true>
+
+//
+// A trace ring frame, consisting of id, header, and optionally stack
+//
+template <class Header, uint STACK_FRAMES>
+struct _TraceRingFrame
 {
-    BYTE _buf[size];
+    uint id;  // unique id in order
+    Header header;
+    void* stack[STACK_FRAMES];
 };
 
 //
-// Capture multiple call stack traces using an in-memory ring buffer. Useful for instrumenting source
-// code to track calls.
+// Trace code execution using an in-memory ring buffer. Capture each trace
+// point with a frame, which contains of custom data and optional stack trace.
+// Useful for instrumenting source code to track execution.
 //
-//  BUFFERS:    Number of stack traces to keep. When all the buffers are filled up, capture will start
-//              over from the beginning and overwrite older traces.
-//  HEADER:     Number of pointer-sized data reserved in the header of each trace. You can save runtime
-//              data in the header of each trace to record runtime state of the stack trace.
-//  FRAMES:     Number of stack frames for each trace.
-//              This can be 0, only captures header data without stack.
-//  SKIPFRAMES: Top frames to skip for each capture. e.g., at least the "StackBackTraceRing::Capture"
-//              frame is useless.
+//  Header:     Custom header data type to capture in each frame. Used to
+//              capture execution state at a trace point.
+//  COUNT:      Number of frames to keep in the ring buffer. When the buffer
+//              is filled up, capture will start over from the beginning.
+//  STACK_FRAMES:
+//              Number of stack frames to capture for each trace frame.
+//              This can be 0, only captures header data without stack trace.
 //  USE_STATIC_BUFFER:
-//              Use embedded buffer instead of dynamically allocate. This may be helpful to avoid
-//              initialization problem when global static StackBackTraceRing.
+//              Use embedded buffer instead of dynamically allocate. This may
+//              be helpful to avoid static data initialization problem.
+//  SKIP_TOP_FRAMES:
+//              Top stack frames to skip for each capture (only on _WIN32).
 //
-//  Usage: Following captures the last 100 stacks that changes scriptContext->debuggerMode:
-//      Declare an instance:                            StackBackTraceRing<100> s_debuggerMode;
-//      Call at every debuggerMode change point:        s_debugModeTrace.Capture(scriptContext, debuggerMode);
+//  Usage: Following captures the last 100 stacks that changes
+//  scriptContext->debuggerMode:
+//          struct DebugModeChange {
+//              ScriptContext* scriptContext;
+//              DebuggerMode debuggerMode;
+//          };
+//          static TraceRing<DebugModeChange, 100> s_ev;
+//      Call at every debuggerMode change point:
+//          DebugModeChange e = { scriptContext, debuggerMode };
+//          s_ev.Capture(e);
 //
-//      x86:
-//      Debug a dump in windbg:     ?? chakra!Js::s_debuggerMode
-//      Inspect trace 0:            dds [buf]
-//      Inspect trace N:            dds [buf]+0n32*4*N
-//      Inspect last trace:         dds [buf]+0n32*4*[cur-1]
+//  Examine trace frame i with its call stack in debugger:
+//  gdb:
+//      p s_ev.buf[i]
+//  windbg:
+//      ?? &s_ev.buf[i]
+//      dds/dqs [above address]
 //
-template <ULONG BUFFERS, ULONG HEADER = 2, ULONG FRAMES = 30, ULONG SKIPFRAMES = 1,
-          bool USE_STATIC_BUFFER = false>
-class StackBackTraceRing
+template <class Header, LONG COUNT,
+          uint STACK_FRAMES = 30,
+          bool USE_STATIC_BUFFER = false,
+          uint SKIP_TOP_FRAMES = 1>
+class TraceRing:
+    protected _TraceRingBuffer<_TraceRingFrame<Header, STACK_FRAMES>, COUNT, USE_STATIC_BUFFER>
 {
-    static const ULONG ONE_TRACE = HEADER + FRAMES;
-
 protected:
-    _SimpleBuffer<sizeof(LPVOID) * ONE_TRACE * BUFFERS, USE_STATIC_BUFFER> _simple_buf;
-    ULONG cur;
+    LONG cur;
 
 public:
-    StackBackTraceRing()
+    TraceRing()
     {
-        cur = 0;
+        cur = (uint)-1;
     }
 
     template <class HeaderFunc>
-    void CaptureWithHeader(HeaderFunc writeHeader)
+    void Capture(const HeaderFunc& writeHeader)
     {
-        cur = cur % BUFFERS;
-        LPVOID* buffer = reinterpret_cast<LPVOID*>(_simple_buf._buf) + ONE_TRACE * cur++;
-        cur = cur % BUFFERS;
-
-        memset(buffer, 0, sizeof(LPVOID) * ONE_TRACE);
-        writeHeader(buffer);
-
-        if (FRAMES > 0)
+        LONG i = InterlockedIncrement(&cur);
+        if (i >= COUNT)
         {
-            LPVOID* frames = &buffer[HEADER];
-            CaptureStackBackTrace(SKIPFRAMES, FRAMES, frames, nullptr);
+            InterlockedCompareExchange(&cur, i % COUNT, i);
+            i %= COUNT;
         }
-    }
 
-    // Capture a stack trace
-    void Capture()
-    {
-        CaptureWithHeader([](LPVOID* buffer)
+        auto* frame = &this->buf[i];
+        *frame = {};
+        frame->id = _trace_ring_next_id();
+        writeHeader(&frame->header);
+        if (STACK_FRAMES > 0)
         {
-        });
+            CaptureStackBackTrace(SKIP_TOP_FRAMES, STACK_FRAMES, frame->stack, nullptr);
+        }
     }
 
-    // Capture a stack trace and save data0 in header
-    template <class T0>
-    void Capture(T0 data0)
+    void Capture(const Header& header)
     {
-        C_ASSERT(HEADER >= 1);
-
-        CaptureWithHeader([=](_Out_writes_(HEADER) LPVOID* buffer)
+        Capture([&](Header* h)
         {
-            buffer[0] = reinterpret_cast<LPVOID>(data0);
+            *h = header;
         });
     }
 
-    // Capture a stack trace and save data0 and data1 in header
-    template <class T0, class T1>
-    void Capture(T0 data0, T1 data1)
+    // Capture a trace (no header data, stack only)
+    void Capture()
     {
-        C_ASSERT(HEADER >= 2);
-
-        CaptureWithHeader([=](_Out_writes_(HEADER) LPVOID* buffer)
-        {
-            buffer[0] = reinterpret_cast<LPVOID>(data0);
-            buffer[1] = reinterpret_cast<LPVOID>(data1);
-        });
-    }
-
-    template <class T0, class T1, class T2>
-    void Capture(T0 data0, T1 data1, T2 data2) {
-      C_ASSERT(HEADER >= 3);
-
-      CaptureWithHeader([=](_Out_writes_(HEADER) LPVOID* buffer) {
-        buffer[0] = reinterpret_cast<LPVOID>(data0);
-        buffer[1] = reinterpret_cast<LPVOID>(data1);
-        buffer[2] = reinterpret_cast<LPVOID>(data2);
-      });
-    }
-
-    template <class T0, class T1, class T2, class T3>
-    void Capture(T0 data0, T1 data1, T2 data2, T3 data3) {
-      C_ASSERT(HEADER >= 4);
-
-      CaptureWithHeader([=](_Out_writes_(HEADER) LPVOID* buffer) {
-        buffer[0] = reinterpret_cast<LPVOID>(data0);
-        buffer[1] = reinterpret_cast<LPVOID>(data1);
-        buffer[2] = reinterpret_cast<LPVOID>(data2);
-        buffer[3] = reinterpret_cast<LPVOID>(data3);
-      });
+        Capture([&](Header* h) { });
     }
 };
 
-#endif
+#endif  // STACK_BACK_TRACE

+ 1 - 1
lib/Common/Exceptions/Throw.cpp

@@ -253,7 +253,7 @@ namespace Js {
     {
         IsInAssert = true;
 
-#ifdef STACK_BACK_TRACE
+#if defined(GENERATE_DUMP) && defined(STACK_BACK_TRACE)
         // This should be the last thing to happen in the process. Therefore, leaks are not an issue.
         stackBackTrace = StackBackTrace::Capture(&NoCheckHeapAllocator::Instance, Throw::StackToSkip, Throw::StackTraceDepth);
 #endif