Skip to content

Commit

Permalink
Modify "platform connect" to connect to processes as well
Browse files Browse the repository at this point in the history
The standard remote debugging workflow with gdb is to start the
application on the remote host under gdbserver (e.g.: gdbserver :5039
a.out) and then connect to it with gdb.

The same workflow is supported by debugserver/lldb-gdbserver with a very
similar syntax but to access all features of lldb we need to be
connected also to an lldb-platform instance running on the target.

Before this change this had to be done manually with starting a separate
lldb-platform on the target machine and then connecting to it with lldb
before connecting to the process.

This change modifies the behavior of "platform connect" with
automatically connecting to the process instance if it was started by
the remote platform. With this command replacing gdbserver in a gdb
based worflow is usually as simple as replacing the command to execute
gdbserver with executing lldb-platform.

Differential revision: http://reviews.llvm.org/D14952

llvm-svn: 255016
  • Loading branch information
Tamas Berghammer committed Dec 8, 2015
1 parent 59d092f commit ccd6cff
Show file tree
Hide file tree
Showing 25 changed files with 576 additions and 151 deletions.
26 changes: 26 additions & 0 deletions lldb/docs/lldb-gdb-remote.txt
Expand Up @@ -1597,3 +1597,29 @@ On MacOSX with debugserver, we expedite the frame pointer backchain for a thread
(up to 256 entries) by reading 2 pointers worth of bytes at the frame pointer (for
the previous FP and PC), and follow the backchain. Most backtraces on MacOSX and
iOS now don't require us to read any memory!

//----------------------------------------------------------------------
// "qQueryGDBServer"
//
// BRIEF
// Ask the platform for the list of gdbservers we have to connect
//
// PRIORITY TO IMPLEMENT
// Low. The packet is required to support connecting to gdbserver started
// by the platform instance automatically.
//----------------------------------------------------------------------

If the remote platform automatically started one or more gdbserver instance (without
lldb asking it) then it have to return the list of port number or socket name for
each of them what can be used by lldb to connect to those instances.

The data in this packet is a JSON array of JSON objects with the following keys:
"port": <the port number to connect> (optional)
"socket_name": <the name of the socket to connect> (optional)

Example packet:
[
{ "port": 1234 },
{ "port": 5432 },
{ "socket_name": "foo" }
]
26 changes: 26 additions & 0 deletions lldb/include/lldb/Target/Platform.h
Expand Up @@ -487,6 +487,13 @@ class ModuleCache;
Target *target, // Can be nullptr, if nullptr create a new target, else use existing one
Error &error);

virtual lldb::ProcessSP
ConnectProcess (const char* connect_url,
const char* plugin_name,
lldb_private::Debugger &debugger,
lldb_private::Target *target,
lldb_private::Error &error);

//------------------------------------------------------------------
/// Attach to an existing process using a process ID.
///
Expand Down Expand Up @@ -1034,6 +1041,25 @@ class ModuleCache;
virtual Error
UnloadImage (lldb_private::Process* process, uint32_t image_token);

//------------------------------------------------------------------
/// Connect to all processes waiting for a debugger to attach
///
/// If the platform have a list of processes waiting for a debugger
/// to connect to them then connect to all of these pending processes.
///
/// @param[in] debugger
/// The debugger used for the connect.
///
/// @param[out] error
/// If an error occurred during the connect then this object will
/// contain the error message.
///
/// @return
/// The number of processes we are succesfully connected to.
//------------------------------------------------------------------
virtual size_t
ConnectToWaitingProcesses(lldb_private::Debugger& debugger, lldb_private::Error& error);

protected:
bool m_is_host;
// Set to true when we are able to actually set the OS version while
Expand Down
6 changes: 4 additions & 2 deletions lldb/packages/Python/lldbsuite/test/lldbtest.py
Expand Up @@ -1115,21 +1115,23 @@ def wrapper(*args, **kwargs):
# @skipIf(bugnumber, ["linux"], "gcc", ['>=', '4.9'], ['i386']), skip for gcc>=4.9 on linux with i386

