Skip to content

Commit dcb3f6b

Browse files
committed
Make stop-hooks fire when lldb first gains control of a process. (llvm#137410)
stop-hooks are supposed to trigger every time the process stops, but as initially implemented they would only fire when control was returned to the user. So for instance when a process was launched the stop hook would only trigger when the process hit a breakpoint or crashed. However, it would be really useful to be able to trigger a stop hook when lldb first gains control over the process. One way to do that would be to implement general "target lifecycle events" and then send process created events that users could bind actions to. OTOH, extending the stop hooks to fire when lldb first gains control over the process is a pretty natural extension to the notion of a stop hook. So this patch takes the shorter route to that ability by making stop-hooks fire when lldb first gains control over the process. I also added the ability to specify whether to trigger the stop hook "on gaining control". I'm on the fence about whether to set the default to be "trigger on gaining control" or "don't trigger on gaining control". Since I think it's a generally useful feature, I've set the default to "trigger on gaining control". (cherry picked from commit 4fdb8cb)
1 parent af09a65 commit dcb3f6b

File tree

12 files changed

+1223
-18
lines changed

12 files changed

+1223
-18
lines changed

lldb/include/lldb/Target/Target.h

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1458,6 +1458,12 @@ class Target : public std::enable_shared_from_this<Target>,
14581458

14591459
bool GetAutoContinue() const { return m_auto_continue; }
14601460

1461+
void SetRunAtInitialStop(bool at_initial_stop) {
1462+
m_at_initial_stop = at_initial_stop;
1463+
}
1464+
1465+
bool GetRunAtInitialStop() const { return m_at_initial_stop; }
1466+
14611467
void GetDescription(Stream &s, lldb::DescriptionLevel level) const;
14621468
virtual void GetSubclassDescription(Stream &s,
14631469
lldb::DescriptionLevel level) const = 0;
@@ -1468,6 +1474,7 @@ class Target : public std::enable_shared_from_this<Target>,
14681474
std::unique_ptr<ThreadSpec> m_thread_spec_up;
14691475
bool m_active = true;
14701476
bool m_auto_continue = false;
1477+
bool m_at_initial_stop = true;
14711478

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

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

15401549
size_t GetStopHookSize();
15411550

lldb/source/Commands/CommandObjectTarget.cpp

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4807,6 +4807,17 @@ class CommandObjectTargetStopHookAdd : public CommandObjectParsed,
48074807
m_one_liner.push_back(std::string(option_arg));
48084808
break;
48094809

4810+
case 'I': {
4811+
bool value, success;
4812+
value = OptionArgParser::ToBoolean(option_arg, false, &success);
4813+
if (success)
4814+
m_at_initial_stop = value;
4815+
else
4816+
error = Status::FromErrorStringWithFormat(
4817+
"invalid boolean value '%s' passed for -F option",
4818+
option_arg.str().c_str());
4819+
} break;
4820+
48104821
default:
48114822
llvm_unreachable("Unimplemented option");
48124823
}
@@ -4833,6 +4844,7 @@ class CommandObjectTargetStopHookAdd : public CommandObjectParsed,
48334844
m_use_one_liner = false;
48344845
m_one_liner.clear();
48354846
m_auto_continue = false;
4847+
m_at_initial_stop = true;
48364848
}
48374849

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

48574870
bool m_auto_continue = false;
48584871
};
@@ -5021,6 +5034,9 @@ Filter Options:
50215034
if (specifier_up)
50225035
new_hook_sp->SetSpecifier(specifier_up.release());
50235036

5037+
// Should we run at the initial stop:
5038+
new_hook_sp->SetRunAtInitialStop(m_options.m_at_initial_stop);
5039+
50245040
// Next see if any of the thread options have been entered:
50255041

