Skip to content
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

Fix a bug with cancelling "attach -w" after you have run a process previously #65822

Merged
merged 2 commits into from
Sep 19, 2023
Merged
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
4 changes: 2 additions & 2 deletions lldb/include/lldb/Interpreter/CommandInterpreter.h
Original file line number Diff line number Diff line change
Expand Up @@ -447,7 +447,7 @@ class CommandInterpreter : public Broadcaster,

Debugger &GetDebugger() { return m_debugger; }

ExecutionContext GetExecutionContext() const;
ExecutionContext GetExecutionContext();

lldb::PlatformSP GetPlatform(bool prefer_target_platform);

Expand Down Expand Up @@ -661,7 +661,7 @@ class CommandInterpreter : public Broadcaster,

void GetProcessOutput();

bool DidProcessStopAbnormally() const;
bool DidProcessStopAbnormally();

void SetSynchronous(bool value);

Expand Down
33 changes: 25 additions & 8 deletions lldb/source/Interpreter/CommandInterpreter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2491,7 +2491,7 @@ PlatformSP CommandInterpreter::GetPlatform(bool prefer_target_platform) {
return platform_sp;
}

bool CommandInterpreter::DidProcessStopAbnormally() const {
bool CommandInterpreter::DidProcessStopAbnormally() {
auto exe_ctx = GetExecutionContext();
TargetSP target_sp = exe_ctx.GetTargetSP();
if (!target_sp)
Expand Down Expand Up @@ -2996,10 +2996,22 @@ void CommandInterpreter::FindCommandsForApropos(llvm::StringRef search_word,
m_alias_dict);
}

ExecutionContext CommandInterpreter::GetExecutionContext() const {
return !m_overriden_exe_contexts.empty()
? m_overriden_exe_contexts.top()
: m_debugger.GetSelectedExecutionContext();
ExecutionContext CommandInterpreter::GetExecutionContext() {
ExecutionContext exe_ctx;
if (!m_overriden_exe_contexts.empty()) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggestion/Bikeshedding: Flip the condition and you can reduce one level of indentation in this method. That's just my preference though, doesn't make a difference either way.

// During the course of a command, the target may have replaced the process
// coming in with another. I fix that here:
exe_ctx = m_overriden_exe_contexts.top();
// Don't use HasProcessScope, that returns false if there is a process but
// it's no longer valid, which is one of the cases we want to catch here.
if (exe_ctx.HasTargetScope() && exe_ctx.GetProcessPtr()) {
ProcessSP actual_proc_sp = exe_ctx.GetTargetSP()->GetProcessSP();
if (actual_proc_sp != exe_ctx.GetProcessSP())
m_overriden_exe_contexts.top().SetContext(actual_proc_sp);
}
return m_overriden_exe_contexts.top();
}
return m_debugger.GetSelectedExecutionContext();
}

void CommandInterpreter::OverrideExecutionContext(
Expand Down Expand Up @@ -3192,12 +3204,17 @@ void CommandInterpreter::IOHandlerInputComplete(IOHandler &io_handler,
}

bool CommandInterpreter::IOHandlerInterrupt(IOHandler &io_handler) {
// InterruptCommand returns true if this is the first time
// we initiate an interrupt for this command. So we give the
// command a chance to handle the interrupt on the first
// interrupt, but if that didn't do anything, a second
// interrupt will do more work to halt the process/interpreter.
if (InterruptCommand())
return true;

ExecutionContext exe_ctx(GetExecutionContext());
Process *process = exe_ctx.GetProcessPtr();

if (InterruptCommand())
return true;

if (process) {
StateType state = process->GetState();
if (StateIsRunningState(state)) {
Expand Down
17 changes: 8 additions & 9 deletions lldb/source/Target/Process.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3156,23 +3156,22 @@ Status Process::Halt(bool clear_thread_plans, bool use_run_lock) {
// case it was already set and some thread plan logic calls halt on its own.
m_clear_thread_plans_on_stop |= clear_thread_plans;

ListenerSP halt_listener_sp(
Listener::MakeListener("lldb.process.halt_listener"));
HijackProcessEvents(halt_listener_sp);

EventSP event_sp;

SendAsyncInterrupt();

if (m_public_state.GetValue() == eStateAttaching) {
// Don't hijack and eat the eStateExited as the code that was doing the
// attach will be waiting for this event...
RestoreProcessEvents();
jimingham marked this conversation as resolved.
Show resolved Hide resolved
SetExitStatus(SIGKILL, "Cancelled async attach.");
Destroy(false);
return Status();
}

ListenerSP halt_listener_sp(
Listener::MakeListener("lldb.process.halt_listener"));
HijackProcessEvents(halt_listener_sp);

EventSP event_sp;

SendAsyncInterrupt();

// Wait for the process halt timeout seconds for the process to stop.
// If we are going to use the run lock, that means we're stopping out to the
// user, so we should also select the most relevant frame.
Expand Down
63 changes: 63 additions & 0 deletions lldb/test/API/commands/process/attach/TestProcessAttach.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@


import os
import threading
import lldb
import shutil
from lldbsuite.test.decorators import *
Expand Down Expand Up @@ -127,3 +128,65 @@ def tearDown(self):

# Call super's tearDown().
TestBase.tearDown(self)

def test_run_then_attach_wait_interrupt(self):
# Test that having run one process doesn't cause us to be unable
# to interrupt a subsequent attach attempt.
self.build()
exe = self.getBuildArtifact(exe_name)

target = lldbutil.run_to_breakpoint_make_target(self, exe_name, True)
launch_info = target.GetLaunchInfo()
launch_info.SetArguments(["q"], True)
error = lldb.SBError()
target.Launch(launch_info, error)
self.assertSuccess(error, "Launched a process")
self.assertState(target.process.state, lldb.eStateExited, "and it exited.")

# Okay now we've run a process, try to attach/wait to something
# and make sure that we can interrupt that.

options = lldb.SBCommandInterpreterRunOptions()
options.SetPrintResults(True)
options.SetEchoCommands(False)

self.stdin_path = self.getBuildArtifact("stdin.txt")

with open(self.stdin_path, "w") as input_handle:
input_handle.write("process attach -w -n noone_would_use_this_name\nquit")

# Python will close the file descriptor if all references
# to the filehandle object lapse, so we need to keep one
# around.
self.filehandle = open(self.stdin_path, "r")
self.dbg.SetInputFileHandle(self.filehandle, False)

# No need to track the output
self.stdout_path = self.getBuildArtifact("stdout.txt")
self.out_filehandle = open(self.stdout_path, "w")
self.dbg.SetOutputFileHandle(self.out_filehandle, False)
self.dbg.SetErrorFileHandle(self.out_filehandle, False)

n_errors, quit_req, crashed = self.dbg.RunCommandInterpreter(
True, True, options, 0, False, False)

while 1:
time.sleep(1)
if target.process.state == lldb.eStateAttaching:
break

self.dbg.DispatchInputInterrupt()
self.dbg.DispatchInputInterrupt()
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is this done twice?


self.out_filehandle.flush()
reader = open(self.stdout_path, "r")
results = reader.readlines()
found_result = False
for line in results:
if "Cancelled async attach" in line:
found_result = True
break
self.assertTrue(found_result, "Found async error in results")
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This fails if results is empty.

********************************
[]
********************************
FAIL: LLDB (/home/david.spickett/build-llvm-aarch64/bin/clang-aarch64) :: test_run_then_attach_wait_interrupt (TestProcessAttach.ProcessAttachTestCase)
<bound method SBProcess.Kill of SBProcess: pid = 0, state = exited, threads = 0, executable = ProcessAttach>: success

Restore dir to: /home/david.spickett/build-llvm-aarch64
======================================================================
FAIL: test_run_then_attach_wait_interrupt (TestProcessAttach.ProcessAttachTestCase)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/home/david.spickett/llvm-project/lldb/test/API/commands/process/attach/TestProcessAttach.py", line 195, in test_run_then_attach_wait_interrupt
    self.assertTrue(found_result, "Found async error in results")
AssertionError: False is not True : Found async error in results
Config=aarch64-/home/david.spickett/build-llvm-aarch64/bin/clang
----------------------------------------------------------------------

When the test works the results contain:

********************************
['error: attach failed: Cancelled async attach.\n', '\n', '... Interrupted.\n']
********************************

Running it in a loop it took ~40 runs to get a failing one.

I wonder if that is because the attach happens to finish a lot faster sometimes, so there's no time to cancel it? If that's down to the OS and machine load, I'm not sure how we'd make this predictable.

The ugly option is to say if the results are empty, pass the test and assume the other 39 runs will check for the bug.

# We shouldn't still have a process in the "attaching" state:
state = self.dbg.GetSelectedTarget().process.state
self.assertState(state, lldb.eStateExited, "Process not exited after attach cancellation")
9 changes: 6 additions & 3 deletions lldb/test/API/commands/process/attach/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,13 @@ int main(int argc, char const *argv[]) {
// Waiting to be attached by the debugger.
temp = 0;

if (argc > 1 && argv[1][0] == 'q')
return 0;

while (temp < 30) {
std::this_thread::sleep_for(std::chrono::seconds(2)); // Waiting to be attached...
temp++;
}
std::this_thread::sleep_for(std::chrono::seconds(2)); // Waiting to be attached...
temp++;
}
bulbazord marked this conversation as resolved.
Show resolved Hide resolved

printf("Exiting now\n");
}
Loading