1 //===-- sanitizer_symbolizer_mac.cpp --------------------------------------===//
2 //
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
6 //
7 //===----------------------------------------------------------------------===//
8 //
9 // This file is shared between various sanitizers' runtime libraries.
10 //
11 // Implementation of Mac-specific "atos" symbolizer.
12 //===----------------------------------------------------------------------===//
13 
14 #include "sanitizer_platform.h"
15 #if SANITIZER_APPLE
16 
17 #  include <dlfcn.h>
18 #  include <errno.h>
19 #  include <stdlib.h>
20 #  include <sys/wait.h>
21 #  include <unistd.h>
22 #  include <util.h>
23 
24 #  include "sanitizer_allocator_internal.h"
25 #  include "sanitizer_mac.h"
26 #  include "sanitizer_symbolizer_mac.h"
27 
28 namespace __sanitizer {
29 
30 bool DlAddrSymbolizer::SymbolizePC(uptr addr, SymbolizedStack *stack) {
31   Dl_info info;
32   int result = dladdr((const void *)addr, &info);
33   if (!result) return false;
34 
35   // Compute offset if possible. `dladdr()` doesn't always ensure that `addr >=
36   // sym_addr` so only compute the offset when this holds. Failure to find the
37   // function offset is not treated as a failure because it might still be
38   // possible to get the symbol name.
39   uptr sym_addr = reinterpret_cast<uptr>(info.dli_saddr);
40   if (addr >= sym_addr) {
41     stack->info.function_offset = addr - sym_addr;
42   }
43 
44   const char *demangled = DemangleSwiftAndCXX(info.dli_sname);
45   if (!demangled)
46     demangled = info.dli_sname;
47   stack->info.function = internal_strdup(demangled);
48   return true;
49 }
50 
51 bool DlAddrSymbolizer::SymbolizeData(uptr addr, DataInfo *datainfo) {
52   Dl_info info;
53   int result = dladdr((const void *)addr, &info);
54   if (!result) return false;
55   const char *demangled = DemangleSwiftAndCXX(info.dli_sname);
56   if (!demangled)
57     demangled = info.dli_sname;
58   datainfo->name = internal_strdup(demangled);
59   datainfo->start = (uptr)info.dli_saddr;
60   return true;
61 }
62 
63 class AtosSymbolizerProcess final : public SymbolizerProcess {
64  public:
65   explicit AtosSymbolizerProcess(const char *path)
66       : SymbolizerProcess(path, /*use_posix_spawn*/ true) {
67     pid_str_[0] = '\0';
68   }
69 
70  private:
71   bool StartSymbolizerSubprocess() override {
72     // Put the string command line argument in the object so that it outlives
73     // the call to GetArgV.
74     internal_snprintf(pid_str_, sizeof(pid_str_), "%d", (int)internal_getpid());
75 
76     // Configure sandbox before starting atos process.
77     return SymbolizerProcess::StartSymbolizerSubprocess();
78   }
79 
80   bool ReachedEndOfOutput(const char *buffer, uptr length) const override {
81     return (length >= 1 && buffer[length - 1] == '\n');
82   }
83 
84   void GetArgV(const char *path_to_binary,
85                const char *(&argv)[kArgVMax]) const override {
86     int i = 0;
87     argv[i++] = path_to_binary;
88     argv[i++] = "-p";
89     argv[i++] = &pid_str_[0];
90     if (GetMacosAlignedVersion() == MacosVersion(10, 9)) {
91       // On Mavericks atos prints a deprecation warning which we suppress by
92       // passing -d. The warning isn't present on other OSX versions, even the
93       // newer ones.
94       argv[i++] = "-d";
95     }
96     argv[i++] = nullptr;
97     CHECK_LE(i, kArgVMax);
98   }
99 
100   char pid_str_[16];
101 };
102 
103 #undef K_ATOS_ENV_VAR
104 
105 static bool ParseCommandOutput(const char *str, uptr addr, char **out_name,
106                                char **out_module, char **out_file, uptr *line,
107                                uptr *start_address) {
108   // Trim ending newlines.
109   char *trim;
110   ExtractTokenUpToDelimiter(str, "\n", &trim);
111 
112   // The line from `atos` is in one of these formats:
113   //   myfunction (in library.dylib) (sourcefile.c:17)
114   //   myfunction (in library.dylib) + 0x1fe
115   //   myfunction (in library.dylib) + 15
116   //   0xdeadbeef (in library.dylib) + 0x1fe
117   //   0xdeadbeef (in library.dylib) + 15
118   //   0xdeadbeef (in library.dylib)
119   //   0xdeadbeef
120 
121   const char *rest = trim;
122   char *symbol_name;
123   rest = ExtractTokenUpToDelimiter(rest, " (in ", &symbol_name);
124   if (rest[0] == '\0') {
125     InternalFree(symbol_name);
126     InternalFree(trim);
127     return false;
128   }
129 
130   if (internal_strncmp(symbol_name, "0x", 2) != 0)
131     *out_name = symbol_name;
132   else
133     InternalFree(symbol_name);
134   rest = ExtractTokenUpToDelimiter(rest, ") ", out_module);
135 
136   if (rest[0] == '(') {
137     if (out_file) {
138       rest++;
139       rest = ExtractTokenUpToDelimiter(rest, ":", out_file);
140       char *extracted_line_number;
141       rest = ExtractTokenUpToDelimiter(rest, ")", &extracted_line_number);
142       if (line) *line = (uptr)internal_atoll(extracted_line_number);
143       InternalFree(extracted_line_number);
144     }
145   } else if (rest[0] == '+') {
146     rest += 2;
147     uptr offset = internal_atoll(rest);
148     if (start_address) *start_address = addr - offset;
149   }
150 
151   InternalFree(trim);
152   return true;
153 }
154 
155 AtosSymbolizer::AtosSymbolizer(const char *path, LowLevelAllocator *allocator)
156     : process_(new (*allocator) AtosSymbolizerProcess(path)) {}
157 
158 bool AtosSymbolizer::SymbolizePC(uptr addr, SymbolizedStack *stack) {
159   if (!process_) return false;
160   if (addr == 0) return false;
161   char command[32];
162   internal_snprintf(command, sizeof(command), "0x%zx\n", addr);
163   const char *buf = process_->SendCommand(command);
164   if (!buf) return false;
165   uptr line;
166   uptr start_address = AddressInfo::kUnknown;
167   if (!ParseCommandOutput(buf, addr, &stack->info.function, &stack->info.module,
168                           &stack->info.file, &line, &start_address)) {
169     Report("WARNING: atos failed to symbolize address \"0x%zx\"\n", addr);
170     return false;
171   }
172   stack->info.line = (int)line;
173 
174   if (start_address == AddressInfo::kUnknown) {
175     // Fallback to dladdr() to get function start address if atos doesn't report
176     // it.
177     Dl_info info;
178     int result = dladdr((const void *)addr, &info);
179     if (result)
180       start_address = reinterpret_cast<uptr>(info.dli_saddr);
181   }
182 
183   // Only assign to `function_offset` if we were able to get the function's
184   // start address and we got a sensible `start_address` (dladdr doesn't always
185   // ensure that `addr >= sym_addr`).
186   if (start_address != AddressInfo::kUnknown && addr >= start_address) {
187     stack->info.function_offset = addr - start_address;
188   }
189   return true;
190 }
191 
192 bool AtosSymbolizer::SymbolizeData(uptr addr, DataInfo *info) {
193   if (!process_) return false;
194   char command[32];
195   internal_snprintf(command, sizeof(command), "0x%zx\n", addr);
196   const char *buf = process_->SendCommand(command);
197   if (!buf) return false;
198   if (!ParseCommandOutput(buf, addr, &info->name, &info->module, nullptr,
199                           nullptr, &info->start)) {
200     process_ = nullptr;
201     return false;
202   }
203   return true;
204 }
205 
206 }  // namespace __sanitizer
207 
208 #endif  // SANITIZER_APPLE
209