50265042
if (m_options.m_thread_specified) {

lldb/source/Commands/Options.td

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1053,8 +1053,14 @@ let Command = "target stop hook add" in {
10531053
Arg<"FunctionName">, Desc<"Set the function name within which the stop hook"
10541054
" will be run.">, Completion<"Symbol">;
10551055
def target_stop_hook_add_auto_continue : Option<"auto-continue", "G">,
1056-
Arg<"Boolean">, Desc<"The breakpoint will auto-continue after running its"
1056+
Arg<"Boolean">, Desc<"The stop-hook will auto-continue after running its"
10571057
" commands.">;
1058+
def target_stop_hook_add_at_initial_stop : Option<"at-initial-stop", "I">,
1059+
Arg<"Boolean">, Desc<"Whether the stop-hook will trigger when lldb "
1060+
"initially gains control of the process. For a process launch, this "
1061+
"initial stop may happen very early on - before the loader has run. You "
1062+
"can use this option if you do not want some stop-hooks to run then. "
1063+
"Defaults to true.">;
10581064
}
10591065

10601066
let Command = "thread backtrace" in {

lldb/source/Target/Process.cpp

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2929,6 +2929,9 @@ Status Process::LoadCore() {
29292929
"Did not get stopped event after loading the core file.");
29302930
}
29312931
RestoreProcessEvents();
2932+
// Since we hijacked the event stream, we will have we won't have run the
2933+
// stop hooks. Make sure we do that here:
2934+
GetTarget().RunStopHooks(/* at_initial_stop= */ true);
29322935
}
29332936
return error;
29342937
}
@@ -3299,6 +3302,9 @@ void Process::CompleteAttach() {
32993302
: "<none>");
33003303
}
33013304
}
3305+
// Since we hijacked the event stream, we will have we won't have run the
3306+
// stop hooks. Make sure we do that here:
3307+
GetTarget().RunStopHooks(/* at_initial_stop= */ true);
33023308
}
33033309

33043310
Status Process::ConnectRemote(llvm::StringRef remote_url) {

lldb/source/Target/Target.cpp

Lines changed: 35 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -3027,7 +3027,7 @@ void Target::SetAllStopHooksActiveState(bool active_state) {
30273027
}
30283028
}
30293029

