Skip to content

Stop hooks initial stop #10608

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 2 commits into
base: swift/release/6.2
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 10 additions & 1 deletion lldb/include/lldb/Target/Target.h
Original file line number Diff line number Diff line change
Expand Up @@ -1458,6 +1458,12 @@ class Target : public std::enable_shared_from_this<Target>,

bool GetAutoContinue() const { return m_auto_continue; }

void SetRunAtInitialStop(bool at_initial_stop) {
m_at_initial_stop = at_initial_stop;
}

bool GetRunAtInitialStop() const { return m_at_initial_stop; }

void GetDescription(Stream &s, lldb::DescriptionLevel level) const;
virtual void GetSubclassDescription(Stream &s,
lldb::DescriptionLevel level) const = 0;
Expand All @@ -1468,6 +1474,7 @@ class Target : public std::enable_shared_from_this<Target>,
std::unique_ptr<ThreadSpec> m_thread_spec_up;
bool m_active = true;
bool m_auto_continue = false;
bool m_at_initial_stop = true;

StopHook(lldb::TargetSP target_sp, lldb::user_id_t uid);
};
Expand Down Expand Up @@ -1535,7 +1542,9 @@ class Target : public std::enable_shared_from_this<Target>,

// Runs the stop hooks that have been registered for this target.
// Returns true if the stop hooks cause the target to resume.
bool RunStopHooks();
// Pass at_initial_stop if this is the stop where lldb gains
// control over the process for the first time.
bool RunStopHooks(bool at_initial_stop = false);

size_t GetStopHookSize();

Expand Down
16 changes: 16 additions & 0 deletions lldb/source/Commands/CommandObjectTarget.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4807,6 +4807,17 @@ class CommandObjectTargetStopHookAdd : public CommandObjectParsed,
m_one_liner.push_back(std::string(option_arg));
break;

case 'I': {
bool value, success;
value = OptionArgParser::ToBoolean(option_arg, false, &success);
if (success)
m_at_initial_stop = value;
else
error = Status::FromErrorStringWithFormat(
"invalid boolean value '%s' passed for -F option",
option_arg.str().c_str());
} break;

default:
llvm_unreachable("Unimplemented option");
}
Expand All @@ -4833,6 +4844,7 @@ class CommandObjectTargetStopHookAdd : public CommandObjectParsed,
m_use_one_liner = false;
m_one_liner.clear();
m_auto_continue = false;
m_at_initial_stop = true;
}

std::string m_class_name;
Expand All @@ -4853,6 +4865,7 @@ class CommandObjectTargetStopHookAdd : public CommandObjectParsed,
// Instance variables to hold the values for one_liner options.
bool m_use_one_liner = false;
std::vector<std::string> m_one_liner;
bool m_at_initial_stop;

bool m_auto_continue = false;
};
Expand Down Expand Up @@ -5021,6 +5034,9 @@ Filter Options:
if (specifier_up)
new_hook_sp->SetSpecifier(specifier_up.release());

// Should we run at the initial stop:
new_hook_sp->SetRunAtInitialStop(m_options.m_at_initial_stop);

// Next see if any of the thread options have been entered:

if (m_options.m_thread_specified) {
Expand Down
8 changes: 7 additions & 1 deletion lldb/source/Commands/Options.td
Original file line number Diff line number Diff line change
Expand Up @@ -1053,8 +1053,14 @@ let Command = "target stop hook add" in {
Arg<"FunctionName">, Desc<"Set the function name within which the stop hook"
" will be run.">, Completion<"Symbol">;
def target_stop_hook_add_auto_continue : Option<"auto-continue", "G">,
Arg<"Boolean">, Desc<"The breakpoint will auto-continue after running its"
Arg<"Boolean">, Desc<"The stop-hook will auto-continue after running its"
" commands.">;
def target_stop_hook_add_at_initial_stop : Option<"at-initial-stop", "I">,
Arg<"Boolean">, Desc<"Whether the stop-hook will trigger when lldb "
"initially gains control of the process. For a process launch, this "
"initial stop may happen very early on - before the loader has run. You "
"can use this option if you do not want some stop-hooks to run then. "
"Defaults to true.">;
}

let Command = "thread backtrace" in {
Expand Down
6 changes: 6 additions & 0 deletions lldb/source/Target/Process.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2929,6 +2929,9 @@ Status Process::LoadCore() {
"Did not get stopped event after loading the core file.");
}
RestoreProcessEvents();
// Since we hijacked the event stream, we will have we won't have run the
// stop hooks. Make sure we do that here:
GetTarget().RunStopHooks(/* at_initial_stop= */ true);
}
return error;
}
Expand Down Expand Up @@ -3299,6 +3302,9 @@ void Process::CompleteAttach() {
: "<none>");
}
}
// Since we hijacked the event stream, we will have we won't have run the
// stop hooks. Make sure we do that here:
GetTarget().RunStopHooks(/* at_initial_stop= */ true);
}

