Support symbolizer and demangler on Windows
authorAndrew Schwartzmeyer <andrew@schwartzmeyer.com>
Sat, 4 Mar 2017 00:50:08 +0000 (16:50 -0800)
committerAndrew Schwartzmeyer <andrew@schwartzmeyer.com>
Tue, 27 Jun 2017 17:50:20 +0000 (10:50 -0700)
CMakeLists.txt
src/demangle.cc
src/demangle_unittest.cc
src/stacktrace_windows-inl.h
src/symbolize.cc
src/symbolize_unittest.cc
src/utilities.h

index d353c00..b8f8bd8 100644 (file)
@@ -457,6 +457,7 @@ endif (HAVE_EXECINFO_H)
 
 if (WIN32)
   set (HAVE_STACKTRACE 1)
+  set (HAVE_SYMBOLIZE 1)
   target_link_libraries (glog PUBLIC Dbghelp.lib)
 endif (WIN32)
 
index e858181..7852043 100644 (file)
 // Note that we only have partial C++0x support yet.
 
 #include <stdio.h>  // for NULL
+#include "utilities.h"
 #include "demangle.h"
 
+#if defined(OS_WINDOWS)
+#include <DbgHelp.h>
+#endif
+
 _START_GOOGLE_NAMESPACE_
 
+#if !defined(OS_WINDOWS)
 typedef struct {
   const char *abbrev;
   const char *real_name;
@@ -1293,12 +1299,37 @@ static bool ParseTopLevelMangledName(State *state) {
   }
   return false;
 }
+#endif
 
 // The demangler entry point.
 bool Demangle(const char *mangled, char *out, int out_size) {
+#if defined(OS_WINDOWS)
+  // When built with incremental linking, the Windows debugger
+  // library provides a more complicated `Symbol->Name` with the
+  // Incremental Linking Table offset, which looks like
+  // `@ILT+1105(?func@Foo@@SAXH@Z)`. However, the demangler expects
+  // only the mangled symbol, `?func@Foo@@SAXH@Z`. Fortunately, the
+  // mangled symbol is guaranteed not to have parentheses,
+  // so we search for `(` and extract up to `)`.
+  //
+  // Since we may be in a signal handler here, we cannot use `std::string`.
+  char buffer[1024];  // Big enough for a sane symbol.
+  const char *lparen = strchr(mangled, '(');
+  if (lparen) {
+    // Extract the string `(?...)`
+    const char *rparen = strchr(lparen, ')');
+    size_t length = rparen - lparen - 1;
+    strncpy(buffer, lparen + 1, length);
+    buffer[length] = '\0';
+    mangled = buffer;
+  } // Else the symbol wasn't inside a set of parentheses
+  // We use the ANSI version to ensure the string type is always `char *`.
+  return UnDecorateSymbolName(mangled, out, out_size, UNDNAME_COMPLETE);
+#else
   State state;
   InitState(&state, mangled, out, out_size);
   return ParseTopLevelMangledName(&state) && !state.overflowed;
+#endif
 }
 
 _END_GOOGLE_NAMESPACE_
index 32f3221..be48341 100644 (file)
@@ -62,18 +62,37 @@ static const char *DemangleIt(const char * const mangled) {
   }
 }
 
+#if defined(OS_WINDOWS)
+
+TEST(Demangle, Windows) {
+  EXPECT_STREQ(
+    "public: static void __cdecl Foo::func(int)",
+    DemangleIt("?func@Foo@@SAXH@Z"));
+  EXPECT_STREQ(
+    "public: static void __cdecl Foo::func(int)",
+    DemangleIt("@ILT+1105(?func@Foo@@SAXH@Z)"));
+  EXPECT_STREQ(
+    "int __cdecl foobarArray(int * const)",
+    DemangleIt("?foobarArray@@YAHQAH@Z"));
+}
+
+#else
+
 // Test corner cases of bounary conditions.
 TEST(Demangle, CornerCases) {
-  char tmp[10];
-  EXPECT_TRUE(Demangle("_Z6foobarv", tmp, sizeof(tmp)));
-  // sizeof("foobar()") == 9
-  EXPECT_STREQ("foobar()", tmp);
-  EXPECT_TRUE(Demangle("_Z6foobarv", tmp, 9));
-  EXPECT_STREQ("foobar()", tmp);
-  EXPECT_FALSE(Demangle("_Z6foobarv", tmp, 8));  // Not enough.
-  EXPECT_FALSE(Demangle("_Z6foobarv", tmp, 1));
-  EXPECT_FALSE(Demangle("_Z6foobarv", tmp, 0));
-  EXPECT_FALSE(Demangle("_Z6foobarv", NULL, 0));  // Should not cause SEGV.
+  const size_t size = 10;
+  char tmp[size] = { 0 };
+  const char *demangled = "foobar()";
+  const char *mangled = "_Z6foobarv";
+  EXPECT_TRUE(Demangle(mangled, tmp, sizeof(tmp)));
+  // sizeof("foobar()") == size - 1
+  EXPECT_STREQ(demangled, tmp);
+  EXPECT_TRUE(Demangle(mangled, tmp, size - 1));
+  EXPECT_STREQ(demangled, tmp);
+  EXPECT_FALSE(Demangle(mangled, tmp, size - 2));  // Not enough.
+  EXPECT_FALSE(Demangle(mangled, tmp, 1));
+  EXPECT_FALSE(Demangle(mangled, tmp, 0));
+  EXPECT_FALSE(Demangle(mangled, NULL, 0));  // Should not cause SEGV.
 }
 
 // Test handling of functions suffixed with .clone.N, which is used by GCC
