Skip to content

Commit 8a5e25a

Browse files
committed
Make stop-hooks fire when lldb first gains control of a process.
1 parent d4898b5 commit 8a5e25a

File tree

12 files changed

+1218
-10
lines changed

12 files changed

+1218
-10
lines changed

lldb/include/lldb/Target/Target.h

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

13711371
bool GetAutoContinue() const { return m_auto_continue; }
1372+
1373+
void SetRunAtFirstStop(bool at_first_stop) {
1374+
m_at_first_stop = at_first_stop;
1375+
}
1376+
1377+
bool GetRunAtFirstStop() const { return m_at_first_stop; }
13721378

13731379
void GetDescription(Stream &s, lldb::DescriptionLevel level) const;
13741380
virtual void GetSubclassDescription(Stream &s,
@@ -1380,6 +1386,7 @@ class Target : public std::enable_shared_from_this<Target>,
13801386
std::unique_ptr<ThreadSpec> m_thread_spec_up;
13811387
bool m_active = true;
13821388
bool m_auto_continue = false;
1389+
bool m_at_first_stop = true;
13831390

13841391
StopHook(lldb::TargetSP target_sp, lldb::user_id_t uid);
13851392
};
@@ -1446,7 +1453,9 @@ class Target : public std::enable_shared_from_this<Target>,
14461453

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

14511460
size_t GetStopHookSize();
14521461

lldb/source/Commands/CommandObjectTarget.cpp

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4795,6 +4795,17 @@ class CommandObjectTargetStopHookAdd : public CommandObjectParsed,
47954795
m_use_one_liner = true;
47964796
m_one_liner.push_back(std::string(option_arg));
47974797
break;
4798+
4799+
case 'F': {
4800+
bool value, success;
4801+
value = OptionArgParser::ToBoolean(option_arg, false, &success);
4802+
if (success) {
4803+
m_at_first_stop = value;
4804+
} else
4805+
error = Status::FromErrorStringWithFormat(
4806+
"invalid boolean value '%s' passed for -F option",
4807+
option_arg.str().c_str());
4808+
} break;
47984809

47994810
default:
48004811
llvm_unreachable("Unimplemented option");
@@ -4822,6 +4833,7 @@ class CommandObjectTargetStopHookAdd : public CommandObjectParsed,
48224833
m_use_one_liner = false;
48234834
m_one_liner.clear();
48244835
m_auto_continue = false;
4836+
m_at_first_stop = true;
48254837
}
48264838

48274839
std::string m_class_name;
@@ -4842,6 +4854,7 @@ class CommandObjectTargetStopHookAdd : public CommandObjectParsed,
48424854
// Instance variables to hold the values for one_liner options.
48434855
bool m_use_one_liner = false;
48444856
std::vector<std::string> m_one_liner;
4857+
bool m_at_first_stop;
48454858

48464859
bool m_auto_continue = false;
48474860
};
@@ -5006,6 +5019,9 @@ Filter Options:
50065019

50075020
if (specifier_up)
50085021
new_hook_sp->SetSpecifier(specifier_up.release());
5022+
5023+
// Should we run at first stop:
5024+
new_hook_sp->SetRunAtFirstStop(m_options.m_at_first_stop);
50095025

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

lldb/source/Commands/Options.td

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1050,8 +1050,13 @@ let Command = "target stop hook add" in {
10501050
Arg<"FunctionName">, Desc<"Set the function name within which the stop hook"
10511051
" will be run.">, Completion<"Symbol">;
10521052
def target_stop_hook_add_auto_continue : Option<"auto-continue", "G">,
1053-
Arg<"Boolean">, Desc<"The breakpoint will auto-continue after running its"
1053+
Arg<"Boolean">, Desc<"The stop-hook will auto-continue after running its"
10541054
" commands.">;
1055+
def target_stop_hook_add_at_first_stop : Option<"at-first-stop", "F">,
1056+
Arg<"Boolean">, Desc<"Whether the stop-hook will trigger when lldb first "
1057+
"gains control of the process. For a process launch, this first stop "
1058+
"may happen very early on - before the loader has run. You might not want "
1059+
"some stop-hooks to run then. Defaults to true.">;
10551060
}
10561061

10571062
let Command = "thread backtrace" in {

lldb/source/Target/Process.cpp

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2830,6 +2830,9 @@ Status Process::LoadCore() {
28302830
"Did not get stopped event after loading the core file.");
28312831
}
28322832
RestoreProcessEvents();
2833+
// Since we hijacked the event stream, we will have we won't have run the
2834+
// stop hooks. Make sure we do that here:
2835+
GetTarget().RunStopHooks(true /* at_initial_stop */);
28332836
}
28342837
return error;
28352838
}
@@ -3200,6 +3203,9 @@ void Process::CompleteAttach() {
32003203
: "<none>");
32013204
}
32023205
}
3206+
// Since we hijacked the event stream, we will have we won't have run the
3207+
// stop hooks. Make sure we do that here:
3208+
GetTarget().RunStopHooks(true /* at_initial_stop */);
32033209
}
32043210

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

lldb/source/Target/Target.cpp

Lines changed: 32 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3038,7 +3038,7 @@ void Target::SetAllStopHooksActiveState(bool active_state) {
30383038
}
30393039
}
30403040