# TODO: refactor current code, to make skipIfxxx functions to call this function
def skipIf(bugnumber=None, oslist=None, compiler=None, compiler_version=None, archs=None, debug_info=None, swig_version=None, py_version=None):
def skipIf(bugnumber=None, oslist=None, compiler=None, compiler_version=None, archs=None, debug_info=None, swig_version=None, py_version=None, remote=None):
def fn(self):
oslist_passes = oslist is None or self.getPlatform() in oslist
compiler_passes = compiler is None or (compiler in self.getCompiler() and self.expectedCompilerVersion(compiler_version))
arch_passes = self.expectedArch(archs)
debug_info_passes = debug_info is None or self.debug_info in debug_info
swig_version_passes = (swig_version is None) or (not hasattr(lldb, 'swig_version')) or (check_expected_version(swig_version[0], swig_version[1], lldb.swig_version))
py_version_passes = (py_version is None) or check_expected_version(py_version[0], py_version[1], sys.version_info)
remote_passes = (remote is None) or (remote == (lldb.remote_platform is not None))

return (oslist_passes and
compiler_passes and
arch_passes and
debug_info_passes and
swig_version_passes and
py_version_passes)
py_version_passes and
remote_passes)

local_vars = locals()
args = [x for x in inspect.getargspec(skipIf).args]
Expand Down
Expand Up @@ -147,29 +147,30 @@ def get_stub_port_from_named_socket(self, read_timeout_seconds=5):

return stub_port

def run_shell_cmd(self, cmd):
platform = self.dbg.GetSelectedPlatform()
shell_cmd = lldb.SBPlatformShellCommand(cmd)
err = platform.Run(shell_cmd)
if err.Fail() or shell_cmd.GetStatus():
m = "remote_platform.RunShellCommand('%s') failed:\n" % cmd
m += ">>> return code: %d\n" % shell_cmd.GetStatus()
if err.Fail():
m += ">>> %s\n" % str(err).strip()
m += ">>> %s\n" % (shell_cmd.GetOutput() or
"Command generated no output.")
raise Exception(m)
return shell_cmd.GetOutput().strip()

def init_llgs_test(self, use_named_pipe=True):
if lldb.remote_platform:
def run_shell_cmd(cmd):
platform = self.dbg.GetSelectedPlatform()
shell_cmd = lldb.SBPlatformShellCommand(cmd)
err = platform.Run(shell_cmd)
if err.Fail() or shell_cmd.GetStatus():
m = "remote_platform.RunShellCommand('%s') failed:\n" % cmd
m += ">>> return code: %d\n" % shell_cmd.GetStatus()
if err.Fail():
m += ">>> %s\n" % str(err).strip()
m += ">>> %s\n" % (shell_cmd.GetOutput() or
"Command generated no output.")
raise Exception(m)
return shell_cmd.GetOutput().strip()
# Remote platforms don't support named pipe based port negotiation
use_named_pipe = False

# Grab the ppid from /proc/[shell pid]/stat
shell_stat = run_shell_cmd("cat /proc/$$/stat")
shell_stat = self.run_shell_cmd("cat /proc/$$/stat")
# [pid] ([executable]) [state] [*ppid*]
pid = re.match(r"^\d+ \(.+\) . (\d+)", shell_stat).group(1)
ls_output = run_shell_cmd("ls -l /proc/%s/exe" % pid)
ls_output = self.run_shell_cmd("ls -l /proc/%s/exe" % pid)
exe = ls_output.split()[-1]

# If the binary has been deleted, the link name has " (deleted)" appended.
Expand Down
@@ -0,0 +1,5 @@
LEVEL = ../../../make

CXX_SOURCES := main.cpp

include $(LEVEL)/Makefile.rules
@@ -0,0 +1,55 @@
from __future__ import print_function

import gdbremote_testcase
from lldbsuite.test.lldbtest import *
import lldbsuite.test.lldbutil as lldbutil

class TestPlatformProcessConnect(gdbremote_testcase.GdbRemoteTestCaseBase):
mydir = TestBase.compute_mydir(__file__)

@llgs_test
@no_debug_info_test
@skipIf(remote=False)
def test_platform_process_connect(self):
self.build()
self.init_llgs_test(False)

