Implement -target-attach and -target-detach

Summary:
This changes add -target-attach and -target-detach. 

-target-attach allows lldb-mi to attach to an existing process by it's process id, matching gdb mi's syntax of '-target-attach <pid'. Additionally, support has been added for attaching to a process by name using '-target-attach -n <name>'. Combining this with --waitfor will allow lldb mi to attach to a process by name when the process starts. 

-target-detach simply detaches from the current process

Patch from chuckr@microsoft.com

Test Plan: I have added three tests, one each for -target-attach <pid>, -target-attach -n <name>, and -target-attach -n <name> --waitfor

Reviewers: paulmaybee, abidh, ChuckR

Subscribers: greggm, lldb-commits

Differential Revision: http://reviews.llvm.org/D9484

llvm-svn: 236705
This commit is contained in:
Ilia K 2015-05-07 07:38:49 +00:00
parent 065397be00
commit 7bb59b360b
9 changed files with 491 additions and 2 deletions

View File

@ -0,0 +1,5 @@
LEVEL = ../../../make
CXX_SOURCES := test_attach.cpp
include $(LEVEL)/Makefile.rules

View File

@ -0,0 +1,125 @@
"""
Test lldb-mi -target-xxx commands.
"""
import lldbmi_testcase
from lldbtest import *
import unittest2
class MiTargetTestCase(lldbmi_testcase.MiTestCaseBase):
mydir = TestBase.compute_mydir(__file__)
@lldbmi_test
@expectedFailureWindows("llvm.org/pr22274: need a pexpect replacement for windows")
@skipIfFreeBSD # llvm.org/pr22411: Failure presumably due to known thread races
@skipIfLinux # cannot attach to process on linux
def test_lldbmi_target_attach_wait_for(self):
"""Test that 'lldb-mi --interpreter' works for -target-attach -n <name> --waitfor."""
# Build target executable with unique name
exeName = self.testMethodName
d = {'EXE': exeName}
self.buildProgram("test_attach.cpp", exeName)
self.addTearDownCleanup(dictionary=d)
self.spawnLldbMi(args = None)
# Load executable
# FIXME: -file-exec-and-sybmols is not required for target attach, but the test will not pass without this
self.runCmd("-file-exec-and-symbols %s" % exeName)
self.expect("\^done")
# Set up attach
self.runCmd("-target-attach -n %s --waitfor" % exeName)
time.sleep(4) # Give attach time to setup
# Start target process
self.spawnSubprocess(os.path.join(os.path.dirname(__file__), exeName));
self.addTearDownHook(self.cleanupSubprocesses)
self.expect("\^done")
# Set breakpoint on printf
line = line_number('test_attach.cpp', '// BP_i++')
self.runCmd("-break-insert -f test_attach.cpp:%d" % line)
self.expect("\^done,bkpt={number=\"1\"")
# Continue to breakpoint
self.runCmd("-exec-continue")
self.expect("\*stopped,reason=\"breakpoint-hit\"")
# Detach
self.runCmd("-target-detach")
self.expect("\^done")
@lldbmi_test
@expectedFailureWindows("llvm.org/pr22274: need a pexpect replacement for windows")
@skipIfFreeBSD # llvm.org/pr22411: Failure presumably due to known thread races
@skipIfLinux # cannot attach to process on linux
def test_lldbmi_target_attach_name(self):
"""Test that 'lldb-mi --interpreter' works for -target-attach -n <name>."""
# Build target executable with unique name
exeName = self.testMethodName
d = {'EXE': exeName}
self.buildProgram("test_attach.cpp", exeName)
self.addTearDownCleanup(dictionary=d)
# Start target process
targetProcess = self.spawnSubprocess(os.path.join(os.path.dirname(__file__), exeName));
self.addTearDownHook(self.cleanupSubprocesses)
self.spawnLldbMi(args = None)
# Set up atatch
self.runCmd("-target-attach -n %s" % exeName)
self.expect("\^done")
# Set breakpoint on printf
line = line_number('test_attach.cpp', '// BP_i++')
self.runCmd("-break-insert -f test_attach.cpp:%d" % line)
self.expect("\^done,bkpt={number=\"1\"")
# Continue to breakpoint
self.runCmd("-exec-continue")
self.expect("\*stopped,reason=\"breakpoint-hit\"")
# Detach
self.runCmd("-target-detach")
self.expect("\^done")
@lldbmi_test
@expectedFailureWindows("llvm.org/pr22274: need a pexpect replacement for windows")
@skipIfFreeBSD # llvm.org/pr22411: Failure presumably due to known thread races
@skipIfLinux # cannot attach to process on linux
def test_lldbmi_target_attach_pid(self):
"""Test that 'lldb-mi --interpreter' works for -target-attach <pid>."""
# Build target executable with unique name
exeName = self.testMethodName
d = {'EXE': exeName}
self.buildProgram("test_attach.cpp", exeName)
self.addTearDownCleanup(dictionary=d)
# Start target process
targetProcess = self.spawnSubprocess(os.path.join(os.path.dirname(__file__), exeName));
self.addTearDownHook(self.cleanupSubprocesses)
self.spawnLldbMi(args = None)
# Set up atatch
self.runCmd("-target-attach %d" % targetProcess.pid)
self.expect("\^done")
# Set breakpoint on printf
line = line_number('test_attach.cpp', '// BP_i++')
self.runCmd("-break-insert -f test_attach.cpp:%d" % line)
self.expect("\^done,bkpt={number=\"1\"")
# Continue to breakpoint
self.runCmd("-exec-continue")
self.expect("\*stopped,reason=\"breakpoint-hit\"")
# Detach
self.runCmd("-target-detach")
self.expect("\^done")

