Support symbolizer and demangler on Windows
This commit is contained in:
parent
e5d36443c6
commit
f1d64f7deb
@ -457,6 +457,7 @@ endif (HAVE_EXECINFO_H)
|
|||||||
|
|
||||||
if (WIN32)
|
if (WIN32)
|
||||||
set (HAVE_STACKTRACE 1)
|
set (HAVE_STACKTRACE 1)
|
||||||
|
set (HAVE_SYMBOLIZE 1)
|
||||||
target_link_libraries (glog PUBLIC Dbghelp.lib)
|
target_link_libraries (glog PUBLIC Dbghelp.lib)
|
||||||
endif (WIN32)
|
endif (WIN32)
|
||||||
|
|
||||||
|
|||||||
@ -35,10 +35,16 @@
|
|||||||
// Note that we only have partial C++0x support yet.
|
// Note that we only have partial C++0x support yet.
|
||||||
|
|
||||||
#include <stdio.h> // for NULL
|
#include <stdio.h> // for NULL
|
||||||
|
#include "utilities.h"
|
||||||
#include "demangle.h"
|
#include "demangle.h"
|
||||||
|
|
||||||
|
#if defined(OS_WINDOWS)
|
||||||
|
#include <DbgHelp.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
_START_GOOGLE_NAMESPACE_
|
_START_GOOGLE_NAMESPACE_
|
||||||
|
|
||||||
|
#if !defined(OS_WINDOWS)
|
||||||
typedef struct {
|
typedef struct {
|
||||||
const char *abbrev;
|
const char *abbrev;
|
||||||
const char *real_name;
|
const char *real_name;
|
||||||
@ -1293,12 +1299,37 @@ static bool ParseTopLevelMangledName(State *state) {
|
|||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
// The demangler entry point.
|
// The demangler entry point.
|
||||||
bool Demangle(const char *mangled, char *out, int out_size) {
|
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;
|
State state;
|
||||||
InitState(&state, mangled, out, out_size);
|
InitState(&state, mangled, out, out_size);
|
||||||
return ParseTopLevelMangledName(&state) && !state.overflowed;
|
return ParseTopLevelMangledName(&state) && !state.overflowed;
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
_END_GOOGLE_NAMESPACE_
|
_END_GOOGLE_NAMESPACE_
|
||||||
|
|||||||
@ -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 corner cases of bounary conditions.
|
||||||
TEST(Demangle, CornerCases) {
|
TEST(Demangle, CornerCases) {
|
||||||
char tmp[10];
|
const size_t size = 10;
|
||||||
EXPECT_TRUE(Demangle("_Z6foobarv", tmp, sizeof(tmp)));
|
char tmp[size] = { 0 };
|
||||||
// sizeof("foobar()") == 9
|
const char *demangled = "foobar()";
|
||||||
EXPECT_STREQ("foobar()", tmp);
|
const char *mangled = "_Z6foobarv";
|
||||||
EXPECT_TRUE(Demangle("_Z6foobarv", tmp, 9));
|
EXPECT_TRUE(Demangle(mangled, tmp, sizeof(tmp)));
|
||||||
EXPECT_STREQ("foobar()", tmp);
|
// sizeof("foobar()") == size - 1
|
||||||
EXPECT_FALSE(Demangle("_Z6foobarv", tmp, 8)); // Not enough.
|
EXPECT_STREQ(demangled, tmp);
|
||||||
EXPECT_FALSE(Demangle("_Z6foobarv", tmp, 1));
|
EXPECT_TRUE(Demangle(mangled, tmp, size - 1));
|
||||||
EXPECT_FALSE(Demangle("_Z6foobarv", tmp, 0));
|
EXPECT_STREQ(demangled, tmp);
|
||||||
EXPECT_FALSE(Demangle("_Z6foobarv", NULL, 0)); // Should not cause SEGV.
|
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
|
// 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) {
|
int main(int argc, char **argv) {
|
||||||
#ifdef HAVE_LIB_GFLAGS
|
#ifdef HAVE_LIB_GFLAGS
|
||||||
ParseCommandLineFlags(&argc, &argv, true);
|
ParseCommandLineFlags(&argc, &argv, true);
|
||||||
|
|||||||
@ -33,54 +33,18 @@
|
|||||||
|
|
||||||
#include "port.h"
|
#include "port.h"
|
||||||
#include "stacktrace.h"
|
#include "stacktrace.h"
|
||||||
#include "Dbghelp.h"
|
#include <DbgHelp.h>
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
_START_GOOGLE_NAMESPACE_
|
_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.
|
// If you change this function, also change GetStackFrames below.
|
||||||
int GetStackTrace(void** result, int max_depth, int skip_count) {
|
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) {
|
if (max_depth > 64) {
|
||||||
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).
|
// This API is thread-safe (moreover it walks only the current thread).
|
||||||
int size = CaptureStackBackTrace(skip_count, max_depth, &stack[0], NULL);
|
return CaptureStackBackTrace(skip_count, max_depth, result, 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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
_END_GOOGLE_NAMESPACE_
|
_END_GOOGLE_NAMESPACE_
|
||||||
|
|||||||
@ -837,6 +837,66 @@ static ATTRIBUTE_NOINLINE bool SymbolizeAndDemangle(void *pc, char *out,
|
|||||||
|
|
||||||
_END_GOOGLE_NAMESPACE_
|
_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
|
#else
|
||||||
# error BUG: HAVE_SYMBOLIZE was wrongly set
|
# error BUG: HAVE_SYMBOLIZE was wrongly set
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@ -49,10 +49,22 @@ using namespace GFLAGS_NAMESPACE;
|
|||||||
using namespace std;
|
using namespace std;
|
||||||
using namespace GOOGLE_NAMESPACE;
|
using namespace GOOGLE_NAMESPACE;
|
||||||
|
|
||||||
#if defined(HAVE_STACKTRACE) && defined(__ELF__)
|
#if defined(HAVE_STACKTRACE)
|
||||||
|
|
||||||
#define always_inline
|
#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.
|
// This unit tests make sense only with GCC.
|
||||||
// Uses lots of GCC specific features.
|
// Uses lots of GCC specific features.
|
||||||
#if defined(__GNUC__) && !defined(__OPENCC__)
|
#if defined(__GNUC__) && !defined(__OPENCC__)
|
||||||
@ -70,16 +82,6 @@ using namespace GOOGLE_NAMESPACE;
|
|||||||
# endif // defined(__i386__) || defined(__x86_64__)
|
# endif // defined(__i386__) || defined(__x86_64__)
|
||||||
#endif
|
#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.
|
// Make them C linkage to avoid mangled names.
|
||||||
extern "C" {
|
extern "C" {
|
||||||
void nonstatic_func() {
|
void nonstatic_func() {
|
||||||
@ -355,11 +357,42 @@ void ATTRIBUTE_NOINLINE TestWithReturnAddress() {
|
|||||||
#endif
|
#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) {
|
int main(int argc, char **argv) {
|
||||||
FLAGS_logtostderr = true;
|
FLAGS_logtostderr = true;
|
||||||
InitGoogleLogging(argv[0]);
|
InitGoogleLogging(argv[0]);
|
||||||
InitGoogleTest(&argc, argv);
|
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
|
// We don't want to get affected by the callback interface, that may be
|
||||||
// used to install some callback function at InitGoogle() time.
|
// used to install some callback function at InitGoogle() time.
|
||||||
InstallSymbolizeCallback(NULL);
|
InstallSymbolizeCallback(NULL);
|
||||||
@ -368,18 +401,15 @@ int main(int argc, char **argv) {
|
|||||||
TestWithPCInsideNonInlineFunction();
|
TestWithPCInsideNonInlineFunction();
|
||||||
TestWithReturnAddress();
|
TestWithReturnAddress();
|
||||||
return RUN_ALL_TESTS();
|
return RUN_ALL_TESTS();
|
||||||
#else
|
# elif defined(OS_WINDOWS)
|
||||||
return 0;
|
TestWithReturnAddress();
|
||||||
#endif
|
return RUN_ALL_TESTS();
|
||||||
}
|
# else // OS_WINDOWS
|
||||||
|
|
||||||
#else
|
|
||||||
int main() {
|
|
||||||
#ifdef HAVE_SYMBOLIZE
|
|
||||||
printf("PASS (no symbolize_unittest support)\n");
|
printf("PASS (no symbolize_unittest support)\n");
|
||||||
|
return 0;
|
||||||
|
# endif // __ELF__
|
||||||
#else
|
#else
|
||||||
printf("PASS (no symbolize support)\n");
|
printf("PASS (no symbolize support)\n");
|
||||||
#endif
|
|
||||||
return 0;
|
return 0;
|
||||||
|
#endif // HAVE_SYMBOLIZE
|
||||||
}
|
}
|
||||||
#endif // HAVE_STACKTRACE
|
|
||||||
|
|||||||
@ -133,6 +133,9 @@
|
|||||||
#elif defined(OS_MACOSX) && defined(HAVE_DLADDR)
|
#elif defined(OS_MACOSX) && defined(HAVE_DLADDR)
|
||||||
// Use dladdr to symbolize.
|
// Use dladdr to symbolize.
|
||||||
# define HAVE_SYMBOLIZE
|
# define HAVE_SYMBOLIZE
|
||||||
|
#elif defined(OS_WINDOWS)
|
||||||
|
// Use DbgHelp to symbolize
|
||||||
|
# define HAVE_SYMBOLIZE
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifndef ARRAYSIZE
|
#ifndef ARRAYSIZE
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user