[lldb] [Process/NetBSD] Copy watchpoints to newly-created threads

NetBSD ptrace interface does not populate watchpoints to newly-created
threads.  Solve this via copying the watchpoints from the current thread
when new thread is reported via TRAP_LWP.

Add a test that verifies that when the user does not have permissions
to set watchpoints on NetBSD, the 'watchpoint set' errors out gracefully
and thread monitoring does not crash on being unable to copy watchpoints
to new threads.

Differential Revision: https://reviews.llvm.org/D70023
This commit is contained in:
Michał Górny 2019-11-09 11:56:08 +01:00
parent 8d9400b65b
commit d970d4d4aa
20 changed files with 114 additions and 19 deletions

View File

@ -18,12 +18,10 @@ class WatchpointForMultipleThreadsTestCase(TestBase):
NO_DEBUG_INFO_TESTCASE = True
main_spec = lldb.SBFileSpec("main.cpp", False)
@expectedFailureNetBSD
def test_watchpoint_before_thread_start(self):
"""Test that we can hit a watchpoint we set before starting another thread"""
self.do_watchpoint_test("Before running the thread")
@expectedFailureNetBSD
def test_watchpoint_after_thread_start(self):
"""Test that we can hit a watchpoint we set after starting another thread"""
self.do_watchpoint_test("After running the thread")

View File

