mirror of
https://github.com/Gericom/teak-llvm.git
synced 2025-06-24 14:05:49 -04:00

static bool Host::GetLLDBPath (lldb::PathType path_type, FileSpec &file_spec); This will fill in "file_spec" with an appropriate path that is appropriate for the current Host OS. MacOSX will return paths within the LLDB.framework, and other unixes will return the paths they want. The current PathType enums are: typedef enum PathType { ePathTypeLLDBShlibDir, // The directory where the lldb.so (unix) or LLDB mach-o file in LLDB.framework (MacOSX) exists ePathTypeSupportExecutableDir, // Find LLDB support executable directory (debugserver, etc) ePathTypeHeaderDir, // Find LLDB header file directory ePathTypePythonDir // Find Python modules (PYTHONPATH) directory } PathType; All places that were finding executables are and python paths are now updated to use this Host call. Added another new host call to launch the inferior in a terminal. This ability will be very host specific and doesn't need to be supported on all systems. MacOSX currently will create a new .command file and tell Terminal.app to open the .command file. It also uses the new "darwin-debug" app which is a small app that uses posix to exec (no fork) and stop at the entry point of the program. The GDB remote plug-in is almost able launch a process and attach to it, it currently will spawn the process, but it won't attach to it just yet. This will let LLDB not have to share the terminal with another process and a new terminal window will pop up when you launch. This won't get hooked up until we work out all of the kinks. The new Host function is: static lldb::pid_t Host::LaunchInNewTerminal ( const char **argv, // argv[0] is executable const char **envp, const ArchSpec *arch_spec, bool stop_at_entry, bool disable_aslr); Cleaned up FileSpec::GetPath to not use strncpy() as it was always zero filling the entire path buffer. Fixed an issue with the dynamic checker function where I missed a '$' prefix that should have been added. llvm-svn: 116690
2322 lines
82 KiB
C++
2322 lines
82 KiB
C++
//===-- ProcessGDBRemote.cpp ------------------------------------*- C++ -*-===//
|
|
//
|
|
// The LLVM Compiler Infrastructure
|
|
//
|
|
// This file is distributed under the University of Illinois Open Source
|
|
// License. See LICENSE.TXT for details.
|
|
//
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
// C Includes
|
|
#include <errno.h>
|
|
#include <spawn.h>
|
|
#include <sys/types.h>
|
|
#include <sys/stat.h>
|
|
|
|
// C++ Includes
|
|
#include <algorithm>
|
|
#include <map>
|
|
|
|
// Other libraries and framework includes
|
|
|
|
#include "lldb/Breakpoint/WatchpointLocation.h"
|
|
#include "lldb/Interpreter/Args.h"
|
|
#include "lldb/Core/ArchSpec.h"
|
|
#include "lldb/Core/Debugger.h"
|
|
#include "lldb/Core/ConnectionFileDescriptor.h"
|
|
#include "lldb/Core/FileSpec.h"
|
|
#include "lldb/Core/InputReader.h"
|
|
#include "lldb/Core/Module.h"
|
|
#include "lldb/Core/PluginManager.h"
|
|
#include "lldb/Core/State.h"
|
|
#include "lldb/Core/StreamString.h"
|
|
#include "lldb/Core/Timer.h"
|
|
#include "lldb/Host/TimeValue.h"
|
|
#include "lldb/Symbol/ObjectFile.h"
|
|
#include "lldb/Target/DynamicLoader.h"
|
|
#include "lldb/Target/Target.h"
|
|
#include "lldb/Target/TargetList.h"
|
|
#include "lldb/Utility/PseudoTerminal.h"
|
|
|
|
// Project includes
|
|
#include "lldb/Host/Host.h"
|
|
#include "Utility/StringExtractorGDBRemote.h"
|
|
#include "GDBRemoteRegisterContext.h"
|
|
#include "ProcessGDBRemote.h"
|
|
#include "ProcessGDBRemoteLog.h"
|
|
#include "ThreadGDBRemote.h"
|
|
#include "MacOSXLibunwindCallbacks.h"
|
|
#include "StopInfoMachException.h"
|
|
|
|
|
|
|
|
#define DEBUGSERVER_BASENAME "debugserver"
|
|
using namespace lldb;
|
|
using namespace lldb_private;
|
|
|
|
static inline uint16_t
|
|
get_random_port ()
|
|
{
|
|
return (arc4random() % (UINT16_MAX - 1000u)) + 1000u;
|
|
}
|
|
|
|
|
|
const char *
|
|
ProcessGDBRemote::GetPluginNameStatic()
|
|
{
|
|
return "process.gdb-remote";
|
|
}
|
|
|
|
const char *
|
|
ProcessGDBRemote::GetPluginDescriptionStatic()
|
|
{
|
|
return "GDB Remote protocol based debugging plug-in.";
|
|
}
|
|
|
|
void
|
|
ProcessGDBRemote::Terminate()
|
|
{
|
|
PluginManager::UnregisterPlugin (ProcessGDBRemote::CreateInstance);
|
|
}
|
|
|
|
|
|
Process*
|
|
ProcessGDBRemote::CreateInstance (Target &target, Listener &listener)
|
|
{
|
|
return new ProcessGDBRemote (target, listener);
|
|
}
|
|
|
|
bool
|
|
ProcessGDBRemote::CanDebug(Target &target)
|
|
{
|
|
// For now we are just making sure the file exists for a given module
|
|
ModuleSP exe_module_sp(target.GetExecutableModule());
|
|
if (exe_module_sp.get())
|
|
return exe_module_sp->GetFileSpec().Exists();
|
|
// However, if there is no executable module, we return true since we might be preparing to attach.
|
|
return true;
|
|
}
|
|
|
|
//----------------------------------------------------------------------
|
|
// ProcessGDBRemote constructor
|
|
//----------------------------------------------------------------------
|
|
ProcessGDBRemote::ProcessGDBRemote(Target& target, Listener &listener) :
|
|
Process (target, listener),
|
|
m_dynamic_loader_ap (),
|
|
m_flags (0),
|
|
m_stdio_communication ("gdb-remote.stdio"),
|
|
m_stdio_mutex (Mutex::eMutexTypeRecursive),
|
|
m_stdout_data (),
|
|
m_byte_order (eByteOrderHost),
|
|
m_gdb_comm(),
|
|
m_debugserver_pid (LLDB_INVALID_PROCESS_ID),
|
|
m_debugserver_thread (LLDB_INVALID_HOST_THREAD),
|
|
m_last_stop_packet (),
|
|
m_register_info (),
|
|
m_async_broadcaster ("lldb.process.gdb-remote.async-broadcaster"),
|
|
m_async_thread (LLDB_INVALID_HOST_THREAD),
|
|
m_curr_tid (LLDB_INVALID_THREAD_ID),
|
|
m_curr_tid_run (LLDB_INVALID_THREAD_ID),
|
|
m_z0_supported (1),
|
|
m_continue_packet(),
|
|
m_dispatch_queue_offsets_addr (LLDB_INVALID_ADDRESS),
|
|
m_packet_timeout (1),
|
|
m_max_memory_size (512),
|
|
m_libunwind_target_type (UNW_TARGET_UNSPECIFIED),
|
|
m_libunwind_addr_space (NULL),
|
|
m_waiting_for_attach (false),
|
|
m_local_debugserver (true)
|
|
{
|
|
}
|
|
|
|
//----------------------------------------------------------------------
|
|
// Destructor
|
|
//----------------------------------------------------------------------
|
|
ProcessGDBRemote::~ProcessGDBRemote()
|
|
{
|
|
if (m_debugserver_thread != LLDB_INVALID_HOST_THREAD)
|
|
{
|
|
Host::ThreadCancel (m_debugserver_thread, NULL);
|
|
thread_result_t thread_result;
|
|
Host::ThreadJoin (m_debugserver_thread, &thread_result, NULL);
|
|
m_debugserver_thread = LLDB_INVALID_HOST_THREAD;
|
|
}
|
|
// m_mach_process.UnregisterNotificationCallbacks (this);
|
|
Clear();
|
|
}
|
|
|
|
//----------------------------------------------------------------------
|
|
// PluginInterface
|
|
//----------------------------------------------------------------------
|
|
const char *
|
|
ProcessGDBRemote::GetPluginName()
|
|
{
|
|
return "Process debugging plug-in that uses the GDB remote protocol";
|
|
}
|
|
|
|
const char *
|
|
ProcessGDBRemote::GetShortPluginName()
|
|
{
|
|
return GetPluginNameStatic();
|
|
}
|
|
|
|
uint32_t
|
|
ProcessGDBRemote::GetPluginVersion()
|
|
{
|
|
return 1;
|
|
}
|
|
|
|
void
|
|
ProcessGDBRemote::GetPluginCommandHelp (const char *command, Stream *strm)
|
|
{
|
|
strm->Printf("TODO: fill this in\n");
|
|
}
|
|
|
|
Error
|
|
ProcessGDBRemote::ExecutePluginCommand (Args &command, Stream *strm)
|
|
{
|
|
Error error;
|
|
error.SetErrorString("No plug-in commands are currently supported.");
|
|
return error;
|
|
}
|
|
|
|
Log *
|
|
ProcessGDBRemote::EnablePluginLogging (Stream *strm, Args &command)
|
|
{
|
|
return NULL;
|
|
}
|
|
|
|
void
|
|
ProcessGDBRemote::BuildDynamicRegisterInfo ()
|
|
{
|
|
char register_info_command[64];
|
|
m_register_info.Clear();
|
|
StringExtractorGDBRemote::Type packet_type = StringExtractorGDBRemote::eResponse;
|
|
uint32_t reg_offset = 0;
|
|
uint32_t reg_num = 0;
|
|
for (; packet_type == StringExtractorGDBRemote::eResponse; ++reg_num)
|
|
{
|
|
::snprintf (register_info_command, sizeof(register_info_command), "qRegisterInfo%x", reg_num);
|
|
StringExtractorGDBRemote response;
|
|
if (m_gdb_comm.SendPacketAndWaitForResponse(register_info_command, response, 2, false))
|
|
{
|
|
packet_type = response.GetType();
|
|
if (packet_type == StringExtractorGDBRemote::eResponse)
|
|
{
|
|
std::string name;
|
|
std::string value;
|
|
ConstString reg_name;
|
|
ConstString alt_name;
|
|
ConstString set_name;
|
|
RegisterInfo reg_info = { NULL, // Name
|
|
NULL, // Alt name
|
|
0, // byte size
|
|
reg_offset, // offset
|
|
eEncodingUint, // encoding
|
|
eFormatHex, // formate
|
|
{
|
|
LLDB_INVALID_REGNUM, // GCC reg num
|
|
LLDB_INVALID_REGNUM, // DWARF reg num
|
|
LLDB_INVALID_REGNUM, // generic reg num
|
|
reg_num, // GDB reg num
|
|
reg_num // native register number
|
|
}
|
|
};
|
|
|
|
while (response.GetNameColonValue(name, value))
|
|
{
|
|
if (name.compare("name") == 0)
|
|
{
|
|
reg_name.SetCString(value.c_str());
|
|
}
|
|
else if (name.compare("alt-name") == 0)
|
|
{
|
|
alt_name.SetCString(value.c_str());
|
|
}
|
|
else if (name.compare("bitsize") == 0)
|
|
{
|
|
reg_info.byte_size = Args::StringToUInt32(value.c_str(), 0, 0) / CHAR_BIT;
|
|
}
|
|
else if (name.compare("offset") == 0)
|
|
{
|
|
uint32_t offset = Args::StringToUInt32(value.c_str(), UINT32_MAX, 0);
|
|
if (reg_offset != offset)
|
|
{
|
|
reg_offset = offset;
|
|
}
|
|
}
|
|
else if (name.compare("encoding") == 0)
|
|
{
|
|
if (value.compare("uint") == 0)
|
|
reg_info.encoding = eEncodingUint;
|
|
else if (value.compare("sint") == 0)
|
|
reg_info.encoding = eEncodingSint;
|
|
else if (value.compare("ieee754") == 0)
|
|
reg_info.encoding = eEncodingIEEE754;
|
|
else if (value.compare("vector") == 0)
|
|
reg_info.encoding = eEncodingVector;
|
|
}
|
|
else if (name.compare("format") == 0)
|
|
{
|
|
if (value.compare("binary") == 0)
|
|
reg_info.format = eFormatBinary;
|
|
else if (value.compare("decimal") == 0)
|
|
reg_info.format = eFormatDecimal;
|
|
else if (value.compare("hex") == 0)
|
|
reg_info.format = eFormatHex;
|
|
else if (value.compare("float") == 0)
|
|
reg_info.format = eFormatFloat;
|
|
else if (value.compare("vector-sint8") == 0)
|
|
reg_info.format = eFormatVectorOfSInt8;
|
|
else if (value.compare("vector-uint8") == 0)
|
|
reg_info.format = eFormatVectorOfUInt8;
|
|
else if (value.compare("vector-sint16") == 0)
|
|
reg_info.format = eFormatVectorOfSInt16;
|
|
else if (value.compare("vector-uint16") == 0)
|
|
reg_info.format = eFormatVectorOfUInt16;
|
|
else if (value.compare("vector-sint32") == 0)
|
|
reg_info.format = eFormatVectorOfSInt32;
|
|
else if (value.compare("vector-uint32") == 0)
|
|
reg_info.format = eFormatVectorOfUInt32;
|
|
else if (value.compare("vector-float32") == 0)
|
|
reg_info.format = eFormatVectorOfFloat32;
|
|
else if (value.compare("vector-uint128") == 0)
|
|
reg_info.format = eFormatVectorOfUInt128;
|
|
}
|
|
else if (name.compare("set") == 0)
|
|
{
|
|
set_name.SetCString(value.c_str());
|
|
}
|
|
else if (name.compare("gcc") == 0)
|
|
{
|
|
reg_info.kinds[eRegisterKindGCC] = Args::StringToUInt32(value.c_str(), LLDB_INVALID_REGNUM, 0);
|
|
}
|
|
else if (name.compare("dwarf") == 0)
|
|
{
|
|
reg_info.kinds[eRegisterKindDWARF] = Args::StringToUInt32(value.c_str(), LLDB_INVALID_REGNUM, 0);
|
|
}
|
|
else if (name.compare("generic") == 0)
|
|
{
|
|
if (value.compare("pc") == 0)
|
|
reg_info.kinds[eRegisterKindGeneric] = LLDB_REGNUM_GENERIC_PC;
|
|
else if (value.compare("sp") == 0)
|
|
reg_info.kinds[eRegisterKindGeneric] = LLDB_REGNUM_GENERIC_SP;
|
|
else if (value.compare("fp") == 0)
|
|
reg_info.kinds[eRegisterKindGeneric] = LLDB_REGNUM_GENERIC_FP;
|
|
else if (value.compare("ra") == 0)
|
|
reg_info.kinds[eRegisterKindGeneric] = LLDB_REGNUM_GENERIC_RA;
|
|
else if (value.compare("flags") == 0)
|
|
reg_info.kinds[eRegisterKindGeneric] = LLDB_REGNUM_GENERIC_FLAGS;
|
|
}
|
|
}
|
|
|
|
reg_info.byte_offset = reg_offset;
|
|
assert (reg_info.byte_size != 0);
|
|
reg_offset += reg_info.byte_size;
|
|
m_register_info.AddRegister(reg_info, reg_name, alt_name, set_name);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
packet_type = StringExtractorGDBRemote::eError;
|
|
}
|
|
}
|
|
|
|
if (reg_num == 0)
|
|
{
|
|
// We didn't get anything. See if we are debugging ARM and fill with
|
|
// a hard coded register set until we can get an updated debugserver
|
|
// down on the devices.
|
|
ArchSpec arm_arch ("arm");
|
|
if (GetTarget().GetArchitecture() == arm_arch)
|
|
m_register_info.HardcodeARMRegisters();
|
|
}
|
|
m_register_info.Finalize ();
|
|
}
|
|
|
|
Error
|
|
ProcessGDBRemote::WillLaunch (Module* module)
|
|
{
|
|
return WillLaunchOrAttach ();
|
|
}
|
|
|
|
Error
|
|
ProcessGDBRemote::WillAttach (lldb::pid_t pid)
|
|
{
|
|
return WillLaunchOrAttach ();
|
|
}
|
|
|
|
Error
|
|
ProcessGDBRemote::WillAttach (const char *process_name, bool wait_for_launch)
|
|
{
|
|
return WillLaunchOrAttach ();
|
|
}
|
|
|
|
Error
|
|
ProcessGDBRemote::WillLaunchOrAttach ()
|
|
{
|
|
Error error;
|
|
// TODO: this is hardcoded for macosx right now. We need this to be more dynamic
|
|
m_dynamic_loader_ap.reset(DynamicLoader::FindPlugin(this, "dynamic-loader.macosx-dyld"));
|
|
|
|
if (m_dynamic_loader_ap.get() == NULL)
|
|
error.SetErrorString("unable to find the dynamic loader named 'dynamic-loader.macosx-dyld'");
|
|
m_stdio_communication.Clear ();
|
|
|
|
return error;
|
|
}
|
|
|
|
//#define LAUNCH_WITH_LAUNCH_SERVICES 1
|
|
//----------------------------------------------------------------------
|
|
// Process Control
|
|
//----------------------------------------------------------------------
|
|
Error
|
|
ProcessGDBRemote::DoLaunch
|
|
(
|
|
Module* module,
|
|
char const *argv[],
|
|
char const *envp[],
|
|
uint32_t launch_flags,
|
|
const char *stdin_path,
|
|
const char *stdout_path,
|
|
const char *stderr_path
|
|
)
|
|
{
|
|
Error error;
|
|
#if defined (LAUNCH_WITH_LAUNCH_SERVICES)
|
|
ArchSpec inferior_arch(module->GetArchitecture());
|
|
|
|
//FileSpec app_file_spec (argv[0]);
|
|
pid_t pid = Host::LaunchInNewTerminal (argv,
|
|
envp,
|
|
&inferior_arch,
|
|
true, // stop at entry
|
|
(launch_flags & eLaunchFlagDisableASLR) != 0);
|
|
|
|
// Let the app get launched and stopped...
|
|
sleep (1);
|
|
|
|
if (pid != LLDB_INVALID_PROCESS_ID)
|
|
{
|
|
FileSpec program(argv[0]);
|
|
error = DoAttachToProcessWithName (program.GetFilename().AsCString(), false);
|
|
//error = DoAttachToProcessWithID (pid);
|
|
}
|
|
else
|
|
{
|
|
error.SetErrorString("Host::LaunchApplication(() failed to launch process");
|
|
}
|
|
#else
|
|
// ::LogSetBitMask (GDBR_LOG_DEFAULT);
|
|
// ::LogSetOptions (LLDB_LOG_OPTION_THREADSAFE | LLDB_LOG_OPTION_PREPEND_TIMESTAMP | LLDB_LOG_OPTION_PREPEND_PROC_AND_THREAD);
|
|
// ::LogSetLogFile ("/dev/stdout");
|
|
|
|
ObjectFile * object_file = module->GetObjectFile();
|
|
if (object_file)
|
|
{
|
|
ArchSpec inferior_arch(module->GetArchitecture());
|
|
char host_port[128];
|
|
snprintf (host_port, sizeof(host_port), "localhost:%u", get_random_port ());
|
|
|
|
bool start_debugserver_with_inferior_args = false;
|
|
if (start_debugserver_with_inferior_args)
|
|
{
|
|
// We want to launch debugserver with the inferior program and its
|
|
// arguments on the command line. We should only do this if we
|
|
// the GDB server we are talking to doesn't support the 'A' packet.
|
|
error = StartDebugserverProcess (host_port,
|
|
argv,
|
|
envp,
|
|
NULL, //stdin_path,
|
|
LLDB_INVALID_PROCESS_ID,
|
|
NULL, false,
|
|
(launch_flags & eLaunchFlagDisableASLR) != 0,
|
|
inferior_arch);
|
|
if (error.Fail())
|
|
return error;
|
|
|
|
error = ConnectToDebugserver (host_port);
|
|
if (error.Success())
|
|
{
|
|
SetID (m_gdb_comm.GetCurrentProcessID (m_packet_timeout));
|
|
}
|
|
}
|
|
else
|
|
{
|
|
error = StartDebugserverProcess (host_port,
|
|
NULL,
|
|
NULL,
|
|
NULL, //stdin_path,
|
|
LLDB_INVALID_PROCESS_ID,
|
|
NULL, false,
|
|
(launch_flags & eLaunchFlagDisableASLR) != 0,
|
|
inferior_arch);
|
|
if (error.Fail())
|
|
return error;
|
|
|
|
error = ConnectToDebugserver (host_port);
|
|
if (error.Success())
|
|
{
|
|
// Send the environment and the program + arguments after we connect
|
|
if (envp)
|
|
{
|
|
const char *env_entry;
|
|
for (int i=0; (env_entry = envp[i]); ++i)
|
|
{
|
|
if (m_gdb_comm.SendEnvironmentPacket(env_entry, m_packet_timeout) != 0)
|
|
break;
|
|
}
|
|
}
|
|
|
|
// FIXME: convert this to use the new set/show variables when they are available
|
|
#if 0
|
|
if (::getenv ("LLDB_DEBUG_DEBUGSERVER"))
|
|
{
|
|
const uint32_t attach_debugserver_secs = 10;
|
|
::printf ("attach to debugserver (pid = %i)\n", m_debugserver_pid);
|
|
for (uint32_t i=0; i<attach_debugserver_secs; ++i)
|
|
{
|
|
printf ("%i\n", attach_debugserver_secs - i);
|
|
sleep (1);
|
|
}
|
|
}
|
|
#endif
|
|
|
|
const uint32_t arg_timeout_seconds = 10;
|
|
int arg_packet_err = m_gdb_comm.SendArgumentsPacket (argv, arg_timeout_seconds);
|
|
if (arg_packet_err == 0)
|
|
{
|
|
std::string error_str;
|
|
if (m_gdb_comm.GetLaunchSuccess (m_packet_timeout, error_str))
|
|
{
|
|
SetID (m_gdb_comm.GetCurrentProcessID (m_packet_timeout));
|
|
}
|
|
else
|
|
{
|
|
error.SetErrorString (error_str.c_str());
|
|
}
|
|
}
|
|
else
|
|
{
|
|
error.SetErrorStringWithFormat("'A' packet returned an error: %i.\n", arg_packet_err);
|
|
}
|
|
|
|
SetID (m_gdb_comm.GetCurrentProcessID (m_packet_timeout));
|
|
}
|
|
}
|
|
|
|
if (GetID() == LLDB_INVALID_PROCESS_ID)
|
|
{
|
|
KillDebugserverProcess ();
|
|
return error;
|
|
}
|
|
|
|
StringExtractorGDBRemote response;
|
|
if (m_gdb_comm.SendPacketAndWaitForResponse("?", 1, response, m_packet_timeout, false))
|
|
SetPrivateState (SetThreadStopInfo (response));
|
|
|
|
}
|
|
else
|
|
{
|
|
// Set our user ID to an invalid process ID.
|
|
SetID(LLDB_INVALID_PROCESS_ID);
|
|
error.SetErrorStringWithFormat("Failed to get object file from '%s' for arch %s.\n", module->GetFileSpec().GetFilename().AsCString(), module->GetArchitecture().AsCString());
|
|
}
|
|
#endif
|
|
return error;
|
|
|
|
}
|
|
|
|
|
|
Error
|
|
ProcessGDBRemote::ConnectToDebugserver (const char *host_port)
|
|
{
|
|
Error error;
|
|
// Sleep and wait a bit for debugserver to start to listen...
|
|
std::auto_ptr<ConnectionFileDescriptor> conn_ap(new ConnectionFileDescriptor());
|
|
if (conn_ap.get())
|
|
{
|
|
std::string connect_url("connect://");
|
|
connect_url.append (host_port);
|
|
const uint32_t max_retry_count = 50;
|
|
uint32_t retry_count = 0;
|
|
while (!m_gdb_comm.IsConnected())
|
|
{
|
|
if (conn_ap->Connect(connect_url.c_str(), &error) == eConnectionStatusSuccess)
|
|
{
|
|
m_gdb_comm.SetConnection (conn_ap.release());
|
|
break;
|
|
}
|
|
retry_count++;
|
|
|
|
if (retry_count >= max_retry_count)
|
|
break;
|
|
|
|
usleep (100000);
|
|
}
|
|
}
|
|
|
|
if (!m_gdb_comm.IsConnected())
|
|
{
|
|
if (error.Success())
|
|
error.SetErrorString("not connected to remote gdb server");
|
|
return error;
|
|
}
|
|
|
|
m_gdb_comm.SetAckMode (true);
|
|
if (m_gdb_comm.StartReadThread(&error))
|
|
{
|
|
// Send an initial ack
|
|
m_gdb_comm.SendAck('+');
|
|
|
|
if (m_debugserver_pid != LLDB_INVALID_PROCESS_ID)
|
|
m_debugserver_thread = Host::StartMonitoringChildProcess (MonitorDebugserverProcess,
|
|
this,
|
|
m_debugserver_pid,
|
|
false);
|
|
|
|
StringExtractorGDBRemote response;
|
|
if (m_gdb_comm.SendPacketAndWaitForResponse("QStartNoAckMode", response, 1, false))
|
|
{
|
|
if (response.IsOKPacket())
|
|
m_gdb_comm.SetAckMode (false);
|
|
}
|
|
|
|
BuildDynamicRegisterInfo ();
|
|
}
|
|
return error;
|
|
}
|
|
|
|
void
|
|
ProcessGDBRemote::DidLaunchOrAttach ()
|
|
{
|
|
ProcessGDBRemoteLog::LogIf (GDBR_LOG_PROCESS, "ProcessGDBRemote::DidLaunch()");
|
|
if (GetID() == LLDB_INVALID_PROCESS_ID)
|
|
{
|
|
m_dynamic_loader_ap.reset();
|
|
}
|
|
else
|
|
{
|
|
m_dispatch_queue_offsets_addr = LLDB_INVALID_ADDRESS;
|
|
|
|
Module * exe_module = GetTarget().GetExecutableModule ().get();
|
|
assert(exe_module);
|
|
|
|
ObjectFile *exe_objfile = exe_module->GetObjectFile();
|
|
assert(exe_objfile);
|
|
|
|
m_byte_order = exe_objfile->GetByteOrder();
|
|
assert (m_byte_order != eByteOrderInvalid);
|
|
|
|
StreamString strm;
|
|
|
|
ArchSpec inferior_arch;
|
|
// See if the GDB server supports the qHostInfo information
|
|
const char *vendor = m_gdb_comm.GetVendorString().AsCString();
|
|
const char *os_type = m_gdb_comm.GetOSString().AsCString();
|
|
ArchSpec arch_spec = GetTarget().GetArchitecture();
|
|
|
|
if (arch_spec.IsValid() && arch_spec == ArchSpec ("arm"))
|
|
{
|
|
// For ARM we can't trust the arch of the process as it could
|
|
// have an armv6 object file, but be running on armv7 kernel.
|
|
inferior_arch = m_gdb_comm.GetHostArchitecture();
|
|
}
|
|
|
|
if (!inferior_arch.IsValid())
|
|
inferior_arch = arch_spec;
|
|
|
|
if (vendor == NULL)
|
|
vendor = Host::GetVendorString().AsCString("apple");
|
|
|
|
if (os_type == NULL)
|
|
os_type = Host::GetOSString().AsCString("darwin");
|
|
|
|
strm.Printf ("%s-%s-%s", inferior_arch.AsCString(), vendor, os_type);
|
|
|
|
std::transform (strm.GetString().begin(),
|
|
strm.GetString().end(),
|
|
strm.GetString().begin(),
|
|
::tolower);
|
|
|
|
m_target_triple.SetCString(strm.GetString().c_str());
|
|
}
|
|
}
|
|
|
|
void
|
|
ProcessGDBRemote::DidLaunch ()
|
|
{
|
|
#if defined (LAUNCH_WITH_LAUNCH_SERVICES)
|
|
DidAttach ();
|
|
#else
|
|
DidLaunchOrAttach ();
|
|
if (m_dynamic_loader_ap.get())
|
|
m_dynamic_loader_ap->DidLaunch();
|
|
#endif
|
|
}
|
|
|
|
Error
|
|
ProcessGDBRemote::DoAttachToProcessWithID (lldb::pid_t attach_pid)
|
|
{
|
|
Error error;
|
|
// Clear out and clean up from any current state
|
|
Clear();
|
|
ArchSpec arch_spec = GetTarget().GetArchitecture();
|
|
|
|
//Log *log = ProcessGDBRemoteLog::GetLogIfAllCategoriesSet (GDBR_LOG_PROCESS);
|
|
|
|
|
|
if (attach_pid != LLDB_INVALID_PROCESS_ID)
|
|
{
|
|
char host_port[128];
|
|
snprintf (host_port, sizeof(host_port), "localhost:%u", get_random_port ());
|
|
error = StartDebugserverProcess (host_port, // debugserver_url
|
|
NULL, // inferior_argv
|
|
NULL, // inferior_envp
|
|
NULL, // stdin_path
|
|
LLDB_INVALID_PROCESS_ID, // attach_pid
|
|
NULL, // attach_pid_name
|
|
false, // wait_for_launch
|
|
false, // disable_aslr
|
|
arch_spec);
|
|
|
|
if (error.Fail())
|
|
{
|
|
const char *error_string = error.AsCString();
|
|
if (error_string == NULL)
|
|
error_string = "unable to launch " DEBUGSERVER_BASENAME;
|
|
|
|
SetExitStatus (-1, error_string);
|
|
}
|
|
else
|
|
{
|
|
error = ConnectToDebugserver (host_port);
|
|
if (error.Success())
|
|
{
|
|
char packet[64];
|
|
const int packet_len = ::snprintf (packet, sizeof(packet), "vAttach;%x", attach_pid);
|
|
StringExtractorGDBRemote response;
|
|
StateType stop_state = m_gdb_comm.SendContinuePacketAndWaitForResponse (this,
|
|
packet,
|
|
packet_len,
|
|
response);
|
|
switch (stop_state)
|
|
{
|
|
case eStateStopped:
|
|
case eStateCrashed:
|
|
case eStateSuspended:
|
|
SetID (attach_pid);
|
|
m_last_stop_packet = response;
|
|
m_last_stop_packet.SetFilePos (0);
|
|
SetPrivateState (stop_state);
|
|
break;
|
|
|
|
case eStateExited:
|
|
m_last_stop_packet = response;
|
|
m_last_stop_packet.SetFilePos (0);
|
|
response.SetFilePos(1);
|
|
SetExitStatus(response.GetHexU8(), NULL);
|
|
break;
|
|
|
|
default:
|
|
SetExitStatus(-1, "unable to attach to process");
|
|
break;
|
|
}
|
|
|
|
}
|
|
}
|
|
}
|
|
|
|
lldb::pid_t pid = GetID();
|
|
if (pid == LLDB_INVALID_PROCESS_ID)
|
|
{
|
|
KillDebugserverProcess();
|
|
}
|
|
return error;
|
|
}
|
|
|
|
size_t
|
|
ProcessGDBRemote::AttachInputReaderCallback
|
|
(
|
|
void *baton,
|
|
InputReader *reader,
|
|
lldb::InputReaderAction notification,
|
|
const char *bytes,
|
|
size_t bytes_len
|
|
)
|
|
{
|
|
if (notification == eInputReaderGotToken)
|
|
{
|
|
ProcessGDBRemote *gdb_process = (ProcessGDBRemote *)baton;
|
|
if (gdb_process->m_waiting_for_attach)
|
|
gdb_process->m_waiting_for_attach = false;
|
|
reader->SetIsDone(true);
|
|
return 1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
Error
|
|
ProcessGDBRemote::DoAttachToProcessWithName (const char *process_name, bool wait_for_launch)
|
|
{
|
|
Error error;
|
|
// Clear out and clean up from any current state
|
|
Clear();
|
|
// HACK: require arch be set correctly at the target level until we can
|
|
// figure out a good way to determine the arch of what we are attaching to
|
|
|
|
//Log *log = ProcessGDBRemoteLog::GetLogIfAllCategoriesSet (GDBR_LOG_PROCESS);
|
|
if (process_name && process_name[0])
|
|
{
|
|
char host_port[128];
|
|
ArchSpec arch_spec = GetTarget().GetArchitecture();
|
|
snprintf (host_port, sizeof(host_port), "localhost:%u", get_random_port ());
|
|
error = StartDebugserverProcess (host_port, // debugserver_url
|
|
NULL, // inferior_argv
|
|
NULL, // inferior_envp
|
|
NULL, // stdin_path
|
|
LLDB_INVALID_PROCESS_ID, // attach_pid
|
|
NULL, // attach_pid_name
|
|
false, // wait_for_launch
|
|
false, // disable_aslr
|
|
arch_spec);
|
|
if (error.Fail())
|
|
{
|
|
const char *error_string = error.AsCString();
|
|
if (error_string == NULL)
|
|
error_string = "unable to launch " DEBUGSERVER_BASENAME;
|
|
|
|
SetExitStatus (-1, error_string);
|
|
}
|
|
else
|
|
{
|
|
error = ConnectToDebugserver (host_port);
|
|
if (error.Success())
|
|
{
|
|
StreamString packet;
|
|
|
|
packet.PutCString("vAttach");
|
|
if (wait_for_launch)
|
|
packet.PutCString("Wait");
|
|
packet.PutChar(';');
|
|
packet.PutBytesAsRawHex8(process_name, strlen(process_name), eByteOrderHost, eByteOrderHost);
|
|
StringExtractorGDBRemote response;
|
|
StateType stop_state = m_gdb_comm.SendContinuePacketAndWaitForResponse (this,
|
|
packet.GetData(),
|
|
packet.GetSize(),
|
|
response);
|
|
switch (stop_state)
|
|
{
|
|
case eStateStopped:
|
|
case eStateCrashed:
|
|
case eStateSuspended:
|
|
SetID (m_gdb_comm.GetCurrentProcessID(m_packet_timeout));
|
|
m_last_stop_packet = response;
|
|
m_last_stop_packet.SetFilePos (0);
|
|
SetPrivateState (stop_state);
|
|
break;
|
|
|
|
case eStateExited:
|
|
m_last_stop_packet = response;
|
|
m_last_stop_packet.SetFilePos (0);
|
|
response.SetFilePos(1);
|
|
SetExitStatus(response.GetHexU8(), NULL);
|
|
break;
|
|
|
|
default:
|
|
SetExitStatus(-1, "unable to attach to process");
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
lldb::pid_t pid = GetID();
|
|
if (pid == LLDB_INVALID_PROCESS_ID)
|
|
{
|
|
KillDebugserverProcess();
|
|
}
|
|
return error;
|
|
}
|
|
|
|
//
|
|
// if (wait_for_launch)
|
|
// {
|
|
// InputReaderSP reader_sp (new InputReader());
|
|
// StreamString instructions;
|
|
// instructions.Printf("Hit any key to cancel waiting for '%s' to launch...", process_name);
|
|
// error = reader_sp->Initialize (AttachInputReaderCallback, // callback
|
|
// this, // baton
|
|
// eInputReaderGranularityByte,
|
|
// NULL, // End token
|
|
// false);
|
|
//
|
|
// StringExtractorGDBRemote response;
|
|
// m_waiting_for_attach = true;
|
|
// FILE *reader_out_fh = reader_sp->GetOutputFileHandle();
|
|
// while (m_waiting_for_attach)
|
|
// {
|
|
// // Wait for one second for the stop reply packet
|
|
// if (m_gdb_comm.WaitForPacket(response, 1))
|
|
// {
|
|
// // Got some sort of packet, see if it is the stop reply packet?
|
|
// char ch = response.GetChar(0);
|
|
// if (ch == 'T')
|
|
// {
|
|
// m_waiting_for_attach = false;
|
|
// }
|
|
// }
|
|
// else
|
|
// {
|
|
// // Put a period character every second
|
|
// fputc('.', reader_out_fh);
|
|
// }
|
|
// }
|
|
// }
|
|
// }
|
|
// return GetID();
|
|
//}
|
|
|
|
void
|
|
ProcessGDBRemote::DidAttach ()
|
|
{
|
|
// If we haven't got an executable module yet, then we should make a dynamic loader, and
|
|
// see if it can find the executable module for us. If we do have an executable module,
|
|
// make sure it matches the process we've just attached to.
|
|
|
|
ModuleSP exe_module_sp = GetTarget().GetExecutableModule();
|
|
if (!m_dynamic_loader_ap.get())
|
|
{
|
|
m_dynamic_loader_ap.reset(DynamicLoader::FindPlugin(this, "dynamic-loader.macosx-dyld"));
|
|
}
|
|
|
|
if (m_dynamic_loader_ap.get())
|
|
m_dynamic_loader_ap->DidAttach();
|
|
|
|
Module * new_exe_module = GetTarget().GetExecutableModule().get();
|
|
if (new_exe_module == NULL)
|
|
{
|
|
|
|
}
|
|
|
|
DidLaunchOrAttach ();
|
|
}
|
|
|
|
Error
|
|
ProcessGDBRemote::WillResume ()
|
|
{
|
|
m_continue_packet.Clear();
|
|
// Start the continue packet we will use to run the target. Each thread
|
|
// will append what it is supposed to be doing to this packet when the
|
|
// ThreadList::WillResume() is called. If a thread it supposed
|
|
// to stay stopped, then don't append anything to this string.
|
|
m_continue_packet.Printf("vCont");
|
|
return Error();
|
|
}
|
|
|
|
Error
|
|
ProcessGDBRemote::DoResume ()
|
|
{
|
|
ProcessGDBRemoteLog::LogIf (GDBR_LOG_PROCESS, "ProcessGDBRemote::Resume()");
|
|
m_async_broadcaster.BroadcastEvent (eBroadcastBitAsyncContinue, new EventDataBytes (m_continue_packet.GetData(), m_continue_packet.GetSize()));
|
|
return Error();
|
|
}
|
|
|
|
size_t
|
|
ProcessGDBRemote::GetSoftwareBreakpointTrapOpcode (BreakpointSite* bp_site)
|
|
{
|
|
const uint8_t *trap_opcode = NULL;
|
|
uint32_t trap_opcode_size = 0;
|
|
|
|
static const uint8_t g_arm_breakpoint_opcode[] = { 0xFE, 0xDE, 0xFF, 0xE7 };
|
|
//static const uint8_t g_thumb_breakpooint_opcode[] = { 0xFE, 0xDE };
|
|
static const uint8_t g_ppc_breakpoint_opcode[] = { 0x7F, 0xC0, 0x00, 0x08 };
|
|
static const uint8_t g_i386_breakpoint_opcode[] = { 0xCC };
|
|
|
|
ArchSpec::CPU arch_cpu = GetTarget().GetArchitecture().GetGenericCPUType();
|
|
switch (arch_cpu)
|
|
{
|
|
case ArchSpec::eCPU_i386:
|
|
case ArchSpec::eCPU_x86_64:
|
|
trap_opcode = g_i386_breakpoint_opcode;
|
|
trap_opcode_size = sizeof(g_i386_breakpoint_opcode);
|
|
break;
|
|
|
|
case ArchSpec::eCPU_arm:
|
|
// TODO: fill this in for ARM. We need to dig up the symbol for
|
|
// the address in the breakpoint locaiton and figure out if it is
|
|
// an ARM or Thumb breakpoint.
|
|
trap_opcode = g_arm_breakpoint_opcode;
|
|
trap_opcode_size = sizeof(g_arm_breakpoint_opcode);
|
|
break;
|
|
|
|
case ArchSpec::eCPU_ppc:
|
|
case ArchSpec::eCPU_ppc64:
|
|
trap_opcode = g_ppc_breakpoint_opcode;
|
|
trap_opcode_size = sizeof(g_ppc_breakpoint_opcode);
|
|
break;
|
|
|
|
default:
|
|
assert(!"Unhandled architecture in ProcessMacOSX::GetSoftwareBreakpointTrapOpcode()");
|
|
break;
|
|
}
|
|
|
|
if (trap_opcode && trap_opcode_size)
|
|
{
|
|
if (bp_site->SetTrapOpcode(trap_opcode, trap_opcode_size))
|
|
return trap_opcode_size;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
uint32_t
|
|
ProcessGDBRemote::UpdateThreadListIfNeeded ()
|
|
{
|
|
// locker will keep a mutex locked until it goes out of scope
|
|
Log *log = ProcessGDBRemoteLog::GetLogIfAllCategoriesSet (GDBR_LOG_THREAD);
|
|
if (log && log->GetMask().IsSet(GDBR_LOG_VERBOSE))
|
|
log->Printf ("ProcessGDBRemote::%s (pid = %i)", __FUNCTION__, GetID());
|
|
|
|
Mutex::Locker locker (m_thread_list.GetMutex ());
|
|
const uint32_t stop_id = GetStopID();
|
|
if (m_thread_list.GetSize(false) == 0 || stop_id != m_thread_list.GetStopID())
|
|
{
|
|
// Update the thread list's stop id immediately so we don't recurse into this function.
|
|
ThreadList curr_thread_list (this);
|
|
curr_thread_list.SetStopID(stop_id);
|
|
|
|
Error err;
|
|
StringExtractorGDBRemote response;
|
|
for (m_gdb_comm.SendPacketAndWaitForResponse("qfThreadInfo", response, 1, false);
|
|
response.IsNormalPacket();
|
|
m_gdb_comm.SendPacketAndWaitForResponse("qsThreadInfo", response, 1, false))
|
|
{
|
|
char ch = response.GetChar();
|
|
if (ch == 'l')
|
|
break;
|
|
if (ch == 'm')
|
|
{
|
|
do
|
|
{
|
|
tid_t tid = response.GetHexMaxU32(false, LLDB_INVALID_THREAD_ID);
|
|
|
|
if (tid != LLDB_INVALID_THREAD_ID)
|
|
{
|
|
ThreadSP thread_sp (GetThreadList().FindThreadByID (tid, false));
|
|
if (thread_sp)
|
|
thread_sp->GetRegisterContext()->Invalidate();
|
|
else
|
|
thread_sp.reset (new ThreadGDBRemote (*this, tid));
|
|
curr_thread_list.AddThread(thread_sp);
|
|
}
|
|
|
|
ch = response.GetChar();
|
|
} while (ch == ',');
|
|
}
|
|
}
|
|
|
|
m_thread_list = curr_thread_list;
|
|
|
|
SetThreadStopInfo (m_last_stop_packet);
|
|
}
|
|
return GetThreadList().GetSize(false);
|
|
}
|
|
|
|
|
|
StateType
|
|
ProcessGDBRemote::SetThreadStopInfo (StringExtractor& stop_packet)
|
|
{
|
|
const char stop_type = stop_packet.GetChar();
|
|
switch (stop_type)
|
|
{
|
|
case 'T':
|
|
case 'S':
|
|
{
|
|
// Stop with signal and thread info
|
|
const uint8_t signo = stop_packet.GetHexU8();
|
|
std::string name;
|
|
std::string value;
|
|
std::string thread_name;
|
|
uint32_t exc_type = 0;
|
|
std::vector<addr_t> exc_data;
|
|
uint32_t tid = LLDB_INVALID_THREAD_ID;
|
|
addr_t thread_dispatch_qaddr = LLDB_INVALID_ADDRESS;
|
|
uint32_t exc_data_count = 0;
|
|
while (stop_packet.GetNameColonValue(name, value))
|
|
{
|
|
if (name.compare("metype") == 0)
|
|
{
|
|
// exception type in big endian hex
|
|
exc_type = Args::StringToUInt32 (value.c_str(), 0, 16);
|
|
}
|
|
else if (name.compare("mecount") == 0)
|
|
{
|
|
// exception count in big endian hex
|
|
exc_data_count = Args::StringToUInt32 (value.c_str(), 0, 16);
|
|
}
|
|
else if (name.compare("medata") == 0)
|
|
{
|
|
// exception data in big endian hex
|
|
exc_data.push_back(Args::StringToUInt64 (value.c_str(), 0, 16));
|
|
}
|
|
else if (name.compare("thread") == 0)
|
|
{
|
|
// thread in big endian hex
|
|
tid = Args::StringToUInt32 (value.c_str(), 0, 16);
|
|
}
|
|
else if (name.compare("name") == 0)
|
|
{
|
|
thread_name.swap (value);
|
|
}
|
|
else if (name.compare("qaddr") == 0)
|
|
{
|
|
thread_dispatch_qaddr = Args::StringToUInt64 (value.c_str(), 0, 16);
|
|
}
|
|
}
|
|
ThreadSP thread_sp (m_thread_list.FindThreadByID(tid, false));
|
|
|
|
if (thread_sp)
|
|
{
|
|
ThreadGDBRemote *gdb_thread = static_cast<ThreadGDBRemote *> (thread_sp.get());
|
|
|
|
gdb_thread->SetThreadDispatchQAddr (thread_dispatch_qaddr);
|
|
gdb_thread->SetName (thread_name.empty() ? thread_name.c_str() : NULL);
|
|
if (exc_type != 0)
|
|
{
|
|
const size_t exc_data_count = exc_data.size();
|
|
|
|
gdb_thread->SetStopInfo (StopInfoMachException::CreateStopReasonWithMachException (*thread_sp,
|
|
exc_type,
|
|
exc_data_count,
|
|
exc_data_count >= 1 ? exc_data[0] : 0,
|
|
exc_data_count >= 2 ? exc_data[1] : 0));
|
|
}
|
|
else if (signo)
|
|
{
|
|
gdb_thread->SetStopInfo (StopInfo::CreateStopReasonWithSignal (*thread_sp, signo));
|
|
}
|
|
else
|
|
{
|
|
StopInfoSP invalid_stop_info_sp;
|
|
gdb_thread->SetStopInfo (invalid_stop_info_sp);
|
|
}
|
|
}
|
|
return eStateStopped;
|
|
}
|
|
break;
|
|
|
|
case 'W':
|
|
// process exited
|
|
return eStateExited;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
return eStateInvalid;
|
|
}
|
|
|
|
void
|
|
ProcessGDBRemote::RefreshStateAfterStop ()
|
|
{
|
|
// FIXME - add a variable to tell that we're in the middle of attaching if we
|
|
// need to know that.
|
|
// We must be attaching if we don't already have a valid architecture
|
|
// if (!GetTarget().GetArchitecture().IsValid())
|
|
// {
|
|
// Module *exe_module = GetTarget().GetExecutableModule().get();
|
|
// if (exe_module)
|
|
// m_arch_spec = exe_module->GetArchitecture();
|
|
// }
|
|
|
|
// Let all threads recover from stopping and do any clean up based
|
|
// on the previous thread state (if any).
|
|
m_thread_list.RefreshStateAfterStop();
|
|
|
|
// Discover new threads:
|
|
UpdateThreadListIfNeeded ();
|
|
}
|
|
|
|
Error
|
|
ProcessGDBRemote::DoHalt ()
|
|
{
|
|
Error error;
|
|
if (m_gdb_comm.IsRunning())
|
|
{
|
|
bool timed_out = false;
|
|
Mutex::Locker locker;
|
|
if (!m_gdb_comm.SendInterrupt (locker, 2, &timed_out))
|
|
{
|
|
if (timed_out)
|
|
error.SetErrorString("timed out sending interrupt packet");
|
|
else
|
|
error.SetErrorString("unknown error sending interrupt packet");
|
|
}
|
|
}
|
|
return error;
|
|
}
|
|
|
|
Error
|
|
ProcessGDBRemote::WillDetach ()
|
|
{
|
|
Error error;
|
|
|
|
if (m_gdb_comm.IsRunning())
|
|
{
|
|
bool timed_out = false;
|
|
Mutex::Locker locker;
|
|
if (!m_gdb_comm.SendInterrupt (locker, 2, &timed_out))
|
|
{
|
|
if (timed_out)
|
|
error.SetErrorString("timed out sending interrupt packet");
|
|
else
|
|
error.SetErrorString("unknown error sending interrupt packet");
|
|
}
|
|
}
|
|
return error;
|
|
}
|
|
|
|
Error
|
|
ProcessGDBRemote::DoDetach()
|
|
{
|
|
Error error;
|
|
Log *log = ProcessGDBRemoteLog::GetLogIfAllCategoriesSet(GDBR_LOG_PROCESS);
|
|
if (log)
|
|
log->Printf ("ProcessGDBRemote::DoDetach()");
|
|
|
|
DisableAllBreakpointSites ();
|
|
|
|
StringExtractorGDBRemote response;
|
|
size_t response_size = m_gdb_comm.SendPacketAndWaitForResponse("D", response, 2, false);
|
|
if (response_size)
|
|
{
|
|
if (response.IsOKPacket())
|
|
{
|
|
if (log)
|
|
log->Printf ("ProcessGDBRemote::DoDetach() detach was successful");
|
|
|
|
}
|
|
else if (log)
|
|
{
|
|
log->Printf ("ProcessGDBRemote::DoDestroy() detach failed: %s", response.GetStringRef().c_str());
|
|
}
|
|
}
|
|
else if (log)
|
|
{
|
|
log->PutCString ("ProcessGDBRemote::DoDestroy() detach failed for unknown reasons");
|
|
}
|
|
StopAsyncThread ();
|
|
m_gdb_comm.StopReadThread();
|
|
KillDebugserverProcess ();
|
|
m_gdb_comm.Disconnect(); // Disconnect from the debug server.
|
|
SetPublicState (eStateDetached);
|
|
return error;
|
|
}
|
|
|
|
Error
|
|
ProcessGDBRemote::DoDestroy ()
|
|
{
|
|
Error error;
|
|
Log *log = ProcessGDBRemoteLog::GetLogIfAllCategoriesSet(GDBR_LOG_PROCESS);
|
|
if (log)
|
|
log->Printf ("ProcessGDBRemote::DoDestroy()");
|
|
|
|
// Interrupt if our inferior is running...
|
|
Mutex::Locker locker;
|
|
m_gdb_comm.SendInterrupt (locker, 1);
|
|
DisableAllBreakpointSites ();
|
|
SetExitStatus(-1, "process killed");
|
|
|
|
StringExtractorGDBRemote response;
|
|
if (m_gdb_comm.SendPacketAndWaitForResponse("k", response, 2, false))
|
|
{
|
|
if (log)
|
|
{
|
|
if (response.IsOKPacket())
|
|
log->Printf ("ProcessGDBRemote::DoDestroy() kill was successful");
|
|
else
|
|
log->Printf ("ProcessGDBRemote::DoDestroy() kill failed: %s", response.GetStringRef().c_str());
|
|
}
|
|
}
|
|
|
|
StopAsyncThread ();
|
|
m_gdb_comm.StopReadThread();
|
|
KillDebugserverProcess ();
|
|
m_gdb_comm.Disconnect(); // Disconnect from the debug server.
|
|
return error;
|
|
}
|
|
|
|
ByteOrder
|
|
ProcessGDBRemote::GetByteOrder () const
|
|
{
|
|
return m_byte_order;
|
|
}
|
|
|
|
//------------------------------------------------------------------
|
|
// Process Queries
|
|
//------------------------------------------------------------------
|
|
|
|
bool
|
|
ProcessGDBRemote::IsAlive ()
|
|
{
|
|
return m_gdb_comm.IsConnected();
|
|
}
|
|
|
|
addr_t
|
|
ProcessGDBRemote::GetImageInfoAddress()
|
|
{
|
|
if (!m_gdb_comm.IsRunning())
|
|
{
|
|
StringExtractorGDBRemote response;
|
|
if (m_gdb_comm.SendPacketAndWaitForResponse("qShlibInfoAddr", ::strlen ("qShlibInfoAddr"), response, 2, false))
|
|
{
|
|
if (response.IsNormalPacket())
|
|
return response.GetHexMaxU64(false, LLDB_INVALID_ADDRESS);
|
|
}
|
|
}
|
|
return LLDB_INVALID_ADDRESS;
|
|
}
|
|
|
|
DynamicLoader *
|
|
ProcessGDBRemote::GetDynamicLoader()
|
|
{
|
|
return m_dynamic_loader_ap.get();
|
|
}
|
|
|
|
//------------------------------------------------------------------
|
|
// Process Memory
|
|
//------------------------------------------------------------------
|
|
size_t
|
|
ProcessGDBRemote::DoReadMemory (addr_t addr, void *buf, size_t size, Error &error)
|
|
{
|
|
if (size > m_max_memory_size)
|
|
{
|
|
// Keep memory read sizes down to a sane limit. This function will be
|
|
// called multiple times in order to complete the task by
|
|
// lldb_private::Process so it is ok to do this.
|
|
size = m_max_memory_size;
|
|
}
|
|
|
|
char packet[64];
|
|
const int packet_len = ::snprintf (packet, sizeof(packet), "m%llx,%zx", (uint64_t)addr, size);
|
|
assert (packet_len + 1 < sizeof(packet));
|
|
StringExtractorGDBRemote response;
|
|
if (m_gdb_comm.SendPacketAndWaitForResponse(packet, packet_len, response, 2, true))
|
|
{
|
|
if (response.IsNormalPacket())
|
|
{
|
|
error.Clear();
|
|
return response.GetHexBytes(buf, size, '\xdd');
|
|
}
|
|
else if (response.IsErrorPacket())
|
|
error.SetErrorStringWithFormat("gdb remote returned an error: %s", response.GetStringRef().c_str());
|
|
else if (response.IsUnsupportedPacket())
|
|
error.SetErrorStringWithFormat("'%s' packet unsupported", packet);
|
|
else
|
|
error.SetErrorStringWithFormat("unexpected response to '%s': '%s'", packet, response.GetStringRef().c_str());
|
|
}
|
|
else
|
|
{
|
|
error.SetErrorStringWithFormat("failed to sent packet: '%s'", packet);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
size_t
|
|
ProcessGDBRemote::DoWriteMemory (addr_t addr, const void *buf, size_t size, Error &error)
|
|
{
|
|
StreamString packet;
|
|
packet.Printf("M%llx,%zx:", addr, size);
|
|
packet.PutBytesAsRawHex8(buf, size, eByteOrderHost, eByteOrderHost);
|
|
StringExtractorGDBRemote response;
|
|
if (m_gdb_comm.SendPacketAndWaitForResponse(packet.GetData(), packet.GetSize(), response, 2, true))
|
|
{
|
|
if (response.IsOKPacket())
|
|
{
|
|
error.Clear();
|
|
return size;
|
|
}
|
|
else if (response.IsErrorPacket())
|
|
error.SetErrorStringWithFormat("gdb remote returned an error: %s", response.GetStringRef().c_str());
|
|
else if (response.IsUnsupportedPacket())
|
|
error.SetErrorStringWithFormat("'%s' packet unsupported", packet.GetString().c_str());
|
|
else
|
|
error.SetErrorStringWithFormat("unexpected response to '%s': '%s'", packet.GetString().c_str(), response.GetStringRef().c_str());
|
|
}
|
|
else
|
|
{
|
|
error.SetErrorStringWithFormat("failed to sent packet: '%s'", packet.GetString().c_str());
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
lldb::addr_t
|
|
ProcessGDBRemote::DoAllocateMemory (size_t size, uint32_t permissions, Error &error)
|
|
{
|
|
addr_t allocated_addr = m_gdb_comm.AllocateMemory (size, permissions, m_packet_timeout);
|
|
if (allocated_addr == LLDB_INVALID_ADDRESS)
|
|
error.SetErrorStringWithFormat("unable to allocate %zu bytes of memory with permissions %u", size, permissions);
|
|
else
|
|
error.Clear();
|
|
return allocated_addr;
|
|
}
|
|
|
|
Error
|
|
ProcessGDBRemote::DoDeallocateMemory (lldb::addr_t addr)
|
|
{
|
|
Error error;
|
|
if (!m_gdb_comm.DeallocateMemory (addr, m_packet_timeout))
|
|
error.SetErrorStringWithFormat("unable to deallocate memory at 0x%llx", addr);
|
|
return error;
|
|
}
|
|
|
|
|
|
//------------------------------------------------------------------
|
|
// Process STDIO
|
|
//------------------------------------------------------------------
|
|
|
|
size_t
|
|
ProcessGDBRemote::GetSTDOUT (char *buf, size_t buf_size, Error &error)
|
|
{
|
|
Mutex::Locker locker(m_stdio_mutex);
|
|
size_t bytes_available = m_stdout_data.size();
|
|
if (bytes_available > 0)
|
|
{
|
|
ProcessGDBRemoteLog::LogIf (GDBR_LOG_PROCESS, "ProcessGDBRemote::%s (&%p[%u]) ...", __FUNCTION__, buf, buf_size);
|
|
if (bytes_available > buf_size)
|
|
{
|
|
memcpy(buf, m_stdout_data.c_str(), buf_size);
|
|
m_stdout_data.erase(0, buf_size);
|
|
bytes_available = buf_size;
|
|
}
|
|
else
|
|
{
|
|
memcpy(buf, m_stdout_data.c_str(), bytes_available);
|
|
m_stdout_data.clear();
|
|
|
|
//ResetEventBits(eBroadcastBitSTDOUT);
|
|
}
|
|
}
|
|
return bytes_available;
|
|
}
|
|
|
|
size_t
|
|
ProcessGDBRemote::GetSTDERR (char *buf, size_t buf_size, Error &error)
|
|
{
|
|
// Can we get STDERR through the remote protocol?
|
|
return 0;
|
|
}
|
|
|
|
size_t
|
|
ProcessGDBRemote::PutSTDIN (const char *src, size_t src_len, Error &error)
|
|
{
|
|
if (m_stdio_communication.IsConnected())
|
|
{
|
|
ConnectionStatus status;
|
|
m_stdio_communication.Write(src, src_len, status, NULL);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
Error
|
|
ProcessGDBRemote::EnableBreakpoint (BreakpointSite *bp_site)
|
|
{
|
|
Error error;
|
|
assert (bp_site != NULL);
|
|
|
|
Log *log = ProcessGDBRemoteLog::GetLogIfAllCategoriesSet(GDBR_LOG_BREAKPOINTS);
|
|
user_id_t site_id = bp_site->GetID();
|
|
const addr_t addr = bp_site->GetLoadAddress();
|
|
if (log)
|
|
log->Printf ("ProcessGDBRemote::EnableBreakpoint (size_id = %d) address = 0x%llx", site_id, (uint64_t)addr);
|
|
|
|
if (bp_site->IsEnabled())
|
|
{
|
|
if (log)
|
|
log->Printf ("ProcessGDBRemote::EnableBreakpoint (size_id = %d) address = 0x%llx -- SUCCESS (already enabled)", site_id, (uint64_t)addr);
|
|
return error;
|
|
}
|
|
else
|
|
{
|
|
const size_t bp_op_size = GetSoftwareBreakpointTrapOpcode (bp_site);
|
|
|
|
if (bp_site->HardwarePreferred())
|
|
{
|
|
// Try and set hardware breakpoint, and if that fails, fall through
|
|
// and set a software breakpoint?
|
|
}
|
|
|
|
if (m_z0_supported)
|
|
{
|
|
char packet[64];
|
|
const int packet_len = ::snprintf (packet, sizeof(packet), "Z0,%llx,%zx", addr, bp_op_size);
|
|
assert (packet_len + 1 < sizeof(packet));
|
|
StringExtractorGDBRemote response;
|
|
if (m_gdb_comm.SendPacketAndWaitForResponse(packet, packet_len, response, 2, true))
|
|
{
|
|
if (response.IsUnsupportedPacket())
|
|
{
|
|
// Disable z packet support and try again
|
|
m_z0_supported = 0;
|
|
return EnableBreakpoint (bp_site);
|
|
}
|
|
else if (response.IsOKPacket())
|
|
{
|
|
bp_site->SetEnabled(true);
|
|
bp_site->SetType (BreakpointSite::eExternal);
|
|
return error;
|
|
}
|
|
else
|
|
{
|
|
uint8_t error_byte = response.GetError();
|
|
if (error_byte)
|
|
error.SetErrorStringWithFormat("%x packet failed with error: %i (0x%2.2x).\n", packet, error_byte, error_byte);
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
return EnableSoftwareBreakpoint (bp_site);
|
|
}
|
|
}
|
|
|
|
if (log)
|
|
{
|
|
const char *err_string = error.AsCString();
|
|
log->Printf ("ProcessGDBRemote::EnableBreakpoint() error for breakpoint at 0x%8.8llx: %s",
|
|
bp_site->GetLoadAddress(),
|
|
err_string ? err_string : "NULL");
|
|
}
|
|
// We shouldn't reach here on a successful breakpoint enable...
|
|
if (error.Success())
|
|
error.SetErrorToGenericError();
|
|
return error;
|
|
}
|
|
|
|
Error
|
|
ProcessGDBRemote::DisableBreakpoint (BreakpointSite *bp_site)
|
|
{
|
|
Error error;
|
|
assert (bp_site != NULL);
|
|
addr_t addr = bp_site->GetLoadAddress();
|
|
user_id_t site_id = bp_site->GetID();
|
|
Log *log = ProcessGDBRemoteLog::GetLogIfAllCategoriesSet(GDBR_LOG_BREAKPOINTS);
|
|
if (log)
|
|
log->Printf ("ProcessGDBRemote::DisableBreakpoint (site_id = %d) addr = 0x%8.8llx", site_id, (uint64_t)addr);
|
|
|
|
if (bp_site->IsEnabled())
|
|
{
|
|
const size_t bp_op_size = GetSoftwareBreakpointTrapOpcode (bp_site);
|
|
|
|
if (bp_site->IsHardware())
|
|
{
|
|
// TODO: disable hardware breakpoint...
|
|
}
|
|
else
|
|
{
|
|
if (m_z0_supported)
|
|
{
|
|
char packet[64];
|
|
const int packet_len = ::snprintf (packet, sizeof(packet), "z0,%llx,%zx", addr, bp_op_size);
|
|
assert (packet_len + 1 < sizeof(packet));
|
|
StringExtractorGDBRemote response;
|
|
if (m_gdb_comm.SendPacketAndWaitForResponse(packet, packet_len, response, 2, true))
|
|
{
|
|
if (response.IsUnsupportedPacket())
|
|
{
|
|
error.SetErrorString("Breakpoint site was set with Z packet, yet remote debugserver states z packets are not supported.");
|
|
}
|
|
else if (response.IsOKPacket())
|
|
{
|
|
if (log)
|
|
log->Printf ("ProcessGDBRemote::DisableBreakpoint (site_id = %d) addr = 0x%8.8llx -- SUCCESS", site_id, (uint64_t)addr);
|
|
bp_site->SetEnabled(false);
|
|
return error;
|
|
}
|
|
else
|
|
{
|
|
uint8_t error_byte = response.GetError();
|
|
if (error_byte)
|
|
error.SetErrorStringWithFormat("%x packet failed with error: %i (0x%2.2x).\n", packet, error_byte, error_byte);
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
return DisableSoftwareBreakpoint (bp_site);
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (log)
|
|
log->Printf ("ProcessGDBRemote::DisableBreakpoint (site_id = %d) addr = 0x%8.8llx -- SUCCESS (already disabled)", site_id, (uint64_t)addr);
|
|
return error;
|
|
}
|
|
|
|
if (error.Success())
|
|
error.SetErrorToGenericError();
|
|
return error;
|
|
}
|
|
|
|
Error
|
|
ProcessGDBRemote::EnableWatchpoint (WatchpointLocation *wp)
|
|
{
|
|
Error error;
|
|
if (wp)
|
|
{
|
|
user_id_t watchID = wp->GetID();
|
|
addr_t addr = wp->GetLoadAddress();
|
|
Log *log = ProcessGDBRemoteLog::GetLogIfAllCategoriesSet(GDBR_LOG_WATCHPOINTS);
|
|
if (log)
|
|
log->Printf ("ProcessGDBRemote::EnableWatchpoint(watchID = %d)", watchID);
|
|
if (wp->IsEnabled())
|
|
{
|
|
if (log)
|
|
log->Printf("ProcessGDBRemote::EnableWatchpoint(watchID = %d) addr = 0x%8.8llx: watchpoint already enabled.", watchID, (uint64_t)addr);
|
|
return error;
|
|
}
|
|
else
|
|
{
|
|
// Pass down an appropriate z/Z packet...
|
|
error.SetErrorString("watchpoints not supported");
|
|
}
|
|
}
|
|
else
|
|
{
|
|
error.SetErrorString("Watchpoint location argument was NULL.");
|
|
}
|
|
if (error.Success())
|
|
error.SetErrorToGenericError();
|
|
return error;
|
|
}
|
|
|
|
Error
|
|
ProcessGDBRemote::DisableWatchpoint (WatchpointLocation *wp)
|
|
{
|
|
Error error;
|
|
if (wp)
|
|
{
|
|
user_id_t watchID = wp->GetID();
|
|
|
|
Log *log = ProcessGDBRemoteLog::GetLogIfAllCategoriesSet(GDBR_LOG_WATCHPOINTS);
|
|
|
|
addr_t addr = wp->GetLoadAddress();
|
|
if (log)
|
|
log->Printf ("ProcessGDBRemote::DisableWatchpoint (watchID = %d) addr = 0x%8.8llx", watchID, (uint64_t)addr);
|
|
|
|
if (wp->IsHardware())
|
|
{
|
|
// Pass down an appropriate z/Z packet...
|
|
error.SetErrorString("watchpoints not supported");
|
|
}
|
|
// TODO: clear software watchpoints if we implement them
|
|
}
|
|
else
|
|
{
|
|
error.SetErrorString("Watchpoint location argument was NULL.");
|
|
}
|
|
if (error.Success())
|
|
error.SetErrorToGenericError();
|
|
return error;
|
|
}
|
|
|
|
void
|
|
ProcessGDBRemote::Clear()
|
|
{
|
|
m_flags = 0;
|
|
m_thread_list.Clear();
|
|
{
|
|
Mutex::Locker locker(m_stdio_mutex);
|
|
m_stdout_data.clear();
|
|
}
|
|
DestoryLibUnwindAddressSpace();
|
|
}
|
|
|
|
Error
|
|
ProcessGDBRemote::DoSignal (int signo)
|
|
{
|
|
Error error;
|
|
Log *log = ProcessGDBRemoteLog::GetLogIfAllCategoriesSet(GDBR_LOG_PROCESS);
|
|
if (log)
|
|
log->Printf ("ProcessGDBRemote::DoSignal (signal = %d)", signo);
|
|
|
|
if (!m_gdb_comm.SendAsyncSignal (signo))
|
|
error.SetErrorStringWithFormat("failed to send signal %i", signo);
|
|
return error;
|
|
}
|
|
|
|
void
|
|
ProcessGDBRemote::STDIOReadThreadBytesReceived (void *baton, const void *src, size_t src_len)
|
|
{
|
|
ProcessGDBRemote *process = (ProcessGDBRemote *)baton;
|
|
process->AppendSTDOUT(static_cast<const char *>(src), src_len);
|
|
}
|
|
|
|
void
|
|
ProcessGDBRemote::AppendSTDOUT (const char* s, size_t len)
|
|
{
|
|
ProcessGDBRemoteLog::LogIf (GDBR_LOG_PROCESS, "ProcessGDBRemote::%s (<%d> %s) ...", __FUNCTION__, len, s);
|
|
Mutex::Locker locker(m_stdio_mutex);
|
|
m_stdout_data.append(s, len);
|
|
|
|
// FIXME: Make a real data object for this and put it out.
|
|
BroadcastEventIfUnique (eBroadcastBitSTDOUT);
|
|
}
|
|
|
|
|
|
Error
|
|
ProcessGDBRemote::StartDebugserverProcess
|
|
(
|
|
const char *debugserver_url, // The connection string to use in the spawned debugserver ("localhost:1234" or "/dev/tty...")
|
|
char const *inferior_argv[], // Arguments for the inferior program including the path to the inferior itself as the first argument
|
|
char const *inferior_envp[], // Environment to pass along to the inferior program
|
|
char const *stdio_path,
|
|
lldb::pid_t attach_pid, // If inferior inferior_argv == NULL, and attach_pid != LLDB_INVALID_PROCESS_ID then attach to this attach_pid
|
|
const char *attach_name, // Wait for the next process to launch whose basename matches "attach_name"
|
|
bool wait_for_launch, // Wait for the process named "attach_name" to launch
|
|
bool disable_aslr, // Disable ASLR
|
|
ArchSpec& inferior_arch // The arch of the inferior that we will launch
|
|
)
|
|
{
|
|
Error error;
|
|
if (m_debugserver_pid == LLDB_INVALID_PROCESS_ID)
|
|
{
|
|
// If we locate debugserver, keep that located version around
|
|
static FileSpec g_debugserver_file_spec;
|
|
|
|
FileSpec debugserver_file_spec;
|
|
char debugserver_path[PATH_MAX];
|
|
|
|
// Always check to see if we have an environment override for the path
|
|
// to the debugserver to use and use it if we do.
|
|
const char *env_debugserver_path = getenv("LLDB_DEBUGSERVER_PATH");
|
|
if (env_debugserver_path)
|
|
debugserver_file_spec.SetFile (env_debugserver_path);
|
|
else
|
|
debugserver_file_spec = g_debugserver_file_spec;
|
|
bool debugserver_exists = debugserver_file_spec.Exists();
|
|
if (!debugserver_exists)
|
|
{
|
|
// The debugserver binary is in the LLDB.framework/Resources
|
|
// directory.
|
|
if (Host::GetLLDBPath (ePathTypeSupportExecutableDir, debugserver_file_spec))
|
|
{
|
|
debugserver_file_spec.GetFilename().SetCString(DEBUGSERVER_BASENAME);
|
|
debugserver_exists = debugserver_file_spec.Exists();
|
|
if (debugserver_exists)
|
|
{
|
|
g_debugserver_file_spec = debugserver_file_spec;
|
|
}
|
|
else
|
|
{
|
|
g_debugserver_file_spec.Clear();
|
|
debugserver_file_spec.Clear();
|
|
}
|
|
}
|
|
}
|
|
|
|
if (debugserver_exists)
|
|
{
|
|
debugserver_file_spec.GetPath (debugserver_path, sizeof(debugserver_path));
|
|
|
|
m_stdio_communication.Clear();
|
|
posix_spawnattr_t attr;
|
|
|
|
Log *log = ProcessGDBRemoteLog::GetLogIfAllCategoriesSet (GDBR_LOG_PROCESS);
|
|
|
|
Error local_err; // Errors that don't affect the spawning.
|
|
if (log)
|
|
log->Printf ("%s ( path='%s', argv=%p, envp=%p, arch=%s )", __FUNCTION__, debugserver_path, inferior_argv, inferior_envp, inferior_arch.AsCString());
|
|
error.SetError( ::posix_spawnattr_init (&attr), eErrorTypePOSIX);
|
|
if (error.Fail() || log)
|
|
error.PutToLog(log, "::posix_spawnattr_init ( &attr )");
|
|
if (error.Fail())
|
|
return error;;
|
|
|
|
#if !defined (__arm__)
|
|
|
|
// We don't need to do this for ARM, and we really shouldn't now
|
|
// that we have multiple CPU subtypes and no posix_spawnattr call
|
|
// that allows us to set which CPU subtype to launch...
|
|
if (inferior_arch.GetType() == eArchTypeMachO)
|
|
{
|
|
cpu_type_t cpu = inferior_arch.GetCPUType();
|
|
if (cpu != 0 && cpu != UINT32_MAX && cpu != LLDB_INVALID_CPUTYPE)
|
|
{
|
|
size_t ocount = 0;
|
|
error.SetError( ::posix_spawnattr_setbinpref_np (&attr, 1, &cpu, &ocount), eErrorTypePOSIX);
|
|
if (error.Fail() || log)
|
|
error.PutToLog(log, "::posix_spawnattr_setbinpref_np ( &attr, 1, cpu_type = 0x%8.8x, count => %zu )", cpu, ocount);
|
|
|
|
if (error.Fail() != 0 || ocount != 1)
|
|
return error;
|
|
}
|
|
}
|
|
|
|
#endif
|
|
|
|
Args debugserver_args;
|
|
char arg_cstr[PATH_MAX];
|
|
bool launch_process = true;
|
|
|
|
if (inferior_argv == NULL && attach_pid != LLDB_INVALID_PROCESS_ID)
|
|
launch_process = false;
|
|
else if (attach_name)
|
|
launch_process = false; // Wait for a process whose basename matches that in inferior_argv[0]
|
|
|
|
bool pass_stdio_path_to_debugserver = true;
|
|
lldb_utility::PseudoTerminal pty;
|
|
if (stdio_path == NULL)
|
|
{
|
|
pass_stdio_path_to_debugserver = false;
|
|
if (pty.OpenFirstAvailableMaster(O_RDWR|O_NOCTTY, NULL, 0))
|
|
{
|
|
struct termios stdin_termios;
|
|
if (::tcgetattr (pty.GetMasterFileDescriptor(), &stdin_termios) == 0)
|
|
{
|
|
stdin_termios.c_lflag &= ~ECHO; // Turn off echoing
|
|
stdin_termios.c_lflag &= ~ICANON; // Get one char at a time
|
|
::tcsetattr (pty.GetMasterFileDescriptor(), TCSANOW, &stdin_termios);
|
|
}
|
|
stdio_path = pty.GetSlaveName (NULL, 0);
|
|
}
|
|
}
|
|
|
|
// Start args with "debugserver /file/path -r --"
|
|
debugserver_args.AppendArgument(debugserver_path);
|
|
debugserver_args.AppendArgument(debugserver_url);
|
|
// use native registers, not the GDB registers
|
|
debugserver_args.AppendArgument("--native-regs");
|
|
// make debugserver run in its own session so signals generated by
|
|
// special terminal key sequences (^C) don't affect debugserver
|
|
debugserver_args.AppendArgument("--setsid");
|
|
|
|
if (disable_aslr)
|
|
debugserver_args.AppendArguments("--disable-aslr");
|
|
|
|
// Only set the inferior
|
|
if (launch_process)
|
|
{
|
|
if (stdio_path && pass_stdio_path_to_debugserver)
|
|
{
|
|
debugserver_args.AppendArgument("-s"); // short for --stdio-path
|
|
StreamString strm;
|
|
strm.Printf("'%s'", stdio_path);
|
|
debugserver_args.AppendArgument(strm.GetData()); // path to file to have inferior open as it's STDIO
|
|
}
|
|
}
|
|
|
|
const char *env_debugserver_log_file = getenv("LLDB_DEBUGSERVER_LOG_FILE");
|
|
if (env_debugserver_log_file)
|
|
{
|
|
::snprintf (arg_cstr, sizeof(arg_cstr), "--log-file=%s", env_debugserver_log_file);
|
|
debugserver_args.AppendArgument(arg_cstr);
|
|
}
|
|
|
|
const char *env_debugserver_log_flags = getenv("LLDB_DEBUGSERVER_LOG_FLAGS");
|
|
if (env_debugserver_log_flags)
|
|
{
|
|
::snprintf (arg_cstr, sizeof(arg_cstr), "--log-flags=%s", env_debugserver_log_flags);
|
|
debugserver_args.AppendArgument(arg_cstr);
|
|
}
|
|
// debugserver_args.AppendArgument("--log-file=/tmp/debugserver.txt");
|
|
// debugserver_args.AppendArgument("--log-flags=0x800e0e");
|
|
|
|
// Now append the program arguments
|
|
if (launch_process)
|
|
{
|
|
if (inferior_argv)
|
|
{
|
|
// Terminate the debugserver args so we can now append the inferior args
|
|
debugserver_args.AppendArgument("--");
|
|
|
|
for (int i = 0; inferior_argv[i] != NULL; ++i)
|
|
debugserver_args.AppendArgument (inferior_argv[i]);
|
|
}
|
|
else
|
|
{
|
|
// Will send environment entries with the 'QEnvironment:' packet
|
|
// Will send arguments with the 'A' packet
|
|
}
|
|
}
|
|
else if (attach_pid != LLDB_INVALID_PROCESS_ID)
|
|
{
|
|
::snprintf (arg_cstr, sizeof(arg_cstr), "--attach=%u", attach_pid);
|
|
debugserver_args.AppendArgument (arg_cstr);
|
|
}
|
|
else if (attach_name && attach_name[0])
|
|
{
|
|
if (wait_for_launch)
|
|
debugserver_args.AppendArgument ("--waitfor");
|
|
else
|
|
debugserver_args.AppendArgument ("--attach");
|
|
debugserver_args.AppendArgument (attach_name);
|
|
}
|
|
|
|
Error file_actions_err;
|
|
posix_spawn_file_actions_t file_actions;
|
|
#if DONT_CLOSE_DEBUGSERVER_STDIO
|
|
file_actions_err.SetErrorString ("Remove this after uncommenting the code block below.");
|
|
#else
|
|
file_actions_err.SetError( ::posix_spawn_file_actions_init (&file_actions), eErrorTypePOSIX);
|
|
if (file_actions_err.Success())
|
|
{
|
|
::posix_spawn_file_actions_addclose (&file_actions, STDIN_FILENO);
|
|
::posix_spawn_file_actions_addclose (&file_actions, STDOUT_FILENO);
|
|
::posix_spawn_file_actions_addclose (&file_actions, STDERR_FILENO);
|
|
}
|
|
#endif
|
|
|
|
if (log)
|
|
{
|
|
StreamString strm;
|
|
debugserver_args.Dump (&strm);
|
|
log->Printf("%s arguments:\n%s", debugserver_args.GetArgumentAtIndex(0), strm.GetData());
|
|
}
|
|
|
|
error.SetError(::posix_spawnp (&m_debugserver_pid,
|
|
debugserver_path,
|
|
file_actions_err.Success() ? &file_actions : NULL,
|
|
&attr,
|
|
debugserver_args.GetArgumentVector(),
|
|
(char * const*)inferior_envp),
|
|
eErrorTypePOSIX);
|
|
|
|
|
|
::posix_spawnattr_destroy (&attr);
|
|
|
|
if (file_actions_err.Success())
|
|
::posix_spawn_file_actions_destroy (&file_actions);
|
|
|
|
// We have seen some cases where posix_spawnp was returning a valid
|
|
// looking pid even when an error was returned, so clear it out
|
|
if (error.Fail())
|
|
m_debugserver_pid = LLDB_INVALID_PROCESS_ID;
|
|
|
|
if (error.Fail() || log)
|
|
error.PutToLog(log, "::posix_spawnp ( pid => %i, path = '%s', file_actions = %p, attr = %p, argv = %p, envp = %p )", m_debugserver_pid, debugserver_path, NULL, &attr, inferior_argv, inferior_envp);
|
|
|
|
// if (m_debugserver_pid != LLDB_INVALID_PROCESS_ID)
|
|
// {
|
|
// std::auto_ptr<ConnectionFileDescriptor> conn_ap(new ConnectionFileDescriptor (pty.ReleaseMasterFileDescriptor(), true));
|
|
// if (conn_ap.get())
|
|
// {
|
|
// m_stdio_communication.SetConnection(conn_ap.release());
|
|
// if (m_stdio_communication.IsConnected())
|
|
// {
|
|
// m_stdio_communication.SetReadThreadBytesReceivedCallback (STDIOReadThreadBytesReceived, this);
|
|
// m_stdio_communication.StartReadThread();
|
|
// }
|
|
// }
|
|
// }
|
|
}
|
|
else
|
|
{
|
|
error.SetErrorStringWithFormat ("Unable to locate " DEBUGSERVER_BASENAME ".\n");
|
|
}
|
|
|
|
if (m_debugserver_pid != LLDB_INVALID_PROCESS_ID)
|
|
StartAsyncThread ();
|
|
}
|
|
return error;
|
|
}
|
|
|
|
bool
|
|
ProcessGDBRemote::MonitorDebugserverProcess
|
|
(
|
|
void *callback_baton,
|
|
lldb::pid_t debugserver_pid,
|
|
int signo, // Zero for no signal
|
|
int exit_status // Exit value of process if signal is zero
|
|
)
|
|
{
|
|
// We pass in the ProcessGDBRemote inferior process it and name it
|
|
// "gdb_remote_pid". The process ID is passed in the "callback_baton"
|
|
// pointer value itself, thus we need the double cast...
|
|
|
|
// "debugserver_pid" argument passed in is the process ID for
|
|
// debugserver that we are tracking...
|
|
|
|
ProcessGDBRemote *process = (ProcessGDBRemote *)callback_baton;
|
|
|
|
if (process)
|
|
{
|
|
// Sleep for a half a second to make sure our inferior process has
|
|
// time to set its exit status before we set it incorrectly when
|
|
// both the debugserver and the inferior process shut down.
|
|
usleep (500000);
|
|
// If our process hasn't yet exited, debugserver might have died.
|
|
// If the process did exit, the we are reaping it.
|
|
if (process->GetState() != eStateExited)
|
|
{
|
|
char error_str[1024];
|
|
if (signo)
|
|
{
|
|
const char *signal_cstr = process->GetUnixSignals().GetSignalAsCString (signo);
|
|
if (signal_cstr)
|
|
::snprintf (error_str, sizeof (error_str), DEBUGSERVER_BASENAME " died with signal %s", signal_cstr);
|
|
else
|
|
::snprintf (error_str, sizeof (error_str), DEBUGSERVER_BASENAME " died with signal %i", signo);
|
|
}
|
|
else
|
|
{
|
|
::snprintf (error_str, sizeof (error_str), DEBUGSERVER_BASENAME " died with an exit status of 0x%8.8x", exit_status);
|
|
}
|
|
|
|
process->SetExitStatus (-1, error_str);
|
|
}
|
|
else
|
|
{
|
|
// Debugserver has exited we need to let our ProcessGDBRemote
|
|
// know that it no longer has a debugserver instance
|
|
process->m_debugserver_pid = LLDB_INVALID_PROCESS_ID;
|
|
// We are returning true to this function below, so we can
|
|
// forget about the monitor handle.
|
|
process->m_debugserver_thread = LLDB_INVALID_HOST_THREAD;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
void
|
|
ProcessGDBRemote::KillDebugserverProcess ()
|
|
{
|
|
if (m_debugserver_pid != LLDB_INVALID_PROCESS_ID)
|
|
{
|
|
::kill (m_debugserver_pid, SIGINT);
|
|
m_debugserver_pid = LLDB_INVALID_PROCESS_ID;
|
|
}
|
|
}
|
|
|
|
void
|
|
ProcessGDBRemote::Initialize()
|
|
{
|
|
static bool g_initialized = false;
|
|
|
|
if (g_initialized == false)
|
|
{
|
|
g_initialized = true;
|
|
PluginManager::RegisterPlugin (GetPluginNameStatic(),
|
|
GetPluginDescriptionStatic(),
|
|
CreateInstance);
|
|
|
|
Log::Callbacks log_callbacks = {
|
|
ProcessGDBRemoteLog::DisableLog,
|
|
ProcessGDBRemoteLog::EnableLog,
|
|
ProcessGDBRemoteLog::ListLogCategories
|
|
};
|
|
|
|
Log::RegisterLogChannel (ProcessGDBRemote::GetPluginNameStatic(), log_callbacks);
|
|
}
|
|
}
|
|
|
|
bool
|
|
ProcessGDBRemote::SetCurrentGDBRemoteThread (int tid)
|
|
{
|
|
if (m_curr_tid == tid)
|
|
return true;
|
|
|
|
char packet[32];
|
|
const int packet_len = ::snprintf (packet, sizeof(packet), "Hg%x", tid);
|
|
assert (packet_len + 1 < sizeof(packet));
|
|
StringExtractorGDBRemote response;
|
|
if (m_gdb_comm.SendPacketAndWaitForResponse(packet, packet_len, response, 2, false))
|
|
{
|
|
if (response.IsOKPacket())
|
|
{
|
|
m_curr_tid = tid;
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool
|
|
ProcessGDBRemote::SetCurrentGDBRemoteThreadForRun (int tid)
|
|
{
|
|
if (m_curr_tid_run == tid)
|
|
return true;
|
|
|
|
char packet[32];
|
|
const int packet_len = ::snprintf (packet, sizeof(packet), "Hg%x", tid);
|
|
assert (packet_len + 1 < sizeof(packet));
|
|
StringExtractorGDBRemote response;
|
|
if (m_gdb_comm.SendPacketAndWaitForResponse(packet, packet_len, response, 2, false))
|
|
{
|
|
if (response.IsOKPacket())
|
|
{
|
|
m_curr_tid_run = tid;
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void
|
|
ProcessGDBRemote::ResetGDBRemoteState ()
|
|
{
|
|
// Reset and GDB remote state
|
|
m_curr_tid = LLDB_INVALID_THREAD_ID;
|
|
m_curr_tid_run = LLDB_INVALID_THREAD_ID;
|
|
m_z0_supported = 1;
|
|
}
|
|
|
|
|
|
bool
|
|
ProcessGDBRemote::StartAsyncThread ()
|
|
{
|
|
ResetGDBRemoteState ();
|
|
|
|
Log *log = ProcessGDBRemoteLog::GetLogIfAllCategoriesSet(GDBR_LOG_PROCESS);
|
|
|
|
if (log)
|
|
log->Printf ("ProcessGDBRemote::%s ()", __FUNCTION__);
|
|
|
|
// Create a thread that watches our internal state and controls which
|
|
// events make it to clients (into the DCProcess event queue).
|
|
m_async_thread = Host::ThreadCreate ("<lldb.process.gdb-remote.async>", ProcessGDBRemote::AsyncThread, this, NULL);
|
|
return m_async_thread != LLDB_INVALID_HOST_THREAD;
|
|
}
|
|
|
|
void
|
|
ProcessGDBRemote::StopAsyncThread ()
|
|
{
|
|
Log *log = ProcessGDBRemoteLog::GetLogIfAllCategoriesSet(GDBR_LOG_PROCESS);
|
|
|
|
if (log)
|
|
log->Printf ("ProcessGDBRemote::%s ()", __FUNCTION__);
|
|
|
|
m_async_broadcaster.BroadcastEvent (eBroadcastBitAsyncThreadShouldExit);
|
|
|
|
// Stop the stdio thread
|
|
if (m_async_thread != LLDB_INVALID_HOST_THREAD)
|
|
{
|
|
Host::ThreadJoin (m_async_thread, NULL, NULL);
|
|
}
|
|
}
|
|
|
|
|
|
void *
|
|
ProcessGDBRemote::AsyncThread (void *arg)
|
|
{
|
|
ProcessGDBRemote *process = (ProcessGDBRemote*) arg;
|
|
|
|
Log *log = ProcessGDBRemoteLog::GetLogIfAllCategoriesSet (GDBR_LOG_PROCESS);
|
|
if (log)
|
|
log->Printf ("ProcessGDBRemote::%s (arg = %p, pid = %i) thread starting...", __FUNCTION__, arg, process->GetID());
|
|
|
|
Listener listener ("ProcessGDBRemote::AsyncThread");
|
|
EventSP event_sp;
|
|
const uint32_t desired_event_mask = eBroadcastBitAsyncContinue |
|
|
eBroadcastBitAsyncThreadShouldExit;
|
|
|
|
if (listener.StartListeningForEvents (&process->m_async_broadcaster, desired_event_mask) == desired_event_mask)
|
|
{
|
|
bool done = false;
|
|
while (!done)
|
|
{
|
|
if (log)
|
|
log->Printf ("ProcessGDBRemote::%s (arg = %p, pid = %i) listener.WaitForEvent (NULL, event_sp)...", __FUNCTION__, arg, process->GetID());
|
|
if (listener.WaitForEvent (NULL, event_sp))
|
|
{
|
|
const uint32_t event_type = event_sp->GetType();
|
|
switch (event_type)
|
|
{
|
|
case eBroadcastBitAsyncContinue:
|
|
{
|
|
const EventDataBytes *continue_packet = EventDataBytes::GetEventDataFromEvent(event_sp.get());
|
|
|
|
if (continue_packet)
|
|
{
|
|
const char *continue_cstr = (const char *)continue_packet->GetBytes ();
|
|
const size_t continue_cstr_len = continue_packet->GetByteSize ();
|
|
if (log)
|
|
log->Printf ("ProcessGDBRemote::%s (arg = %p, pid = %i) got eBroadcastBitAsyncContinue: %s", __FUNCTION__, arg, process->GetID(), continue_cstr);
|
|
|
|
process->SetPrivateState(eStateRunning);
|
|
StringExtractorGDBRemote response;
|
|
StateType stop_state = process->GetGDBRemote().SendContinuePacketAndWaitForResponse (process, continue_cstr, continue_cstr_len, response);
|
|
|
|
switch (stop_state)
|
|
{
|
|
case eStateStopped:
|
|
case eStateCrashed:
|
|
case eStateSuspended:
|
|
process->m_last_stop_packet = response;
|
|
process->m_last_stop_packet.SetFilePos (0);
|
|
process->SetPrivateState (stop_state);
|
|
break;
|
|
|
|
case eStateExited:
|
|
process->m_last_stop_packet = response;
|
|
process->m_last_stop_packet.SetFilePos (0);
|
|
response.SetFilePos(1);
|
|
process->SetExitStatus(response.GetHexU8(), NULL);
|
|
done = true;
|
|
break;
|
|
|
|
case eStateInvalid:
|
|
break;
|
|
|
|
default:
|
|
process->SetPrivateState (stop_state);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
|
|
case eBroadcastBitAsyncThreadShouldExit:
|
|
if (log)
|
|
log->Printf ("ProcessGDBRemote::%s (arg = %p, pid = %i) got eBroadcastBitAsyncThreadShouldExit...", __FUNCTION__, arg, process->GetID());
|
|
done = true;
|
|
break;
|
|
|
|
default:
|
|
if (log)
|
|
log->Printf ("ProcessGDBRemote::%s (arg = %p, pid = %i) got unknown event 0x%8.8x", __FUNCTION__, arg, process->GetID(), event_type);
|
|
done = true;
|
|
break;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (log)
|
|
log->Printf ("ProcessGDBRemote::%s (arg = %p, pid = %i) listener.WaitForEvent (NULL, event_sp) => false", __FUNCTION__, arg, process->GetID());
|
|
done = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (log)
|
|
log->Printf ("ProcessGDBRemote::%s (arg = %p, pid = %i) thread exiting...", __FUNCTION__, arg, process->GetID());
|
|
|
|
process->m_async_thread = LLDB_INVALID_HOST_THREAD;
|
|
return NULL;
|
|
}
|
|
|
|
lldb_private::unw_addr_space_t
|
|
ProcessGDBRemote::GetLibUnwindAddressSpace ()
|
|
{
|
|
unw_targettype_t target_type = UNW_TARGET_UNSPECIFIED;
|
|
|
|
ArchSpec::CPU arch_cpu = m_target.GetArchitecture().GetGenericCPUType();
|
|
if (arch_cpu == ArchSpec::eCPU_i386)
|
|
target_type = UNW_TARGET_I386;
|
|
else if (arch_cpu == ArchSpec::eCPU_x86_64)
|
|
target_type = UNW_TARGET_X86_64;
|
|
|
|
if (m_libunwind_addr_space)
|
|
{
|
|
if (m_libunwind_target_type != target_type)
|
|
DestoryLibUnwindAddressSpace();
|
|
else
|
|
return m_libunwind_addr_space;
|
|
}
|
|
unw_accessors_t callbacks = get_macosx_libunwind_callbacks ();
|
|
m_libunwind_addr_space = unw_create_addr_space (&callbacks, target_type);
|
|
if (m_libunwind_addr_space)
|
|
m_libunwind_target_type = target_type;
|
|
else
|
|
m_libunwind_target_type = UNW_TARGET_UNSPECIFIED;
|
|
return m_libunwind_addr_space;
|
|
}
|
|
|
|
void
|
|
ProcessGDBRemote::DestoryLibUnwindAddressSpace ()
|
|
{
|
|
if (m_libunwind_addr_space)
|
|
{
|
|
unw_destroy_addr_space (m_libunwind_addr_space);
|
|
m_libunwind_addr_space = NULL;
|
|
}
|
|
m_libunwind_target_type = UNW_TARGET_UNSPECIFIED;
|
|
}
|
|
|
|
|
|
const char *
|
|
ProcessGDBRemote::GetDispatchQueueNameForThread
|
|
(
|
|
addr_t thread_dispatch_qaddr,
|
|
std::string &dispatch_queue_name
|
|
)
|
|
{
|
|
dispatch_queue_name.clear();
|
|
if (thread_dispatch_qaddr != 0 && thread_dispatch_qaddr != LLDB_INVALID_ADDRESS)
|
|
{
|
|
// Cache the dispatch_queue_offsets_addr value so we don't always have
|
|
// to look it up
|
|
if (m_dispatch_queue_offsets_addr == LLDB_INVALID_ADDRESS)
|
|
{
|
|
static ConstString g_dispatch_queue_offsets_symbol_name ("dispatch_queue_offsets");
|
|
const Symbol *dispatch_queue_offsets_symbol = NULL;
|
|
ModuleSP module_sp(GetTarget().GetImages().FindFirstModuleForFileSpec (FileSpec("libSystem.B.dylib")));
|
|
if (module_sp)
|
|
dispatch_queue_offsets_symbol = module_sp->FindFirstSymbolWithNameAndType (g_dispatch_queue_offsets_symbol_name, eSymbolTypeData);
|
|
|
|
if (dispatch_queue_offsets_symbol == NULL)
|
|
{
|
|
module_sp = GetTarget().GetImages().FindFirstModuleForFileSpec (FileSpec("libdispatch.dylib"));
|
|
if (module_sp)
|
|
dispatch_queue_offsets_symbol = module_sp->FindFirstSymbolWithNameAndType (g_dispatch_queue_offsets_symbol_name, eSymbolTypeData);
|
|
}
|
|
if (dispatch_queue_offsets_symbol)
|
|
m_dispatch_queue_offsets_addr = dispatch_queue_offsets_symbol->GetValue().GetLoadAddress(&m_target);
|
|
|
|
if (m_dispatch_queue_offsets_addr == LLDB_INVALID_ADDRESS)
|
|
return NULL;
|
|
}
|
|
|
|
uint8_t memory_buffer[8];
|
|
DataExtractor data(memory_buffer, sizeof(memory_buffer), GetByteOrder(), GetAddressByteSize());
|
|
|
|
// Excerpt from src/queue_private.h
|
|
struct dispatch_queue_offsets_s
|
|
{
|
|
uint16_t dqo_version;
|
|
uint16_t dqo_label;
|
|
uint16_t dqo_label_size;
|
|
} dispatch_queue_offsets;
|
|
|
|
|
|
Error error;
|
|
if (ReadMemory (m_dispatch_queue_offsets_addr, memory_buffer, sizeof(dispatch_queue_offsets), error) == sizeof(dispatch_queue_offsets))
|
|
{
|
|
uint32_t data_offset = 0;
|
|
if (data.GetU16(&data_offset, &dispatch_queue_offsets.dqo_version, sizeof(dispatch_queue_offsets)/sizeof(uint16_t)))
|
|
{
|
|
if (ReadMemory (thread_dispatch_qaddr, &memory_buffer, data.GetAddressByteSize(), error) == data.GetAddressByteSize())
|
|
{
|
|
data_offset = 0;
|
|
lldb::addr_t queue_addr = data.GetAddress(&data_offset);
|
|
lldb::addr_t label_addr = queue_addr + dispatch_queue_offsets.dqo_label;
|
|
dispatch_queue_name.resize(dispatch_queue_offsets.dqo_label_size, '\0');
|
|
size_t bytes_read = ReadMemory (label_addr, &dispatch_queue_name[0], dispatch_queue_offsets.dqo_label_size, error);
|
|
if (bytes_read < dispatch_queue_offsets.dqo_label_size)
|
|
dispatch_queue_name.erase (bytes_read);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if (dispatch_queue_name.empty())
|
|
return NULL;
|
|
return dispatch_queue_name.c_str();
|
|
}
|
|
|
|
uint32_t
|
|
ProcessGDBRemote::ListProcessesMatchingName (const char *name, StringList &matches, std::vector<lldb::pid_t> &pids)
|
|
{
|
|
// If we are planning to launch the debugserver remotely, then we need to fire up a debugserver
|
|
// process and ask it for the list of processes. But if we are local, we can let the Host do it.
|
|
if (m_local_debugserver)
|
|
{
|
|
return Host::ListProcessesMatchingName (name, matches, pids);
|
|
}
|
|
else
|
|
{
|
|
// FIXME: Implement talking to the remote debugserver.
|
|
return 0;
|
|
}
|
|
|
|
}
|