@@ -123,6 +142,8 @@ TEST(Demangle, FromFile) {
   }
 }
 
+#endif
+
 int main(int argc, char **argv) {
 #ifdef HAVE_LIB_GFLAGS
   ParseCommandLineFlags(&argc, &argv, true);
index 5606408..f329a7c 100644 (file)
 
 #include "port.h"
 #include "stacktrace.h"
-#include "Dbghelp.h"
-#include <vector>
+#include <DbgHelp.h>
 
 _START_GOOGLE_NAMESPACE_
 
-static bool ready_to_run = false;
-class StackTraceInit {
-public:
-  HANDLE hProcess;
-  StackTraceInit() {
-    // Initialize the symbol handler
-    // https://msdn.microsoft.com/en-us/library/windows/desktop/ms680344(v=vs.85).aspx
-    hProcess = GetCurrentProcess();
-    SymSetOptions(SYMOPT_UNDNAME | SYMOPT_DEFERRED_LOADS);
-    SymInitialize(hProcess, NULL, true);
-    ready_to_run = true;
-  }
-  ~StackTraceInit() {
-    SymCleanup(hProcess);
-    ready_to_run = false;
-  }
-};
-
-static const StackTraceInit module_initializer;  // Force initialization
-
 // If you change this function, also change GetStackFrames below.
 int GetStackTrace(void** result, int max_depth, int skip_count) {
-  if (!ready_to_run) {
-    return 0;
-  }
-  skip_count++;  // we want to skip the current frame as well
   if (max_depth > 64) {
     max_depth = 64;
   }
-  std::vector<void*> stack(max_depth);
+  skip_count++;  // we want to skip the current frame as well
   // This API is thread-safe (moreover it walks only the current thread).
-  int size = CaptureStackBackTrace(skip_count, max_depth, &stack[0], NULL);
-  for (int i = 0; i < size; ++i) {
-    // Resolve symbol information from address.
-    char buffer[sizeof(SYMBOL_INFO) + MAX_SYM_NAME * sizeof(TCHAR)];
-    SYMBOL_INFO* symbol = reinterpret_cast<SYMBOL_INFO*>(buffer);
-    symbol->SizeOfStruct = sizeof(SYMBOL_INFO);
-    symbol->MaxNameLen = MAX_SYM_NAME;
-    SymFromAddr(module_initializer.hProcess, reinterpret_cast<DWORD64>(stack[i]), 0, symbol);
-    result[i] = stack[i];
-  }
-
-  return size;
+  return CaptureStackBackTrace(skip_count, max_depth, result, NULL);
 }
 
 _END_GOOGLE_NAMESPACE_
index f83c309..24dcddc 100644 (file)
@@ -837,6 +837,66 @@ static ATTRIBUTE_NOINLINE bool SymbolizeAndDemangle(void *pc, char *out,
 
 _END_GOOGLE_NAMESPACE_
 
+#elif defined(OS_WINDOWS)
+
+#include <DbgHelp.h>
+
+_START_GOOGLE_NAMESPACE_
+
+class SymInitializer {
+public:
+  HANDLE process = NULL;
+  bool ready = false;
+  SymInitializer() {
+    // Initialize the symbol handler.
+    // https://msdn.microsoft.com/en-us/library/windows/desktop/ms680344(v=vs.85).aspx
+    process = GetCurrentProcess();
+    // Defer symbol loading.
+    // We do not request undecorated symbols with SYMOPT_UNDNAME
+    // because the mangling library calls UnDecorateSymbolName.
+    SymSetOptions(SYMOPT_DEFERRED_LOADS);
+    if (SymInitialize(process, NULL, true)) {
+      ready = true;
+    }
+  }
+  ~SymInitializer() {
+    SymCleanup(process);
+    // We do not need to close `HANDLE process` because it's a "pseudo handle."
+  }
+private:
+  SymInitializer(const SymInitializer&);
+  SymInitializer& operator=(const SymInitializer&);
+};
+
+static ATTRIBUTE_NOINLINE bool SymbolizeAndDemangle(void *pc, char *out,
+                                                      int out_size) {
+  const static SymInitializer symInitializer;
+  if (!symInitializer.ready) {
+    return false;
+  }
+  // Resolve symbol information from address.
+  // https://msdn.microsoft.com/en-us/library/windows/desktop/ms680578(v=vs.85).aspx
+  char buf[sizeof(SYMBOL_INFO) + MAX_SYM_NAME];
+  SYMBOL_INFO *symbol = reinterpret_cast<SYMBOL_INFO *>(buf);
+  symbol->SizeOfStruct = sizeof(SYMBOL_INFO);
+  symbol->MaxNameLen = MAX_SYM_NAME;
+  // We use the ANSI version to ensure the string type is always `char *`.
+  // This could break if a symbol has Unicode in it.
+  BOOL ret = SymFromAddr(symInitializer.process,
+                         reinterpret_cast<DWORD64>(pc), 0, symbol);
+  if (ret == 1 && static_cast<int>(symbol->NameLen) < out_size) {
+    // `NameLen` does not include the null terminating character.
+    strncpy(out, symbol->Name, static_cast<size_t>(symbol->NameLen) + 1);
+    out[static_cast<size_t>(symbol->NameLen)] = '\0';
+    // Symbolization succeeded.  Now we try to demangle the symbol.
+    DemangleInplace(out, out_size);
+    return true;
+  }
+  return false;
+}
+
+_END_GOOGLE_NAMESPACE_
+
 #else
 # error BUG: HAVE_SYMBOLIZE was wrongly set
 #endif
index bdd2f03..a0a9737 100644 (file)
@@ -49,10 +49,22 @@ using namespace GFLAGS_NAMESPACE;
 using namespace std;
 using namespace GOOGLE_NAMESPACE;
 
-#if defined(HAVE_STACKTRACE) && defined(__ELF__)
+#if defined(HAVE_STACKTRACE)
 
 #define always_inline
 
+// A wrapper function for Symbolize() to make the unit test simple.
+static const char *TrySymbolize(void *pc) {
+  static char symbol[4096];
+  if (Symbolize(pc, symbol, sizeof(symbol))) {
+    return symbol;
+  } else {
+    return NULL;
+  }
+}
+
+# if defined(__ELF__)
+
 // This unit tests make sense only with GCC.
 // Uses lots of GCC specific features.
 #if defined(__GNUC__) && !defined(__OPENCC__)
@@ -70,16 +82,6 @@ using namespace GOOGLE_NAMESPACE;
 #  endif  // defined(__i386__) || defined(__x86_64__)
 #endif
 
-// A wrapper function for Symbolize() to make the unit test simple.
-static const char *TrySymbolize(void *pc) {
-  static char symbol[4096];
-  if (Symbolize(pc, symbol, sizeof(symbol))) {
-    return symbol;
-  } else {
-    return NULL;
-  }
-}
-
 // Make them C linkage to avoid mangled names.
 extern "C" {
 void nonstatic_func() {
@@ -355,11 +357,42 @@ void ATTRIBUTE_NOINLINE TestWithReturnAddress() {
 #endif
 }
 
+# elif defined(OS_WINDOWS)
+
+#include <intrin.h>
+#pragma intrinsic(_ReturnAddress)
+
+struct Foo {
+  static void func(int x);
+};
+
+__declspec(noinline) void Foo::func(int x) {
+  volatile int a = x;
+  ++a;
+}
+
+TEST(Symbolize, SymbolizeWithDemangling) {
+  Foo::func(100);
+  const char* ret = TrySymbolize((void *)(&Foo::func));
+  EXPECT_STREQ("public: static void __cdecl Foo::func(int)", ret);
+}
+
+__declspec(noinline) void TestWithReturnAddress() {
+  void *return_address = _ReturnAddress();
+  const char *symbol = TrySymbolize(return_address);
+  CHECK(symbol != NULL);
+  CHECK_STREQ(symbol, "main");
+  cout << "Test case TestWithReturnAddress passed." << endl;
+}
+# endif  // __ELF__
+#endif  // HAVE_STACKTRACE
+
 int main(int argc, char **argv) {
   FLAGS_logtostderr = true;
   InitGoogleLogging(argv[0]);
   InitGoogleTest(&argc, argv);
-#ifdef HAVE_SYMBOLIZE
+#if defined(HAVE_SYMBOLIZE)
+# if defined(__ELF__)
   // We don't want to get affected by the callback interface, that may be
   // used to install some callback function at InitGoogle() time.
   InstallSymbolizeCallback(NULL);
@@ -368,18 +401,15 @@ int main(int argc, char **argv) {
   TestWithPCInsideNonInlineFunction();
   TestWithReturnAddress();
   return RUN_ALL_TESTS();
-#else
-  return 0;
-#endif
-}
-
-#else
-int main() {
-#ifdef HAVE_SYMBOLIZE
+# elif defined(OS_WINDOWS)
+  TestWithReturnAddress();
+  return RUN_ALL_TESTS();
+# else  // OS_WINDOWS
   printf("PASS (no symbolize_unittest support)\n");
+  return 0;
+# endif  // __ELF__
 #else
   printf("PASS (no symbolize support)\n");
-#endif
   return 0;
+#endif  // HAVE_SYMBOLIZE
 }
-#endif  // HAVE_STACKTRACE
index a10735b..be2cff4 100644 (file)
 #elif defined(OS_MACOSX) && defined(HAVE_DLADDR)
 // Use dladdr to symbolize.
 # define HAVE_SYMBOLIZE
+#elif defined(OS_WINDOWS)
+// Use DbgHelp to symbolize
+# define HAVE_SYMBOLIZE
 #endif
 
 #ifndef ARRAYSIZE