@ -16,7 +16,6 @@ class ConcurrentBreakpointsDelayedBreakpointOneWatchpoint(
@skipIfFreeBSD # timing out on buildbot
# Atomic sequences are not supported yet for MIPS in LLDB.
@skipIf(triple='^mips')
@expectedFailureNetBSD
@add_test_categories(["watchpoint"])
def test(self):
"""Test a breakpoint, a delayed breakpoint, and one watchpoint thread. """

View File

@ -15,7 +15,6 @@ class ConcurrentDelaySignalWatch(ConcurrentEventsBase):
@skipIfFreeBSD # timing out on buildbot
# Atomic sequences are not supported yet for MIPS in LLDB.
@skipIf(triple='^mips')
@expectedFailureNetBSD
@add_test_categories(["watchpoint"])
def test(self):
"""Test a watchpoint and a (1 second delay) signal in multiple threads."""

View File

@ -15,7 +15,6 @@ class ConcurrentDelayWatchBreak(ConcurrentEventsBase):
@skipIfFreeBSD # timing out on buildbot
# Atomic sequences are not supported yet for MIPS in LLDB.
@skipIf(triple='^mips')
@expectedFailureNetBSD
@add_test_categories(["watchpoint"])
def test(self):
"""Test (1-second delay) watchpoint and a breakpoint in multiple threads."""

View File

@ -15,7 +15,6 @@ class ConcurrentSignalWatch(ConcurrentEventsBase):
@skipIfFreeBSD # timing out on buildbot
# Atomic sequences are not supported yet for MIPS in LLDB.
@skipIf(triple='^mips')
@expectedFailureNetBSD
@add_test_categories(["watchpoint"])
def test(self):
"""Test a watchpoint and a signal in multiple threads."""

View File

@ -15,7 +15,6 @@ class ConcurrentTwoBreakpointsOneWatchpoint(ConcurrentEventsBase):
@skipIfFreeBSD # timing out on buildbot
# Atomic sequences are not supported yet for MIPS in LLDB.
@skipIf(triple='^mips')
@expectedFailureNetBSD
@add_test_categories(["watchpoint"])
def test(self):
"""Test two threads that trigger a breakpoint and one watchpoint thread. """

View File

@ -15,7 +15,6 @@ class ConcurrentWatchBreak(ConcurrentEventsBase):
@skipIfFreeBSD # timing out on buildbot
# Atomic sequences are not supported yet for MIPS in LLDB.
@skipIf(triple='^mips')
@expectedFailureNetBSD
@add_test_categories(["watchpoint"])
def test(self):
"""Test watchpoint and a breakpoint in multiple threads."""

View File

@ -15,7 +15,6 @@ class ConcurrentWatchBreakDelay(ConcurrentEventsBase):
@skipIfFreeBSD # timing out on buildbot
# Atomic sequences are not supported yet for MIPS in LLDB.
@skipIf(triple='^mips')
@expectedFailureNetBSD
@add_test_categories(["watchpoint"])
def test(self):
"""Test watchpoint and a (1 second delay) breakpoint in multiple threads."""

View File

@ -15,7 +15,6 @@ class ConcurrentWatchpointDelayWatchpointOneBreakpoint(ConcurrentEventsBase):
@skipIfFreeBSD # timing out on buildbot
# Atomic sequences are not supported yet for MIPS in LLDB.
@skipIf(triple='^mips')
@expectedFailureNetBSD
@add_test_categories(["watchpoint"])
def test(self):
"""Test two threads that trigger a watchpoint (one with a 1 second delay) and one breakpoint thread. """

View File

@ -15,7 +15,6 @@ class ConcurrentWatchpointWithDelayWatchpointThreads(ConcurrentEventsBase):
@skipIfFreeBSD # timing out on buildbot
# Atomic sequences are not supported yet for MIPS in LLDB.
@skipIf(triple='^mips')
@expectedFailureNetBSD
@add_test_categories(["watchpoint"])
def test(self):
"""Test two threads that trigger a watchpoint where one thread has a 1 second delay. """

View File

@ -272,12 +272,21 @@ void NativeProcessNetBSD::MonitorSIGTRAP(lldb::pid_t pid) {
}
switch (pst.pe_report_event) {
case PTRACE_LWP_CREATE:
case PTRACE_LWP_CREATE: {
LLDB_LOG(log,
"monitoring new thread, pid = {0}, LWP = {1}", pid,
pst.pe_lwp);
AddThread(pst.pe_lwp);
break;
NativeThreadNetBSD& t = AddThread(pst.pe_lwp);
error = t.CopyWatchpointsFrom(
static_cast<NativeThreadNetBSD &>(*GetCurrentThread()));
if (error.Fail()) {
LLDB_LOG(log,
"failed to copy watchpoints to new thread {0}: {1}",
pst.pe_lwp, error);
SetState(StateType::eStateInvalid);
return;
}
} break;
case PTRACE_LWP_EXIT:
LLDB_LOG(log,
"removing exited thread, pid = {0}, LWP = {1}", pid,

View File

@ -8,6 +8,8 @@
#include "NativeRegisterContextNetBSD.h"
#include "Plugins/Process/NetBSD/NativeProcessNetBSD.h"
#include "lldb/Host/common/NativeProcessProtocol.h"
using namespace lldb_private;

View File

@ -11,12 +11,13 @@
#include "lldb/Host/common/NativeThreadProtocol.h"
#include "Plugins/Process/NetBSD/NativeProcessNetBSD.h"
#include "Plugins/Process/Utility/NativeRegisterContextRegisterInfo.h"
namespace lldb_private {
namespace process_netbsd {
class NativeProcessNetBSD;
class NativeRegisterContextNetBSD : public NativeRegisterContextRegisterInfo {
public:
NativeRegisterContextNetBSD(NativeThreadProtocol &native_thread,
@ -30,6 +31,8 @@ public:
static NativeRegisterContextNetBSD *
CreateHostNativeRegisterContextNetBSD(const ArchSpec &target_arch,
NativeThreadProtocol &native_thread);
virtual Status
CopyHardwareWatchpointsFrom(NativeRegisterContextNetBSD &source) = 0;
protected:
Status DoRegisterSet(int req, void *buf);

View File

@ -988,4 +988,19 @@ uint32_t NativeRegisterContextNetBSD_x86_64::NumSupportedHardwareWatchpoints() {
return 4;
}
Status NativeRegisterContextNetBSD_x86_64::CopyHardwareWatchpointsFrom(
NativeRegisterContextNetBSD &source) {
auto &r_source = static_cast<NativeRegisterContextNetBSD_x86_64&>(source);
Status res = r_source.ReadRegisterSet(DBRegSet);
if (!res.Fail()) {
// copy dbregs only if any watchpoints were set
if ((r_source.m_dbr_x86_64.dr[7] & 0xFF) == 0)
return res;
m_dbr_x86_64 = r_source.m_dbr_x86_64;
res = WriteRegisterSet(DBRegSet);
}
return res;
}
#endif // defined(__x86_64__)

View File

@ -71,6 +71,9 @@ public:
uint32_t NumSupportedHardwareWatchpoints() override;
Status
CopyHardwareWatchpointsFrom(NativeRegisterContextNetBSD &source) override;
private:
// Private member types.
enum { GPRegSet, FPRegSet, XStateRegSet, DBRegSet };

View File

@ -221,9 +221,9 @@ bool NativeThreadNetBSD::GetStopReason(ThreadStopInfo &stop_info,
llvm_unreachable("unhandled StateType!");
}
NativeRegisterContext& NativeThreadNetBSD::GetRegisterContext() {
NativeRegisterContextNetBSD &NativeThreadNetBSD::GetRegisterContext() {
assert(m_reg_context_up);
return *m_reg_context_up;
return *m_reg_context_up;
}
Status NativeThreadNetBSD::SetWatchpoint(lldb::addr_t addr, size_t size,
@ -284,3 +284,13 @@ Status NativeThreadNetBSD::RemoveHardwareBreakpoint(lldb::addr_t addr) {
return Status("Clearing hardware breakpoint failed.");
}
Status NativeThreadNetBSD::CopyWatchpointsFrom(NativeThreadNetBSD &source) {
Status s = GetRegisterContext().CopyHardwareWatchpointsFrom(
source.GetRegisterContext());
if (!s.Fail()) {
m_watchpoint_index_map = source.m_watchpoint_index_map;
m_hw_break_index_map = source.m_hw_break_index_map;
}
return s;
}

View File

@ -11,6 +11,8 @@
#include "lldb/Host/common/NativeThreadProtocol.h"
#include "Plugins/Process/NetBSD/NativeRegisterContextNetBSD.h"
#include <csignal>
#include <map>
#include <string>
@ -34,7 +36,7 @@ public:
bool GetStopReason(ThreadStopInfo &stop_info,
std::string &description) override;
NativeRegisterContext& GetRegisterContext() override;
NativeRegisterContextNetBSD &GetRegisterContext() override;
Status SetWatchpoint(lldb::addr_t addr, size_t size, uint32_t watch_flags,
bool hardware) override;
@ -62,10 +64,12 @@ private:
void SetRunning();
void SetStepping();
Status CopyWatchpointsFrom(NativeThreadNetBSD& source);
// Member Variables
lldb::StateType m_state;
ThreadStopInfo m_stop_info;
std::unique_ptr<NativeRegisterContext> m_reg_context_up;
std::unique_ptr<NativeRegisterContextNetBSD> m_reg_context_up;
std::string m_stop_description;
using WatchpointIndexMap = std::map<lldb::addr_t, uint32_t>;
WatchpointIndexMap m_watchpoint_index_map;

View File

@ -0,0 +1,23 @@
#include <pthread.h>
int g_watchme = 0;
void *thread_func(void *arg) {
/* watchpoint trigger from subthread */
g_watchme = 2;
return 0;
}
int main() {
pthread_t thread;
if (pthread_create(&thread, 0, thread_func, 0))
return 1;
/* watchpoint trigger from main thread */
g_watchme = 1;
if (pthread_join(thread, 0))
return 2;
return 0;
}

View File

@ -0,0 +1,22 @@
# Check that 'watchpoint set' errors out gracefully when we can't set dbregs
# and that new threads are monitored correctly even though we can't copy dbregs.
# REQUIRES: system-netbsd && (target-x86 || target-x86_64) && !dbregs-set
# RUN: %clang_host %p/Inputs/thread-dbreg.c -pthread -g -o %t.out
# RUN: %lldb -b -o 'settings set interpreter.stop-command-source-on-error false' -s %s %t.out 2>&1 | FileCheck %s
settings show interpreter.stop-command-source-on-error
# CHECK: interpreter.stop-command-source-on-error (boolean) = false
b main
# CHECK: Breakpoint {{[0-9]+}}: where = {{.*}}`main
b thread_func
# CHECK: Breakpoint {{[0-9]+}}: where = {{.*}}`thread_func
run
# CHECK: stop reason = breakpoint
watchpoint set variable g_watchme
# CHECK: error: Watchpoint creation failed
cont
# CHECK: stop reason = breakpoint
cont
# CHECK: Process {{[0-9]+}} exited with status = 0

View File

@ -5,6 +5,7 @@ import platform
import re
import shutil
import site
import subprocess
import sys
import lit.formats
@ -103,3 +104,17 @@ if config.lldb_enable_lzma:
if find_executable('xz') != None:
config.available_features.add('xz')
# NetBSD permits setting dbregs either if one is root
# or if user_set_dbregs is enabled
can_set_dbregs = True
if platform.system() == 'NetBSD' and os.geteuid() != 0:
try:
output = subprocess.check_output(["/sbin/sysctl", "-n",
"security.models.extensions.user_set_dbregs"]).decode().strip()
if output != "1":
can_set_dbregs = False
except subprocess.CalledProcessError:
can_set_dbregs = False
if can_set_dbregs:
config.available_features.add('dbregs-set')