Skip to content
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
2 changes: 2 additions & 0 deletions lldb/include/lldb/Host/HostThread.h
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,8 @@ class HostThread {

bool EqualsThread(lldb::thread_t thread) const;

bool HasThread() const;

private:
std::shared_ptr<HostNativeThreadBase> m_native_thread;
};
Expand Down
2 changes: 2 additions & 0 deletions lldb/include/lldb/Target/Process.h
Original file line number Diff line number Diff line change
Expand Up @@ -2547,6 +2547,8 @@ void PruneThreadPlans();

bool CurrentThreadIsPrivateStateThread();

bool CurrentThreadPosesAsPrivateStateThread();

virtual Status SendEventData(const char *data) {
return Status::FromErrorString(
"Sending an event is not supported for this process.");
Expand Down
6 changes: 6 additions & 0 deletions lldb/source/Host/common/HostThread.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -44,3 +44,9 @@ lldb::thread_result_t HostThread::GetResult() const {
bool HostThread::EqualsThread(lldb::thread_t thread) const {
return m_native_thread->EqualsThread(thread);
}

bool HostThread::HasThread() const {
if (!m_native_thread)
return false;
return m_native_thread->GetSystemHandle() != LLDB_INVALID_HOST_THREAD;
}
28 changes: 19 additions & 9 deletions lldb/source/Target/Process.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1271,7 +1271,7 @@ uint32_t Process::AssignIndexIDToThread(uint64_t thread_id) {
}

StateType Process::GetState() {
if (CurrentThreadIsPrivateStateThread())
if (CurrentThreadPosesAsPrivateStateThread())
return m_private_state.GetValue();
else
return m_public_state.GetValue();
Expand Down Expand Up @@ -3144,16 +3144,19 @@ void Process::CompleteAttach() {
}
}

if (!m_os_up) {
// If we don't have an operating system plugin loaded yet, see if
// LoadOperatingSystemPlugin can find one (and stuff it in m_os_up).
if (!m_os_up)
LoadOperatingSystemPlugin(false);
if (m_os_up) {
// Somebody might have gotten threads before now, but we need to force the
// update after we've loaded the OperatingSystem plugin or it won't get a
// chance to process the threads.
m_thread_list.Clear();
UpdateThreadListIfNeeded();
}

if (m_os_up) {
Copy link
Member

Choose a reason for hiding this comment

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

My first instinct here was "should this be an else" before I realized that LoadOperatingSystemPlugin probably initializes m_os_up. Might be wroth a comment.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I added a comment to make this clear.

// Somebody might have gotten threads before we loaded the OS Plugin above,
// so we need to force the update now or the newly loaded plugin won't get
// a chance to process the threads.
m_thread_list.Clear();
UpdateThreadListIfNeeded();
}

// Figure out which one is the executable, and set that in our target:
ModuleSP new_executable_module_sp;
for (ModuleSP module_sp : GetTarget().GetImages().Modules()) {
Expand Down Expand Up @@ -5856,6 +5859,13 @@ bool Process::CurrentThreadIsPrivateStateThread()
return m_private_state_thread.EqualsThread(Host::GetCurrentThread());
}

bool Process::CurrentThreadPosesAsPrivateStateThread() {
// If we haven't started up the private state thread yet, then whatever thread
// is fetching this event should be temporarily the private state thread.
if (!m_private_state_thread.HasThread())
return true;
return m_private_state_thread.EqualsThread(Host::GetCurrentThread());
}

void Process::Flush() {
m_thread_list.Flush();
Expand Down
2 changes: 1 addition & 1 deletion lldb/source/Target/StackFrameList.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -723,7 +723,7 @@ void StackFrameList::SelectMostRelevantFrame() {
// Don't call into the frame recognizers on the private state thread as
// they can cause code to run in the target, and that can cause deadlocks
// when fetching stop events for the expression.
if (m_thread.GetProcess()->CurrentThreadIsPrivateStateThread())
if (m_thread.GetProcess()->CurrentThreadPosesAsPrivateStateThread())
return;

Log *log = GetLog(LLDBLog::Thread);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,10 @@ def create_thread(self, tid, context):
return None

def get_thread_info(self):
if self.process.state != lldb.eStateStopped:
print("Error: get_thread_info called with state not stopped")
return []

if not self.threads:
self.threads = [
{
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
C_SOURCES := main.c
ENABLE_THREADS := YES

include Makefile.rules
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
"""
Test that an OS plugin in a dSYM sees the right process state
when run from a dSYM on attach
"""

from lldbsuite.test.decorators import *
from lldbsuite.test.lldbtest import *
import lldbsuite.test.lldbutil as lldbutil
from lldbgdbserverutils import get_debugserver_exe

import os
import lldb
import time
import socket
import shutil


class TestOSPluginIndSYM(TestBase):
NO_DEBUG_INFO_TESTCASE = True

# The port used by debugserver.
PORT = 54638

# The number of attempts.
ATTEMPTS = 10

# Time given to the binary to launch and to debugserver to attach to it for
# every attempt. We'll wait a maximum of 10 times 2 seconds while the
# inferior will wait 10 times 10 seconds.
TIMEOUT = 2

def no_debugserver(self):
if get_debugserver_exe() is None:
return "no debugserver"
return None

def port_not_available(self):
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
if s.connect_ex(("127.0.0.1", self.PORT)) == 0:
return "{} not available".format(self.PORT)
return None

@skipUnlessDarwin
def test_python_os_plugin(self):
self.do_test_python_os_plugin(False)

@skipTestIfFn(no_debugserver)
@skipTestIfFn(port_not_available)
def test_python_os_plugin_remote(self):
self.do_test_python_os_plugin(True)

def do_test_python_os_plugin(self, remote):
"""Test that the environment for os plugins in dSYM's is correct"""
executable = self.build_dsym("my_binary")

# Make sure we're set up to load the symbol file's python
self.runCmd("settings set target.load-script-from-symbol-file true")

target = self.dbg.CreateTarget(None)

error = lldb.SBError()

# Now run the process, and then attach. When the attach
# succeeds, make sure that we were in the right state when
# the OS plugins were run.
if not remote:
popen = self.spawnSubprocess(executable, [])

process = target.AttachToProcessWithID(lldb.SBListener(), popen.pid, error)
self.assertSuccess(error, "Attach succeeded")
else:
self.setup_remote_platform(executable)
process = target.process
self.assertTrue(process.IsValid(), "Got a valid process from debugserver")

# We should have figured out the target from the result of the attach:
self.assertTrue(target.IsValid, "Got a valid target")

# Make sure that we got the right plugin:
self.expect(
"settings show target.process.python-os-plugin-path",
substrs=["operating_system.py"],
)

for thread in process.threads:
stack_depth = thread.num_frames
reg_threads = thread.frames[0].reg

# OKAY, that realized the threads, now see if the creation
# state was correct. The way we use the OS plugin, it doesn't need
# to create a thread, and doesn't have to call get_register_info,
# so we don't expect those to get called.
self.expect(
"test_report_command",
substrs=[
"in_init=1",
"in_get_thread_info=1",
"in_create_thread=2",
"in_get_register_info=2",
"in_get_register_data=1",
],
)

def build_dsym(self, name):
self.build(debug_info="dsym", dictionary={"EXE": name})
executable = self.getBuildArtifact(name)
dsym_path = self.getBuildArtifact(name + ".dSYM")
python_dir_path = dsym_path
python_dir_path = os.path.join(dsym_path, "Contents", "Resources", "Python")
if not os.path.exists(python_dir_path):
os.mkdir(python_dir_path)
python_file_name = name + ".py"

os_plugin_dir = os.path.join(python_dir_path, "OS_Plugin")
if not os.path.exists(os_plugin_dir):
os.mkdir(os_plugin_dir)

plugin_dest_path = os.path.join(os_plugin_dir, "operating_system.py")
plugin_origin_path = os.path.join(self.getSourceDir(), "operating_system.py")
shutil.copy(plugin_origin_path, plugin_dest_path)

module_dest_path = os.path.join(python_dir_path, python_file_name)
with open(module_dest_path, "w") as f:
f.write("def __lldb_init_module(debugger, unused):\n")
f.write(
f" debugger.HandleCommand(\"settings set target.process.python-os-plugin-path '{plugin_dest_path}'\")\n"
)
f.close()

return executable

def setup_remote_platform(self, exe):
# Get debugserver to start up our process for us, and then we
# can use `process connect` to attach to it.
debugserver = get_debugserver_exe()
debugserver_args = ["localhost:{}".format(self.PORT), exe]
self.spawnSubprocess(debugserver, debugserver_args)

# Select the platform.
self.runCmd("platform select remote-gdb-server")

# Connect to debugserver
interpreter = self.dbg.GetCommandInterpreter()
connected = False
for i in range(self.ATTEMPTS):
result = lldb.SBCommandReturnObject()
interpreter.HandleCommand(f"gdb-remote localhost:{self.PORT}", result)
connected = result.Succeeded()
if connected:
break
time.sleep(self.TIMEOUT)

self.assertTrue(connected, "could not connect to debugserver")
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
#include <unistd.h>

int main() {
while (1) {
sleep(1);
}
return 0;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
#!/usr/bin/env python

import lldb
import struct

# Value is:
# 0 called - state is not stopped
# 1 called - state is stopped
# 2 not called

stop_state = {
"in_init": 2,
"in_get_thread_info": 2,
"in_create_thread": 2,
"in_get_register_info": 2,
"in_get_register_data": 2,
}


def ReportCommand(debugger, command, exe_ctx, result, unused):
global stop_state
for state in stop_state:
result.AppendMessage(f"{state}={stop_state[state]}\n")
result.SetStatus(lldb.eReturnStatusSuccessFinishResult)


class OperatingSystemPlugIn:
"""This class checks that all the"""

def __init__(self, process):
"""Initialization needs a valid.SBProcess object.
global stop_state

This plug-in will get created after a live process is valid and has stopped for the
first time."""
self.process = process
stop_state["in_init"] = self.state_is_stopped()
interp = process.target.debugger.GetCommandInterpreter()
result = lldb.SBCommandReturnObject()
cmd_str = (
f"command script add test_report_command -o -f {__name__}.ReportCommand"
)
interp.HandleCommand(cmd_str, result)

def state_is_stopped(self):
if self.process.state == lldb.eStateStopped:
return 1
else:
return 0

def does_plugin_report_all_threads(self):
return True

def create_thread(self, tid, context):
global stop_state
stop_state["in_create_thread"] = self.state_is_stopped()

return None

def get_thread_info(self):
global stop_state
stop_state["in_get_thread_info"] = self.state_is_stopped()
idx = self.process.threads[0].idx
return [
{
"tid": 0x111111111,
"name": "one",
"queue": "queue1",
"state": "stopped",
"stop_reason": "breakpoint",
"core": idx,
}
]

def get_register_info(self):
global stop_state
stop_state["in_get_register_info"] = self.state_is_stopped()
return None

def get_register_data(self, tid):
global stop_state
stop_state["in_get_register_data"] = self.state_is_stopped()
return None
Loading