View File

@ -0,0 +1,21 @@
//===-- main.cpp ------------------------------------------------*- C++ -*-===//
//
// The LLVM Compiler Infrastructure
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//
#include <cstdio>
int
main(int argc, char const *argv[])
{
int i = 0;
for (;;)
{
i++; // BP_i++
}
return 0;
}

View File

@ -22,6 +22,9 @@
#include "MICmnLLDBDebugger.h"
#include "MICmnLLDBDebugSessionInfo.h"
#include "MICmdArgValString.h"
#include "MICmdArgValOptionLong.h"
#include "MICmdArgValOptionShort.h"
#include "MICmdArgValNumber.h"
//++ ------------------------------------------------------------------------------------
// Details: CMICmdCmdTargetSelect constructor.
@ -204,3 +207,263 @@ CMICmdCmdTargetSelect::CreateSelf(void)
{
return new CMICmdCmdTargetSelect();
}
//++ ------------------------------------------------------------------------------------
// Details: CMICmdCmdTargetAttach constructor.
// Type: Method.
// Args: None.
// Return: None.
// Throws: None.
//--
CMICmdCmdTargetAttach::CMICmdCmdTargetAttach(void)
: m_constStrArgPid("pid")
, m_constStrArgNamedFile("n")
, m_constStrArgWaitFor("waitfor")
{
// Command factory matches this name with that received from the stdin stream
m_strMiCmd = "target-attach";
// Required by the CMICmdFactory when registering *this command
m_pSelfCreatorFn = &CMICmdCmdTargetAttach::CreateSelf;
}
//++ ------------------------------------------------------------------------------------
// Details: CMICmdCmdTargetAttach destructor.
// Type: Overrideable.
// Args: None.
// Return: None.
// Throws: None.
//--
CMICmdCmdTargetAttach::~CMICmdCmdTargetAttach(void)
{
}
//++ ------------------------------------------------------------------------------------
// Details: The invoker requires this function. The parses the command line options
// arguments to extract values for each of those arguments.
// Type: Overridden.
// Args: None.
// Return: MIstatus::success - Functional succeeded.
// MIstatus::failure - Functional failed.
// Throws: None.
//--
bool
CMICmdCmdTargetAttach::ParseArgs(void)
{
bool bOk = m_setCmdArgs.Add(*(new CMICmdArgValNumber(m_constStrArgPid, false, true)));
bOk = bOk && m_setCmdArgs.Add(*(new CMICmdArgValOptionShort(m_constStrArgNamedFile, false, true,
CMICmdArgValListBase::eArgValType_String, 1)));
bOk = bOk && m_setCmdArgs.Add(*(new CMICmdArgValOptionLong(m_constStrArgWaitFor, false, true)));
return (bOk && ParseValidateCmdOptions());
}
//++ ------------------------------------------------------------------------------------
// Details: The invoker requires this function. The command does work in this function.
// The command is likely to communicate with the LLDB SBDebugger in here.
// Synopsis: -target-attach file
// Ref: http://sourceware.org/gdb/onlinedocs/gdb/GDB_002fMI-Target-Manipulation.html#GDB_002fMI-Target-Manipulation
// Type: Overridden.
// Args: None.
// Return: MIstatus::success - Functional succeeded.
// MIstatus::failure - Functional failed.
// Throws: None.
//--
bool
CMICmdCmdTargetAttach::Execute(void)
{
CMICMDBASE_GETOPTION(pArgPid, Number, m_constStrArgPid);
CMICMDBASE_GETOPTION(pArgFile, OptionShort, m_constStrArgNamedFile);
CMICMDBASE_GETOPTION(pArgWaitFor, OptionLong, m_constStrArgWaitFor);
CMICmnLLDBDebugSessionInfo &rSessionInfo(CMICmnLLDBDebugSessionInfo::Instance());
// If the current target is invalid, create one
lldb::SBTarget target = rSessionInfo.GetTarget();
if (!target.IsValid())
{
target = rSessionInfo.GetDebugger().CreateTarget(NULL);
if (!target.IsValid())
{
SetError(CMIUtilString::Format(MIRSRC(IDS_CMD_ERR_INVALID_TARGET_CURRENT), m_cmdData.strMiCmd.c_str()));
return MIstatus::failure;
}
}
lldb::SBError error;
lldb::SBListener listener;
if (pArgPid->GetFound() && pArgPid->GetValid())
{
lldb::pid_t pid;
pid = pArgPid->GetValue();
target.AttachToProcessWithID(listener, pid, error);
}
else if (pArgFile->GetFound() && pArgFile->GetValid())
{
bool bWaitFor = (pArgWaitFor->GetFound());
CMIUtilString file;
pArgFile->GetExpectedOption<CMICmdArgValString>(file);
target.AttachToProcessWithName(listener, file.c_str(), bWaitFor, error);
}
else
{
SetError(CMIUtilString::Format(MIRSRC(IDS_CMD_ERR_ATTACH_BAD_ARGS), m_cmdData.strMiCmd.c_str()));
return MIstatus::failure;
}
lldb::SBStream errMsg;
if (error.Fail())
{
SetError(CMIUtilString::Format(MIRSRC(IDS_CMD_ERR_ATTACH_FAILED), m_cmdData.strMiCmd.c_str(), errMsg.GetData()));
return MIstatus::failure;
}
return MIstatus::success;
}
//++ ------------------------------------------------------------------------------------
// Details: The invoker requires this function. The command prepares a MI Record Result
// for the work carried out in the Execute().
// Type: Overridden.
// Args: None.
// Return: MIstatus::success - Functional succeeded.
// MIstatus::failure - Functional failed.
// Throws: None.
//--
bool
CMICmdCmdTargetAttach::Acknowledge(void)
{
const CMICmnMIResultRecord miRecordResult(m_cmdData.strMiCmdToken, CMICmnMIResultRecord::eResultClass_Done);
m_miResultRecord = miRecordResult;
CMICmnLLDBDebugSessionInfo &rSessionInfo(CMICmnLLDBDebugSessionInfo::Instance());
lldb::pid_t pid = rSessionInfo.GetProcess().GetProcessID();
// Prod the client i.e. Eclipse with out-of-band results to help it 'continue' because it is using LLDB debugger
// Give the client '=thread-group-started,id="i1"'
m_bHasResultRecordExtra = true;
const CMICmnMIValueConst miValueConst2("i1");
const CMICmnMIValueResult miValueResult2("id", miValueConst2);
const CMIUtilString strPid(CMIUtilString::Format("%lld", pid));
const CMICmnMIValueConst miValueConst(strPid);
const CMICmnMIValueResult miValueResult("pid", miValueConst);
CMICmnMIOutOfBandRecord miOutOfBand(CMICmnMIOutOfBandRecord::eOutOfBand_ThreadGroupStarted, miValueResult2);
miOutOfBand.Add(miValueResult);
m_miResultRecordExtra = miOutOfBand.GetString();
return MIstatus::success;
}
//++ ------------------------------------------------------------------------------------
// Details: Required by the CMICmdFactory when registering *this command. The factory
// calls this function to create an instance of *this command.
// Type: Static method.
// Args: None.
// Return: CMICmdBase * - Pointer to a new command.
// Throws: None.
//--
CMICmdBase *
CMICmdCmdTargetAttach::CreateSelf(void)
{
return new CMICmdCmdTargetAttach();
}
//++ ------------------------------------------------------------------------------------
// Details: CMICmdCmdTargetDetach constructor.
// Type: Method.
// Args: None.
// Return: None.
// Throws: None.
//--
CMICmdCmdTargetDetach::CMICmdCmdTargetDetach()
{
// Command factory matches this name with that received from the stdin stream
m_strMiCmd = "target-detach";
// Required by the CMICmdFactory when registering *this command
m_pSelfCreatorFn = &CMICmdCmdTargetDetach::CreateSelf;
}
//++ ------------------------------------------------------------------------------------
// Details: CMICmdCmdTargetDetach destructor.
// Type: Overrideable.
// Args: None.
// Return: None.
// Throws: None.
//--
CMICmdCmdTargetDetach::~CMICmdCmdTargetDetach(void)
{
}
//++ ------------------------------------------------------------------------------------
// Details: The invoker requires this function. The parses the command line options
// arguments to extract values for each of those arguments.
// Type: Overridden.
// Args: None.
// Return: MIstatus::success - Functional succeeded.
// MIstatus::failure - Functional failed.
// Throws: None.
//--
bool
CMICmdCmdTargetDetach::ParseArgs(void)
{
return MIstatus::success;
}
//++ ------------------------------------------------------------------------------------
// Details: The invoker requires this function. The command does work in this function.
// The command is likely to communicate with the LLDB SBDebugger in here.
// Synopsis: -target-attach file
// Ref: http://sourceware.org/gdb/onlinedocs/gdb/GDB_002fMI-Target-Manipulation.html#GDB_002fMI-Target-Manipulation
// Type: Overridden.
// Args: None.
// Return: MIstatus::success - Functional succeeded.
// MIstatus::failure - Functional failed.
// Throws: None.
//--
bool
CMICmdCmdTargetDetach::Execute(void)
{
CMICmnLLDBDebugSessionInfo &rSessionInfo(CMICmnLLDBDebugSessionInfo::Instance());
lldb::SBProcess process = rSessionInfo.GetProcess();
if (!process.IsValid())
{
SetError(CMIUtilString::Format(MIRSRC(IDS_CMD_ERR_INVALID_PROCESS), m_cmdData.strMiCmd.c_str()));
return MIstatus::failure;
}
process.Detach();
return MIstatus::success;
}
//++ ------------------------------------------------------------------------------------
// Details: The invoker requires this function. The command prepares a MI Record Result
// for the work carried out in the Execute().
// Type: Overridden.
// Args: None.
// Return: MIstatus::success - Functional succeeded.
// MIstatus::failure - Functional failed.
// Throws: None.
//--
bool
CMICmdCmdTargetDetach::Acknowledge(void)
{
const CMICmnMIResultRecord miRecordResult(m_cmdData.strMiCmdToken, CMICmnMIResultRecord::eResultClass_Done);
m_miResultRecord = miRecordResult;
return MIstatus::success;
}
//++ ------------------------------------------------------------------------------------
// Details: Required by the CMICmdFactory when registering *this command. The factory
// calls this function to create an instance of *this command.
// Type: Static method.
// Args: None.
// Return: CMICmdBase * - Pointer to a new command.
// Throws: None.
//--
CMICmdBase *
CMICmdCmdTargetDetach::CreateSelf(void)
{
return new CMICmdCmdTargetDetach();
}