3030-
bool Target::RunStopHooks() {
3030+
bool Target::RunStopHooks(bool at_initial_stop) {
30313031
if (m_suppress_stop_hooks)
30323032
return false;
30333033

@@ -3036,21 +3036,20 @@ bool Target::RunStopHooks() {
30363036

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

30423043
if (m_stop_hooks.empty())
30433044
return false;
30443045

3045-
// If there aren't any active stop hooks, don't bother either.
3046-
bool any_active_hooks = false;
3047-
for (auto hook : m_stop_hooks) {
3048-
if (hook.second->IsActive()) {
3049-
any_active_hooks = true;
3050-
break;
3051-
}
3052-
}
3053-
if (!any_active_hooks)
3046+
bool no_active_hooks =
3047+
llvm::none_of(m_stop_hooks, [at_initial_stop](auto &p) {
3048+
bool should_run_now =
3049+
!at_initial_stop || p.second->GetRunAtInitialStop();
3050+
return p.second->IsActive() && should_run_now;
3051+
});
3052+
if (no_active_hooks)
30543053
return false;
30553054

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

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

30863098
StreamSP output_sp = m_debugger.GetAsyncOutputStream();
30873099

@@ -3096,6 +3108,8 @@ bool Target::RunStopHooks() {
30963108
StopHookSP cur_hook_sp = stop_entry.second;
30973109
if (!cur_hook_sp->IsActive())
30983110
continue;
3111+
if (at_initial_stop && !cur_hook_sp->GetRunAtInitialStop())
3112+
continue;
30993113

31003114
bool any_thread_matched = false;
31013115
for (auto exc_ctx : exc_ctx_with_reasons) {
@@ -3472,10 +3486,14 @@ Status Target::Launch(ProcessLaunchInfo &launch_info, Stream *stream) {
34723486
m_process_sp->RestoreProcessEvents();
34733487

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

34803498
switch (state) {
34813499
case eStateStopped: {
@@ -3628,6 +3646,10 @@ Status Target::Attach(ProcessAttachInfo &attach_info, Stream *stream) {
36283646
true, SelectMostRelevantFrame);
36293647
process_sp->RestoreProcessEvents();
36303648

3649+
// Run the stop hooks here. Since we were hijacking the events, they
3650+
// wouldn't have gotten run as part of event delivery.
3651+
RunStopHooks(/* at_initial_stop= */ true);
3652+
36313653
if (state != eStateStopped) {
36323654
const char *exit_desc = process_sp->GetExitDescription();
36333655
if (exit_desc)

lldb/test/API/commands/target/stop-hooks/TestStopHookScripted.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,11 @@ def test_bad_handler(self):
5050

5151
def test_stop_hooks_scripted(self):
5252
"""Test that a scripted stop hook works with no specifiers"""
53-
self.stop_hooks_scripted(5)
53+
self.stop_hooks_scripted(5, "-I false")
54+
55+
def test_stop_hooks_scripted_no_entry(self):
56+
"""Test that a scripted stop hook works with no specifiers"""
57+
self.stop_hooks_scripted(10)
5458

5559
def test_stop_hooks_scripted_right_func(self):
5660
"""Test that a scripted stop hook fires when there is a function match"""

lldb/test/API/commands/target/stop-hooks/TestStopHooks.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ def step_out_test(self):
4747
def after_expr_test(self):
4848
interp = self.dbg.GetCommandInterpreter()
4949
result = lldb.SBCommandReturnObject()
50-
interp.HandleCommand("target stop-hook add -o 'expr g_var++'", result)
50+
interp.HandleCommand("target stop-hook add -o 'expr g_var++' -I false", result)
5151
self.assertTrue(result.Succeeded(), "Set the target stop hook")
5252

5353
(target, process, thread, first_bkpt) = lldbutil.run_to_source_breakpoint(
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
"""
2+
Test that stop hooks fire on core load (first stop)
3+
"""
4+
5+
6+
import lldb
7+
import os
8+
from lldbsuite.test.decorators import *
9+
from lldbsuite.test.lldbtest import *
10+
from lldbsuite.test import lldbutil
11+
12+
13+
class TestStopOnCoreLoad(TestBase):
14+
NO_DEBUG_INFO_TESTCASE = True
15+
16+
# This was originally marked as expected failure on Windows, but it has
17+
# started timing out instead, so the expectedFailure attribute no longer
18+
# correctly tracks it: llvm.org/pr37371
19+
@skipIfWindows
20+
def test_hook_runs_no_threads(self):
21+
# Create core form YAML.
22+
core_path = self.getBuildArtifact("test.core")
23+
self.yaml2obj("test.core.yaml", core_path)
24+
25+
# Since mach core files don't have stop reasons, we should choose
26+
# the first thread:
27+
self.do_test(core_path, 1)
28+
29+
def test_hook_one_thread(self):
30+
core_path = os.path.join(self.getSourceDir(), "linux-x86_64.core")
31+
self.do_test(core_path, 3)
32+
33+
def do_test(self, core_path, stop_thread):
34+
# Set debugger into synchronous mode
35+
self.dbg.SetAsync(False)
36+
37+
# Create a target by the debugger.
38+
target = self.dbg.CreateTarget("")
39+
40+
# load the stop hook module and add the stop hook:
41+
stop_hook_path = os.path.join(self.getSourceDir(), "stop_hook.py")
42+
self.runCmd(f"command script import {stop_hook_path}")
43+
self.runCmd("target stop-hook add -P stop_hook.stop_handler")
44+
45+
# Load core.
46+
process = target.LoadCore(core_path)
47+
self.assertTrue(process, PROCESS_IS_VALID)
48+
# Now run our report command and make sure we get the right answer.
49+
50+
result = lldb.SBCommandReturnObject()
51+
self.dbg.GetCommandInterpreter().HandleCommand("report_command", result)
52+
print(f"Command Output: '{result.GetOutput}'")
53+
self.assertIn(
54+
f"Stop Threads: {stop_thread}", result.GetOutput(), "Ran the stop hook"
55+
)
Binary file not shown.
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
import lldb
2+
3+
4+
def report_command(debugger, command, exe_ctx, result, internal_dict):
5+
global stop_thread
6+
print(f"About to report out stop_thread: {stop_thread}")
7+
mssg = f"Stop Threads: {stop_thread}"
8+
result.AppendMessage(mssg)
9+
10+
result.SetStatus(lldb.eReturnStatusSuccessFinishResult)
11+
12+
13+
class stop_handler:
14+
def __init__(self, target, extra_args, dict):
15+
global stop_thread
16+
stop_thead = 0
17+
self.target = target
18+
19+
def handle_stop(self, exe_ctx, stream):
20+
global stop_thread
21+
thread = exe_ctx.thread
22+
stop_thread = thread.idx
23+
24+
25+
def __lldb_init_module(debugger, internal_dict):
26+
global stop_thread
27+
stop_thread = 0
28+
debugger.HandleCommand(
29+
f"command script add -o -f '{__name__}.report_command' report_command"
30+
)

0 commit comments

Comments
 (0)