Index: include/lldb/API/SBPlatform.h =================================================================== --- include/lldb/API/SBPlatform.h +++ include/lldb/API/SBPlatform.h @@ -12,6 +12,9 @@ #include "lldb/API/SBDefines.h" +#include +#include + struct PlatformConnectOptions; struct PlatformShellCommand; @@ -100,6 +103,35 @@ PlatformShellCommand *m_opaque_ptr; }; + class SBPlatformLaunchCommand + { + public: + SBPlatformLaunchCommand (const char* command, + const char* working_dir, + const char* arch); + + const char* + GetCommand () const; + + const char* + GetWorkingDir () const; + + const char* + GetArch () const; + + const lldb::pid_t& + GetPID () const; + + void + SetPID (const lldb::pid_t& pid); + + private: + const std::string m_command; + const std::string m_working_dir; + const std::string m_arch; + lldb::pid_t m_pid; + }; + class SBPlatform { public: @@ -171,6 +203,12 @@ Run (SBPlatformShellCommand &shell_command); SBError + Launch (SBPlatformLaunchCommand &launch_command); + + SBError + Kill (const lldb::pid_t pid); + + SBError MakeDirectory (const char *path, uint32_t file_permissions = eFilePermissionsDirectoryDefault); uint32_t @@ -190,6 +228,9 @@ void SetSP (const lldb::PlatformSP& platform_sp); + SBError + ExecuteConnected (const std::function& func); + lldb::PlatformSP m_opaque_sp; }; Index: include/lldb/Target/Platform.h =================================================================== --- include/lldb/Target/Platform.h +++ include/lldb/Target/Platform.h @@ -380,6 +380,12 @@ LaunchProcess (ProcessLaunchInfo &launch_info); //------------------------------------------------------------------ + /// Kill process on a platform. + //------------------------------------------------------------------ + virtual Error + KillProcess (const lldb::pid_t pid); + + //------------------------------------------------------------------ /// Lets a platform answer if it is compatible with a given /// architecture and the target triple contained within. //------------------------------------------------------------------ Index: scripts/Python/interface/SBPlatform.i =================================================================== --- scripts/Python/interface/SBPlatform.i +++ scripts/Python/interface/SBPlatform.i @@ -83,6 +83,29 @@ GetOutput (); }; +class SBPlatformLaunchCommand +{ +public: + SBPlatformLaunchCommand (const char* command, + const char* working_dir, + const char* arch); + + const char* + GetCommand () const; + + const char* + GetWorkingDir () const; + + const char* + GetArch () const; + + const lldb::pid_t& + GetPID () const; + + void + SetPID (const lldb::pid_t& pid); +}; + %feature("docstring", "A class that represents the a platform that can represent the current host or a remote host debug platform. @@ -174,6 +197,12 @@ Run (lldb::SBPlatformShellCommand &shell_command); lldb::SBError + Launch (lldb::SBPlatformLaunchCommand &launch_command); + + lldb::SBError + Kill (const lldb::pid_t pid); + + lldb::SBError MakeDirectory (const char *path, uint32_t file_permissions = lldb::eFilePermissionsDirectoryDefault); uint32_t Index: source/API/SBPlatform.cpp =================================================================== --- source/API/SBPlatform.cpp +++ source/API/SBPlatform.cpp @@ -14,6 +14,7 @@ #include "lldb/Core/Error.h" #include "lldb/Host/File.h" #include "lldb/Interpreter/Args.h" +#include "lldb/Target/ProcessLaunchInfo.h" #include "lldb/Target/Target.h" #include "lldb/Target/Platform.h" @@ -215,6 +216,7 @@ return m_opaque_ptr->m_working_dir.c_str(); } + void SBPlatformShellCommand::SetWorkingDirectory (const char *path) { @@ -257,6 +259,50 @@ } //---------------------------------------------------------------------- +// SBPlatformLaunchCommand +//---------------------------------------------------------------------- + +SBPlatformLaunchCommand::SBPlatformLaunchCommand (const char* command, + const char* working_dir, + const char* arch) : + m_command (command ? command : ""), + m_working_dir (working_dir ? working_dir: ""), + m_arch (arch ? arch: ""), + m_pid(LLDB_INVALID_PROCESS_ID) +{ +} + +const char* +SBPlatformLaunchCommand::GetCommand () const +{ + return m_command.c_str (); +} + +const char* +SBPlatformLaunchCommand::GetWorkingDir () const +{ + return m_working_dir.c_str (); +} + +const char* +SBPlatformLaunchCommand::GetArch () const +{ + return m_arch.c_str (); +} + +const lldb::pid_t& +SBPlatformLaunchCommand::GetPID () const +{ + return m_pid; +} + +void +SBPlatformLaunchCommand::SetPID (const lldb::pid_t& pid) +{ + m_pid = pid; +} + +//---------------------------------------------------------------------- // SBPlatform //---------------------------------------------------------------------- SBPlatform::SBPlatform () : @@ -484,104 +530,129 @@ SBPlatform::Put (SBFileSpec &src, SBFileSpec &dst) { - SBError sb_error; - - PlatformSP platform_sp(GetSP()); - if (platform_sp) - { - if (src.Exists()) + return ExecuteConnected( + [&](const lldb::PlatformSP& platform_sp) + { + if (src.Exists()) + { + uint32_t permissions = src.ref().GetPermissions(); + if (permissions == 0) + { + if (src.ref().GetFileType() == FileSpec::eFileTypeDirectory) + permissions = eFilePermissionsDirectoryDefault; + else + permissions = eFilePermissionsFileDefault; + } + + return platform_sp->PutFile(src.ref(), dst.ref(), permissions); + } + + Error error; + error.SetErrorStringWithFormat("'src' argument doesn't exist: '%s'", src.ref().GetPath().c_str()); + return error; + }); +} + +SBError +SBPlatform::Install (SBFileSpec &src, + SBFileSpec &dst) +{ + return ExecuteConnected( + [&](const lldb::PlatformSP& platform_sp) + { + if (src.Exists()) + return platform_sp->Install(src.ref(), dst.ref()); + + Error error; + error.SetErrorStringWithFormat("'src' argument doesn't exist: '%s'", src.ref().GetPath().c_str()); + return error; + }); +} + + +SBError +SBPlatform::Run (SBPlatformShellCommand &shell_command) +{ + return ExecuteConnected( + [&](const lldb::PlatformSP& platform_sp) { - uint32_t permissions = src.ref().GetPermissions(); - if (permissions == 0) + const char *command = shell_command.GetCommand(); + if (!command) + return Error("invalid shell command (empty)"); + + const char *working_dir = shell_command.GetWorkingDirectory(); + if (working_dir == NULL) { - if (src.ref().GetFileType() == FileSpec::eFileTypeDirectory) - permissions = eFilePermissionsDirectoryDefault; - else - permissions = eFilePermissionsFileDefault; + working_dir = platform_sp->GetWorkingDirectory().GetCString(); + if (working_dir) + shell_command.SetWorkingDirectory(working_dir); } + return platform_sp->RunShellCommand(command, + working_dir, + &shell_command.m_opaque_ptr->m_status, + &shell_command.m_opaque_ptr->m_signo, + &shell_command.m_opaque_ptr->m_output, + shell_command.m_opaque_ptr->m_timeout_sec); + }); +} - sb_error.ref() = platform_sp->PutFile(src.ref(), - dst.ref(), - permissions); - } - else +SBError +SBPlatform::Launch (SBPlatformLaunchCommand &launch_command) +{ + return ExecuteConnected( + [&](const lldb::PlatformSP& platform_sp) { - sb_error.ref().SetErrorStringWithFormat("'src' argument doesn't exist: '%s'", src.ref().GetPath().c_str()); - } - } - else - { - sb_error.SetErrorString("invalid platform"); - } - return sb_error; + auto command = launch_command.GetCommand(); + if (!command) + return Error("invalid launch command (empty)"); + + ProcessLaunchInfo launch_info; + launch_info.SetArguments(Args(command), true); + + auto working_dir = launch_command.GetWorkingDir(); + if (working_dir == nullptr) + working_dir = platform_sp->GetWorkingDirectory().GetCString(); + launch_info.SetWorkingDirectory(working_dir); + auto arch = launch_command.GetArch(); + if (arch) + launch_info.SetArchitecture(ArchSpec(arch)); + else + launch_info.SetArchitecture(platform_sp->GetRemoteSystemArchitecture()); + + auto error = platform_sp->LaunchProcess(launch_info); + if (error.Success()) + launch_command.SetPID(launch_info.GetProcessID()); + + return error; + }); } SBError -SBPlatform::Install (SBFileSpec &src, - SBFileSpec &dst) +SBPlatform::Kill (const lldb::pid_t pid) { - SBError sb_error; - PlatformSP platform_sp(GetSP()); - if (platform_sp) - { - if (src.Exists()) + return ExecuteConnected( + [&](const lldb::PlatformSP& platform_sp) { - sb_error.ref() = platform_sp->Install(src.ref(), dst.ref()); - } - else - { - sb_error.ref().SetErrorStringWithFormat("'src' argument doesn't exist: '%s'", src.ref().GetPath().c_str()); - } - } - else - { - sb_error.SetErrorString("invalid platform"); - } - return sb_error; + return platform_sp->KillProcess(pid); + }); } - SBError -SBPlatform::Run (SBPlatformShellCommand &shell_command) +SBPlatform::ExecuteConnected (const std::function& func) { SBError sb_error; - PlatformSP platform_sp(GetSP()); + const auto platform_sp(GetSP()); if (platform_sp) { if (platform_sp->IsConnected()) - { - const char *command = shell_command.GetCommand(); - if (command) - { - const char *working_dir = shell_command.GetWorkingDirectory(); - if (working_dir == NULL) - { - working_dir = platform_sp->GetWorkingDirectory().GetCString(); - if (working_dir) - shell_command.SetWorkingDirectory(working_dir); - } - sb_error.ref() = platform_sp->RunShellCommand(command, - working_dir, - &shell_command.m_opaque_ptr->m_status, - &shell_command.m_opaque_ptr->m_signo, - &shell_command.m_opaque_ptr->m_output, - shell_command.m_opaque_ptr->m_timeout_sec); - } - else - { - sb_error.SetErrorString("invalid shell command (empty)"); - } - } + sb_error.ref() = func(platform_sp); else - { sb_error.SetErrorString("not connected"); - } } else - { sb_error.SetErrorString("invalid platform"); - } - return sb_error; + + return sb_error; } SBError Index: source/Plugins/Platform/gdb-server/PlatformRemoteGDBServer.h =================================================================== --- source/Plugins/Platform/gdb-server/PlatformRemoteGDBServer.h +++ source/Plugins/Platform/gdb-server/PlatformRemoteGDBServer.h @@ -86,7 +86,10 @@ virtual lldb_private::Error LaunchProcess (lldb_private::ProcessLaunchInfo &launch_info); - + + virtual lldb_private::Error + KillProcess (const lldb::pid_t pid); + virtual lldb::ProcessSP DebugProcess (lldb_private::ProcessLaunchInfo &launch_info, lldb_private::Debugger &debugger, Index: source/Plugins/Platform/gdb-server/PlatformRemoteGDBServer.cpp =================================================================== --- source/Plugins/Platform/gdb-server/PlatformRemoteGDBServer.cpp +++ source/Plugins/Platform/gdb-server/PlatformRemoteGDBServer.cpp @@ -430,7 +430,6 @@ { Log *log(lldb_private::GetLogIfAllCategoriesSet (LIBLLDB_LOG_PLATFORM)); Error error; - lldb::pid_t pid = LLDB_INVALID_PROCESS_ID; if (log) log->Printf ("PlatformRemoteGDBServer::%s() called", __FUNCTION__); @@ -475,7 +474,7 @@ std::string error_str; if (m_gdb_client.GetLaunchSuccess (error_str)) { - pid = m_gdb_client.GetCurrentProcessID (); + const auto pid = m_gdb_client.GetCurrentProcessID (false); if (pid != LLDB_INVALID_PROCESS_ID) { launch_info.SetProcessID (pid); @@ -486,7 +485,7 @@ { if (log) log->Printf ("PlatformRemoteGDBServer::%s() launch succeeded but we didn't get a valid process id back!", __FUNCTION__); - // FIXME isn't this an error condition? Do we need to set an error here? Check with Greg. + error.SetErrorString ("failed to get PID"); } } else @@ -503,6 +502,14 @@ return error; } +Error +PlatformRemoteGDBServer::KillProcess (const lldb::pid_t pid) +{ + if (!m_gdb_client.KillSpawnedProcess(pid)) + return Error("failed to kill remote spawned process"); + return Error(); +} + lldb::ProcessSP PlatformRemoteGDBServer::DebugProcess (lldb_private::ProcessLaunchInfo &launch_info, lldb_private::Debugger &debugger, Index: source/Plugins/Process/gdb-remote/GDBRemoteCommunicationClient.h =================================================================== --- source/Plugins/Process/gdb-remote/GDBRemoteCommunicationClient.h +++ source/Plugins/Process/gdb-remote/GDBRemoteCommunicationClient.h @@ -109,7 +109,7 @@ bool &timed_out); lldb::pid_t - GetCurrentProcessID (); + GetCurrentProcessID (bool allow_lazy = true); bool GetLaunchSuccess (std::string &error_str); @@ -534,7 +534,7 @@ StringExtractorGDBRemote &response); bool - GetCurrentProcessInfo (); + GetCurrentProcessInfo (bool allow_lazy_pid = true); bool GetGDBServerVersion(); Index: source/Plugins/Process/gdb-remote/GDBRemoteCommunicationClient.cpp =================================================================== --- source/Plugins/Process/gdb-remote/GDBRemoteCommunicationClient.cpp +++ source/Plugins/Process/gdb-remote/GDBRemoteCommunicationClient.cpp @@ -1211,13 +1211,13 @@ } lldb::pid_t -GDBRemoteCommunicationClient::GetCurrentProcessID () +GDBRemoteCommunicationClient::GetCurrentProcessID (bool allow_lazy) { - if (m_curr_pid_is_valid == eLazyBoolYes) + if (allow_lazy && m_curr_pid_is_valid == eLazyBoolYes) return m_curr_pid; // First try to retrieve the pid via the qProcessInfo request. - GetCurrentProcessInfo (); + GetCurrentProcessInfo (allow_lazy); if (m_curr_pid_is_valid == eLazyBoolYes) { // We really got it. @@ -2409,14 +2409,17 @@ } bool -GDBRemoteCommunicationClient::GetCurrentProcessInfo () +GDBRemoteCommunicationClient::GetCurrentProcessInfo (bool allow_lazy) { Log *log (ProcessGDBRemoteLog::GetLogIfAnyCategoryIsSet (GDBR_LOG_PROCESS | GDBR_LOG_PACKETS)); - if (m_qProcessInfo_is_valid == eLazyBoolYes) - return true; - if (m_qProcessInfo_is_valid == eLazyBoolNo) - return false; + if (allow_lazy) + { + if (m_qProcessInfo_is_valid == eLazyBoolYes) + return true; + if (m_qProcessInfo_is_valid == eLazyBoolNo) + return false; + } GetHostInfo (); Index: source/Plugins/Process/gdb-remote/GDBRemoteCommunicationServer.cpp =================================================================== --- source/Plugins/Process/gdb-remote/GDBRemoteCommunicationServer.cpp +++ source/Plugins/Process/gdb-remote/GDBRemoteCommunicationServer.cpp @@ -603,14 +603,12 @@ // add to list of spawned processes. On an lldb-gdbserver, we // would expect there to be only one. - lldb::pid_t pid; - if ( (pid = m_process_launch_info.GetProcessID()) != LLDB_INVALID_PROCESS_ID ) + const auto pid = m_process_launch_info.GetProcessID(); + if (pid != LLDB_INVALID_PROCESS_ID) { // add to spawned pids - { - Mutex::Locker locker (m_spawned_pids_mutex); - m_spawned_pids.insert(pid); - } + Mutex::Locker locker (m_spawned_pids_mutex); + m_spawned_pids.insert(pid); } return error; @@ -1424,23 +1422,33 @@ GDBRemoteCommunication::PacketResult GDBRemoteCommunicationServer::Handle_qProcessInfo (StringExtractorGDBRemote &packet) { - // Only the gdb server handles this. - if (!IsGdbServer ()) - return SendUnimplementedResponse (packet.GetStringRef ().c_str ()); - - // Fail if we don't have a current process. - if (!m_debugged_process_sp || (m_debugged_process_sp->GetID () == LLDB_INVALID_PROCESS_ID)) - return SendErrorResponse (68); - - ProcessInstanceInfo proc_info; - if (Host::GetProcessInfo (m_debugged_process_sp->GetID (), proc_info)) + lldb::pid_t pid = LLDB_INVALID_PROCESS_ID; + + if (IsGdbServer ()) { - StreamString response; - CreateProcessInfoResponse_DebugServerStyle(proc_info, response); - return SendPacketNoLock (response.GetData (), response.GetSize ()); + // Fail if we don't have a current process. + if (!m_debugged_process_sp || (m_debugged_process_sp->GetID () == LLDB_INVALID_PROCESS_ID)) + return SendErrorResponse (68); + + pid = m_debugged_process_sp->GetID (); } - - return SendErrorResponse (1); + else if (m_is_platform) + { + pid = m_process_launch_info.GetProcessID (); + } + else + return SendUnimplementedResponse (packet.GetStringRef ().c_str ()); + + if (pid == LLDB_INVALID_PROCESS_ID) + return SendErrorResponse (1); + + ProcessInstanceInfo proc_info; + if (!Host::GetProcessInfo (pid, proc_info)) + return SendErrorResponse (1); + + StreamString response; + CreateProcessInfoResponse_DebugServerStyle(proc_info, response); + return SendPacketNoLock (response.GetData (), response.GetSize ()); } GDBRemoteCommunication::PacketResult Index: source/Target/Platform.cpp =================================================================== --- source/Target/Platform.cpp +++ source/Target/Platform.cpp @@ -1103,6 +1103,20 @@ return error; } +Error +Platform::KillProcess (const lldb::pid_t pid) +{ + Log *log(lldb_private::GetLogIfAllCategoriesSet (LIBLLDB_LOG_PLATFORM)); + if (log) + log->Printf ("Platform::%s, pid %" PRIu64, __FUNCTION__, pid); + + if (!IsHost ()) + return Error ("base lldb_private::Platform class can't launch remote processes"); + + Host::Kill (pid, SIGTERM); + return Error(); +} + lldb::ProcessSP Platform::DebugProcess (ProcessLaunchInfo &launch_info, Debugger &debugger, Index: test/functionalities/process_attach/TestProcessAttach.py =================================================================== --- test/functionalities/process_attach/TestProcessAttach.py +++ test/functionalities/process_attach/TestProcessAttach.py @@ -53,14 +53,16 @@ popen = self.spawnSubprocess(exe) self.addTearDownHook(self.cleanupSubprocesses) - self.runCmd("process attach -p " + str(popen.pid)) + if lldb.remote_platform: + self.runCmd("platform process attach -p " + str(popen.pid)) + else: + self.runCmd("process attach -p " + str(popen.pid)) target = self.dbg.GetSelectedTarget() process = target.GetProcess() self.assertTrue(process, PROCESS_IS_VALID) - def process_attach_by_name(self): """Test attach by process name""" @@ -70,7 +72,10 @@ popen = self.spawnSubprocess(exe) self.addTearDownHook(self.cleanupSubprocesses) - self.runCmd("process attach -n " + exe_name) + if lldb.remote_platform: + self.runCmd("platform process attach -n " + exe_name) + else: + self.runCmd("process attach -n " + exe_name) target = self.dbg.GetSelectedTarget() Index: test/lldbtest.py =================================================================== --- test/lldbtest.py +++ test/lldbtest.py @@ -31,6 +31,7 @@ $ """ +import abc import os, sys, traceback import os.path import re @@ -41,6 +42,7 @@ import types import unittest2 import lldb +from _pyio import __metaclass__ # See also dotest.parseOptionsAndInitTestdirs(), where the environment variables # LLDB_COMMAND_TRACE and LLDB_DO_CLEANUP are set from '-t' and '-r dir' options. @@ -233,6 +235,72 @@ print >> self.session, self.getvalue() self.close() +class _BaseProcess(object): + __metaclass__ = abc.ABCMeta + + @abc.abstractproperty + def pid(self): + """Returns process PID if has been launched already.""" + + @abc.abstractmethod + def launch(self, executable, args): + """Launches new process with given executable and args.""" + + @abc.abstractmethod + def terminate(self): + """Terminates previously launched process..""" + +class _LocalProcess(_BaseProcess): + + def __init__(self, trace_on): + self._proc = None + self._trace_on = trace_on + + @property + def pid(self): + return self._proc.pid + + def launch(self, executable, args): + self._proc = Popen([executable] + args, + stdout = open(os.devnull) if not self._trace_on else None, + stdin = PIPE) + + def terminate(self): + if self._proc.poll() == None: + self._proc.terminate() + +class _RemoteProcess(_BaseProcess): + + def __init__(self): + self._pid = None + + @property + def pid(self): + return self._pid + + def launch(self, executable, args): + remote_work_dir = lldb.remote_platform.GetWorkingDirectory() + src_path = executable + dst_path = os.path.join(remote_work_dir, os.path.basename(executable)) + + err = lldb.remote_platform.Install(lldb.SBFileSpec(src_path, True), + lldb.SBFileSpec(dst_path, False)) + if err.Fail(): + raise Exception("remote_platform.Install('%s', '%s') failed: %s" % (src_path, dst_path, err)) + + cmd = lldb.SBPlatformLaunchCommand(' '.join([dst_path] + args), + remote_work_dir, + None) + err = lldb.remote_platform.Launch(cmd) + if err.Fail(): + raise Exception("remote_platform.Launch('%s', '%s') failed: %s" % (dst_path, args, err)) + self._pid = cmd.GetPID() + + def terminate(self): + err = lldb.remote_platform.Kill(self._pid) + if err.Fail(): + raise Exception("remote_platform.Kill(%d) failed: %s" % (self._pid, err)) + # From 2.7's subprocess.check_output() convenience function. # Return a tuple (stdoutdata, stderrdata). def system(commands, **kwargs): @@ -956,8 +1024,7 @@ def cleanupSubprocesses(self): # Ensure any subprocesses are cleaned up for p in self.subprocesses: - if p.poll() == None: - p.terminate() + p.terminate() del p del self.subprocesses[:] # Ensure any forked processes are cleaned up @@ -974,11 +1041,8 @@ otherwise the test suite will leak processes. """ - - # Don't display the stdout if not in TraceOn() mode. - proc = Popen([executable] + args, - stdout = open(os.devnull) if not self.TraceOn() else None, - stdin = PIPE) + proc = _RemoteProcess() if lldb.remote_platform else _LocalProcess(self.TraceOn()) + proc.launch(executable, args) self.subprocesses.append(proc) return proc