1 //===-- sanitizer_symbolizer_win.cpp --------------------------------------===//
3 // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4 // See https://llvm.org/LICENSE.txt for license information.
5 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
7 //===----------------------------------------------------------------------===//
9 // This file is shared between AddressSanitizer and ThreadSanitizer
10 // run-time libraries.
11 // Windows-specific implementation of symbolizer parts.
12 //===----------------------------------------------------------------------===//
14 #include "sanitizer_platform.h"
17 #include "sanitizer_dbghelp.h"
18 #include "sanitizer_symbolizer_internal.h"
20 namespace __sanitizer
{
22 decltype(::StackWalk64
) *StackWalk64
;
23 decltype(::SymCleanup
) *SymCleanup
;
24 decltype(::SymFromAddr
) *SymFromAddr
;
25 decltype(::SymFunctionTableAccess64
) *SymFunctionTableAccess64
;
26 decltype(::SymGetLineFromAddr64
) *SymGetLineFromAddr64
;
27 decltype(::SymGetModuleBase64
) *SymGetModuleBase64
;
28 decltype(::SymGetSearchPathW
) *SymGetSearchPathW
;
29 decltype(::SymInitialize
) *SymInitialize
;
30 decltype(::SymSetOptions
) *SymSetOptions
;
31 decltype(::SymSetSearchPathW
) *SymSetSearchPathW
;
32 decltype(::UnDecorateSymbolName
) *UnDecorateSymbolName
;
36 class WinSymbolizerTool final
: public SymbolizerTool
{
38 // The constructor is provided to avoid synthesized memsets.
39 WinSymbolizerTool() {}
41 bool SymbolizePC(uptr addr
, SymbolizedStack
*stack
) override
;
42 bool SymbolizeData(uptr addr
, DataInfo
*info
) override
{
45 const char *Demangle(const char *name
) override
;
48 bool is_dbghelp_initialized
= false;
50 bool TrySymInitialize() {
51 SymSetOptions(SYMOPT_DEFERRED_LOADS
| SYMOPT_UNDNAME
| SYMOPT_LOAD_LINES
);
52 return SymInitialize(GetCurrentProcess(), 0, TRUE
);
53 // FIXME: We don't call SymCleanup() on exit yet - should we?
58 // Initializes DbgHelp library, if it's not yet initialized. Calls to this
59 // function should be synchronized with respect to other calls to DbgHelp API
60 // (e.g. from WinSymbolizerTool).
61 void InitializeDbgHelpIfNeeded() {
62 if (is_dbghelp_initialized
)
65 HMODULE dbghelp
= LoadLibraryA("dbghelp.dll");
66 CHECK(dbghelp
&& "failed to load dbghelp.dll");
68 #define DBGHELP_IMPORT(name) \
71 reinterpret_cast<decltype(::name) *>(GetProcAddress(dbghelp, #name)); \
72 CHECK(name != nullptr); \
74 DBGHELP_IMPORT(StackWalk64
);
75 DBGHELP_IMPORT(SymCleanup
);
76 DBGHELP_IMPORT(SymFromAddr
);
77 DBGHELP_IMPORT(SymFunctionTableAccess64
);
78 DBGHELP_IMPORT(SymGetLineFromAddr64
);
79 DBGHELP_IMPORT(SymGetModuleBase64
);
80 DBGHELP_IMPORT(SymGetSearchPathW
);
81 DBGHELP_IMPORT(SymInitialize
);
82 DBGHELP_IMPORT(SymSetOptions
);
83 DBGHELP_IMPORT(SymSetSearchPathW
);
84 DBGHELP_IMPORT(UnDecorateSymbolName
);
87 if (!TrySymInitialize()) {
88 // OK, maybe the client app has called SymInitialize already.
89 // That's a bit unfortunate for us as all the DbgHelp functions are
90 // single-threaded and we can't coordinate with the app.
91 // FIXME: Can we stop the other threads at this point?
92 // Anyways, we have to reconfigure stuff to make sure that SymInitialize
93 // has all the appropriate options set.
94 // Cross our fingers and reinitialize DbgHelp.
95 Report("*** WARNING: Failed to initialize DbgHelp! ***\n");
96 Report("*** Most likely this means that the app is already ***\n");
97 Report("*** using DbgHelp, possibly with incompatible flags. ***\n");
98 Report("*** Due to technical reasons, symbolization might crash ***\n");
99 Report("*** or produce wrong results. ***\n");
100 SymCleanup(GetCurrentProcess());
103 is_dbghelp_initialized
= true;
105 // When an executable is run from a location different from the one where it
106 // was originally built, we may not see the nearby PDB files.
107 // To work around this, let's append the directory of the main module
108 // to the symbol search path. All the failures below are not fatal.
109 const size_t kSymPathSize
= 2048;
110 static wchar_t path_buffer
[kSymPathSize
+ 1 + MAX_PATH
];
111 if (!SymGetSearchPathW(GetCurrentProcess(), path_buffer
, kSymPathSize
)) {
112 Report("*** WARNING: Failed to SymGetSearchPathW ***\n");
115 size_t sz
= wcslen(path_buffer
);
117 CHECK_EQ(0, wcscat_s(path_buffer
, L
";"));
120 DWORD res
= GetModuleFileNameW(NULL
, path_buffer
+ sz
, MAX_PATH
);
121 if (res
== 0 || res
== MAX_PATH
) {
122 Report("*** WARNING: Failed to getting the EXE directory ***\n");
125 // Write the zero character in place of the last backslash to get the
126 // directory of the main module at the end of path_buffer.
127 wchar_t *last_bslash
= wcsrchr(path_buffer
+ sz
, L
'\\');
128 CHECK_NE(last_bslash
, 0);
129 *last_bslash
= L
'\0';
130 if (!SymSetSearchPathW(GetCurrentProcess(), path_buffer
)) {
131 Report("*** WARNING: Failed to SymSetSearchPathW\n");
136 bool WinSymbolizerTool::SymbolizePC(uptr addr
, SymbolizedStack
*frame
) {
137 InitializeDbgHelpIfNeeded();
139 // See https://docs.microsoft.com/en-us/windows/win32/debug/retrieving-symbol-information-by-address
140 InternalMmapVector
<char> buffer(sizeof(SYMBOL_INFO
) +
141 MAX_SYM_NAME
* sizeof(CHAR
));
142 PSYMBOL_INFO symbol
= (PSYMBOL_INFO
)&buffer
[0];
143 symbol
->SizeOfStruct
= sizeof(SYMBOL_INFO
);
144 symbol
->MaxNameLen
= MAX_SYM_NAME
;
146 BOOL got_objname
= SymFromAddr(GetCurrentProcess(),
147 (DWORD64
)addr
, &offset
, symbol
);
152 IMAGEHLP_LINE64 line_info
;
153 line_info
.SizeOfStruct
= sizeof(IMAGEHLP_LINE64
);
154 BOOL got_fileline
= SymGetLineFromAddr64(GetCurrentProcess(), (DWORD64
)addr
,
155 &unused
, &line_info
);
156 frame
->info
.function
= internal_strdup(symbol
->Name
);
157 frame
->info
.function_offset
= (uptr
)offset
;
159 frame
->info
.file
= internal_strdup(line_info
.FileName
);
160 frame
->info
.line
= line_info
.LineNumber
;
162 // Only consider this a successful symbolization attempt if we got file info.
163 // Otherwise, try llvm-symbolizer.
167 const char *WinSymbolizerTool::Demangle(const char *name
) {
168 CHECK(is_dbghelp_initialized
);
169 static char demangle_buffer
[1000];
170 if (name
[0] == '\01' &&
171 UnDecorateSymbolName(name
+ 1, demangle_buffer
, sizeof(demangle_buffer
),
173 return demangle_buffer
;
178 const char *Symbolizer::PlatformDemangle(const char *name
) {
183 struct ScopedHandle
{
184 ScopedHandle() : h_(nullptr) {}
185 explicit ScopedHandle(HANDLE h
) : h_(h
) {}
190 HANDLE
get() { return h_
; }
191 HANDLE
*receive() { return &h_
; }
201 bool SymbolizerProcess::StartSymbolizerSubprocess() {
202 // Create inherited pipes for stdin and stdout.
203 ScopedHandle stdin_read
, stdin_write
;
204 ScopedHandle stdout_read
, stdout_write
;
205 SECURITY_ATTRIBUTES attrs
;
206 attrs
.nLength
= sizeof(SECURITY_ATTRIBUTES
);
207 attrs
.bInheritHandle
= TRUE
;
208 attrs
.lpSecurityDescriptor
= nullptr;
209 if (!::CreatePipe(stdin_read
.receive(), stdin_write
.receive(), &attrs
, 0) ||
210 !::CreatePipe(stdout_read
.receive(), stdout_write
.receive(), &attrs
, 0)) {
211 VReport(2, "WARNING: %s CreatePipe failed (error code: %d)\n",
212 SanitizerToolName
, path_
, GetLastError());
216 // Don't inherit the writing end of stdin or the reading end of stdout.
217 if (!SetHandleInformation(stdin_write
.get(), HANDLE_FLAG_INHERIT
, 0) ||
218 !SetHandleInformation(stdout_read
.get(), HANDLE_FLAG_INHERIT
, 0)) {
219 VReport(2, "WARNING: %s SetHandleInformation failed (error code: %d)\n",
220 SanitizerToolName
, path_
, GetLastError());
224 // Compute the command line. Wrap double quotes around everything.
225 const char *argv
[kArgVMax
];
226 GetArgV(path_
, argv
);
227 InternalScopedString command_line
;
228 for (int i
= 0; argv
[i
]; i
++) {
229 const char *arg
= argv
[i
];
230 int arglen
= internal_strlen(arg
);
231 // Check that tool command lines are simple and that complete escaping is
233 CHECK(!internal_strchr(arg
, '"') && "quotes in args unsupported");
234 CHECK(!internal_strstr(arg
, "\\\\") &&
235 "double backslashes in args unsupported");
236 CHECK(arglen
> 0 && arg
[arglen
- 1] != '\\' &&
237 "args ending in backslash and empty args unsupported");
238 command_line
.append("\"%s\" ", arg
);
240 VReport(3, "Launching symbolizer command: %s\n", command_line
.data());
242 // Launch llvm-symbolizer with stdin and stdout redirected.
244 memset(&si
, 0, sizeof(si
));
246 si
.dwFlags
|= STARTF_USESTDHANDLES
;
247 si
.hStdInput
= stdin_read
.get();
248 si
.hStdOutput
= stdout_write
.get();
249 PROCESS_INFORMATION pi
;
250 memset(&pi
, 0, sizeof(pi
));
251 if (!CreateProcessA(path_
, // Executable
252 command_line
.data(), // Command line
253 nullptr, // Process handle not inheritable
254 nullptr, // Thread handle not inheritable
255 TRUE
, // Set handle inheritance to TRUE
257 nullptr, // Use parent's environment block
258 nullptr, // Use parent's starting directory
260 VReport(2, "WARNING: %s failed to create process for %s (error code: %d)\n",
261 SanitizerToolName
, path_
, GetLastError());
265 // Process creation succeeded, so transfer handle ownership into the fields.
266 input_fd_
= stdout_read
.release();
267 output_fd_
= stdin_write
.release();
269 // The llvm-symbolizer process is responsible for quitting itself when the
270 // stdin pipe is closed, so we don't need these handles. Close them to prevent
271 // leaks. If we ever want to try to kill the symbolizer process from the
272 // parent, we'll want to hang on to these handles.
273 CloseHandle(pi
.hProcess
);
274 CloseHandle(pi
.hThread
);
278 static void ChooseSymbolizerTools(IntrusiveList
<SymbolizerTool
> *list
,
279 LowLevelAllocator
*allocator
) {
280 if (!common_flags()->symbolize
) {
281 VReport(2, "Symbolizer is disabled.\n");
285 // Add llvm-symbolizer.
286 const char *user_path
= common_flags()->external_symbolizer_path
;
288 if (user_path
&& internal_strchr(user_path
, '%')) {
289 char *new_path
= (char *)InternalAlloc(kMaxPathLength
);
290 SubstituteForFlagValue(user_path
, new_path
, kMaxPathLength
);
291 user_path
= new_path
;
295 user_path
? user_path
: FindPathToBinary("llvm-symbolizer.exe");
297 VReport(2, "Using llvm-symbolizer at %spath: %s\n",
298 user_path
? "user-specified " : "", path
);
299 list
->push_back(new(*allocator
) LLVMSymbolizer(path
, allocator
));
301 if (user_path
&& user_path
[0] == '\0') {
302 VReport(2, "External symbolizer is explicitly disabled.\n");
304 VReport(2, "External symbolizer is not present.\n");
308 // Add the dbghelp based symbolizer.
309 list
->push_back(new(*allocator
) WinSymbolizerTool());
312 Symbolizer
*Symbolizer::PlatformInit() {
313 IntrusiveList
<SymbolizerTool
> list
;
315 ChooseSymbolizerTools(&list
, &symbolizer_allocator_
);
317 return new(symbolizer_allocator_
) Symbolizer(list
);
320 void Symbolizer::LateInitialize() {
321 Symbolizer::GetOrInit();
324 } // namespace __sanitizer