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;nSetPC (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 + jump_regex_cmd_ap(new CommandObjectRegexCommand (*this, + "_regexp-jump", + "Sets the program counter to a new address.", + "_regexp-jump []\n" + "_regexp-jump [<+-lineoffset>]\n" + "_regexp-jump [:]\n" + "_regexp-jump [*]\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 +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 +}