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) \
70 name = reinterpret_cast<decltype(::name) *>( \
71 (void *)GetProcAddress(dbghelp, #name)); \
72 CHECK(name != nullptr); \
75 DBGHELP_IMPORT(StackWalk64
);
76 DBGHELP_IMPORT(SymCleanup
);
77 DBGHELP_IMPORT(SymFromAddr
);
78 DBGHELP_IMPORT(SymFunctionTableAccess64
);
79 DBGHELP_IMPORT(SymGetLineFromAddr64
);
80 DBGHELP_IMPORT(SymGetModuleBase64
);
81 DBGHELP_IMPORT(SymGetSearchPathW
);
82 DBGHELP_IMPORT(SymInitialize
);
83 DBGHELP_IMPORT(SymSetOptions
);
84 DBGHELP_IMPORT(SymSetSearchPathW
);
85 DBGHELP_IMPORT(UnDecorateSymbolName
);
88 if (!TrySymInitialize()) {
89 // OK, maybe the client app has called SymInitialize already.
90 // That's a bit unfortunate for us as all the DbgHelp functions are
91 // single-threaded and we can't coordinate with the app.
92 // FIXME: Can we stop the other threads at this point?
93 // Anyways, we have to reconfigure stuff to make sure that SymInitialize
94 // has all the appropriate options set.
95 // Cross our fingers and reinitialize DbgHelp.
96 Report("*** WARNING: Failed to initialize DbgHelp! ***\n");
97 Report("*** Most likely this means that the app is already ***\n");
98 Report("*** using DbgHelp, possibly with incompatible flags. ***\n");
99 Report("*** Due to technical reasons, symbolization might crash ***\n");
100 Report("*** or produce wrong results. ***\n");
101 SymCleanup(GetCurrentProcess());
104 is_dbghelp_initialized
= true;
106 // When an executable is run from a location different from the one where it
107 // was originally built, we may not see the nearby PDB files.
108 // To work around this, let's append the directory of the main module
109 // to the symbol search path. All the failures below are not fatal.
110 const size_t kSymPathSize
= 2048;
111 static wchar_t path_buffer
[kSymPathSize
+ 1 + MAX_PATH
];
112 if (!SymGetSearchPathW(GetCurrentProcess(), path_buffer
, kSymPathSize
)) {
113 Report("*** WARNING: Failed to SymGetSearchPathW ***\n");
116 size_t sz
= wcslen(path_buffer
);
118 CHECK_EQ(0, wcscat_s(path_buffer
, L
";"));
121 DWORD res
= GetModuleFileNameW(NULL
, path_buffer
+ sz
, MAX_PATH
);
122 if (res
== 0 || res
== MAX_PATH
) {
123 Report("*** WARNING: Failed to getting the EXE directory ***\n");
126 // Write the zero character in place of the last backslash to get the
127 // directory of the main module at the end of path_buffer.
128 wchar_t *last_bslash
= wcsrchr(path_buffer
+ sz
, L
'\\');
129 CHECK_NE(last_bslash
, 0);
130 *last_bslash
= L
'\0';
131 if (!SymSetSearchPathW(GetCurrentProcess(), path_buffer
)) {
132 Report("*** WARNING: Failed to SymSetSearchPathW\n");
137 bool WinSymbolizerTool::SymbolizePC(uptr addr
, SymbolizedStack
*frame
) {
138 InitializeDbgHelpIfNeeded();
140 // See https://docs.microsoft.com/en-us/windows/win32/debug/retrieving-symbol-information-by-address
141 InternalMmapVector
<char> buffer(sizeof(SYMBOL_INFO
) +
142 MAX_SYM_NAME
* sizeof(CHAR
));
143 PSYMBOL_INFO symbol
= (PSYMBOL_INFO
)&buffer
[0];
144 symbol
->SizeOfStruct
= sizeof(SYMBOL_INFO
);
145 symbol
->MaxNameLen
= MAX_SYM_NAME
;
147 BOOL got_objname
= SymFromAddr(GetCurrentProcess(),
148 (DWORD64
)addr
, &offset
, symbol
);
153 IMAGEHLP_LINE64 line_info
;
154 line_info
.SizeOfStruct
= sizeof(IMAGEHLP_LINE64
);
155 BOOL got_fileline
= SymGetLineFromAddr64(GetCurrentProcess(), (DWORD64
)addr
,
156 &unused
, &line_info
);
157 frame
->info
.function
= internal_strdup(symbol
->Name
);
158 frame
->info
.function_offset
= (uptr
)offset
;
160 frame
->info
.file
= internal_strdup(line_info
.FileName
);
161 frame
->info
.line
= line_info
.LineNumber
;
163 // Only consider this a successful symbolization attempt if we got file info.
164 // Otherwise, try llvm-symbolizer.
168 const char *WinSymbolizerTool::Demangle(const char *name
) {
169 CHECK(is_dbghelp_initialized
);
170 static char demangle_buffer
[1000];
171 if (name
[0] == '\01' &&
172 UnDecorateSymbolName(name
+ 1, demangle_buffer
, sizeof(demangle_buffer
),
174 return demangle_buffer
;
179 const char *Symbolizer::PlatformDemangle(const char *name
) { return nullptr; }
182 struct ScopedHandle
{
183 ScopedHandle() : h_(nullptr) {}
184 explicit ScopedHandle(HANDLE h
) : h_(h
) {}
189 HANDLE
get() { return h_
; }
190 HANDLE
*receive() { return &h_
; }
200 bool SymbolizerProcess::StartSymbolizerSubprocess() {
201 // Create inherited pipes for stdin and stdout.
202 ScopedHandle stdin_read
, stdin_write
;
203 ScopedHandle stdout_read
, stdout_write
;
204 SECURITY_ATTRIBUTES attrs
;
205 attrs
.nLength
= sizeof(SECURITY_ATTRIBUTES
);
206 attrs
.bInheritHandle
= TRUE
;
207 attrs
.lpSecurityDescriptor
= nullptr;
208 if (!::CreatePipe(stdin_read
.receive(), stdin_write
.receive(), &attrs
, 0) ||
209 !::CreatePipe(stdout_read
.receive(), stdout_write
.receive(), &attrs
, 0)) {
210 VReport(2, "WARNING: %s CreatePipe failed (error code: %d)\n",
211 SanitizerToolName
, path_
, GetLastError());
215 // Don't inherit the writing end of stdin or the reading end of stdout.
216 if (!SetHandleInformation(stdin_write
.get(), HANDLE_FLAG_INHERIT
, 0) ||
217 !SetHandleInformation(stdout_read
.get(), HANDLE_FLAG_INHERIT
, 0)) {
218 VReport(2, "WARNING: %s SetHandleInformation failed (error code: %d)\n",
219 SanitizerToolName
, path_
, GetLastError());
223 // Compute the command line. Wrap double quotes around everything.
224 const char *argv
[kArgVMax
];
225 GetArgV(path_
, argv
);
226 InternalScopedString command_line
;
227 for (int i
= 0; argv
[i
]; i
++) {
228 const char *arg
= argv
[i
];
229 int arglen
= internal_strlen(arg
);
230 // Check that tool command lines are simple and that complete escaping is
232 CHECK(!internal_strchr(arg
, '"') && "quotes in args unsupported");
233 CHECK(arglen
> 0 && arg
[arglen
- 1] != '\\' &&
234 "args ending in backslash and empty args unsupported");
235 command_line
.AppendF("\"%s\" ", arg
);
237 VReport(3, "Launching symbolizer command: %s\n", command_line
.data());
239 // Launch llvm-symbolizer with stdin and stdout redirected.
241 memset(&si
, 0, sizeof(si
));
243 si
.dwFlags
|= STARTF_USESTDHANDLES
;
244 si
.hStdInput
= stdin_read
.get();
245 si
.hStdOutput
= stdout_write
.get();
246 PROCESS_INFORMATION pi
;
247 memset(&pi
, 0, sizeof(pi
));
248 if (!CreateProcessA(path_
, // Executable
249 command_line
.data(), // Command line
250 nullptr, // Process handle not inheritable
251 nullptr, // Thread handle not inheritable
252 TRUE
, // Set handle inheritance to TRUE
254 nullptr, // Use parent's environment block
255 nullptr, // Use parent's starting directory
257 VReport(2, "WARNING: %s failed to create process for %s (error code: %d)\n",
258 SanitizerToolName
, path_
, GetLastError());
262 // Process creation succeeded, so transfer handle ownership into the fields.
263 input_fd_
= stdout_read
.release();
264 output_fd_
= stdin_write
.release();
266 // The llvm-symbolizer process is responsible for quitting itself when the
267 // stdin pipe is closed, so we don't need these handles. Close them to prevent
268 // leaks. If we ever want to try to kill the symbolizer process from the
269 // parent, we'll want to hang on to these handles.
270 CloseHandle(pi
.hProcess
);
271 CloseHandle(pi
.hThread
);
275 static void ChooseSymbolizerTools(IntrusiveList
<SymbolizerTool
> *list
,
276 LowLevelAllocator
*allocator
) {
277 if (!common_flags()->symbolize
) {
278 VReport(2, "Symbolizer is disabled.\n");
282 // Add llvm-symbolizer.
283 const char *user_path
= common_flags()->external_symbolizer_path
;
285 if (user_path
&& internal_strchr(user_path
, '%')) {
286 char *new_path
= (char *)InternalAlloc(kMaxPathLength
);
287 SubstituteForFlagValue(user_path
, new_path
, kMaxPathLength
);
288 user_path
= new_path
;
292 user_path
? user_path
: FindPathToBinary("llvm-symbolizer.exe");
294 if (user_path
&& user_path
[0] == '\0') {
295 VReport(2, "External symbolizer is explicitly disabled.\n");
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
));
302 VReport(2, "External symbolizer is not present.\n");
305 // Add the dbghelp based symbolizer.
306 list
->push_back(new(*allocator
) WinSymbolizerTool());
309 Symbolizer
*Symbolizer::PlatformInit() {
310 IntrusiveList
<SymbolizerTool
> list
;
312 ChooseSymbolizerTools(&list
, &symbolizer_allocator_
);
314 return new(symbolizer_allocator_
) Symbolizer(list
);
317 void Symbolizer::LateInitialize() {
318 Symbolizer::GetOrInit();
321 } // namespace __sanitizer