working_dir = lldb.remote_platform.GetWorkingDirectory()
err = lldb.remote_platform.Put(lldb.SBFileSpec(os.path.join(os.getcwd(), "a.out")),
lldb.SBFileSpec(os.path.join(working_dir, "a.out")))
if err.Fail():
raise RuntimeError("Unable copy '%s' to '%s'.\n>>> %s" % (f, wd, err.GetCString()))

port_file = "%s/port" % working_dir
commandline_args = ["platform", "--listen", "*:0", "--socket-file", port_file, "--", "%s/a.out" % working_dir, "foo"]
self.spawnSubprocess(self.debug_monitor_exe, commandline_args, install_remote=False)
self.addTearDownHook(self.cleanupSubprocesses)
new_port = self.run_shell_cmd("while [ ! -f %s ]; do sleep 0.25; done && cat %s" % (port_file, port_file))

new_debugger = lldb.SBDebugger.Create()
new_debugger.SetAsync(False)
def del_debugger():
del new_debugger
self.addTearDownHook(del_debugger)

new_platform = lldb.SBPlatform(lldb.remote_platform.GetName())
new_debugger.SetSelectedPlatform(new_platform)
new_interpreter = new_debugger.GetCommandInterpreter()

m = re.search("(.*):[0-9]+", configuration.lldb_platform_url)
command = "platform connect %s:%s" % (m.group(1), new_port)
result = lldb.SBCommandReturnObject()
new_interpreter.HandleCommand(command, result)
self.assertTrue(result.Succeeded(), "platform process connect failed: %s" % result.GetOutput())

target = new_debugger.GetSelectedTarget()
process = target.GetProcess()
thread = process.GetThreadAtIndex(0)

breakpoint = target.BreakpointCreateByName("main")
process.Continue()

frame = thread.GetFrameAtIndex(0)
self.assertEqual(frame.GetFunction().GetName(), "main")
self.assertEqual(frame.FindVariable("argc").GetValueAsSigned(), 2)
process.Continue()
@@ -0,0 +1,7 @@
#include <cstdio>

int main (int argc, char **argv)
{
printf("argc: %d\n", argc);
return argv[0][0];
}
11 changes: 9 additions & 2 deletions lldb/source/Commands/CommandObjectPlatform.cpp
Expand Up @@ -409,12 +409,19 @@ class CommandObjectPlatformConnect : public CommandObjectParsed
if (error.Success())
{
platform_sp->GetStatus (ostrm);
result.SetStatus (eReturnStatusSuccessFinishResult);
result.SetStatus (eReturnStatusSuccessFinishResult);

platform_sp->ConnectToWaitingProcesses(m_interpreter.GetDebugger(), error);
if (error.Fail())
{
result.AppendError (error.AsCString());
result.SetStatus (eReturnStatusFailed);
}
}
else
{
result.AppendErrorWithFormat ("%s\n", error.AsCString());
result.SetStatus (eReturnStatusFailed);
result.SetStatus (eReturnStatusFailed);
}
}
else
Expand Down
90 changes: 29 additions & 61 deletions lldb/source/Commands/CommandObjectProcess.cpp
Expand Up @@ -1051,78 +1051,46 @@ class CommandObjectProcessConnect : public CommandObjectParsed
bool
DoExecute (Args& command, CommandReturnObject &result) override
{

TargetSP target_sp (m_interpreter.GetDebugger().GetSelectedTarget());
Error error;
Process *process = m_exe_ctx.GetProcessPtr();
if (process)
if (command.GetArgumentCount() != 1)
{
if (process->IsAlive())
{
result.AppendErrorWithFormat ("Process %" PRIu64 " is currently being debugged, kill the process before connecting.\n",
process->GetID());
result.SetStatus (eReturnStatusFailed);
return false;
}
result.AppendErrorWithFormat ("'%s' takes exactly one argument:\nUsage: %s\n",
m_cmd_name.c_str(),
m_cmd_syntax.c_str());
result.SetStatus (eReturnStatusFailed);
return false;
}


if (!target_sp)
Process *process = m_exe_ctx.GetProcessPtr();
if (process && process->IsAlive())
{
// If there isn't a current target create one.

error = m_interpreter.GetDebugger().GetTargetList().CreateTarget (m_interpreter.GetDebugger(),
NULL,
NULL,
false,
NULL, // No platform options
target_sp);
if (!target_sp || error.Fail())
{
result.AppendError(error.AsCString("Error creating target"));
result.SetStatus (eReturnStatusFailed);
return false;
}
m_interpreter.GetDebugger().GetTargetList().SetSelectedTarget(target_sp.get());
result.AppendErrorWithFormat ("Process %" PRIu64 " is currently being debugged, kill the process before connecting.\n",
process->GetID());
result.SetStatus (eReturnStatusFailed);
return false;
}

if (command.GetArgumentCount() == 1)
{
const char *plugin_name = NULL;
if (!m_options.plugin_name.empty())
plugin_name = m_options.plugin_name.c_str();

const char *remote_url = command.GetArgumentAtIndex(0);
process = target_sp->CreateProcess (m_interpreter.GetDebugger().GetListener(), plugin_name, NULL).get();

if (process)
{
error = process->ConnectRemote (process->GetTarget().GetDebugger().GetOutputFile().get(), remote_url);
const char *plugin_name = nullptr;
if (!m_options.plugin_name.empty())
plugin_name = m_options.plugin_name.c_str();

if (error.Fail())
{
result.AppendError(error.AsCString("Remote connect failed"));
result.SetStatus (eReturnStatusFailed);
target_sp->DeleteCurrentProcess();
return false;
}
}
else
{
result.AppendErrorWithFormat ("Unable to find process plug-in for remote URL '%s'.\nPlease specify a process plug-in name with the --plugin option, or specify an object file using the \"file\" command.\n",
remote_url);
result.SetStatus (eReturnStatusFailed);
}
}
else
Error error;
Debugger& debugger = m_interpreter.GetDebugger();
PlatformSP platform_sp = m_interpreter.GetPlatform(true);
ProcessSP process_sp = platform_sp->ConnectProcess(command.GetArgumentAtIndex(0),
plugin_name,
debugger,
debugger.GetSelectedTarget().get(),
error);
if (error.Fail() || process_sp == nullptr)
{
result.AppendErrorWithFormat ("'%s' takes exactly one argument:\nUsage: %s\n",
m_cmd_name.c_str(),
m_cmd_syntax.c_str());
result.AppendError(error.AsCString("Error connecting to the process"));
result.SetStatus (eReturnStatusFailed);
return false;
}
return result.Succeeded();
return true;
}