Status Process::ConnectRemote(llvm::StringRef remote_url) {
Expand Down
49 changes: 36 additions & 13 deletions lldb/source/Target/Target.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3027,7 +3027,7 @@ void Target::SetAllStopHooksActiveState(bool active_state) {
}
}

bool Target::RunStopHooks() {
bool Target::RunStopHooks(bool at_initial_stop) {
if (m_suppress_stop_hooks)
return false;

Expand All @@ -3036,21 +3036,21 @@ bool Target::RunStopHooks() {

// Somebody might have restarted the process:
// Still return false, the return value is about US restarting the target.
if (m_process_sp->GetState() != eStateStopped)
lldb::StateType state = m_process_sp->GetState();
if (!(state == eStateStopped || state == eStateAttaching))
return false;

if (m_stop_hooks.empty())
return false;

// If there aren't any active stop hooks, don't bother either.
bool any_active_hooks = false;
for (auto hook : m_stop_hooks) {
if (hook.second->IsActive()) {
any_active_hooks = true;
break;
}
}
if (!any_active_hooks)

bool no_active_hooks =
llvm::none_of(m_stop_hooks, [at_initial_stop](auto &p) {
bool should_run_now =
!at_initial_stop || p.second->GetRunAtInitialStop();
return p.second->IsActive() && should_run_now;
});
if (no_active_hooks)
return false;

// Make sure we check that we are not stopped because of us running a user
Expand Down Expand Up @@ -3079,9 +3079,22 @@ bool Target::RunStopHooks() {
}

// If no threads stopped for a reason, don't run the stop-hooks.
// However, if this is the FIRST stop for this process, then we are in the
// state where an attach or a core file load was completed without designating
// a particular thread as responsible for the stop. In that case, we do
// want to run the stop hooks, but do so just on one thread.
size_t num_exe_ctx = exc_ctx_with_reasons.size();
if (num_exe_ctx == 0)
return false;
if (num_exe_ctx == 0) {
if (at_initial_stop && num_threads > 0) {
lldb::ThreadSP thread_to_use_sp = cur_threadlist.GetThreadAtIndex(0);
exc_ctx_with_reasons.emplace_back(
m_process_sp.get(), thread_to_use_sp.get(),
thread_to_use_sp->GetStackFrameAtIndex(0).get());
num_exe_ctx = 1;
} else {
return false;
}
}

StreamSP output_sp = m_debugger.GetAsyncOutputStream();

Expand All @@ -3096,6 +3109,8 @@ bool Target::RunStopHooks() {
StopHookSP cur_hook_sp = stop_entry.second;
if (!cur_hook_sp->IsActive())
continue;
if (at_initial_stop && !cur_hook_sp->GetRunAtInitialStop())
continue;

bool any_thread_matched = false;
for (auto exc_ctx : exc_ctx_with_reasons) {
Expand Down Expand Up @@ -3472,10 +3487,14 @@ Status Target::Launch(ProcessLaunchInfo &launch_info, Stream *stream) {
m_process_sp->RestoreProcessEvents();

if (rebroadcast_first_stop) {
// We don't need to run the stop hooks by hand here, they will get
// triggered when this rebroadcast event gets fetched.
assert(first_stop_event_sp);
m_process_sp->BroadcastEvent(first_stop_event_sp);
return error;
}
// Run the stop hooks that want to run at entry.
RunStopHooks(true /* at entry point */);

switch (state) {
case eStateStopped: {
Expand Down Expand Up @@ -3628,6 +3647,10 @@ Status Target::Attach(ProcessAttachInfo &attach_info, Stream *stream) {
true, SelectMostRelevantFrame);
process_sp->RestoreProcessEvents();

// Run the stop hooks here. Since we were hijacking the events, they
// wouldn't have gotten run as part of event delivery.
RunStopHooks(/* at_initial_stop= */ true);

if (state != eStateStopped) {
const char *exit_desc = process_sp->GetExitDescription();
if (exit_desc)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,11 @@ def test_bad_handler(self):

def test_stop_hooks_scripted(self):
"""Test that a scripted stop hook works with no specifiers"""
self.stop_hooks_scripted(5)
self.stop_hooks_scripted(5, "-I false")

def test_stop_hooks_scripted_no_entry(self):
"""Test that a scripted stop hook works with no specifiers"""
self.stop_hooks_scripted(10)

def test_stop_hooks_scripted_right_func(self):
"""Test that a scripted stop hook fires when there is a function match"""
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ def step_out_test(self):
def after_expr_test(self):
interp = self.dbg.GetCommandInterpreter()
result = lldb.SBCommandReturnObject()
interp.HandleCommand("target stop-hook add -o 'expr g_var++'", result)
interp.HandleCommand("target stop-hook add -o 'expr g_var++' -I false", result)
self.assertTrue(result.Succeeded(), "Set the target stop hook")

(target, process, thread, first_bkpt) = lldbutil.run_to_source_breakpoint(
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
"""
Test that stop hooks fire on core load (first stop)
"""


import lldb
import os
from lldbsuite.test.decorators import *
from lldbsuite.test.lldbtest import *
from lldbsuite.test import lldbutil


class TestStopOnCoreLoad(TestBase):
NO_DEBUG_INFO_TESTCASE = True

# This was originally marked as expected failure on Windows, but it has
# started timing out instead, so the expectedFailure attribute no longer
# correctly tracks it: llvm.org/pr37371
@skipIfWindows
def test_hook_runs_no_threads(self):
# Create core form YAML.
core_path = self.getBuildArtifact("test.core")
self.yaml2obj("test.core.yaml", core_path)

# Since mach core files don't have stop reasons, we should choose
# the first thread:
self.do_test(core_path, 1)

def test_hook_one_thread(self):
core_path = os.path.join(self.getSourceDir(), "linux-x86_64.core")
self.do_test(core_path, 3)

def do_test(self, core_path, stop_thread):
# Set debugger into synchronous mode
self.dbg.SetAsync(False)

# Create a target by the debugger.
target = self.dbg.CreateTarget("")

# load the stop hook module and add the stop hook:
stop_hook_path = os.path.join(self.getSourceDir(), "stop_hook.py")
self.runCmd(f"command script import {stop_hook_path}")
self.runCmd("target stop-hook add -P stop_hook.stop_handler")

# Load core.
process = target.LoadCore(core_path)
self.assertTrue(process, PROCESS_IS_VALID)
# Now run our report command and make sure we get the right answer.

result = lldb.SBCommandReturnObject()
self.dbg.GetCommandInterpreter().HandleCommand("report_command", result)
print(f"Command Output: '{result.GetOutput}'")
self.assertIn(
f"Stop Threads: {stop_thread}", result.GetOutput(), "Ran the stop hook"
)
Binary file not shown.
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import lldb


def report_command(debugger, command, exe_ctx, result, internal_dict):
global stop_thread
print(f"About to report out stop_thread: {stop_thread}")
mssg = f"Stop Threads: {stop_thread}"
result.AppendMessage(mssg)

result.SetStatus(lldb.eReturnStatusSuccessFinishResult)


class stop_handler:
def __init__(self, target, extra_args, dict):
global stop_thread
stop_thead = 0
self.target = target

def handle_stop(self, exe_ctx, stream):
global stop_thread
thread = exe_ctx.thread
stop_thread = thread.idx


def __lldb_init_module(debugger, internal_dict):
global stop_thread
stop_thread = 0
debugger.HandleCommand(
f"command script add -o -f '{__name__}.report_command' report_command"
)
Loading