1// Copyright (c) 2009 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5// A smaller wrapper around the dbghelp symbol resolution routines.
6// For example:
7//   SymResolver resolver("ntdll.dll");
8//   resolver.Resolve("ntdll!NtBlahBlah");
9
10#ifndef TRACELINE_SYM_RESOLVER_H_
11#define TRACELINE_SYM_RESOLVER_H_
12
13#include <windows.h>
14#include <dbghelp.h>
15
16#include <vector>
17#include <string>
18#include <map>
19
20static BOOL CALLBACK SymEnumer(PCSTR name, DWORD64 base, PVOID context) {
21  reinterpret_cast<std::vector<DWORD64>*>(context)->push_back(base);
22  return TRUE;
23}
24
25class SymResolver {
26 public:
27
28  // Constructor to load a single DLL.
29  SymResolver(const char* dllname, HANDLE proc = ::GetCurrentProcess())
30      : proc_(proc) {
31
32    // TODO(deanm): Would be nice to get this from WinDBG, but it's buried
33    // in the workspace data blob... _NT_SYMBOL_PATH is not usually set...
34    static char* kSymbolPath =
35        "C:\\Program Files\\Debugging Tools for Windows (x86)\\sym;"
36        "C:\\Program Files\\Debugging Tools for Windows\\sym";
37
38    // If we want to load a specific DLL, or we want to load all.
39    if (::SymInitialize(proc_, kSymbolPath, dllname ? FALSE : TRUE) != TRUE) {
40      NOTREACHED("SymInitialize failed: %d", GetLastError());
41    }
42
43    base_ = 0;
44
45    if (dllname) {
46      base_ = ::SymLoadModuleEx(proc_,
47                                NULL,
48                                const_cast<char*>(dllname),
49                                NULL,
50                                reinterpret_cast<DWORD64>(
51                                    GetModuleHandleA(dllname)),
52                                0,
53                                NULL,
54                                0);
55      if (base_ == 0) {
56        NOTREACHED("SymLoadModuleEx(%s) failed: %d", dllname, GetLastError());
57      }
58    }
59
60    std::vector<DWORD64> bases;
61    // The name returned from SymEnumerateModules64 doesn't include the ext,
62    // so we can't differentiate between a dll and exe of the same name. So
63    // collect all of the base addresses and query for more info.
64    // The prototype changed from PSTR to PCSTR, so in order to support older
65    // SDKs we have to cast SymEnumer.
66    PSYM_ENUMMODULES_CALLBACK64 enumer =
67        reinterpret_cast<PSYM_ENUMMODULES_CALLBACK64>(&SymEnumer);
68    if (SymEnumerateModules64(proc_, enumer, &bases) != TRUE) {
69      NOTREACHED("SymEnumerateModules64 failed: %d\n", GetLastError());
70    }
71    for (size_t i = 0; i < bases.size(); ++i) {
72      // This was failing, turns out I was just using the system32
73      // dbghelp.dll which is old, use the one from windbg :(
74      IMAGEHLP_MODULE64 info;
75      info.SizeOfStruct = sizeof(info);
76      if (SymGetModuleInfo64(proc_, bases[i], &info) != TRUE) {
77        NOTREACHED("SymGetModuleInfo64 failed: %d\n", GetLastError());
78      }
79      std::string filename(info.ImageName);
80      size_t last_slash = filename.find_last_of('\\');
81      if (last_slash != std::string::npos)
82        filename = filename.substr(filename.find_last_of('\\') + 1);
83
84      // Map the base address to the image name...
85      dlls_[static_cast<int>(bases[i])] = filename;
86    }
87
88    // TODO(deanm): check the symbols are rad and stuff...
89  }
90
91  char* Resolve(const char* name) {
92    // The API writes to the space after SYMBOL_INFO...
93    struct {
94      SYMBOL_INFO info;
95      char buf[128];
96    } info = {0};
97
98    info.info.SizeOfStruct = sizeof(info.info);
99    info.info.ModBase = base_;
100    info.info.MaxNameLen = 127;
101
102    if (SymFromName(proc_, const_cast<char*>(name), &info.info) != TRUE) {
103      NOTREACHED("SymFromName(%s) failed: %d", name, GetLastError());
104    }
105
106    return reinterpret_cast<char*>(info.info.Address);
107  }
108
109  std::string Unresolve(int ptr) {
110    // The API writes to the space after SYMBOL_INFO...
111    struct {
112      SYMBOL_INFO info;
113      char buf[128];
114    } info = {0};
115
116    info.info.SizeOfStruct = sizeof(info.info);
117    info.info.ModBase = base_;
118    info.info.MaxNameLen = 127;
119    if (!::SymFromAddr(proc_, static_cast<DWORD64>(ptr), NULL, &info.info)) {
120      return std::string("failed");
121    }
122
123    std::string name;
124    int addr = static_cast<int>(info.info.Address);
125    int base = static_cast<int>(info.info.ModBase);
126
127    if (dlls_.count(base) == 1) {
128      name.append(dlls_[base]);
129    } else {
130      name.append("unknown_mod");
131    }
132    name.push_back('!');
133    name.append(info.info.Name);
134
135    char buf[32];
136    _itoa_s(ptr - addr, buf, sizeof(buf), 16);
137    name.append("+0x");
138    name.append(buf);
139
140    DWORD disp;
141    IMAGEHLP_LINE64 line;
142    if (::SymGetLineFromAddr64(
143            proc_, static_cast<DWORD64>(ptr), &disp, &line)) {
144      name.append(" [ ");
145      name.append(line.FileName);
146      name.append(":");
147      _itoa_s(line.LineNumber, buf, sizeof(buf), 10);
148      name.append(buf);
149      name.append(" ]");
150    }
151
152    return name;
153  }
154
155  ~SymResolver() {
156    if (::SymCleanup(proc_) != TRUE) {
157      NOTREACHED("SymCleanup failed: %d", GetLastError());
158    }
159  }
160
161 private:
162  HANDLE proc_;
163  ULONG64 base_;
164  std::map<int, std::string> dlls_;
165};
166
167#endif  // TRACELINE_SYM_RESOLVER_H_
168