Jason Molenda 8825c5c9b4 Re-commit the (fixed) changes from r248985 which were reverted by Pavel
when they introduced android testsuite regressions.  Pavel has run the
testsuite against the updated patch and it completes cleanly now.

The original commit message:


Fixing a subtle issue on Mac OS X systems with dSYMs (possibly
introduced by r235737 but I didn't look into it too closely).

A dSYM can have a per-UUID plist in it which tells lldb where
to find an executable binary for the dSYM (DBGSymbolRichExecutable)
- other information can be included in this plist, like how to
remap the source file paths from their build pathnames to their
long-term storage pathnames.

This per-UUID plist is a unusual; it is used probably exclusively
inside apple with our build system.  It is not created by default
in normal dSYMs.

The problem was like this:

  1. lldb wants to find an executable, given only a UUID
     (this happens when lldb is doing cross-host debugging
      and doesn't have a copy of the target system's binaries)

  2. It eventually calls LocateMacOSXFilesUsingDebugSymbols
     which does a spotlight search for the dSYM on the local
     system, and failing that, tries the DBGShellCommands
     command to find the dSYM.

  3. It gets a dSYM.  It reads the per-UUID plist in the dSYM.
     The dSYM has a DBGSymbolRichExecutable kv pair pointing to
     the binary on a network filesystem.

  4. Using the binary on the network filesystem, lldb now goes
     to find the dSYM.

  5. It starts by looking for a dSYM next to the binary it found.

  6. lldb is now reading the dSYM over a network filesystem,
     ignoring the one it found on its local filesystem earlier.

Everything still *works* but it's much slower.

This would be a tricky one to write up in a testsuite case;
you really need the binary to not exist on the local system.
And LocateMacOSXFilesUsingDebugSymbols will only compile on
Mac OS X - even if I found a way to write up a test case, it
would not run anywhere but on a mac.

One change Greg wanted while I was touching this code was to
have LocateMacOSXFilesUsingDebugSymbols (which could be asked
to find a binary OR find a dSYM) to instead return a ModuleSpec
with the sum total of everything it could find.  This
change of passing around a ModuleSpec instead of a FileSpec
was percolated up into ModuleList::GetSharedModule.

The changes to LocateMacOSXFilesUsingDebugSymbols look larger
than they really are - there's a lot of simple whitespace changes
in there.

I ran the testsuites on mac, no new regressions introduced

<rdar://problem/21993813> 

llvm-svn: 249755
2015-10-08 21:48:35 +00:00

560 lines
26 KiB
C++

//===-- Symbols.cpp ---------------------------------------------*- C++ -*-===//
//
// The LLVM Compiler Infrastructure
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//
#include "lldb/Host/Symbols.h"
// C Includes
#include <dirent.h>
#include <pwd.h>
#include "lldb/Utility/SafeMachO.h"
// C++ Includes
// Other libraries and framework includes
#include <CoreFoundation/CoreFoundation.h>
// Project includes
#include "lldb/Core/ArchSpec.h"
#include "lldb/Core/DataBuffer.h"
#include "lldb/Core/DataExtractor.h"
#include "lldb/Core/Log.h"
#include "lldb/Core/Module.h"
#include "lldb/Core/ModuleSpec.h"
#include "lldb/Core/StreamString.h"
#include "lldb/Core/Timer.h"
#include "lldb/Core/UUID.h"
#include "lldb/Host/Endian.h"
#include "lldb/Host/Host.h"
#include "lldb/Symbol/ObjectFile.h"
#include "lldb/Utility/CleanUp.h"
#include "Host/macosx/cfcpp/CFCBundle.h"
#include "Host/macosx/cfcpp/CFCData.h"
#include "Host/macosx/cfcpp/CFCReleaser.h"
#include "Host/macosx/cfcpp/CFCString.h"
#include "mach/machine.h"
using namespace lldb;
using namespace lldb_private;
using namespace llvm::MachO;
#if !defined (__arm__) && !defined (__arm64__) && !defined (__aarch64__) // No DebugSymbols on the iOS devices
extern "C" {
CFURLRef DBGCopyFullDSYMURLForUUID (CFUUIDRef uuid, CFURLRef exec_url);
CFDictionaryRef DBGCopyDSYMPropertyLists (CFURLRef dsym_url);
}
#endif
int
LocateMacOSXFilesUsingDebugSymbols
(
const ModuleSpec &module_spec,
ModuleSpec &return_module_spec
)
{
return_module_spec = module_spec;
return_module_spec.GetFileSpec().Clear();
return_module_spec.GetSymbolFileSpec().Clear();
int items_found = 0;
#if !defined (__arm__) && !defined (__arm64__) && !defined (__aarch64__) // No DebugSymbols on the iOS devices
const UUID *uuid = module_spec.GetUUIDPtr();
const ArchSpec *arch = module_spec.GetArchitecturePtr();
if (uuid && uuid->IsValid())
{
// Try and locate the dSYM file using DebugSymbols first
const UInt8 *module_uuid = (const UInt8 *)uuid->GetBytes();
if (module_uuid != NULL)
{
CFCReleaser<CFUUIDRef> module_uuid_ref(::CFUUIDCreateWithBytes (NULL,
module_uuid[0],
module_uuid[1],
module_uuid[2],
module_uuid[3],
module_uuid[4],
module_uuid[5],
module_uuid[6],
module_uuid[7],
module_uuid[8],
module_uuid[9],
module_uuid[10],
module_uuid[11],
module_uuid[12],
module_uuid[13],
module_uuid[14],
module_uuid[15]));
if (module_uuid_ref.get())
{
CFCReleaser<CFURLRef> exec_url;
const FileSpec *exec_fspec = module_spec.GetFileSpecPtr();
Log *log = lldb_private::GetLogIfAllCategoriesSet(LIBLLDB_LOG_HOST);
if (exec_fspec)
{
char exec_cf_path[PATH_MAX];
if (exec_fspec->GetPath(exec_cf_path, sizeof(exec_cf_path)))
exec_url.reset(::CFURLCreateFromFileSystemRepresentation (NULL,
(const UInt8 *)exec_cf_path,
strlen(exec_cf_path),
FALSE));
}
CFCReleaser<CFURLRef> dsym_url (::DBGCopyFullDSYMURLForUUID(module_uuid_ref.get(), exec_url.get()));
char path[PATH_MAX];
if (dsym_url.get())
{
if (::CFURLGetFileSystemRepresentation (dsym_url.get(), true, (UInt8*)path, sizeof(path)-1))
{
if (log)
{
log->Printf ("DebugSymbols framework returned dSYM path of %s for UUID %s -- looking for the dSYM", path, uuid->GetAsString().c_str());
}
FileSpec dsym_filespec(path, path[0] == '~');
if (dsym_filespec.GetFileType () == FileSpec::eFileTypeDirectory)
{
dsym_filespec = Symbols::FindSymbolFileInBundle (dsym_filespec, uuid, arch);
++items_found;
}
else
{
++items_found;
}
return_module_spec.GetSymbolFileSpec() = dsym_filespec;
}
bool success = false;
if (log)
{
if (::CFURLGetFileSystemRepresentation (dsym_url.get(), true, (UInt8*)path, sizeof(path)-1))
{
log->Printf ("DebugSymbols framework returned dSYM path of %s for UUID %s -- looking for an exec file", path, uuid->GetAsString().c_str());
}
}
CFCReleaser<CFDictionaryRef> dict(::DBGCopyDSYMPropertyLists (dsym_url.get()));
CFDictionaryRef uuid_dict = NULL;
if (dict.get())
{
CFCString uuid_cfstr (uuid->GetAsString().c_str());
uuid_dict = static_cast<CFDictionaryRef>(::CFDictionaryGetValue (dict.get(), uuid_cfstr.get()));
}
if (uuid_dict)
{
CFStringRef exec_cf_path = static_cast<CFStringRef>(::CFDictionaryGetValue (uuid_dict, CFSTR("DBGSymbolRichExecutable")));
if (exec_cf_path && ::CFStringGetFileSystemRepresentation (exec_cf_path, path, sizeof(path)))
{
if (log)
{
log->Printf ("plist bundle has exec path of %s for UUID %s", path, uuid->GetAsString().c_str());
}
++items_found;
FileSpec exec_filespec (path, path[0] == '~');
if (exec_filespec.Exists())
{
success = true;
return_module_spec.GetFileSpec() = exec_filespec;
}
}
}
if (!success)
{
// No dictionary, check near the dSYM bundle for an executable that matches...
if (::CFURLGetFileSystemRepresentation (dsym_url.get(), true, (UInt8*)path, sizeof(path)-1))
{
char *dsym_extension_pos = ::strstr (path, ".dSYM");
if (dsym_extension_pos)
{
*dsym_extension_pos = '\0';
if (log)
{
log->Printf ("Looking for executable binary next to dSYM bundle with name with name %s", path);
}
FileSpec file_spec (path, true);
ModuleSpecList module_specs;
ModuleSpec matched_module_spec;
switch (file_spec.GetFileType())
{
case FileSpec::eFileTypeDirectory: // Bundle directory?
{
CFCBundle bundle (path);
CFCReleaser<CFURLRef> bundle_exe_url (bundle.CopyExecutableURL ());
if (bundle_exe_url.get())
{
if (::CFURLGetFileSystemRepresentation (bundle_exe_url.get(), true, (UInt8*)path, sizeof(path)-1))
{
FileSpec bundle_exe_file_spec (path, true);
if (ObjectFile::GetModuleSpecifications(bundle_exe_file_spec, 0, 0, module_specs) &&
module_specs.FindMatchingModuleSpec(module_spec, matched_module_spec))
{
++items_found;
return_module_spec.GetFileSpec() = bundle_exe_file_spec;
if (log)
{
log->Printf ("Executable binary %s next to dSYM is compatible; using", path);
}
}
}
}
}
break;
case FileSpec::eFileTypePipe: // Forget pipes
case FileSpec::eFileTypeSocket: // We can't process socket files
case FileSpec::eFileTypeInvalid: // File doesn't exist...
break;
case FileSpec::eFileTypeUnknown:
case FileSpec::eFileTypeRegular:
case FileSpec::eFileTypeSymbolicLink:
case FileSpec::eFileTypeOther:
if (ObjectFile::GetModuleSpecifications(file_spec, 0, 0, module_specs) &&
module_specs.FindMatchingModuleSpec(module_spec, matched_module_spec))
{
++items_found;
return_module_spec.GetFileSpec() = file_spec;
if (log)
{
log->Printf ("Executable binary %s next to dSYM is compatible; using", path);
}
}
break;
}
}
}
}
}
}
}
}
#endif // #if !defined (__arm__) && !defined (__arm64__) && !defined (__aarch64__)
return items_found;
}
FileSpec
Symbols::FindSymbolFileInBundle (const FileSpec& dsym_bundle_fspec,
const lldb_private::UUID *uuid,
const ArchSpec *arch)
{
char path[PATH_MAX];
FileSpec dsym_fspec;
if (dsym_bundle_fspec.GetPath(path, sizeof(path)))
{
::strncat (path, "/Contents/Resources/DWARF", sizeof(path) - strlen(path) - 1);
lldb_utility::CleanUp <DIR *, int> dirp (opendir(path), NULL, closedir);
if (dirp.is_valid())
{
dsym_fspec.GetDirectory().SetCString(path);
struct dirent* dp;
while ((dp = readdir(dirp.get())) != NULL)
{
// Only search directories
if (dp->d_type == DT_DIR || dp->d_type == DT_UNKNOWN)
{
if (dp->d_namlen == 1 && dp->d_name[0] == '.')
continue;
if (dp->d_namlen == 2 && dp->d_name[0] == '.' && dp->d_name[1] == '.')
continue;
}
if (dp->d_type == DT_REG || dp->d_type == DT_UNKNOWN)
{
dsym_fspec.GetFilename().SetCString(dp->d_name);
ModuleSpecList module_specs;
if (ObjectFile::GetModuleSpecifications(dsym_fspec, 0, 0, module_specs))
{
ModuleSpec spec;
for (size_t i = 0; i < module_specs.GetSize(); ++i)
{
assert(module_specs.GetModuleSpecAtIndex(i, spec));
if ((uuid == NULL || (spec.GetUUIDPtr() && spec.GetUUID() == *uuid)) &&
(arch == NULL || (spec.GetArchitecturePtr() && spec.GetArchitecture().IsCompatibleMatch(*arch))))
{
return dsym_fspec;
}
}
}
}
}
}
}
dsym_fspec.Clear();
return dsym_fspec;
}
static bool
GetModuleSpecInfoFromUUIDDictionary (CFDictionaryRef uuid_dict, ModuleSpec &module_spec)
{
Log *log = lldb_private::GetLogIfAllCategoriesSet(LIBLLDB_LOG_HOST);
bool success = false;
if (uuid_dict != NULL && CFGetTypeID (uuid_dict) == CFDictionaryGetTypeID ())
{
std::string str;
CFStringRef cf_str;
cf_str = (CFStringRef)CFDictionaryGetValue ((CFDictionaryRef) uuid_dict, CFSTR("DBGSymbolRichExecutable"));
if (cf_str && CFGetTypeID (cf_str) == CFStringGetTypeID ())
{
if (CFCString::FileSystemRepresentation(cf_str, str))
{
module_spec.GetFileSpec().SetFile (str.c_str(), true);
if (log)
{
log->Printf ("From dsymForUUID plist: Symbol rich executable is at '%s'", str.c_str());
}
}
}
cf_str = (CFStringRef)CFDictionaryGetValue ((CFDictionaryRef) uuid_dict, CFSTR("DBGDSYMPath"));
if (cf_str && CFGetTypeID (cf_str) == CFStringGetTypeID ())
{
if (CFCString::FileSystemRepresentation(cf_str, str))
{
module_spec.GetSymbolFileSpec().SetFile (str.c_str(), true);
success = true;
if (log)
{
log->Printf ("From dsymForUUID plist: dSYM is at '%s'", str.c_str());
}
}
}
cf_str = (CFStringRef)CFDictionaryGetValue ((CFDictionaryRef) uuid_dict, CFSTR("DBGArchitecture"));
if (cf_str && CFGetTypeID (cf_str) == CFStringGetTypeID ())
{
if (CFCString::FileSystemRepresentation(cf_str, str))
module_spec.GetArchitecture().SetTriple(str.c_str());
}
std::string DBGBuildSourcePath;
std::string DBGSourcePath;
cf_str = (CFStringRef)CFDictionaryGetValue ((CFDictionaryRef) uuid_dict, CFSTR("DBGBuildSourcePath"));
if (cf_str && CFGetTypeID (cf_str) == CFStringGetTypeID ())
{
CFCString::FileSystemRepresentation(cf_str, DBGBuildSourcePath);
}
cf_str = (CFStringRef)CFDictionaryGetValue ((CFDictionaryRef) uuid_dict, CFSTR("DBGSourcePath"));
if (cf_str && CFGetTypeID (cf_str) == CFStringGetTypeID ())
{
CFCString::FileSystemRepresentation(cf_str, DBGSourcePath);
}
if (!DBGBuildSourcePath.empty() && !DBGSourcePath.empty())
{
module_spec.GetSourceMappingList().Append (ConstString(DBGBuildSourcePath.c_str()), ConstString(DBGSourcePath.c_str()), true);
}
}
return success;
}
bool
Symbols::DownloadObjectAndSymbolFile (ModuleSpec &module_spec, bool force_lookup)
{
bool success = false;
const UUID *uuid_ptr = module_spec.GetUUIDPtr();
const FileSpec *file_spec_ptr = module_spec.GetFileSpecPtr();
// It's expensive to check for the DBGShellCommands defaults setting, only do it once per
// lldb run and cache the result.
static bool g_have_checked_for_dbgshell_command = false;
static const char *g_dbgshell_command = NULL;
if (g_have_checked_for_dbgshell_command == false)
{
g_have_checked_for_dbgshell_command = true;
CFTypeRef defaults_setting = CFPreferencesCopyAppValue (CFSTR ("DBGShellCommands"), CFSTR ("com.apple.DebugSymbols"));
if (defaults_setting && CFGetTypeID (defaults_setting) == CFStringGetTypeID())
{
char cstr_buf[PATH_MAX];
if (CFStringGetCString ((CFStringRef) defaults_setting, cstr_buf, sizeof (cstr_buf), kCFStringEncodingUTF8))
{
g_dbgshell_command = strdup (cstr_buf); // this malloc'ed memory will never be freed
}
}
if (defaults_setting)
{
CFRelease (defaults_setting);
}
}
// When g_dbgshell_command is NULL, the user has not enabled the use of an external program
// to find the symbols, don't run it for them.
if (force_lookup == false && g_dbgshell_command == NULL)
{
return false;
}
if (uuid_ptr || (file_spec_ptr && file_spec_ptr->Exists()))
{
static bool g_located_dsym_for_uuid_exe = false;
static bool g_dsym_for_uuid_exe_exists = false;
static char g_dsym_for_uuid_exe_path[PATH_MAX];
if (!g_located_dsym_for_uuid_exe)
{
g_located_dsym_for_uuid_exe = true;
const char *dsym_for_uuid_exe_path_cstr = getenv("LLDB_APPLE_DSYMFORUUID_EXECUTABLE");
FileSpec dsym_for_uuid_exe_spec;
if (dsym_for_uuid_exe_path_cstr)
{
dsym_for_uuid_exe_spec.SetFile(dsym_for_uuid_exe_path_cstr, true);
g_dsym_for_uuid_exe_exists = dsym_for_uuid_exe_spec.Exists();
}
if (!g_dsym_for_uuid_exe_exists)
{
dsym_for_uuid_exe_spec.SetFile("/usr/local/bin/dsymForUUID", false);
g_dsym_for_uuid_exe_exists = dsym_for_uuid_exe_spec.Exists();
if (!g_dsym_for_uuid_exe_exists)
{
long bufsize;
if ((bufsize = sysconf(_SC_GETPW_R_SIZE_MAX)) != -1)
{
char buffer[bufsize];
struct passwd pwd;
struct passwd *tilde_rc = NULL;
// we are a library so we need to use the reentrant version of getpwnam()
if (getpwnam_r ("rc", &pwd, buffer, bufsize, &tilde_rc) == 0
&& tilde_rc
&& tilde_rc->pw_dir)
{
std::string dsymforuuid_path(tilde_rc->pw_dir);
dsymforuuid_path += "/bin/dsymForUUID";
dsym_for_uuid_exe_spec.SetFile(dsymforuuid_path.c_str(), false);
g_dsym_for_uuid_exe_exists = dsym_for_uuid_exe_spec.Exists();
}
}
}
}
if (!g_dsym_for_uuid_exe_exists && g_dbgshell_command != NULL)
{
dsym_for_uuid_exe_spec.SetFile(g_dbgshell_command, true);
g_dsym_for_uuid_exe_exists = dsym_for_uuid_exe_spec.Exists();
}
if (g_dsym_for_uuid_exe_exists)
dsym_for_uuid_exe_spec.GetPath (g_dsym_for_uuid_exe_path, sizeof(g_dsym_for_uuid_exe_path));
}
if (g_dsym_for_uuid_exe_exists)
{
std::string uuid_str;
char file_path[PATH_MAX];
file_path[0] = '\0';
if (uuid_ptr)
uuid_str = uuid_ptr->GetAsString();
if (file_spec_ptr)
file_spec_ptr->GetPath(file_path, sizeof(file_path));
StreamString command;
if (!uuid_str.empty())
command.Printf("%s --ignoreNegativeCache --copyExecutable %s", g_dsym_for_uuid_exe_path, uuid_str.c_str());
else if (file_path[0] != '\0')
command.Printf("%s --ignoreNegativeCache --copyExecutable %s", g_dsym_for_uuid_exe_path, file_path);
if (!command.GetString().empty())
{
Log *log = lldb_private::GetLogIfAllCategoriesSet(LIBLLDB_LOG_HOST);
int exit_status = -1;
int signo = -1;
std::string command_output;
if (log)
{
if (!uuid_str.empty())
log->Printf("Calling %s with UUID %s to find dSYM", g_dsym_for_uuid_exe_path, uuid_str.c_str());
else if (file_path[0] != '\0')
log->Printf("Calling %s with file %s to find dSYM", g_dsym_for_uuid_exe_path, file_path);
}
Error error = Host::RunShellCommand (command.GetData(),
NULL, // current working directory
&exit_status, // Exit status
&signo, // Signal int *
&command_output, // Command output
30, // Large timeout to allow for long dsym download times
false); // Don't run in a shell (we don't need shell expansion)
if (error.Success() && exit_status == 0 && !command_output.empty())
{
CFCData data (CFDataCreateWithBytesNoCopy (NULL,
(const UInt8 *)command_output.data(),
command_output.size(),
kCFAllocatorNull));
CFCReleaser<CFDictionaryRef> plist((CFDictionaryRef)::CFPropertyListCreateFromXMLData (NULL, data.get(), kCFPropertyListImmutable, NULL));
if (plist.get() && CFGetTypeID (plist.get()) == CFDictionaryGetTypeID ())
{
if (!uuid_str.empty())
{
CFCString uuid_cfstr(uuid_str.c_str());
CFDictionaryRef uuid_dict = (CFDictionaryRef)CFDictionaryGetValue (plist.get(), uuid_cfstr.get());
success = GetModuleSpecInfoFromUUIDDictionary (uuid_dict, module_spec);
}
else
{
const CFIndex num_values = ::CFDictionaryGetCount(plist.get());
if (num_values > 0)
{
std::vector<CFStringRef> keys (num_values, NULL);
std::vector<CFDictionaryRef> values (num_values, NULL);
::CFDictionaryGetKeysAndValues(plist.get(), NULL, (const void **)&values[0]);
if (num_values == 1)
{
return GetModuleSpecInfoFromUUIDDictionary (values[0], module_spec);
}
else
{
for (CFIndex i=0; i<num_values; ++i)
{
ModuleSpec curr_module_spec;
if (GetModuleSpecInfoFromUUIDDictionary (values[i], curr_module_spec))
{
if (module_spec.GetArchitecture().IsCompatibleMatch(curr_module_spec.GetArchitecture()))
{
module_spec = curr_module_spec;
return true;
}
}
}
}
}
}
}
}
else
{
if (log)
{
if (!uuid_str.empty())
log->Printf("Called %s on %s, no matches", g_dsym_for_uuid_exe_path, uuid_str.c_str());
else if (file_path[0] != '\0')
log->Printf("Called %s on %s, no matches", g_dsym_for_uuid_exe_path, file_path);
}
}
}
}
}
return success;
}