3041-
bool Target::RunStopHooks() {
3041+
bool Target::RunStopHooks(bool at_initial_stop) {
30423042
if (m_suppress_stop_hooks)
30433043
return false;
30443044

@@ -3047,14 +3047,18 @@ bool Target::RunStopHooks() {
30473047

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

30533054
if (m_stop_hooks.empty())
30543055
return false;
30553056

30563057
bool no_active_hooks =
3057-
llvm::none_of(m_stop_hooks, [](auto &p) { return p.second->IsActive(); });
3058+
llvm::none_of(m_stop_hooks, [at_initial_stop](auto &p) {
3059+
bool should_run_now = !at_initial_stop || p.second->GetRunAtFirstStop();
3060+
return p.second->IsActive() && should_run_now;
3061+
});
30583062
if (no_active_hooks)
30593063
return false;
30603064

@@ -3084,9 +3088,22 @@ bool Target::RunStopHooks() {
30843088
}
30853089

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

30913108
StreamSP output_sp = m_debugger.GetAsyncOutputStream();
30923109
auto on_exit = llvm::make_scope_exit([output_sp] { output_sp->Flush(); });
@@ -3100,6 +3117,8 @@ bool Target::RunStopHooks() {
31003117
StopHookSP cur_hook_sp = stop_entry.second;
31013118
if (!cur_hook_sp->IsActive())
31023119
continue;
3120+
if (at_initial_stop && !cur_hook_sp->GetRunAtFirstStop())
3121+
continue;
31033122

31043123
bool any_thread_matched = false;
31053124
for (auto exc_ctx : exc_ctx_with_reasons) {
@@ -3426,10 +3445,14 @@ Status Target::Launch(ProcessLaunchInfo &launch_info, Stream *stream) {
34263445
m_process_sp->RestoreProcessEvents();
34273446

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

34343457
switch (state) {
34353458
case eStateStopped: {
@@ -3581,6 +3604,10 @@ Status Target::Attach(ProcessAttachInfo &attach_info, Stream *stream) {
35813604
std::nullopt, nullptr, false, attach_info.GetHijackListener(), stream,
35823605
true, SelectMostRelevantFrame);
35833606
process_sp->RestoreProcessEvents();
3607+
3608+
// Run the stop hooks here. Since we were hijacking the events, they
3609+
// wouldn't have gotten run as part of event delivery.
3610+
RunStopHooks(true /* at_initial_stop */);
35843611

35853612
if (state != eStateStopped) {
35863613
const char *exit_desc = process_sp->GetExitDescription();

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, "-F 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: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ def step_out_test(self):
5555
def after_expr_test(self):
5656
interp = self.dbg.GetCommandInterpreter()
5757
result = lldb.SBCommandReturnObject()
58-
interp.HandleCommand("target stop-hook add -o 'expr g_var++'", result)
58+
interp.HandleCommand("target stop-hook add -o 'expr g_var++' -F false", result)
5959
self.assertTrue(result.Succeeded(), "Set the target stop hook")
6060

6161
(target, process, thread, first_bkpt) = lldbutil.run_to_source_breakpoint(
@@ -109,3 +109,4 @@ def before_and_after_target(self):
109109
self.expect(
110110
"target stop-hook list", substrs=["expr g_var++", "thread backtrace"]
111111
)
112+
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+
34+
def do_test(self, core_path, stop_thread):
35+
# Set debugger into synchronous mode
36+
self.dbg.SetAsync(False)
37+
38+
# Create a target by the debugger.
39+
target = self.dbg.CreateTarget("")
40+
41+
# load the stop hook module and add the stop hook:
42+
stop_hook_path = os.path.join(self.getSourceDir(), "stop_hook.py")
43+
self.runCmd(f"command script import {stop_hook_path}")
44+
self.runCmd("target stop-hook add -P stop_hook.stop_handler")
45+
46+
# Load core.
47+
process = target.LoadCore(core_path)
48+
self.assertTrue(process, PROCESS_IS_VALID)
49+
# Now run our report command and make sure we get the right answer.
50+
51+
result = lldb.SBCommandReturnObject()
52+
self.dbg.GetCommandInterpreter().HandleCommand("report_command", result)
53+
print(f"Command Output: '{result.GetOutput}'")
54+
self.assertIn(f"Stop Threads: {stop_thread}", result.GetOutput(), "Ran the stop hook")
55+
Binary file not shown.
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
import lldb
2+
3+
def report_command(debugger, command, exe_ctx, result, internal_dict):
4+
global stop_thread
5+
print(f"About to report out stop_thread: {stop_thread}")
6+
mssg = f"Stop Threads: {stop_thread}"
7+
result.AppendMessage(mssg)
8+
9+
result.SetStatus(lldb.eReturnStatusSuccessFinishResult)
10+
11+
class stop_handler:
12+
def __init__(self, target, extra_args, dict):
13+
global stop_thread
14+
stop_thead = 0
15+
self.target = target
16+
17+
def handle_stop(self, exe_ctx, stream):
18+
global stop_thread
19+
thread = exe_ctx.thread
20+
stop_thread = thread.idx
21+
22+
def __lldb_init_module(debugger, internal_dict):
23+
global stop_thread
24+
stop_thread = 0
25+
debugger.HandleCommand(
26+
f"command script add -o -f '{__name__}.report_command' report_command"
27+
)
28+

0 commit comments

Comments
 (0)