CommandOptions m_options;
};

Expand Down
Expand Up @@ -223,3 +223,53 @@ PlatformAndroidRemoteGDBServer::MakeConnectURL(const lldb::pid_t pid,

return error;
}

lldb::ProcessSP
PlatformAndroidRemoteGDBServer::ConnectProcess(const char* connect_url,
const char* plugin_name,
lldb_private::Debugger &debugger,
lldb_private::Target *target,
lldb_private::Error &error)
{
// We don't have the pid of the remote gdbserver when it isn't started by us but we still want
// to store the list of port forwards we set up in our port forward map. Generate a fake pid for
// these cases what won't collide with any other valid pid on android.
static lldb::pid_t s_remote_gdbserver_fake_pid = 0xffffffffffffffffULL;

int remote_port;
std::string scheme, host, path;
if (!UriParser::Parse(connect_url, scheme, host, remote_port, path))
{
error.SetErrorStringWithFormat("Invalid URL: %s", connect_url);
return nullptr;
}

std::string new_connect_url;
error = MakeConnectURL(s_remote_gdbserver_fake_pid--,
(remote_port < 0) ? 0 : remote_port,
path.c_str(),
new_connect_url);
if (error.Fail())
return nullptr;

return PlatformRemoteGDBServer::ConnectProcess(new_connect_url.c_str(),
plugin_name,
debugger,
target,
error);
}

size_t
PlatformAndroidRemoteGDBServer::ConnectToWaitingProcesses(Debugger& debugger, Error& error)
{
std::vector<std::string> connection_urls;
GetPendingGdbServerList(connection_urls);

for (size_t i = 0; i < connection_urls.size(); ++i)
{
ConnectProcess(connection_urls[i].c_str(), nullptr, debugger, nullptr, error);
if (error.Fail())
return i; // We already connected to i process succsessfully
}
return connection_urls.size();
}

0 comments on commit ccd6cff

Please sign in to comment.