View File

@ -58,3 +58,62 @@ class CMICmdCmdTargetSelect : public CMICmdBase
const CMIUtilString m_constStrArgNamedType;
const CMIUtilString m_constStrArgNamedParameters;
};
//++ ============================================================================
// Details: MI command class. MI commands derived from the command base class.
// *this class implements MI command "target-attach".
// http://sourceware.org/gdb/onlinedocs/gdb/GDB_002fMI-Target-Manipulation.html#GDB_002fMI-Target-Manipulation
//--
class CMICmdCmdTargetAttach : public CMICmdBase
{
// Statics:
public:
// Required by the CMICmdFactory when registering *this command
static CMICmdBase *CreateSelf(void);
// Methods:
public:
/* ctor */ CMICmdCmdTargetAttach(void);
// Overridden:
public:
// From CMICmdInvoker::ICmd
virtual bool Execute(void);
virtual bool Acknowledge(void);
virtual bool ParseArgs(void);
// From CMICmnBase
/* dtor */ virtual ~CMICmdCmdTargetAttach(void);
// Attributes:
private:
const CMIUtilString m_constStrArgPid;
const CMIUtilString m_constStrArgNamedFile;
const CMIUtilString m_constStrArgWaitFor;
};
//++ ============================================================================
// Details: MI command class. MI commands derived from the command base class.
// *this class implements MI command "target-attach".
// http://sourceware.org/gdb/onlinedocs/gdb/GDB_002fMI-Target-Manipulation.html#GDB_002fMI-Target-Manipulation
//--
class CMICmdCmdTargetDetach : public CMICmdBase
{
// Statics:
public:
// Required by the CMICmdFactory when registering *this command
static CMICmdBase *CreateSelf(void);
// Methods:
public:
/* ctor */ CMICmdCmdTargetDetach(void);
// Overridden:
public:
// From CMICmdInvoker::ICmd
virtual bool Execute(void);
virtual bool Acknowledge(void);
virtual bool ParseArgs(void);
// From CMICmnBase
/* dtor */ virtual ~CMICmdCmdTargetDetach(void);
};

