1//===-- LocateSymbolFile.cpp ------------------------------------*- C++ -*-===//
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 "lldb/Symbol/LocateSymbolFile.h"
10
11#include "lldb/Core/ModuleList.h"
12#include "lldb/Core/ModuleSpec.h"
13#include "lldb/Host/FileSystem.h"
14#include "lldb/Symbol/ObjectFile.h"
15#include "lldb/Utility/ArchSpec.h"
16#include "lldb/Utility/DataBuffer.h"
17#include "lldb/Utility/DataExtractor.h"
18#include "lldb/Utility/Log.h"
19#include "lldb/Utility/StreamString.h"
20#include "lldb/Utility/Timer.h"
21#include "lldb/Utility/UUID.h"
22
23#include "llvm/Support/FileSystem.h"
24
25// From MacOSX system header "mach/machine.h"
26typedef int cpu_type_t;
27typedef int cpu_subtype_t;
28
29using namespace lldb;
30using namespace lldb_private;
31
32#if defined(__APPLE__)
33
34// Forward declaration of method defined in source/Host/macosx/Symbols.cpp
35int LocateMacOSXFilesUsingDebugSymbols(const ModuleSpec &module_spec,
36                                       ModuleSpec &return_module_spec);
37
38#else
39
40int LocateMacOSXFilesUsingDebugSymbols(const ModuleSpec &module_spec,
41                                       ModuleSpec &return_module_spec) {
42  // Cannot find MacOSX files using debug symbols on non MacOSX.
43  return 0;
44}
45
46#endif
47
48static bool FileAtPathContainsArchAndUUID(const FileSpec &file_fspec,
49                                          const ArchSpec *arch,
50                                          const lldb_private::UUID *uuid) {
51  ModuleSpecList module_specs;
52  if (ObjectFile::GetModuleSpecifications(file_fspec, 0, 0, module_specs)) {
53    ModuleSpec spec;
54    for (size_t i = 0; i < module_specs.GetSize(); ++i) {
55      bool got_spec = module_specs.GetModuleSpecAtIndex(i, spec);
56      UNUSED_IF_ASSERT_DISABLED(got_spec);
57      assert(got_spec);
58      if ((uuid == nullptr || (spec.GetUUIDPtr() && spec.GetUUID() == *uuid)) &&
59          (arch == nullptr ||
60           (spec.GetArchitecturePtr() &&
61            spec.GetArchitecture().IsCompatibleMatch(*arch)))) {
62        return true;
63      }
64    }
65  }
66  return false;
67}
68
69// Given a binary exec_fspec, and a ModuleSpec with an architecture/uuid,
70// return true if there is a matching dSYM bundle next to the exec_fspec,
71// and return that value in dsym_fspec.
72// If there is a .dSYM.yaa compressed archive next to the exec_fspec,
73// call through Symbols::DownloadObjectAndSymbolFile to download the
74// expanded/uncompressed dSYM and return that filepath in dsym_fspec.
75
76static bool LookForDsymNextToExecutablePath(const ModuleSpec &mod_spec,
77                                            const FileSpec &exec_fspec,
78                                            FileSpec &dsym_fspec) {
79  ConstString filename = exec_fspec.GetFilename();
80  FileSpec dsym_directory = exec_fspec;
81  dsym_directory.RemoveLastPathComponent();
82
83  std::string dsym_filename = filename.AsCString();
84  dsym_filename += ".dSYM";
85  dsym_directory.AppendPathComponent(dsym_filename);
86  dsym_directory.AppendPathComponent("Contents");
87  dsym_directory.AppendPathComponent("Resources");
88  dsym_directory.AppendPathComponent("DWARF");
89
90  if (FileSystem::Instance().Exists(dsym_directory)) {
91
92    // See if the binary name exists in the dSYM DWARF
93    // subdir.
94    dsym_fspec = dsym_directory;
95    dsym_fspec.AppendPathComponent(filename.AsCString());
96    if (FileSystem::Instance().Exists(dsym_fspec) &&
97        FileAtPathContainsArchAndUUID(dsym_fspec, mod_spec.GetArchitecturePtr(),
98                                      mod_spec.GetUUIDPtr())) {
99      return true;
100    }
101
102    // See if we have "../CF.framework" - so we'll look for
103    // CF.framework.dSYM/Contents/Resources/DWARF/CF
104    // We need to drop the last suffix after '.' to match
105    // 'CF' in the DWARF subdir.
106    std::string binary_name(filename.AsCString());
107    auto last_dot = binary_name.find_last_of('.');
108    if (last_dot != std::string::npos) {
109      binary_name.erase(last_dot);
110      dsym_fspec = dsym_directory;
111      dsym_fspec.AppendPathComponent(binary_name);
112      if (FileSystem::Instance().Exists(dsym_fspec) &&
113          FileAtPathContainsArchAndUUID(dsym_fspec,
114                                        mod_spec.GetArchitecturePtr(),
115                                        mod_spec.GetUUIDPtr())) {
116        return true;
117      }
118    }
119  }
120
121  // See if we have a .dSYM.yaa next to this executable path.
122  FileSpec dsym_yaa_fspec = exec_fspec;
123  dsym_yaa_fspec.RemoveLastPathComponent();
124  std::string dsym_yaa_filename = filename.AsCString();
125  dsym_yaa_filename += ".dSYM.yaa";
126  dsym_yaa_fspec.AppendPathComponent(dsym_yaa_filename);
127
128  if (FileSystem::Instance().Exists(dsym_yaa_fspec)) {
129    ModuleSpec mutable_mod_spec = mod_spec;
130    if (Symbols::DownloadObjectAndSymbolFile(mutable_mod_spec, true) &&
131        FileSystem::Instance().Exists(mutable_mod_spec.GetSymbolFileSpec())) {
132      dsym_fspec = mutable_mod_spec.GetSymbolFileSpec();
133      return true;
134    }
135  }
136
137  return false;
138}
139
140// Given a ModuleSpec with a FileSpec and optionally uuid/architecture
141// filled in, look for a .dSYM bundle next to that binary.  Returns true
142// if a .dSYM bundle is found, and that path is returned in the dsym_fspec
143// FileSpec.
144//
145// This routine looks a few directory layers above the given exec_path -
146// exec_path might be /System/Library/Frameworks/CF.framework/CF and the
147// dSYM might be /System/Library/Frameworks/CF.framework.dSYM.
148//
149// If there is a .dSYM.yaa compressed archive found next to the binary,
150// we'll call DownloadObjectAndSymbolFile to expand it into a plain .dSYM
151
152static bool LocateDSYMInVincinityOfExecutable(const ModuleSpec &module_spec,
153                                              FileSpec &dsym_fspec) {
154  Log *log = lldb_private::GetLogIfAllCategoriesSet(LIBLLDB_LOG_HOST);
155  const FileSpec &exec_fspec = module_spec.GetFileSpec();
156  if (exec_fspec) {
157    if (::LookForDsymNextToExecutablePath(module_spec, exec_fspec,
158                                          dsym_fspec)) {
159      if (log) {
160        LLDB_LOGF(log, "dSYM with matching UUID & arch found at %s",
161                  dsym_fspec.GetPath().c_str());
162      }
163      return true;
164    } else {
165      FileSpec parent_dirs = exec_fspec;
166
167      // Remove the binary name from the FileSpec
168      parent_dirs.RemoveLastPathComponent();
169
170      // Add a ".dSYM" name to each directory component of the path,
171      // stripping off components.  e.g. we may have a binary like
172      // /S/L/F/Foundation.framework/Versions/A/Foundation and
173      // /S/L/F/Foundation.framework.dSYM
174      //
175      // so we'll need to start with
176      // /S/L/F/Foundation.framework/Versions/A, add the .dSYM part to the
177      // "A", and if that doesn't exist, strip off the "A" and try it again
178      // with "Versions", etc., until we find a dSYM bundle or we've
179      // stripped off enough path components that there's no need to
180      // continue.
181
182      for (int i = 0; i < 4; i++) {
183        // Does this part of the path have a "." character - could it be a
184        // bundle's top level directory?
185        const char *fn = parent_dirs.GetFilename().AsCString();
186        if (fn == nullptr)
187          break;
188        if (::strchr(fn, '.') != nullptr) {
189          if (::LookForDsymNextToExecutablePath(module_spec, parent_dirs,
190                                                dsym_fspec)) {
191            if (log) {
192              LLDB_LOGF(log, "dSYM with matching UUID & arch found at %s",
193                        dsym_fspec.GetPath().c_str());
194            }
195            return true;
196          }
197        }
198        parent_dirs.RemoveLastPathComponent();
199      }
200    }
201  }
202  dsym_fspec.Clear();
203  return false;
204}
205
206static FileSpec LocateExecutableSymbolFileDsym(const ModuleSpec &module_spec) {
207  const FileSpec *exec_fspec = module_spec.GetFileSpecPtr();
208  const ArchSpec *arch = module_spec.GetArchitecturePtr();
209  const UUID *uuid = module_spec.GetUUIDPtr();
210
211  static Timer::Category func_cat(LLVM_PRETTY_FUNCTION);
212  Timer scoped_timer(
213      func_cat,
214      "LocateExecutableSymbolFileDsym (file = %s, arch = %s, uuid = %p)",
215      exec_fspec ? exec_fspec->GetFilename().AsCString("<NULL>") : "<NULL>",
216      arch ? arch->GetArchitectureName() : "<NULL>", (const void *)uuid);
217
218  FileSpec symbol_fspec;
219  ModuleSpec dsym_module_spec;
220  // First try and find the dSYM in the same directory as the executable or in
221  // an appropriate parent directory
222  if (!LocateDSYMInVincinityOfExecutable(module_spec, symbol_fspec)) {
223    // We failed to easily find the dSYM above, so use DebugSymbols
224    LocateMacOSXFilesUsingDebugSymbols(module_spec, dsym_module_spec);
225  } else {
226    dsym_module_spec.GetSymbolFileSpec() = symbol_fspec;
227  }
228  return dsym_module_spec.GetSymbolFileSpec();
229}
230
231ModuleSpec Symbols::LocateExecutableObjectFile(const ModuleSpec &module_spec) {
232  ModuleSpec result;
233  const FileSpec &exec_fspec = module_spec.GetFileSpec();
234  const ArchSpec *arch = module_spec.GetArchitecturePtr();
235  const UUID *uuid = module_spec.GetUUIDPtr();
236  static Timer::Category func_cat(LLVM_PRETTY_FUNCTION);
237  Timer scoped_timer(
238      func_cat, "LocateExecutableObjectFile (file = %s, arch = %s, uuid = %p)",
239      exec_fspec ? exec_fspec.GetFilename().AsCString("<NULL>") : "<NULL>",
240      arch ? arch->GetArchitectureName() : "<NULL>", (const void *)uuid);
241
242  ModuleSpecList module_specs;
243  ModuleSpec matched_module_spec;
244  if (exec_fspec &&
245      ObjectFile::GetModuleSpecifications(exec_fspec, 0, 0, module_specs) &&
246      module_specs.FindMatchingModuleSpec(module_spec, matched_module_spec)) {
247    result.GetFileSpec() = exec_fspec;
248  } else {
249    LocateMacOSXFilesUsingDebugSymbols(module_spec, result);
250  }
251  return result;
252}
253
254// Keep "symbols.enable-external-lookup" description in sync with this function.
255
256FileSpec
257Symbols::LocateExecutableSymbolFile(const ModuleSpec &module_spec,
258                                    const FileSpecList &default_search_paths) {
259  FileSpec symbol_file_spec = module_spec.GetSymbolFileSpec();
260  if (symbol_file_spec.IsAbsolute() &&
261      FileSystem::Instance().Exists(symbol_file_spec))
262    return symbol_file_spec;
263
264  FileSpecList debug_file_search_paths = default_search_paths;
265
266  // Add module directory.
267  FileSpec module_file_spec = module_spec.GetFileSpec();
268  // We keep the unresolved pathname if it fails.
269  FileSystem::Instance().ResolveSymbolicLink(module_file_spec,
270                                             module_file_spec);
271
272  ConstString file_dir = module_file_spec.GetDirectory();
273  {
274    FileSpec file_spec(file_dir.AsCString("."));
275    FileSystem::Instance().Resolve(file_spec);
276    debug_file_search_paths.AppendIfUnique(file_spec);
277  }
278
279  if (ModuleList::GetGlobalModuleListProperties().GetEnableExternalLookup()) {
280
281    // Add current working directory.
282    {
283      FileSpec file_spec(".");
284      FileSystem::Instance().Resolve(file_spec);
285      debug_file_search_paths.AppendIfUnique(file_spec);
286    }
287
288#ifndef _WIN32
289#if defined(__NetBSD__)
290    // Add /usr/libdata/debug directory.
291    {
292      FileSpec file_spec("/usr/libdata/debug");
293      FileSystem::Instance().Resolve(file_spec);
294      debug_file_search_paths.AppendIfUnique(file_spec);
295    }
296#else
297    // Add /usr/lib/debug directory.
298    {
299      FileSpec file_spec("/usr/lib/debug");
300      FileSystem::Instance().Resolve(file_spec);
301      debug_file_search_paths.AppendIfUnique(file_spec);
302    }
303#endif
304#endif // _WIN32
305  }
306
307  std::string uuid_str;
308  const UUID &module_uuid = module_spec.GetUUID();
309  if (module_uuid.IsValid()) {
310    // Some debug files are stored in the .build-id directory like this:
311    //   /usr/lib/debug/.build-id/ff/e7fe727889ad82bb153de2ad065b2189693315.debug
312    uuid_str = module_uuid.GetAsString("");
313    std::transform(uuid_str.begin(), uuid_str.end(), uuid_str.begin(),
314                   ::tolower);
315    uuid_str.insert(2, 1, '/');
316    uuid_str = uuid_str + ".debug";
317  }
318
319  size_t num_directories = debug_file_search_paths.GetSize();
320  for (size_t idx = 0; idx < num_directories; ++idx) {
321    FileSpec dirspec = debug_file_search_paths.GetFileSpecAtIndex(idx);
322    FileSystem::Instance().Resolve(dirspec);
323    if (!FileSystem::Instance().IsDirectory(dirspec))
324      continue;
325
326    std::vector<std::string> files;
327    std::string dirname = dirspec.GetPath();
328
329    if (!uuid_str.empty())
330      files.push_back(dirname + "/.build-id/" + uuid_str);
331    if (symbol_file_spec.GetFilename()) {
332      files.push_back(dirname + "/" +
333                      symbol_file_spec.GetFilename().GetCString());
334      files.push_back(dirname + "/.debug/" +
335                      symbol_file_spec.GetFilename().GetCString());
336
337      // Some debug files may stored in the module directory like this:
338      //   /usr/lib/debug/usr/lib/library.so.debug
339      if (!file_dir.IsEmpty())
340        files.push_back(dirname + file_dir.AsCString() + "/" +
341                        symbol_file_spec.GetFilename().GetCString());
342    }
343
344    const uint32_t num_files = files.size();
345    for (size_t idx_file = 0; idx_file < num_files; ++idx_file) {
346      const std::string &filename = files[idx_file];
347      FileSpec file_spec(filename);
348      FileSystem::Instance().Resolve(file_spec);
349
350      if (llvm::sys::fs::equivalent(file_spec.GetPath(),
351                                    module_file_spec.GetPath()))
352        continue;
353
354      if (FileSystem::Instance().Exists(file_spec)) {
355        lldb_private::ModuleSpecList specs;
356        const size_t num_specs =
357            ObjectFile::GetModuleSpecifications(file_spec, 0, 0, specs);
358        assert(num_specs <= 1 &&
359               "Symbol Vendor supports only a single architecture");
360        if (num_specs == 1) {
361          ModuleSpec mspec;
362          if (specs.GetModuleSpecAtIndex(0, mspec)) {
363            // Skip the uuids check if module_uuid is invalid. For example,
364            // this happens for *.dwp files since at the moment llvm-dwp
365            // doesn't output build ids, nor does binutils dwp.
366            if (!module_uuid.IsValid() || module_uuid == mspec.GetUUID())
367              return file_spec;
368          }
369        }
370      }
371    }
372  }
373
374  return LocateExecutableSymbolFileDsym(module_spec);
375}
376
377#if !defined(__APPLE__)
378
379FileSpec Symbols::FindSymbolFileInBundle(const FileSpec &symfile_bundle,
380                                         const lldb_private::UUID *uuid,
381                                         const ArchSpec *arch) {
382  // FIXME
383  return FileSpec();
384}
385
386bool Symbols::DownloadObjectAndSymbolFile(ModuleSpec &module_spec,
387                                          bool force_lookup) {
388  // Fill in the module_spec.GetFileSpec() for the object file and/or the
389  // module_spec.GetSymbolFileSpec() for the debug symbols file.
390  return false;
391}
392
393#endif
394