1 | //===-- SymbolVendorMacOSX.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 | #include "SymbolVendorMacOSX.h" |
10 | |
11 | #include <cstring> |
12 | |
13 | #include "Plugins/ObjectFile/Mach-O/ObjectFileMachO.h" |
14 | #include "lldb/Core/Module.h" |
15 | #include "lldb/Core/ModuleSpec.h" |
16 | #include "lldb/Core/PluginManager.h" |
17 | #include "lldb/Core/Section.h" |
18 | #include "lldb/Host/Host.h" |
19 | #include "lldb/Host/XML.h" |
20 | #include "lldb/Symbol/ObjectFile.h" |
21 | #include "lldb/Target/Target.h" |
22 | #include "lldb/Utility/StreamString.h" |
23 | #include "lldb/Utility/Timer.h" |
24 | |
25 | using namespace lldb; |
26 | using namespace lldb_private; |
27 | |
28 | LLDB_PLUGIN_DEFINE(SymbolVendorMacOSX) |
29 | |
30 | // SymbolVendorMacOSX constructor |
31 | SymbolVendorMacOSX::SymbolVendorMacOSX(const lldb::ModuleSP &module_sp) |
32 | : SymbolVendor(module_sp) {} |
33 | |
34 | static bool UUIDsMatch(Module *module, ObjectFile *ofile, |
35 | lldb_private::Stream *feedback_strm) { |
36 | if (module && ofile) { |
37 | // Make sure the UUIDs match |
38 | lldb_private::UUID dsym_uuid = ofile->GetUUID(); |
39 | if (!dsym_uuid) { |
40 | if (feedback_strm) { |
41 | feedback_strm->PutCString( |
42 | cstr: "warning: failed to get the uuid for object file: '" ); |
43 | ofile->GetFileSpec().Dump(s&: feedback_strm->AsRawOstream()); |
44 | feedback_strm->PutCString(cstr: "\n" ); |
45 | } |
46 | return false; |
47 | } |
48 | |
49 | if (dsym_uuid == module->GetUUID()) |
50 | return true; |
51 | |
52 | // Emit some warning messages since the UUIDs do not match! |
53 | if (feedback_strm) { |
54 | feedback_strm->PutCString( |
55 | cstr: "warning: UUID mismatch detected between modules:\n " ); |
56 | module->GetUUID().Dump(s&: *feedback_strm); |
57 | feedback_strm->PutChar(ch: ' '); |
58 | module->GetFileSpec().Dump(s&: feedback_strm->AsRawOstream()); |
59 | feedback_strm->PutCString(cstr: "\n " ); |
60 | dsym_uuid.Dump(s&: *feedback_strm); |
61 | feedback_strm->PutChar(ch: ' '); |
62 | ofile->GetFileSpec().Dump(s&: feedback_strm->AsRawOstream()); |
63 | feedback_strm->EOL(); |
64 | } |
65 | } |
66 | return false; |
67 | } |
68 | |
69 | void SymbolVendorMacOSX::Initialize() { |
70 | PluginManager::RegisterPlugin(name: GetPluginNameStatic(), |
71 | description: GetPluginDescriptionStatic(), create_callback: CreateInstance); |
72 | } |
73 | |
74 | void SymbolVendorMacOSX::Terminate() { |
75 | PluginManager::UnregisterPlugin(create_callback: CreateInstance); |
76 | } |
77 | |
78 | llvm::StringRef SymbolVendorMacOSX::GetPluginDescriptionStatic() { |
79 | return "Symbol vendor for MacOSX that looks for dSYM files that match " |
80 | "executables." ; |
81 | } |
82 | |
83 | // CreateInstance |
84 | // |
85 | // Platforms can register a callback to use when creating symbol vendors to |
86 | // allow for complex debug information file setups, and to also allow for |
87 | // finding separate debug information files. |
88 | SymbolVendor * |
89 | SymbolVendorMacOSX::CreateInstance(const lldb::ModuleSP &module_sp, |
90 | lldb_private::Stream *feedback_strm) { |
91 | if (!module_sp) |
92 | return NULL; |
93 | |
94 | ObjectFile *obj_file = |
95 | llvm::dyn_cast_or_null<ObjectFileMachO>(Val: module_sp->GetObjectFile()); |
96 | if (!obj_file) |
97 | return NULL; |
98 | |
99 | static Timer::Category func_cat(LLVM_PRETTY_FUNCTION); |
100 | Timer scoped_timer(func_cat, |
101 | "SymbolVendorMacOSX::CreateInstance (module = %s)" , |
102 | module_sp->GetFileSpec().GetPath().c_str()); |
103 | SymbolVendorMacOSX *symbol_vendor = new SymbolVendorMacOSX(module_sp); |
104 | if (symbol_vendor) { |
105 | char path[PATH_MAX]; |
106 | path[0] = '\0'; |
107 | |
108 | // Try and locate the dSYM file on Mac OS X |
109 | static Timer::Category func_cat2( |
110 | "SymbolVendorMacOSX::CreateInstance() locate dSYM" ); |
111 | Timer scoped_timer2( |
112 | func_cat2, |
113 | "SymbolVendorMacOSX::CreateInstance (module = %s) locate dSYM" , |
114 | module_sp->GetFileSpec().GetPath().c_str()); |
115 | |
116 | // First check to see if the module has a symbol file in mind already. If |
117 | // it does, then we MUST use that. |
118 | FileSpec dsym_fspec(module_sp->GetSymbolFileFileSpec()); |
119 | |
120 | ObjectFileSP dsym_objfile_sp; |
121 | // On Darwin, we store the debug information either in object files, |
122 | // using the debug map to tie them to the executable, or in a dSYM. We |
123 | // pass through this routine both for binaries and for .o files, but in the |
124 | // latter case there will never be an external debug file. So we shouldn't |
125 | // do all the stats needed to find it. |
126 | if (!dsym_fspec && module_sp->GetObjectFile()->CalculateType() != |
127 | ObjectFile::eTypeObjectFile) { |
128 | // No symbol file was specified in the module, lets try and find one |
129 | // ourselves. |
130 | FileSpec file_spec = obj_file->GetFileSpec(); |
131 | if (!file_spec) |
132 | file_spec = module_sp->GetFileSpec(); |
133 | |
134 | ModuleSpec module_spec(file_spec, module_sp->GetArchitecture()); |
135 | module_spec.GetUUID() = module_sp->GetUUID(); |
136 | FileSpecList search_paths = Target::GetDefaultDebugFileSearchPaths(); |
137 | dsym_fspec = |
138 | PluginManager::LocateExecutableSymbolFile(module_spec, default_search_paths: search_paths); |
139 | if (module_spec.GetSourceMappingList().GetSize()) |
140 | module_sp->GetSourceMappingList().Append( |
141 | rhs: module_spec.GetSourceMappingList(), notify: true); |
142 | } |
143 | |
144 | if (dsym_fspec) { |
145 | // Compute dSYM root. |
146 | std::string dsym_root = dsym_fspec.GetPath(); |
147 | const size_t pos = dsym_root.find(s: "/Contents/Resources/" ); |
148 | dsym_root = pos != std::string::npos ? dsym_root.substr(pos: 0, n: pos) : "" ; |
149 | |
150 | DataBufferSP dsym_file_data_sp; |
151 | lldb::offset_t dsym_file_data_offset = 0; |
152 | dsym_objfile_sp = |
153 | ObjectFile::FindPlugin(module_sp, file_spec: &dsym_fspec, file_offset: 0, |
154 | file_size: FileSystem::Instance().GetByteSize(file_spec: dsym_fspec), |
155 | data_sp&: dsym_file_data_sp, data_offset&: dsym_file_data_offset); |
156 | // Important to save the dSYM FileSpec so we don't call |
157 | // PluginManager::LocateExecutableSymbolFile a second time while trying to |
158 | // add the symbol ObjectFile to this Module. |
159 | if (dsym_objfile_sp && !module_sp->GetSymbolFileFileSpec()) { |
160 | module_sp->SetSymbolFileFileSpec(dsym_fspec); |
161 | } |
162 | if (UUIDsMatch(module: module_sp.get(), ofile: dsym_objfile_sp.get(), feedback_strm)) { |
163 | // We need a XML parser if we hope to parse a plist... |
164 | if (XMLDocument::XMLEnabled()) { |
165 | if (module_sp->GetSourceMappingList().IsEmpty()) { |
166 | lldb_private::UUID dsym_uuid = dsym_objfile_sp->GetUUID(); |
167 | if (dsym_uuid) { |
168 | std::string uuid_str = dsym_uuid.GetAsString(); |
169 | if (!uuid_str.empty() && !dsym_root.empty()) { |
170 | char dsym_uuid_plist_path[PATH_MAX]; |
171 | snprintf(s: dsym_uuid_plist_path, maxlen: sizeof(dsym_uuid_plist_path), |
172 | format: "%s/Contents/Resources/%s.plist" , dsym_root.c_str(), |
173 | uuid_str.c_str()); |
174 | FileSpec dsym_uuid_plist_spec(dsym_uuid_plist_path); |
175 | if (FileSystem::Instance().Exists(file_spec: dsym_uuid_plist_spec)) { |
176 | ApplePropertyList plist(dsym_uuid_plist_path); |
177 | if (plist) { |
178 | std::string DBGBuildSourcePath; |
179 | std::string DBGSourcePath; |
180 | |
181 | // DBGSourcePathRemapping is a dictionary in the plist |
182 | // with keys which are DBGBuildSourcePath file paths and |
183 | // values which are DBGSourcePath file paths |
184 | |
185 | StructuredData::ObjectSP plist_sp = |
186 | plist.GetStructuredData(); |
187 | if (plist_sp.get() && plist_sp->GetAsDictionary() && |
188 | plist_sp->GetAsDictionary()->HasKey( |
189 | key: "DBGSourcePathRemapping" ) && |
190 | plist_sp->GetAsDictionary() |
191 | ->GetValueForKey(key: "DBGSourcePathRemapping" ) |
192 | ->GetAsDictionary()) { |
193 | |
194 | // If DBGVersion 1 or DBGVersion missing, ignore |
195 | // DBGSourcePathRemapping. If DBGVersion 2, strip last two |
196 | // components of path remappings from |
197 | // entries to fix an issue with a |
198 | // specific set of DBGSourcePathRemapping |
199 | // entries that lldb worked with. |
200 | // If DBGVersion 3, trust & use the source path remappings |
201 | // as-is. |
202 | // |
203 | |
204 | bool new_style_source_remapping_dictionary = false; |
205 | bool do_truncate_remapping_names = false; |
206 | std::string original_DBGSourcePath_value = DBGSourcePath; |
207 | if (plist_sp->GetAsDictionary()->HasKey(key: "DBGVersion" )) { |
208 | std::string version_string = |
209 | std::string(plist_sp->GetAsDictionary() |
210 | ->GetValueForKey(key: "DBGVersion" ) |
211 | ->GetStringValue(fail_value: "" )); |
212 | if (!version_string.empty() && |
213 | isdigit(version_string[0])) { |
214 | int version_number = atoi(nptr: version_string.c_str()); |
215 | if (version_number > 1) { |
216 | new_style_source_remapping_dictionary = true; |
217 | } |
218 | if (version_number == 2) { |
219 | do_truncate_remapping_names = true; |
220 | } |
221 | } |
222 | } |
223 | |
224 | StructuredData::Dictionary *remappings_dict = |
225 | plist_sp->GetAsDictionary() |
226 | ->GetValueForKey(key: "DBGSourcePathRemapping" ) |
227 | ->GetAsDictionary(); |
228 | remappings_dict->ForEach( |
229 | callback: [&module_sp, new_style_source_remapping_dictionary, |
230 | original_DBGSourcePath_value, |
231 | do_truncate_remapping_names]( |
232 | llvm::StringRef key, |
233 | StructuredData::Object *object) -> bool { |
234 | if (object && object->GetAsString()) { |
235 | |
236 | // key is DBGBuildSourcePath |
237 | // object is DBGSourcePath |
238 | std::string DBGSourcePath = |
239 | std::string(object->GetStringValue()); |
240 | if (!new_style_source_remapping_dictionary && |
241 | !original_DBGSourcePath_value.empty()) { |
242 | DBGSourcePath = original_DBGSourcePath_value; |
243 | } |
244 | module_sp->GetSourceMappingList().Append( |
245 | path: key, replacement: DBGSourcePath, notify: true); |
246 | // With version 2 of DBGSourcePathRemapping, we |
247 | // can chop off the last two filename parts |
248 | // from the source remapping and get a more |
249 | // general source remapping that still works. |
250 | // Add this as another option in addition to |
251 | // the full source path remap. |
252 | if (do_truncate_remapping_names) { |
253 | FileSpec build_path(key); |
254 | FileSpec source_path(DBGSourcePath.c_str()); |
255 | build_path.RemoveLastPathComponent(); |
256 | build_path.RemoveLastPathComponent(); |
257 | source_path.RemoveLastPathComponent(); |
258 | source_path.RemoveLastPathComponent(); |
259 | module_sp->GetSourceMappingList().Append( |
260 | path: build_path.GetPath(), replacement: source_path.GetPath(), |
261 | notify: true); |
262 | } |
263 | } |
264 | return true; |
265 | }); |
266 | } |
267 | |
268 | // If we have a DBGBuildSourcePath + DBGSourcePath pair, |
269 | // append those to the source path remappings. |
270 | |
271 | plist.GetValueAsString(key: "DBGBuildSourcePath" , |
272 | value&: DBGBuildSourcePath); |
273 | plist.GetValueAsString(key: "DBGSourcePath" , value&: DBGSourcePath); |
274 | if (!DBGBuildSourcePath.empty() && !DBGSourcePath.empty()) { |
275 | module_sp->GetSourceMappingList().Append( |
276 | path: DBGBuildSourcePath, replacement: DBGSourcePath, notify: true); |
277 | } |
278 | } |
279 | } |
280 | } |
281 | } |
282 | } |
283 | } |
284 | |
285 | symbol_vendor->AddSymbolFileRepresentation(objfile_sp: dsym_objfile_sp); |
286 | return symbol_vendor; |
287 | } |
288 | } |
289 | |
290 | // Just create our symbol vendor using the current objfile as this is |
291 | // either an executable with no dSYM (that we could locate), an executable |
292 | // with a dSYM that has a UUID that doesn't match. |
293 | symbol_vendor->AddSymbolFileRepresentation(objfile_sp: obj_file->shared_from_this()); |
294 | } |
295 | return symbol_vendor; |
296 | } |
297 | |