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 
SymEnumer(PCSTR name,DWORD64 base,PVOID context)20 static BOOL CALLBACK SymEnumer(PCSTR name, DWORD64 base, PVOID context) {
21   reinterpret_cast<std::vector<DWORD64>*>(context)->push_back(base);
22   return TRUE;
23 }
24 
25 class SymResolver {
26  public:
27 
28   // Constructor to load a single DLL.
29   SymResolver(const char* dllname, HANDLE proc = ::GetCurrentProcess())
proc_(proc)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 
Resolve(const char * name)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 
Unresolve(int ptr)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 
~SymResolver()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