View File

@ -121,6 +121,8 @@ MICmnCommands::RegisterAll(void)
bOk &= Register<CMICmdCmdSupportListFeatures>();
bOk &= Register<CMICmdCmdSymbolListLines>();
bOk &= Register<CMICmdCmdTargetSelect>();
bOk &= Register<CMICmdCmdTargetAttach>();
bOk &= Register<CMICmdCmdTargetDetach>();
bOk &= Register<CMICmdCmdThreadInfo>();
bOk &= Register<CMICmdCmdVarAssign>();
bOk &= Register<CMICmdCmdVarCreate>();

View File

@ -250,7 +250,9 @@ const CMICmnResources::SRsrcTextData CMICmnResources::ms_pResourceId2TextData[]
{IDS_CMD_ERR_GDBSET_OPT_PRINT_UNKNOWN_OPTION, "'print' error. The option '%s' not found"},
{IDS_CMD_ERR_GDBSHOW_OPT_PRINT_BAD_ARGS, "'print' expects option-name and \"on\" or \"off\""},
{IDS_CMD_ERR_GDBSHOW_OPT_PRINT_UNKNOWN_OPTION, "'print' error. The option '%s' not found"},
{IDS_CMD_ERR_EXPR_INVALID, "Failed to evaluate expression: %s"}};
{IDS_CMD_ERR_EXPR_INVALID, "Failed to evaluate expression: %s"},
{IDS_CMD_ERR_ATTACH_FAILED, "Command '%s'. Attach to processs failed: %s"},
{IDS_CMD_ERR_ATTACH_BAD_ARGS, "Command '%s'. Must specify either a PID or a Name"}};
//++ ------------------------------------------------------------------------------------
// Details: CMICmnResources constructor.

