This patch adds a 'jump' command, similar to GDBs.
This allows the PC to be directly changed to a different line.
It's similar to the example python script in examples/python/jump.py, except
implemented as a builtin.
Also this version will track the current function correctly even if the target
line resolves to multiple addresses. (e.g. debugging a templated function)
http://llvm-reviews.chandlerc.com/D1452
Files:
source/Commands/CommandObjectThread.cpp
source/Interpreter/CommandInterpreter.cpp
test/functionalities/thread/jump/Makefile
test/functionalities/thread/jump/TestThreadJump.py
test/functionalities/thread/jump/main.cpp
test/functionalities/thread/jump/other.cpp
Index: source/Commands/CommandObjectThread.cpp
===================================================================
--- source/Commands/CommandObjectThread.cpp
+++ source/Commands/CommandObjectThread.cpp
@@ -16,6 +16,7 @@
// Other libraries and framework includes
// Project includes
#include "lldb/lldb-private.h"
+#include "lldb/Core/AddressResolverFileLine.h"
#include "lldb/Core/State.h"
#include "lldb/Core/SourceManager.h"
#include "lldb/Host/Host.h"
@@ -1463,6 +1464,216 @@
};
//-------------------------------------------------------------------------
+// CommandObjectThreadJump
+//-------------------------------------------------------------------------
+
+class CommandObjectThreadJump : public CommandObjectParsed
+{
+public:
+ class CommandOptions : public Options
+ {
+ public:
+
+ CommandOptions (CommandInterpreter &interpreter) :
+ Options (interpreter)
+ {
+ OptionParsingStarting ();
+ }
+
+ void
+ OptionParsingStarting ()
+ {
+ m_filenames.Clear();
+ m_line_num = 0;
+ m_line_offset = 0;
+ m_load_addr = LLDB_INVALID_ADDRESS;
+ }
+
+ virtual
+ ~CommandOptions ()
+ {
+ }
+
+ virtual Error
+ SetOptionValue (uint32_t option_idx, const char *option_arg)
+ {
+ Error error;
+ const int short_option = m_getopt_table[option_idx].val;
+
+ switch (short_option)
+ {
+ case 'f':
+ m_filenames.AppendIfUnique (FileSpec(option_arg, false));
+ break;
+ case 'l':
+ m_line_num = Args::StringToUInt32 (option_arg, 0);
+ break;
+ case 'b':
+ m_line_offset = Args::StringToSInt32 (option_arg, 0);
+ break;
+ case 'a':
+ {
+ ExecutionContext exe_ctx (m_interpreter.GetExecutionContext());
+ m_load_addr = Args::StringToAddress(&exe_ctx, option_arg, LLDB_INVALID_ADDRESS, &error);
+ }
+ break;
+
+ default:
+ error.SetErrorStringWithFormat("invalid short option character '%c'", short_option);
+ break;
+
+ }
+ return error;
+ }
+
+ const OptionDefinition*
+ GetDefinitions ()
+ {
+ return g_option_table;
+ }
+
+ FileSpecList m_filenames;
+ uint32_t m_line_num;
+ int32_t m_line_offset;
+ lldb::addr_t m_load_addr;
+
+ static OptionDefinition g_option_table[];
+ };
+
+ virtual
+ Options *
+ GetOptions ()
+ {
+ return &m_options;
+ }
+
+ CommandObjectThreadJump (CommandInterpreter &interpreter) :
+ CommandObjectParsed (interpreter,
+ "thread jump",
+ "Sets the program counter to a new address.",
+ "thread jump",
+ eFlagRequiresFrame |
+ eFlagTryTargetAPILock |
+ eFlagProcessMustBeLaunched |
+ eFlagProcessMustBePaused ),
+ m_options (interpreter)
+ {
+ }
+
+ ~CommandObjectThreadJump()
+ {
+ }
+
+protected:
+
+ bool DoExecute (Args& args, CommandReturnObject &result)
+ {
+ RegisterContext *reg_ctx = m_exe_ctx.GetRegisterContext();
+ StackFrame *frame = m_exe_ctx.GetFramePtr();
+ Thread *thread = m_exe_ctx.GetThreadPtr();
+ Target *target = m_exe_ctx.GetTargetPtr();
+
+ lldb::addr_t dest = LLDB_INVALID_ADDRESS;
+
+ if (m_options.m_load_addr != LLDB_INVALID_ADDRESS)
+ {
+ // Use this address directly.
+ dest = m_options.m_load_addr;
+ }
+ else
+ {
+ const SymbolContext &sym_ctx = frame->GetSymbolContext (eSymbolContextLineEntry);
+
+ // Pick either the absolute line, or work out a relative one.
+ int32_t line = (int32_t)m_options.m_line_num;
+ if (line == 0)
+ line = sym_ctx.line_entry.line + m_options.m_line_offset;
+
+ // Try the current file, but override if asked.
+ FileSpec file = sym_ctx.line_entry.file;
+ if (m_options.m_filenames.GetSize() == 1)
+ file = m_options.m_filenames.GetFileSpecAtIndex(0);
+
+ if (!file)
+ {
+ result.AppendErrorWithFormat ("No source file available for the current location.");
+ result.SetStatus (eReturnStatusFailed);
+ return false;
+ }
+
+ // Find candiate places to jump to.
+ SearchFilterSP sp(target->GetSearchFilterForModuleList (NULL));
+ AddressResolverFileLine resolver(file, line, true);
+ resolver.ResolveAddress (*sp.get());
+
+ if (resolver.GetNumberOfAddresses() == 1)
+ {
+ // If there's just one, use that.
+ dest = resolver.GetAddressRangeAtIndex (0).GetBaseAddress().GetCallableLoadAddress (target);
+ }
+ else if (resolver.GetNumberOfAddresses() >= 2)
+ {
+ // Multiple addresses. The sensible thing to do is to keep the PC
+ // within the same function it was previously in.
+ for (size_t n=0;n<resolver.GetNumberOfAddresses();n++)
+ {
+ Address addr = resolver.GetAddressRangeAtIndex(n).GetBaseAddress();
+ Function *f = addr.CalculateSymbolContextFunction();
+ if (f && f == sym_ctx.function)
+ {
+ dest = addr.GetCallableLoadAddress (target);
+ break;
+ }
+ }
+
+ if (dest == LLDB_INVALID_ADDRESS)
+ {
+ result.AppendErrorWithFormat ("%s:%i has multiple candidate locations.", file.GetFilename().AsCString(), line);
+ result.SetStatus (eReturnStatusFailed);
+ return false;
+ }
+ }
+
+ if (dest == LLDB_INVALID_ADDRESS)
+ {
+ result.AppendErrorWithFormat ("Cannot locate an address for %s:%i.", file.GetFilename().AsCString(), line);
+ result.SetStatus (eReturnStatusFailed);
+ return false;
+ }
+ }
+
+ if (!reg_ctx->SetPC (dest))
+ {
+ result.AppendErrorWithFormat ("Error changing PC value for thread %d.", thread->GetIndexID());
+ result.SetStatus (eReturnStatusFailed);
+ return false;
+ }
+
+ result.SetStatus (eReturnStatusSuccessFinishResult);
+ return true;
+ }
+
+ CommandOptions m_options;
+};
+OptionDefinition
+CommandObjectThreadJump::CommandOptions::g_option_table[] =
+{
+ { LLDB_OPT_SET_1, false, "file", 'f', required_argument, NULL, CommandCompletions::eSourceFileCompletion, eArgTypeFilename,
+ "Specifies the source file to jump to."},
+
+ { LLDB_OPT_SET_1, true, "line", 'l', required_argument, NULL, 0, eArgTypeLineNum,
+ "Specifies the line number to jump to."},
+
+ { LLDB_OPT_SET_2, true, "by", 'b', required_argument, NULL, 0, eArgTypeOffset,
+ "Jumps by a relative line offset from the current line."},
+
+ { LLDB_OPT_SET_3, true, "address", 'a', required_argument, NULL, 0, eArgTypeAddressOrExpression,
+ "Jumps to a specific address."},
+
+ { 0, false, NULL, 0, 0, NULL, 0, eArgTypeNone, NULL }
+};
+
+//-------------------------------------------------------------------------
// CommandObjectMultiwordThread
//-------------------------------------------------------------------------
@@ -1476,6 +1687,7 @@
LoadSubCommand ("continue", CommandObjectSP (new CommandObjectThreadContinue (interpreter)));
LoadSubCommand ("list", CommandObjectSP (new CommandObjectThreadList (interpreter)));
LoadSubCommand ("return", CommandObjectSP (new CommandObjectThreadReturn (interpreter)));
+ LoadSubCommand ("jump", CommandObjectSP (new CommandObjectThreadJump (interpreter)));
LoadSubCommand ("select", CommandObjectSP (new CommandObjectThreadSelect (interpreter)));
LoadSubCommand ("until", CommandObjectSP (new CommandObjectThreadUntil (interpreter)));
LoadSubCommand ("step-in", CommandObjectSP (new CommandObjectThreadStepWithTypeAndScope (
Index: source/Interpreter/CommandInterpreter.cpp
===================================================================
--- source/Interpreter/CommandInterpreter.cpp
+++ source/Interpreter/CommandInterpreter.cpp
@@ -230,6 +230,13 @@
AddAlias ("t", cmd_obj_sp);
}
+ cmd_obj_sp = GetCommandSPExact ("_regexp-jump",false);
+ if (cmd_obj_sp)
+ {
+ AddAlias ("j", cmd_obj_sp);
+ AddAlias ("jump", cmd_obj_sp);
+ }
+
cmd_obj_sp = GetCommandSPExact ("_regexp-list", false);
if (cmd_obj_sp)
{
@@ -620,6 +627,26 @@
}
}
+ std::unique_ptr<CommandObjectRegexCommand>
+ jump_regex_cmd_ap(new CommandObjectRegexCommand (*this,
+ "_regexp-jump",
+ "Sets the program counter to a new address.",
+ "_regexp-jump [<line>]\n"
+ "_regexp-jump [<+-lineoffset>]\n"
+ "_regexp-jump [<file>:<line>]\n"
+ "_regexp-jump [*<addr>]\n", 2));
+ if (jump_regex_cmd_ap.get())
+ {
+ if (jump_regex_cmd_ap->AddRegexCommand("^\\*(.*)$", "thread jump --addr %1") &&
+ jump_regex_cmd_ap->AddRegexCommand("^([0-9]+)$", "thread jump --line %1") &&
+ jump_regex_cmd_ap->AddRegexCommand("^([^:]+):([0-9]+)$", "thread jump --file %1 --line %2") &&
+ jump_regex_cmd_ap->AddRegexCommand("^([+\\-][0-9]+)$", "thread jump --by %1"))
+ {
+ CommandObjectSP jump_regex_cmd_sp(jump_regex_cmd_ap.release());
+ m_command_dict[jump_regex_cmd_sp->GetCommandName ()] = jump_regex_cmd_sp;
+ }
+ }
+
}
int
Index: test/functionalities/thread/jump/Makefile
===================================================================
--- /dev/null
+++ test/functionalities/thread/jump/Makefile
@@ -0,0 +1,4 @@
+LEVEL = ../../../make
+
+CXX_SOURCES := main.cpp other.cpp
+include $(LEVEL)/Makefile.rules
Index: test/functionalities/thread/jump/TestThreadJump.py
===================================================================
--- /dev/null
+++ test/functionalities/thread/jump/TestThreadJump.py
@@ -0,0 +1,71 @@
+"""
+Test jumping to different places.
+"""
+
+import os, time
+import unittest2
+import lldb
+from lldbtest import *
+import lldbutil
+
+class ThreadJumpTestCase(TestBase):
+
+ mydir = os.path.join("functionalities", "thread", "jump")
+
+ @unittest2.skipUnless(sys.platform.startswith("darwin"), "requires Darwin")
+ @dsym_test
+ def test_with_dsym(self):
+ """Test thread jump handling."""
+ self.buildDsym(dictionary=self.getBuildFlags())
+ self.thread_jump_test()
+
+ @dwarf_test
+ def test_with_dwarf(self):
+ """Test thread jump handling."""
+ self.buildDwarf(dictionary=self.getBuildFlags())
+ self.thread_jump_test()
+
+ def do_min_test(self, start, jump, var, value):
+ self.runCmd("j %i" % start) # jump to the start marker
+ self.runCmd("thread step-in") # step into the min fn
+ self.runCmd("j %i" % jump) # jump to the branch we're interested in
+ self.runCmd("thread step-out") # return out
+ self.runCmd("thread step-over") # assign to the global
+ self.expect("expr %s" % var, substrs = [value]) # check it
+
+ def thread_jump_test(self):
+ """Test thread exit handling."""
+ exe = os.path.join(os.getcwd(), "a.out")
+ self.runCmd("file " + exe, CURRENT_EXECUTABLE_SET)
+
+ # Find the line numbers for our breakpoints.
+ self.mark1 = line_number('main.cpp', '// 1st marker')
+ self.mark2 = line_number('main.cpp', '// 2nd marker')
+ self.mark3 = line_number('main.cpp', '// 3rd marker')
+ self.mark4 = line_number('main.cpp', '// 4th marker')
+ self.mark5 = line_number('other.cpp', '// other marker')
+
+ lldbutil.run_break_set_by_file_and_line (self, "main.cpp", self.mark3, num_expected_locations=1)
+ self.runCmd("run", RUN_SUCCEEDED)
+
+ # The stop reason of the thread should be breakpoint 1.
+ self.expect("thread list", STOPPED_DUE_TO_BREAKPOINT + " 1",
+ substrs = ['stopped',
+ '* thread #1',
+ 'stop reason = breakpoint 1'])
+
+ self.do_min_test(self.mark3, self.mark1, "i", "4"); # Try the int path, force it to return 'a'
+ self.do_min_test(self.mark3, self.mark2, "i", "5"); # Try the int path, force it to return 'b'
+ self.do_min_test(self.mark4, self.mark1, "j", "7"); # Try the double path, force it to return 'a'
+ self.do_min_test(self.mark4, self.mark2, "j", "8"); # Try the double path, force it to return 'b'
+
+ # Try jumping to another function in a different file.
+ self.runCmd("j other.cpp:%i" % self.mark5)
+ self.expect("process status",
+ substrs = ["at other.cpp:%i" % self.mark5])
+
+if __name__ == '__main__':
+ import atexit
+ lldb.SBDebugger.Initialize()
+ atexit.register(lambda: lldb.SBDebugger.Terminate())
+ unittest2.main()
Index: test/functionalities/thread/jump/main.cpp
===================================================================
--- /dev/null
+++ test/functionalities/thread/jump/main.cpp
@@ -0,0 +1,34 @@
+//===-- main.cpp ------------------------------------------------*- C++ -*-===//
+//
+// The LLVM Compiler Infrastructure
+//
+// This file is distributed under the University of Illinois Open Source
+// License. See LICENSE.TXT for details.
+//
+//===----------------------------------------------------------------------===//
+
+// This test verifies the correct handling of program counter jumps.
+
+int otherfn();
+
+template<typename T>
+T min(T a, T b)
+{
+ if (a < b)
+ {
+ return a; // 1st marker
+ } else {
+ return b; // 2nd marker
+ }
+}
+
+int main ()
+{
+ int i;
+ double j;
+
+ i = min(4, 5); // 3rd marker
+ j = min(7.0, 8.0); // 4th marker
+
+ return 0;
+}
Index: test/functionalities/thread/jump/other.cpp
===================================================================
--- /dev/null
+++ test/functionalities/thread/jump/other.cpp
@@ -0,0 +1,13 @@
+//===-- other.cpp -----------------------------------------------*- C++ -*-===//
+//
+// The LLVM Compiler Infrastructure
+//
+// This file is distributed under the University of Illinois Open Source
+// License. See LICENSE.TXT for details.
+//
+//===----------------------------------------------------------------------===//
+
+int otherfn()
+{
+ return 4; // other marker
+}
_______________________________________________
lldb-commits mailing list
[email protected]
http://lists.cs.uiuc.edu/mailman/listinfo/lldb-commits