View File

@ -268,7 +268,9 @@ enum
IDS_CMD_ERR_GDBSET_OPT_PRINT_UNKNOWN_OPTION,
IDS_CMD_ERR_GDBSHOW_OPT_PRINT_BAD_ARGS,
IDS_CMD_ERR_GDBSHOW_OPT_PRINT_UNKNOWN_OPTION,
IDS_CMD_ERR_EXPR_INVALID
IDS_CMD_ERR_EXPR_INVALID,
IDS_CMD_ERR_ATTACH_FAILED,
IDS_CMD_ERR_ATTACH_BAD_ARGS
};
//++ ============================================================================

View File

@ -91,3 +91,13 @@ The =library-loaded notification has 3 extra fields:
For example:
=library-loaded,id="/Users/IliaK/p/hello",target-name="/Users/IliaK/p/hello",host-name="/Users/IliaK/p/hello",symbols-loaded="1",symbols-path="/Users/IliaK/p/hello.dSYM/Contents/Resources/DWARF/hello",loaded_addr="-"
=library-loaded,id="/usr/lib/dyld",target-name="/usr/lib/dyld",host-name="/usr/lib/dyld",symbols-loaded="0",loaded_addr="0x00007fff5fc00000"
# -target-attach
Synopsis
Additional syntax provided by lldb-mi:
-target-attach -n <executable-name> [--waitfor]
Attach to an executable. Using -n allows specifying an executable name to attach to.
Using this with --watifor can do a deffered attach. The flags -n and --waitfor match the syntax of lldb proper's 'process attach' command.