diff options
35 files changed, 6319 insertions, 128 deletions
diff --git a/lldb/include/lldb/Host/Debug.h b/lldb/include/lldb/Host/Debug.h index 7954ee0b6b3..9c4dd04c2a4 100644 --- a/lldb/include/lldb/Host/Debug.h +++ b/lldb/include/lldb/Host/Debug.h @@ -201,7 +201,7 @@ namespace lldb_private { { uint64_t type; uint32_t data_count; - lldb::addr_t data[2]; + lldb::addr_t data[8]; } exception; } details; }; diff --git a/lldb/include/lldb/Host/common/NativeProcessProtocol.h b/lldb/include/lldb/Host/common/NativeProcessProtocol.h index d306a58f3ca..2c89cb4c153 100644 --- a/lldb/include/lldb/Host/common/NativeProcessProtocol.h +++ b/lldb/include/lldb/Host/common/NativeProcessProtocol.h @@ -37,12 +37,6 @@ namespace lldb_private public: - // lldb_private::Host calls should be used to launch a process for debugging, and - // then the process should be attached to. When attaching to a process - // lldb_private::Host calls should be used to locate the process to attach to, - // and then this function should be called. - NativeProcessProtocol (lldb::pid_t pid); - virtual ~NativeProcessProtocol () { } @@ -379,6 +373,12 @@ namespace lldb_private int m_terminal_fd; uint32_t m_stop_id; + // lldb_private::Host calls should be used to launch a process for debugging, and + // then the process should be attached to. When attaching to a process + // lldb_private::Host calls should be used to locate the process to attach to, + // and then this function should be called. + NativeProcessProtocol (lldb::pid_t pid); + // ----------------------------------------------------------- // Internal interface for state handling // ----------------------------------------------------------- @@ -415,6 +415,12 @@ namespace lldb_private NativeThreadProtocolSP GetThreadByIDUnlocked (lldb::tid_t tid); + // ----------------------------------------------------------- + // Static helper methods for derived classes. + // ----------------------------------------------------------- + static Error + ResolveProcessArchitecture(lldb::pid_t pid, ArchSpec &arch); + private: void diff --git a/lldb/include/lldb/lldb-private-forward.h b/lldb/include/lldb/lldb-private-forward.h index bcfeb68b0b2..043ab5e698d 100644 --- a/lldb/include/lldb/lldb-private-forward.h +++ b/lldb/include/lldb/lldb-private-forward.h @@ -24,6 +24,7 @@ namespace lldb_private class NativeProcessProtocol; class NativeRegisterContext; class NativeThreadProtocol; + class ResumeActionList; class UnixSignals; // --------------------------------------------------------------- diff --git a/lldb/packages/Python/lldbsuite/test/tools/lldb-server/TestGdbRemoteProcessInfo.py b/lldb/packages/Python/lldbsuite/test/tools/lldb-server/TestGdbRemoteProcessInfo.py index f26b6204361..fd89ddfa28f 100644 --- a/lldb/packages/Python/lldbsuite/test/tools/lldb-server/TestGdbRemoteProcessInfo.py +++ b/lldb/packages/Python/lldbsuite/test/tools/lldb-server/TestGdbRemoteProcessInfo.py @@ -155,6 +155,13 @@ class TestGdbRemoteProcessInfo(gdbremote_testcase.GdbRemoteTestCaseBase): self.build() self.qProcessInfo_contains_keys(set(['cputype', 'cpusubtype'])) + @skipUnlessDarwin + @llgs_test + def test_qProcessInfo_contains_cputype_cpusubtype_llgs_darwin(self): + self.init_llgs_test() + self.build() + self.qProcessInfo_contains_keys(set(['cputype', 'cpusubtype'])) + @skipUnlessPlatform(["linux"]) @llgs_test def test_qProcessInfo_contains_triple_llgs_linux(self): @@ -172,6 +179,16 @@ class TestGdbRemoteProcessInfo(gdbremote_testcase.GdbRemoteTestCaseBase): # for the remote Host and Process. self.qProcessInfo_does_not_contain_keys(set(['triple'])) + @skipUnlessDarwin + @llgs_test + def test_qProcessInfo_does_not_contain_triple_llgs_darwin(self): + self.init_llgs_test() + self.build() + # We don't expect to see triple on darwin. If we do, we'll prefer triple + # to cputype/cpusubtype and skip some darwin-based ProcessGDBRemote ArchSpec setup + # for the remote Host and Process. + self.qProcessInfo_does_not_contain_keys(set(['triple'])) + @skipUnlessPlatform(["linux"]) @llgs_test def test_qProcessInfo_does_not_contain_cputype_cpusubtype_llgs_linux(self): diff --git a/lldb/packages/Python/lldbsuite/test/tools/lldb-server/TestLldbGdbServer.py b/lldb/packages/Python/lldbsuite/test/tools/lldb-server/TestLldbGdbServer.py index b60d08dc136..9cc20c11f74 100644 --- a/lldb/packages/Python/lldbsuite/test/tools/lldb-server/TestLldbGdbServer.py +++ b/lldb/packages/Python/lldbsuite/test/tools/lldb-server/TestLldbGdbServer.py @@ -97,101 +97,6 @@ class LldbGdbServerTestCase(gdbremote_testcase.GdbRemoteTestCaseBase): self.init_llgs_test() self.list_threads_in_stop_reply_supported() - def install_and_create_launch_args(self): - exe_path = os.path.abspath('a.out') - if not lldb.remote_platform: - return [exe_path] - remote_path = lldbutil.append_to_process_working_directory(os.path.basename(exe_path)) - remote_file_spec = lldb.SBFileSpec(remote_path, False) - err = lldb.remote_platform.Install(lldb.SBFileSpec(exe_path, True), remote_file_spec) - if err.Fail(): - raise Exception("remote_platform.Install('%s', '%s') failed: %s" % (exe_path, remote_path, err)) - return [remote_path] - - def start_inferior(self): - launch_args = self.install_and_create_launch_args() - - server = self.connect_to_debug_monitor() - self.assertIsNotNone(server) - - self.add_no_ack_remote_stream() - self.test_sequence.add_log_lines( - ["read packet: %s" % lldbgdbserverutils.build_gdbremote_A_packet(launch_args), - "send packet: $OK#9a"], - True) - self.expect_gdbremote_sequence() - - @debugserver_test - def test_start_inferior_debugserver(self): - self.init_debugserver_test() - self.build() - self.start_inferior() - - @llgs_test - def test_start_inferior_llgs(self): - self.init_llgs_test() - self.build() - self.start_inferior() - - def inferior_exit_0(self): - launch_args = self.install_and_create_launch_args() - - server = self.connect_to_debug_monitor() - self.assertIsNotNone(server) - - self.add_no_ack_remote_stream() - self.add_verified_launch_packets(launch_args) - self.test_sequence.add_log_lines( - ["read packet: $vCont;c#a8", - "send packet: $W00#00"], - True) - - self.expect_gdbremote_sequence() - - @debugserver_test - def test_inferior_exit_0_debugserver(self): - self.init_debugserver_test() - self.build() - self.inferior_exit_0() - - @llgs_test - def test_inferior_exit_0_llgs(self): - self.init_llgs_test() - self.build() - self.inferior_exit_0() - - def inferior_exit_42(self): - launch_args = self.install_and_create_launch_args() - - server = self.connect_to_debug_monitor() - self.assertIsNotNone(server) - - RETVAL = 42 - - # build launch args - launch_args += ["retval:%d" % RETVAL] - - self.add_no_ack_remote_stream() - self.add_verified_launch_packets(launch_args) - self.test_sequence.add_log_lines( - ["read packet: $vCont;c#a8", - "send packet: $W{0:02x}#00".format(RETVAL)], - True) - - self.expect_gdbremote_sequence() - - @debugserver_test - def test_inferior_exit_42_debugserver(self): - self.init_debugserver_test() - self.build() - self.inferior_exit_42() - - @llgs_test - def test_inferior_exit_42_llgs(self): - self.init_llgs_test() - self.build() - self.inferior_exit_42() - def c_packet_works(self): launch_args = self.install_and_create_launch_args() diff --git a/lldb/packages/Python/lldbsuite/test/tools/lldb-server/exit-code/Makefile b/lldb/packages/Python/lldbsuite/test/tools/lldb-server/exit-code/Makefile new file mode 100644 index 00000000000..1370b53b5a6 --- /dev/null +++ b/lldb/packages/Python/lldbsuite/test/tools/lldb-server/exit-code/Makefile @@ -0,0 +1,10 @@ +LEVEL = ../../../make + +VPATH = .. + +override CFLAGS_EXTRAS += -D__STDC_LIMIT_MACROS -D__STDC_FORMAT_MACROS +ENABLE_THREADS := YES +CXX_SOURCES := main.cpp +MAKE_DSYM :=NO + +include $(LEVEL)/Makefile.rules diff --git a/lldb/packages/Python/lldbsuite/test/tools/lldb-server/exit-code/TestGdbRemoteExitCode.py b/lldb/packages/Python/lldbsuite/test/tools/lldb-server/exit-code/TestGdbRemoteExitCode.py new file mode 100644 index 00000000000..e77f2b7acec --- /dev/null +++ b/lldb/packages/Python/lldbsuite/test/tools/lldb-server/exit-code/TestGdbRemoteExitCode.py @@ -0,0 +1,124 @@ +from __future__ import print_function + +# lldb test suite imports +from lldbsuite.test.decorators import * +from lldbsuite.test.lldbtest import TestBase + +# gdb-remote-specific imports +import lldbgdbserverutils +from gdbremote_testcase import GdbRemoteTestCaseBase + + +class TestGdbRemoteExitCode(GdbRemoteTestCaseBase): + + mydir = TestBase.compute_mydir(__file__) + + FAILED_LAUNCH_CODE = "E08" + + def get_launch_fail_reason(self): + self.reset_test_sequence() + self.test_sequence.add_log_lines( + ["read packet: $qLaunchSuccess#00"], + True) + self.test_sequence.add_log_lines( + [{"direction": "send", "regex": r"^\$(.+)#[0-9a-fA-F]{2}$", + "capture": {1: "launch_result"}}], + True) + context = self.expect_gdbremote_sequence() + self.assertIsNotNone(context) + return context.get("launch_result")[1:] + + def start_inferior(self): + launch_args = self.install_and_create_launch_args() + + server = self.connect_to_debug_monitor() + self.assertIsNotNone(server) + + self.add_no_ack_remote_stream() + self.test_sequence.add_log_lines( + ["read packet: %s" % lldbgdbserverutils.build_gdbremote_A_packet( + launch_args)], + True) + self.test_sequence.add_log_lines( + [{"direction": "send", "regex": r"^\$(.+)#[0-9a-fA-F]{2}$", + "capture": {1: "A_result"}}], + True) + context = self.expect_gdbremote_sequence() + self.assertIsNotNone(context) + + launch_result = context.get("A_result") + self.assertIsNotNone(launch_result) + if launch_result == self.FAILED_LAUNCH_CODE: + fail_reason = self.get_launch_fail_reason() + self.fail("failed to launch inferior: " + fail_reason) + + @debugserver_test + def test_start_inferior_debugserver(self): + self.init_debugserver_test() + self.build() + self.start_inferior() + + @llgs_test + def test_start_inferior_llgs(self): + self.init_llgs_test() + self.build() + self.start_inferior() + + def inferior_exit_0(self): + launch_args = self.install_and_create_launch_args() + + server = self.connect_to_debug_monitor() + self.assertIsNotNone(server) + + self.add_no_ack_remote_stream() + self.add_verified_launch_packets(launch_args) + self.test_sequence.add_log_lines( + ["read packet: $vCont;c#a8", + "send packet: $W00#00"], + True) + + self.expect_gdbremote_sequence() + + @debugserver_test + def test_inferior_exit_0_debugserver(self): + self.init_debugserver_test() + self.build() + self.inferior_exit_0() + + @llgs_test + def test_inferior_exit_0_llgs(self): + self.init_llgs_test() + self.build() + self.inferior_exit_0() + + def inferior_exit_42(self): + launch_args = self.install_and_create_launch_args() + + server = self.connect_to_debug_monitor() + self.assertIsNotNone(server) + + RETVAL = 42 + + # build launch args + launch_args += ["retval:%d" % RETVAL] + + self.add_no_ack_remote_stream() + self.add_verified_launch_packets(launch_args) + self.test_sequence.add_log_lines( + ["read packet: $vCont;c#a8", + "send packet: $W{0:02x}#00".format(RETVAL)], + True) + + self.expect_gdbremote_sequence() + + @debugserver_test + def test_inferior_exit_42_debugserver(self): + self.init_debugserver_test() + self.build() + self.inferior_exit_42() + + @llgs_test + def test_inferior_exit_42_llgs(self): + self.init_llgs_test() + self.build() + self.inferior_exit_42() diff --git a/lldb/packages/Python/lldbsuite/test/tools/lldb-server/gdbremote_testcase.py b/lldb/packages/Python/lldbsuite/test/tools/lldb-server/gdbremote_testcase.py index 4db630d50d1..12c1033cba1 100644 --- a/lldb/packages/Python/lldbsuite/test/tools/lldb-server/gdbremote_testcase.py +++ b/lldb/packages/Python/lldbsuite/test/tools/lldb-server/gdbremote_testcase.py @@ -133,7 +133,7 @@ class GdbRemoteTestCaseBase(TestBase): self.debug_monitor_extra_args.append("--log-file=" + log_file) self.debug_monitor_extra_args.append("--log-channels={}".format(":".join(lldbtest_config.channels))) else: - self.debug_monitor_extra_args = ["--log-file=" + self.log_file, "--log-flags=0x800000"] + self.debug_monitor_extra_args = ["--log-file=" + log_file, "--log-flags=0x800000"] def get_next_port(self): return 12000 + random.randint(0,3999) @@ -1370,3 +1370,16 @@ class GdbRemoteTestCaseBase(TestBase): def maybe_strict_output_regex(self, regex): return '.*'+regex+'.*' if lldbplatformutil.hasChattyStderr(self) else '^'+regex+'$' + def install_and_create_launch_args(self): + exe_path = os.path.abspath('a.out') + if not lldb.remote_platform: + return [exe_path] + remote_path = lldbutil.append_to_process_working_directory( + os.path.basename(exe_path)) + remote_file_spec = lldb.SBFileSpec(remote_path, False) + err = lldb.remote_platform.Install(lldb.SBFileSpec(exe_path, True), + remote_file_spec) + if err.Fail(): + raise Exception("remote_platform.Install('%s', '%s') failed: %s" % + (exe_path, remote_path, err)) + return [remote_path] diff --git a/lldb/packages/Python/lldbsuite/test/tools/lldb-server/host-info/Makefile b/lldb/packages/Python/lldbsuite/test/tools/lldb-server/host-info/Makefile new file mode 100644 index 00000000000..1370b53b5a6 --- /dev/null +++ b/lldb/packages/Python/lldbsuite/test/tools/lldb-server/host-info/Makefile @@ -0,0 +1,10 @@ +LEVEL = ../../../make + +VPATH = .. + +override CFLAGS_EXTRAS += -D__STDC_LIMIT_MACROS -D__STDC_FORMAT_MACROS +ENABLE_THREADS := YES +CXX_SOURCES := main.cpp +MAKE_DSYM :=NO + +include $(LEVEL)/Makefile.rules diff --git a/lldb/packages/Python/lldbsuite/test/tools/lldb-server/host-info/TestGdbRemoteHostInfo.py b/lldb/packages/Python/lldbsuite/test/tools/lldb-server/host-info/TestGdbRemoteHostInfo.py new file mode 100644 index 00000000000..6502f03336a --- /dev/null +++ b/lldb/packages/Python/lldbsuite/test/tools/lldb-server/host-info/TestGdbRemoteHostInfo.py @@ -0,0 +1,125 @@ +from __future__ import print_function + +# lldb test suite imports +from lldbsuite.test.decorators import * +from lldbsuite.test.lldbtest import TestBase + +# gdb-remote-specific imports +import lldbgdbserverutils +from gdbremote_testcase import GdbRemoteTestCaseBase + + +class TestGdbRemoteHostInfo(GdbRemoteTestCaseBase): + + mydir = TestBase.compute_mydir(__file__) + + KNOWN_HOST_INFO_KEYS = set([ + "cputype", + "cpusubtype", + "distribution_id", + "endian", + "hostname", + "ostype", + "os_build", + "os_kernel", + "os_version", + "ptrsize", + "triple", + "vendor", + "watchpoint_exceptions_received" + ]) + + DARWIN_REQUIRED_HOST_INFO_KEYS = set([ + "cputype", + "cpusubtype", + "endian", + "ostype", + "ptrsize", + "vendor", + "watchpoint_exceptions_received" + ]) + + def add_host_info_collection_packets(self): + self.test_sequence.add_log_lines( + ["read packet: $qHostInfo#9b", + {"direction": "send", "regex": r"^\$(.+)#[0-9a-fA-F]{2}$", + "capture": {1: "host_info_raw"}}], + True) + + def parse_host_info_response(self, context): + # Ensure we have a host info response. + self.assertIsNotNone(context) + host_info_raw = context.get("host_info_raw") + self.assertIsNotNone(host_info_raw) + + # Pull out key:value; pairs. + host_info_dict = {match.group(1): match.group(2) + for match in re.finditer(r"([^:]+):([^;]+);", + host_info_raw)} + + import pprint + print("\nqHostInfo response:") + pprint.pprint(host_info_dict) + + # Validate keys are known. + for (key, val) in list(host_info_dict.items()): + self.assertTrue(key in self.KNOWN_HOST_INFO_KEYS, + "unknown qHostInfo key: " + key) + self.assertIsNotNone(val) + + # Return the key:val pairs. + return host_info_dict + + def get_qHostInfo_response(self): + # Launch the debug monitor stub, attaching to the inferior. + server = self.connect_to_debug_monitor() + self.assertIsNotNone(server) + self.add_no_ack_remote_stream() + + # Request qHostInfo and get response + self.add_host_info_collection_packets() + context = self.expect_gdbremote_sequence() + self.assertIsNotNone(context) + + # Parse qHostInfo response. + host_info = self.parse_host_info_response(context) + self.assertIsNotNone(host_info) + self.assertGreater(len(host_info), 0, "qHostInfo should have returned " + "at least one key:val pair.") + return host_info + + def validate_darwin_minimum_host_info_keys(self, host_info_dict): + self.assertIsNotNone(host_info_dict) + missing_keys = [key for key in self.DARWIN_REQUIRED_HOST_INFO_KEYS + if key not in host_info_dict] + self.assertEquals(0, len(missing_keys), + "qHostInfo is missing the following required " + "keys: " + str(missing_keys)) + + @debugserver_test + def test_qHostInfo_returns_at_least_one_key_val_pair_debugserver(self): + self.init_debugserver_test() + self.build() + self.get_qHostInfo_response() + + @llgs_test + def test_qHostInfo_returns_at_least_one_key_val_pair_llgs(self): + self.init_llgs_test() + self.build() + self.get_qHostInfo_response() + + @skipUnlessDarwin + @debugserver_test + def test_qHostInfo_contains_darwin_required_keys_debugserver(self): + self.init_debugserver_test() + self.build() + host_info_dict = self.get_qHostInfo_response() + self.validate_darwin_minimum_host_info_keys(host_info_dict) + + @skipUnlessDarwin + @llgs_test + def test_qHostInfo_contains_darwin_required_keys_llgs(self): + self.init_llgs_test() + self.build() + host_info_dict = self.get_qHostInfo_response() + self.validate_darwin_minimum_host_info_keys(host_info_dict) diff --git a/lldb/source/Host/common/NativeProcessProtocol.cpp b/lldb/source/Host/common/NativeProcessProtocol.cpp index dfac0cb5645..bdc58fb8b43 100644 --- a/lldb/source/Host/common/NativeProcessProtocol.cpp +++ b/lldb/source/Host/common/NativeProcessProtocol.cpp @@ -12,12 +12,15 @@ #include "lldb/lldb-enumerations.h" #include "lldb/Core/ArchSpec.h" #include "lldb/Core/Log.h" +#include "lldb/Core/ModuleSpec.h" #include "lldb/Core/State.h" #include "lldb/Host/Host.h" #include "lldb/Host/common/NativeRegisterContext.h" - #include "lldb/Host/common/NativeThreadProtocol.h" #include "lldb/Host/common/SoftwareBreakpoint.h" +#include "lldb/Symbol/ObjectFile.h" +#include "lldb/Target/Process.h" +#include "lldb/Utility/LLDBAssert.h" using namespace lldb; using namespace lldb_private; @@ -436,6 +439,29 @@ NativeProcessProtocol::DoStopIDBumped (uint32_t /* newBumpId */) // Default implementation does nothing. } +Error +NativeProcessProtocol::ResolveProcessArchitecture(lldb::pid_t pid, + ArchSpec &arch) +{ + // Grab process info for the running process. + ProcessInstanceInfo process_info; + if (!Host::GetProcessInfo(pid, process_info)) + return Error("failed to get process info"); + + // Resolve the executable module. + ModuleSpecList module_specs; + if (!ObjectFile::GetModuleSpecifications(process_info.GetExecutableFile(), + 0, 0, module_specs)) + return Error("failed to get module specifications"); + lldbassert(module_specs.GetSize() == 1); + + arch = module_specs.GetModuleSpecRefAtIndex(0).GetArchitecture(); + if (arch.IsValid()) + return Error(); + else + return Error("failed to retrieve a valid architecture from the exe module"); +} + #ifndef __linux__ // These need to be implemented to support lldb-gdb-server on a given platform. Stubs are // provided to make the rest of the code link on non-supported platforms. diff --git a/lldb/source/Plugins/Process/Darwin/CFBundle.cpp b/lldb/source/Plugins/Process/Darwin/CFBundle.cpp new file mode 100644 index 00000000000..fdcb7cc2fcb --- /dev/null +++ b/lldb/source/Plugins/Process/Darwin/CFBundle.cpp @@ -0,0 +1,97 @@ +//===-- CFBundle.cpp --------------------------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// Created by Greg Clayton on 1/16/08. +// +//===----------------------------------------------------------------------===// + +#include "CFBundle.h" +#include "CFString.h" + +//---------------------------------------------------------------------- +// CFBundle constructor +//---------------------------------------------------------------------- +CFBundle::CFBundle(const char *path) : + CFReleaser<CFBundleRef>(), + m_bundle_url() +{ + if (path && path[0]) + SetPath(path); +} + +//---------------------------------------------------------------------- +// CFBundle copy constructor +//---------------------------------------------------------------------- +CFBundle::CFBundle(const CFBundle& rhs) : + CFReleaser<CFBundleRef>(rhs), + m_bundle_url(rhs.m_bundle_url) +{ + +} + +//---------------------------------------------------------------------- +// CFBundle copy constructor +//---------------------------------------------------------------------- +CFBundle& +CFBundle::operator=(const CFBundle& rhs) +{ + *this = rhs; + return *this; +} + +//---------------------------------------------------------------------- +// Destructor +//---------------------------------------------------------------------- +CFBundle::~CFBundle() +{ +} + +//---------------------------------------------------------------------- +// Set the path for a bundle by supplying a +//---------------------------------------------------------------------- +bool +CFBundle::SetPath (const char *path) +{ + CFAllocatorRef alloc = kCFAllocatorDefault; + // Release our old bundle and ULR + reset(); // This class is a CFReleaser<CFBundleRef> + m_bundle_url.reset(); + // Make a CFStringRef from the supplied path + CFString cf_path; + cf_path.SetFileSystemRepresentation(path); + if (cf_path.get()) + { + // Make our Bundle URL + m_bundle_url.reset (::CFURLCreateWithFileSystemPath (alloc, cf_path.get(), kCFURLPOSIXPathStyle, true)); + if (m_bundle_url.get()) + { + reset (::CFBundleCreate (alloc, m_bundle_url.get())); + } + } + return get() != NULL; +} + +CFStringRef +CFBundle::GetIdentifier () const +{ + CFBundleRef bundle = get(); + if (bundle != NULL) + return ::CFBundleGetIdentifier (bundle); + return NULL; +} + + +CFURLRef +CFBundle::CopyExecutableURL () const +{ + CFBundleRef bundle = get(); + if (bundle != NULL) + return CFBundleCopyExecutableURL(bundle); + return NULL; +} diff --git a/lldb/source/Plugins/Process/Darwin/CFBundle.h b/lldb/source/Plugins/Process/Darwin/CFBundle.h new file mode 100644 index 00000000000..e08290add73 --- /dev/null +++ b/lldb/source/Plugins/Process/Darwin/CFBundle.h @@ -0,0 +1,43 @@ +//===-- CFBundle.h ----------------------------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// Created by Greg Clayton on 1/16/08. +// +//===----------------------------------------------------------------------===// + +#ifndef __CFBundle_h__ +#define __CFBundle_h__ + +#include "CFUtils.h" + +class CFBundle : public CFReleaser<CFBundleRef> +{ +public: + //------------------------------------------------------------------ + // Constructors and Destructors + //------------------------------------------------------------------ + CFBundle(const char *path = NULL); + CFBundle(const CFBundle& rhs); + CFBundle& operator=(const CFBundle& rhs); + virtual + ~CFBundle(); + bool + SetPath (const char *path); + + CFStringRef + GetIdentifier () const; + + CFURLRef + CopyExecutableURL () const; + +protected: + CFReleaser<CFURLRef> m_bundle_url; +}; + +#endif // #ifndef __CFBundle_h__ diff --git a/lldb/source/Plugins/Process/Darwin/CFString.cpp b/lldb/source/Plugins/Process/Darwin/CFString.cpp new file mode 100644 index 00000000000..819024ca3bc --- /dev/null +++ b/lldb/source/Plugins/Process/Darwin/CFString.cpp @@ -0,0 +1,201 @@ +//===-- CFString.cpp --------------------------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// Created by Greg Clayton on 1/16/08. +// +//===----------------------------------------------------------------------===// + +#include "CFString.h" +#include <string> +#include <glob.h> + +//---------------------------------------------------------------------- +// CFString constructor +//---------------------------------------------------------------------- +CFString::CFString(CFStringRef s) : + CFReleaser<CFStringRef> (s) +{ +} + +//---------------------------------------------------------------------- +// CFString copy constructor +//---------------------------------------------------------------------- +CFString::CFString(const CFString& rhs) : + CFReleaser<CFStringRef> (rhs) +{ + +} + +//---------------------------------------------------------------------- +// CFString copy constructor +//---------------------------------------------------------------------- +CFString& +CFString::operator=(const CFString& rhs) +{ + if (this != &rhs) + *this = rhs; + return *this; +} + +CFString::CFString (const char *cstr, CFStringEncoding cstr_encoding) : + CFReleaser<CFStringRef> () +{ + if (cstr && cstr[0]) + { + reset(::CFStringCreateWithCString(kCFAllocatorDefault, cstr, cstr_encoding)); + } +} + +//---------------------------------------------------------------------- +// Destructor +//---------------------------------------------------------------------- +CFString::~CFString() +{ +} + +const char * +CFString::GetFileSystemRepresentation(std::string& s) +{ + return CFString::FileSystemRepresentation(get(), s); +} + +CFStringRef +CFString::SetFileSystemRepresentation (const char *path) +{ + CFStringRef new_value = NULL; + if (path && path[0]) + new_value = ::CFStringCreateWithFileSystemRepresentation (kCFAllocatorDefault, path); + reset(new_value); + return get(); +} + + +CFStringRef +CFString::SetFileSystemRepresentationFromCFType (CFTypeRef cf_type) +{ + CFStringRef new_value = NULL; + if (cf_type != NULL) + { + CFTypeID cf_type_id = ::CFGetTypeID(cf_type); + + if (cf_type_id == ::CFStringGetTypeID()) + { + // Retain since we are using the existing object + new_value = (CFStringRef)::CFRetain(cf_type); + } + else if (cf_type_id == ::CFURLGetTypeID()) + { + new_value = ::CFURLCopyFileSystemPath((CFURLRef)cf_type, kCFURLPOSIXPathStyle); + } + } + reset(new_value); + return get(); +} + +CFStringRef +CFString::SetFileSystemRepresentationAndExpandTilde (const char *path) +{ + std::string expanded_path; + if (CFString::GlobPath(path, expanded_path)) + SetFileSystemRepresentation(expanded_path.c_str()); + else + reset(); + return get(); +} + +const char * +CFString::UTF8(std::string& str) +{ + return CFString::UTF8(get(), str); +} + +// Static function that puts a copy of the UTF8 contents of CF_STR into STR +// and returns the C string pointer that is contained in STR when successful, else +// NULL is returned. This allows the std::string parameter to own the extracted string, +// and also allows that string to be returned as a C string pointer that can be used. + +const char * +CFString::UTF8 (CFStringRef cf_str, std::string& str) +{ + if (cf_str) + { + const CFStringEncoding encoding = kCFStringEncodingUTF8; + CFIndex max_utf8_str_len = CFStringGetLength (cf_str); + max_utf8_str_len = CFStringGetMaximumSizeForEncoding (max_utf8_str_len, encoding); + if (max_utf8_str_len > 0) + { + str.resize(max_utf8_str_len); + if (!str.empty()) + { + if (CFStringGetCString (cf_str, &str[0], str.size(), encoding)) + { + str.resize(strlen(str.c_str())); + return str.c_str(); + } + } + } + } + return NULL; +} + +// Static function that puts a copy of the file system representation of CF_STR +// into STR and returns the C string pointer that is contained in STR when +// successful, else NULL is returned. This allows the std::string parameter +// to own the extracted string, and also allows that string to be returned as +// a C string pointer that can be used. + +const char * +CFString::FileSystemRepresentation (CFStringRef cf_str, std::string& str) +{ + if (cf_str) + { + CFIndex max_length = ::CFStringGetMaximumSizeOfFileSystemRepresentation (cf_str); + if (max_length > 0) + { + str.resize(max_length); + if (!str.empty()) + { + if (::CFStringGetFileSystemRepresentation (cf_str, &str[0], str.size())) + { + str.erase(::strlen(str.c_str())); + return str.c_str(); + } + } + } + } + str.erase(); + return NULL; +} + + +CFIndex +CFString::GetLength() const +{ + CFStringRef str = get(); + if (str) + return CFStringGetLength (str); + return 0; +} + + +const char* +CFString::GlobPath(const char* path, std::string &expanded_path) +{ + glob_t globbuf; + if (::glob (path, GLOB_TILDE, NULL, &globbuf) == 0) + { + expanded_path = globbuf.gl_pathv[0]; + ::globfree (&globbuf); + } + else + expanded_path.clear(); + + return expanded_path.c_str(); +} + diff --git a/lldb/source/Plugins/Process/Darwin/CFString.h b/lldb/source/Plugins/Process/Darwin/CFString.h new file mode 100644 index 00000000000..73945a28a65 --- /dev/null +++ b/lldb/source/Plugins/Process/Darwin/CFString.h @@ -0,0 +1,43 @@ +//===-- CFString.h ----------------------------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// Created by Greg Clayton on 1/16/08. +// +//===----------------------------------------------------------------------===// + +#ifndef __CFString_h__ +#define __CFString_h__ + +#include "CFUtils.h" +#include <iosfwd> + +class CFString : public CFReleaser<CFStringRef> +{ +public: + //------------------------------------------------------------------ + // Constructors and Destructors + //------------------------------------------------------------------ + CFString (CFStringRef cf_str = NULL); + CFString (const char *s, CFStringEncoding encoding = kCFStringEncodingUTF8); + CFString (const CFString& rhs); + CFString& operator= (const CFString& rhs); + virtual ~CFString (); + + const char * GetFileSystemRepresentation (std::string& str); + CFStringRef SetFileSystemRepresentation (const char *path); + CFStringRef SetFileSystemRepresentationFromCFType (CFTypeRef cf_type); + CFStringRef SetFileSystemRepresentationAndExpandTilde (const char *path); + const char * UTF8 (std::string& str); + CFIndex GetLength() const; + static const char *UTF8 (CFStringRef cf_str, std::string& str); + static const char *FileSystemRepresentation (CFStringRef cf_str, std::string& str); + static const char* GlobPath(const char* path, std::string &expanded_path); +}; + +#endif // #ifndef __CFString_h__ diff --git a/lldb/source/Plugins/Process/Darwin/CFUtils.h b/lldb/source/Plugins/Process/Darwin/CFUtils.h new file mode 100644 index 00000000000..afa984fa11c --- /dev/null +++ b/lldb/source/Plugins/Process/Darwin/CFUtils.h @@ -0,0 +1,81 @@ +//===-- CFUtils.h -----------------------------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// Created by Greg Clayton on 3/5/07. +// +//===----------------------------------------------------------------------===// + +#ifndef __CFUtils_h__ +#define __CFUtils_h__ + +#include <CoreFoundation/CoreFoundation.h> + +#ifdef __cplusplus + +//---------------------------------------------------------------------- +// Templatized CF helper class that can own any CF pointer and will +// call CFRelease() on any valid pointer it owns unless that pointer is +// explicitly released using the release() member function. +//---------------------------------------------------------------------- +template <class T> +class CFReleaser +{ +public: + // Type names for the avlue + typedef T element_type; + + // Constructors and destructors + CFReleaser(T ptr = NULL) : _ptr(ptr) { } + CFReleaser(const CFReleaser& copy) : _ptr(copy.get()) + { + if (get()) + ::CFRetain(get()); + } + virtual ~CFReleaser() { reset(); } + + // Assignments + CFReleaser& operator= (const CFReleaser<T>& copy) + { + if (copy != *this) + { + // Replace our owned pointer with the new one + reset(copy.get()); + // Retain the current pointer that we own + if (get()) + ::CFRetain(get()); + } + } + // Get the address of the contained type + T * ptr_address() { return &_ptr; } + + // Access the pointer itself + const T get() const { return _ptr; } + T get() { return _ptr; } + + // Set a new value for the pointer and CFRelease our old + // value if we had a valid one. + void reset(T ptr = NULL) + { + if (ptr != _ptr) + { + if (_ptr != NULL) + ::CFRelease(_ptr); + _ptr = ptr; + } + } + + // Release ownership without calling CFRelease + T release() { T tmp = _ptr; _ptr = NULL; return tmp; } +private: + element_type _ptr; +}; + +#endif // #ifdef __cplusplus +#endif // #ifndef __CFUtils_h__ + diff --git a/lldb/source/Plugins/Process/Darwin/DarwinProcessLauncher.cpp b/lldb/source/Plugins/Process/Darwin/DarwinProcessLauncher.cpp new file mode 100644 index 00000000000..91abe4f26e2 --- /dev/null +++ b/lldb/source/Plugins/Process/Darwin/DarwinProcessLauncher.cpp @@ -0,0 +1,737 @@ +// +// DarwinProcessLauncher.cpp +// lldb +// +// Created by Todd Fiala on 8/30/16. +// +// + +#include "DarwinProcessLauncher.h" + +// C includes +#include <spawn.h> +#include <sys/ptrace.h> +#include <sys/stat.h> +#include <sys/sysctl.h> + +#ifndef _POSIX_SPAWN_DISABLE_ASLR +#define _POSIX_SPAWN_DISABLE_ASLR 0x0100 +#endif + +// LLDB includes +#include "lldb/lldb-enumerations.h" + +#include "lldb/Core/Error.h" +#include "lldb/Core/Log.h" +#include "lldb/Core/StreamString.h" +#include "lldb/Target/ProcessLaunchInfo.h" +#include "lldb/Utility/PseudoTerminal.h" + +#include "CFBundle.h" +#include "CFString.h" + +using namespace lldb; +using namespace lldb_private; +using namespace lldb_private::process_darwin; +using namespace lldb_private::darwin_process_launcher; + +namespace +{ + static LaunchFlavor g_launch_flavor = LaunchFlavor::Default; +} + +namespace lldb_private +{ +namespace darwin_process_launcher +{ + +static uint32_t +GetCPUTypeForLocalProcess(::pid_t pid) +{ + int mib[CTL_MAXNAME]={0,}; + size_t len = CTL_MAXNAME; + if (::sysctlnametomib("sysctl.proc_cputype", mib, &len)) + return 0; + + mib[len] = pid; + len++; + + cpu_type_t cpu; + size_t cpu_len = sizeof(cpu); + if (::sysctl (mib, static_cast<u_int>(len), &cpu, &cpu_len, 0, 0)) + cpu = 0; + return cpu; +} + +static bool +ResolveExecutablePath(const char *path, char *resolved_path, + size_t resolved_path_size) +{ + if (path == NULL || path[0] == '\0') + return false; + + char max_path[PATH_MAX]; + std::string result; + CFString::GlobPath(path, result); + + if (result.empty()) + result = path; + + struct stat path_stat; + if (::stat(path, &path_stat) == 0) + { + if ((path_stat.st_mode & S_IFMT) == S_IFDIR) + { + CFBundle bundle(path); + CFReleaser<CFURLRef> url(bundle.CopyExecutableURL ()); + if (url.get()) + { + if (::CFURLGetFileSystemRepresentation(url.get(), true, + (UInt8*)resolved_path, + resolved_path_size)) + return true; + } + } + } + + if (realpath(path, max_path)) + { + // Found the path relatively... + ::strncpy(resolved_path, max_path, resolved_path_size); + return strlen(resolved_path) + 1 < resolved_path_size; + } + else + { + // Not a relative path, check the PATH environment variable if the + const char *PATH = getenv("PATH"); + if (PATH) + { + const char *curr_path_start = PATH; + const char *curr_path_end; + while (curr_path_start && *curr_path_start) + { + curr_path_end = strchr(curr_path_start, ':'); + if (curr_path_end == NULL) + { + result.assign(curr_path_start); + curr_path_start = NULL; + } + else if (curr_path_end > curr_path_start) + { + size_t len = curr_path_end - curr_path_start; + result.assign(curr_path_start, len); + curr_path_start += len + 1; + } + else + break; + + result += '/'; + result += path; + struct stat s; + if (stat(result.c_str(), &s) == 0) + { + ::strncpy(resolved_path, result.c_str(), + resolved_path_size); + return result.size() + 1 < resolved_path_size; + } + } + } + } + return false; +} + +// TODO check if we have a general purpose fork and exec. We may be +// able to get rid of this entirely. +static Error +ForkChildForPTraceDebugging(const char *path, + char const *argv[], + char const *envp[], + ::pid_t *pid, + int *pty_fd) +{ + Error error; + if (!path || !argv || !envp || !pid || !pty_fd) + { + error.SetErrorString("invalid arguments"); + return error; + } + + // Use a fork that ties the child process's stdin/out/err to a pseudo + // terminal so we can read it in our MachProcess::STDIOThread + // as unbuffered io. + lldb_utility::PseudoTerminal pty; + char fork_error[256]; + memset(fork_error, 0, sizeof(fork_error)); + *pid = static_cast<::pid_t>(pty.Fork(fork_error, sizeof(fork_error))); + if (*pid < 0) + { + //-------------------------------------------------------------- + // Error during fork. + //-------------------------------------------------------------- + *pid = static_cast<::pid_t>(LLDB_INVALID_PROCESS_ID); + error.SetErrorStringWithFormat("%s(): fork failed: %s", + __FUNCTION__, fork_error); + return error; + } + else if (pid == 0) + { + //-------------------------------------------------------------- + // Child process + //-------------------------------------------------------------- + + // Debug this process. + ::ptrace(PT_TRACE_ME, 0, 0, 0); + + // Get BSD signals as mach exceptions. + ::ptrace(PT_SIGEXC, 0, 0, 0); + + // If our parent is setgid, lets make sure we don't inherit those + // extra powers due to nepotism. + if (::setgid(getgid()) == 0) + { + // Let the child have its own process group. We need to execute + // this call in both the child and parent to avoid a race + // condition between the two processes. + + // Set the child process group to match its pid. + ::setpgid(0, 0); + + // Sleep a bit to before the exec call. + ::sleep(1); + + // Turn this process into the given executable. + ::execv(path, (char * const *)argv); + } + // Exit with error code. Child process should have taken + // over in above exec call and if the exec fails it will + // exit the child process below. + ::exit(127); + } + else + { + //-------------------------------------------------------------- + // Parent process + //-------------------------------------------------------------- + // Let the child have its own process group. We need to execute + // this call in both the child and parent to avoid a race condition + // between the two processes. + + // Set the child process group to match its pid + ::setpgid(*pid, *pid); + if (pty_fd) + { + // Release our master pty file descriptor so the pty class doesn't + // close it and so we can continue to use it in our STDIO thread + *pty_fd = pty.ReleaseMasterFileDescriptor(); + } + } + return error; +} + +static Error +CreatePosixSpawnFileAction(const FileAction &action, + posix_spawn_file_actions_t *file_actions) +{ + Error error; + + // Log it. + Log *log(GetLogIfAllCategoriesSet(LIBLLDB_LOG_PROCESS)); + if (log) + { + StreamString stream; + stream.PutCString("converting file action for posix_spawn(): "); + action.Dump(stream); + stream.Flush(); + log->PutCString(stream.GetString().c_str()); + } + + // Validate args. + if (!file_actions) + { + error.SetErrorString("mandatory file_actions arg is null"); + return error; + } + + // Build the posix file action. + switch (action.GetAction()) + { + case FileAction::eFileActionOpen: + { + const int error_code = + ::posix_spawn_file_actions_addopen(file_actions, + action.GetFD(), + action.GetPath(), + action.GetActionArgument(), + 0); + if (error_code != 0) + { + error.SetError(error_code, eErrorTypePOSIX); + return error; + } + break; + } + + case FileAction::eFileActionClose: + { + const int error_code = + ::posix_spawn_file_actions_addclose(file_actions, + action.GetFD()); + if (error_code != 0) + { + error.SetError(error_code, eErrorTypePOSIX); + return error; + } + break; + } + + case FileAction::eFileActionDuplicate: + { + const int error_code = + ::posix_spawn_file_actions_adddup2(file_actions, + action.GetFD(), + action.GetActionArgument()); + if (error_code != 0) + { + error.SetError(error_code, eErrorTypePOSIX); + return error; + } + break; + } + + case FileAction::eFileActionNone: + default: + if (log) + log->Printf("%s(): unsupported file action %u", + __FUNCTION__, action.GetAction()); + break; + } + + return error; +} + +static Error +PosixSpawnChildForPTraceDebugging(const char *path, + ProcessLaunchInfo &launch_info, + ::pid_t *pid, + cpu_type_t *actual_cpu_type) +{ + Error error; + Log *log(GetLogIfAllCategoriesSet(LIBLLDB_LOG_PROCESS)); + + if (!pid) + { + error.SetErrorStringWithFormat("%s(): pid arg cannot be null", + __FUNCTION__); + return error; + } + + posix_spawnattr_t attr; + short flags; + if (log) + { + StreamString stream; + stream.Printf("%s(path='%s',...)\n", __FUNCTION__, path); + launch_info.Dump(stream, nullptr); + stream.Flush(); + log->PutCString(stream.GetString().c_str()); + } + + int error_code; + if ((error_code = ::posix_spawnattr_init(&attr)) != 0) + { + if (log) + log->Printf("::posix_spawnattr_init(&attr) failed"); + error.SetError(error_code, eErrorTypePOSIX); + return error; + } + + // Ensure we clean up the spawnattr structure however we exit this + // function. + std::unique_ptr<posix_spawnattr_t, int(*)(posix_spawnattr_t*)> + spawnattr_up(&attr, ::posix_spawnattr_destroy); + + + flags = POSIX_SPAWN_START_SUSPENDED | POSIX_SPAWN_SETSIGDEF | + POSIX_SPAWN_SETSIGMASK; + if (launch_info.GetFlags().Test(eLaunchFlagDisableASLR)) + flags |= _POSIX_SPAWN_DISABLE_ASLR; + + sigset_t no_signals; + sigset_t all_signals; + sigemptyset (&no_signals); + sigfillset (&all_signals); + ::posix_spawnattr_setsigmask(&attr, &no_signals); + ::posix_spawnattr_setsigdefault(&attr, &all_signals); + + if ((error_code = ::posix_spawnattr_setflags(&attr, flags)) != 0) + { + if (log) + log->Printf("::posix_spawnattr_setflags(&attr, " + "POSIX_SPAWN_START_SUSPENDED%s) failed: %s", + flags & _POSIX_SPAWN_DISABLE_ASLR ? + " | _POSIX_SPAWN_DISABLE_ASLR" : "", + strerror(error_code)); + error.SetError(error_code, eErrorTypePOSIX); + return error; + } + +#if !defined(__arm__) + + // We don't need to do this for ARM, and we really shouldn't now that we + // have multiple CPU subtypes and no posix_spawnattr call that allows us + // to set which CPU subtype to launch... + cpu_type_t desired_cpu_type = + launch_info.GetArchitecture().GetMachOCPUType(); + if (desired_cpu_type != LLDB_INVALID_CPUTYPE) + { + size_t ocount = 0; + error_code = ::posix_spawnattr_setbinpref_np(&attr, 1, + &desired_cpu_type, + &ocount); + if (error_code != 0) + { + if (log) + log->Printf("::posix_spawnattr_setbinpref_np(&attr, 1, " + "cpu_type = 0x%8.8x, count => %llu): %s", + desired_cpu_type, (uint64_t)ocount, + strerror(error_code)); + error.SetError(error_code, eErrorTypePOSIX); + return error; + } + if (ocount != 1) + { + error.SetErrorStringWithFormat("posix_spawnattr_setbinpref_np " + "did not set the expected number " + "of cpu_type entries: expected 1 " + "but was %zu", ocount); + return error; + } + } +#endif + + posix_spawn_file_actions_t file_actions; + if ((error_code = ::posix_spawn_file_actions_init(&file_actions)) != 0) + { + if (log) + log->Printf("::posix_spawn_file_actions_init(&file_actions) " + "failed: %s", + strerror(error_code)); + error.SetError(error_code, eErrorTypePOSIX); + return error; + } + + // Ensure we clean up file actions however we exit this. When the + // file_actions_up below goes out of scope, we'll get our file action + // cleanup. + std::unique_ptr<posix_spawn_file_actions_t, + int(*)(posix_spawn_file_actions_t*)> + file_actions_up(&file_actions, ::posix_spawn_file_actions_destroy); + + // We assume the caller has setup the file actions appropriately. We + // are not in the business of figuring out what we really need here. + // lldb-server will have already called FinalizeFileActions() as well + // to button these up properly. + const size_t num_actions = launch_info.GetNumFileActions(); + for (size_t action_index = 0; action_index < num_actions; ++action_index) + { + const FileAction *const action = + launch_info.GetFileActionAtIndex(action_index); + if (!action) + continue; + + error = CreatePosixSpawnFileAction(*action, &file_actions); + if (!error.Success()) + { + if (log) + log->Printf("%s(): error converting FileAction to posix_spawn " + "file action: %s", __FUNCTION__, error.AsCString()); + return error; + } + } + + // TODO: Verify if we can set the working directory back immediately + // after the posix_spawnp call without creating a race condition??? + const char *const working_directory = + launch_info.GetWorkingDirectory().GetCString(); + if (working_directory && working_directory[0]) + ::chdir(working_directory); + + auto argv = launch_info.GetArguments().GetArgumentVector(); + auto envp = launch_info.GetEnvironmentEntries().GetArgumentVector(); + error_code = ::posix_spawnp(pid, path, &file_actions, &attr, + (char * const*)argv, (char * const*)envp); + if (error_code != 0) + { + if (log) + log->Printf("::posix_spawnp(pid => %p, path = '%s', file_actions " + "= %p, attr = %p, argv = %p, envp = %p) failed: %s", + pid, path, &file_actions, &attr, argv, envp, + strerror(error_code)); + error.SetError(error_code, eErrorTypePOSIX); + return error; + } + + // Validate we got a pid. + if (pid == LLDB_INVALID_PROCESS_ID) + { + error.SetErrorString("posix_spawn() did not indicate a failure but it " + "failed to return a pid, aborting."); + return error; + } + + if (actual_cpu_type) + { + *actual_cpu_type = GetCPUTypeForLocalProcess(*pid); + if (log) + log->Printf("%s(): cpu type for launched process pid=%i: " + "cpu_type=0x%8.8x", __FUNCTION__, *pid, + *actual_cpu_type); + } + + return error; +} + +Error +LaunchInferior(ProcessLaunchInfo &launch_info, int *pty_master_fd, + LaunchFlavor *launch_flavor) +{ + Error error; + Log *log(GetLogIfAllCategoriesSet(LIBLLDB_LOG_PROCESS)); + + if (!launch_flavor) + { + error.SetErrorString("mandatory launch_flavor field was null"); + return error; + } + + if (log) + { + StreamString stream; + stream.Printf("NativeProcessDarwin::%s(): launching with the " + "following launch info:", __FUNCTION__); + launch_info.Dump(stream, nullptr); + stream.Flush(); + log->PutCString(stream.GetString().c_str()); + } + + // Retrieve the binary name given to us. + char given_path[PATH_MAX]; + given_path[0] = '\0'; + launch_info.GetExecutableFile().GetPath(given_path, sizeof(given_path)); + + // Determine the manner in which we'll launch. + *launch_flavor = g_launch_flavor; + if (*launch_flavor == LaunchFlavor::Default) + { + // Our default launch method is posix spawn + *launch_flavor = LaunchFlavor::PosixSpawn; +#if defined WITH_FBS + // Check if we have an app bundle, if so launch using BackBoard Services. + if (strstr(given_path, ".app")) + { + *launch_flavor = eLaunchFlavorFBS; + } +#elif defined WITH_BKS + // Check if we have an app bundle, if so launch using BackBoard Services. + if (strstr(given_path, ".app")) + { + *launch_flavor = eLaunchFlavorBKS; + } +#elif defined WITH_SPRINGBOARD + // Check if we have an app bundle, if so launch using SpringBoard. + if (strstr(given_path, ".app")) + { + *launch_flavor = eLaunchFlavorSpringBoard; + } +#endif + } + + // Attempt to resolve the binary name to an absolute path. + char resolved_path[PATH_MAX]; + resolved_path[0] = '\0'; + + if (log) + log->Printf("%s(): attempting to resolve given binary path: \"%s\"", + __FUNCTION__, given_path); + + // If we fail to resolve the path to our executable, then just use what we + // were given and hope for the best + if (!ResolveExecutablePath(given_path, resolved_path, + sizeof(resolved_path)) ) + { + if (log) + log->Printf("%s(): failed to resolve binary path, using " + "what was given verbatim and hoping for the best", + __FUNCTION__); + ::strncpy(resolved_path, given_path, + sizeof(resolved_path)); + } + else + { + if (log) + log->Printf("%s(): resolved given binary path to: \"%s\"", + __FUNCTION__, resolved_path); + } + + char launch_err_str[PATH_MAX]; + launch_err_str[0] = '\0'; + + // TODO figure out how to handle QSetProcessEvent + // const char *process_event = ctx.GetProcessEvent(); + + // Ensure the binary is there. + struct stat path_stat; + if (::stat(resolved_path, &path_stat) == -1) + { + error.SetErrorToErrno(); + return error; + } + + // Fork a child process for debugging + // state_callback(eStateLaunching); + + const auto argv = launch_info.GetArguments().GetConstArgumentVector(); + const auto envp = + launch_info.GetEnvironmentEntries().GetConstArgumentVector(); + + switch (*launch_flavor) + { + case LaunchFlavor::ForkExec: + { + ::pid_t pid = LLDB_INVALID_PROCESS_ID; + error = ForkChildForPTraceDebugging(resolved_path, argv, envp, + &pid, pty_master_fd); + if (error.Success()) + { + launch_info.SetProcessID(static_cast<lldb::pid_t>(pid)); + } + else + { + // Reset any variables that might have been set during a failed + // launch attempt. + if (pty_master_fd) + *pty_master_fd = -1; + + // We're done. + return error; + } + } + break; + +#ifdef WITH_FBS + case LaunchFlavor::FBS: + { + const char *app_ext = strstr(path, ".app"); + if (app_ext && (app_ext[4] == '\0' || app_ext[4] == '/')) + { + std::string app_bundle_path(path, app_ext + strlen(".app")); + m_flags |= eMachProcessFlagsUsingFBS; + if (BoardServiceLaunchForDebug (app_bundle_path.c_str(), argv, envp, no_stdio, disable_aslr, event_data, launch_err) != 0) + return m_pid; // A successful SBLaunchForDebug() returns and assigns a non-zero m_pid. + else + break; // We tried a FBS launch, but didn't succeed lets get out + } + } + break; +#endif + +#ifdef WITH_BKS + case LaunchFlavor::BKS: + { + const char *app_ext = strstr(path, ".app"); + if (app_ext && (app_ext[4] == '\0' || app_ext[4] == '/')) + { + std::string app_bundle_path(path, app_ext + strlen(".app")); + m_flags |= eMachProcessFlagsUsingBKS; + if (BoardServiceLaunchForDebug (app_bundle_path.c_str(), argv, envp, no_stdio, disable_aslr, event_data, launch_err) != 0) + return m_pid; // A successful SBLaunchForDebug() returns and assigns a non-zero m_pid. + else + break; // We tried a BKS launch, but didn't succeed lets get out + } + } + break; +#endif + +#ifdef WITH_SPRINGBOARD + case LaunchFlavor::SpringBoard: + { + // .../whatever.app/whatever ? + // Or .../com.apple.whatever.app/whatever -- be careful of ".app" in "com.apple.whatever" here + const char *app_ext = strstr (path, ".app/"); + if (app_ext == NULL) + { + // .../whatever.app ? + int len = strlen (path); + if (len > 5) + { + if (strcmp (path + len - 4, ".app") == 0) + { + app_ext = path + len - 4; + } + } + } + if (app_ext) + { + std::string app_bundle_path(path, app_ext + strlen(".app")); + if (SBLaunchForDebug (app_bundle_path.c_str(), argv, envp, no_stdio, disable_aslr, launch_err) != 0) + return m_pid; // A successful SBLaunchForDebug() returns and assigns a non-zero m_pid. + else + break; // We tried a springboard launch, but didn't succeed lets get out + } + } + break; +#endif + + case LaunchFlavor::PosixSpawn: + { + ::pid_t pid = LLDB_INVALID_PROCESS_ID; + + // Retrieve paths for stdin/stdout/stderr. + cpu_type_t actual_cpu_type = 0; + error = PosixSpawnChildForPTraceDebugging(resolved_path, + launch_info, + &pid, + &actual_cpu_type); + if (error.Success()) + { + launch_info.SetProcessID(static_cast<lldb::pid_t>(pid)); + if (pty_master_fd) + *pty_master_fd = launch_info.GetPTY(). + ReleaseMasterFileDescriptor(); + } + else + { + // Reset any variables that might have been set during a failed + // launch attempt. + if (pty_master_fd) + *pty_master_fd = -1; + + // We're done. + return error; + } + break; + } + + default: + // Invalid launch flavor. + error.SetErrorStringWithFormat("NativeProcessDarwin::%s(): unknown " + "launch flavor %d", __FUNCTION__, + (int)*launch_flavor); + return error; + } + + if (launch_info.GetProcessID() == LLDB_INVALID_PROCESS_ID) + { + // If we don't have a valid process ID and no one has set the error, + // then return a generic error. + if (error.Success()) + error.SetErrorStringWithFormat("%s(): failed to launch, no reason " + "specified", __FUNCTION__); + } + + // We're done with the launch side of the operation. + return error; +} + +}} // namespaces + diff --git a/lldb/source/Plugins/Process/Darwin/DarwinProcessLauncher.h b/lldb/source/Plugins/Process/Darwin/DarwinProcessLauncher.h new file mode 100644 index 00000000000..d06056afbb6 --- /dev/null +++ b/lldb/source/Plugins/Process/Darwin/DarwinProcessLauncher.h @@ -0,0 +1,51 @@ +//===-- DarwinProcessLauncher.h ---------------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef DarwinProcessLauncher_h +#define DarwinProcessLauncher_h + +// C headers +#include <mach/machine.h> +#include <sys/types.h> + +// C++ headers +#include <functional> + +// LLDB headers +#include "lldb/lldb-enumerations.h" +#include "lldb/lldb-forward.h" + +#include "LaunchFlavor.h" + +namespace lldb_private +{ +namespace darwin_process_launcher +{ +// ============================================================================= +/// Launches a process for debugging. +/// +/// @param[inout] launch_info +/// Specifies details about the process to launch (e.g. path, architecture, +/// etc.). On output, includes the launched ProcessID (pid). +/// +/// @param[out] pty_master_fd +/// Returns the master side of the pseudo-terminal used to communicate +/// with stdin/stdout from the launched process. May be nullptr. +/// +/// @param[out] launch_flavor +/// Contains the launch flavor used when launching the process. +// ============================================================================= +Error +LaunchInferior(ProcessLaunchInfo &launch_info, int *pty_master_fd, + lldb_private::process_darwin::LaunchFlavor *launch_flavor); + +} // darwin_process_launcher +} // lldb_private + +#endif /* DarwinProcessLauncher_h */ diff --git a/lldb/source/Plugins/Process/Darwin/LaunchFlavor.h b/lldb/source/Plugins/Process/Darwin/LaunchFlavor.h new file mode 100644 index 00000000000..02182f7528a --- /dev/null +++ b/lldb/source/Plugins/Process/Darwin/LaunchFlavor.h @@ -0,0 +1,34 @@ +//===-- LaunchFlavor.h ---------------------------------------- -*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LaunchFlavor_h +#define LaunchFlavor_h + +namespace lldb_private { +namespace process_darwin { + +enum class LaunchFlavor +{ + Default = 0, + PosixSpawn = 1, + ForkExec = 2, +#ifdef WITH_SPRINGBOARD + SpringBoard = 3, +#endif +#ifdef WITH_BKS + BKS = 4, +#endif +#ifdef WITH_FBS + FBS = 5 +#endif +}; + +}} // namespaces + +#endif /* LaunchFlavor_h */ diff --git a/lldb/source/Plugins/Process/Darwin/MachException.cpp b/lldb/source/Plugins/Process/Darwin/MachException.cpp new file mode 100644 index 00000000000..d9693195e38 --- /dev/null +++ b/lldb/source/Plugins/Process/Darwin/MachException.cpp @@ -0,0 +1,684 @@ +//===-- MachException.cpp ---------------------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// Created by Greg Clayton on 6/18/07. +// +//===----------------------------------------------------------------------===// + +#include "MachException.h" + +// C includes +#include <errno.h> +#include <sys/types.h> +#include <sys/ptrace.h> + +// C++ includes +#include <mutex> + +// LLDB includes +#include "lldb/Core/Error.h" +#include "lldb/Core/Log.h" +#include "lldb/Core/Stream.h" +#include "lldb/Target/UnixSignals.h" +#include "lldb/Utility/LLDBAssert.h" + +using namespace lldb; +using namespace lldb_private; +using namespace lldb_private::process_darwin; + +// Routine mach_exception_raise +extern "C" +kern_return_t catch_mach_exception_raise +( + mach_port_t exception_port, + mach_port_t thread, + mach_port_t task, + exception_type_t exception, + mach_exception_data_t code, + mach_msg_type_number_t codeCnt +); + +extern "C" +kern_return_t catch_mach_exception_raise_state +( + mach_port_t exception_port, + exception_type_t exception, + const mach_exception_data_t code, + mach_msg_type_number_t codeCnt, + int *flavor, + const thread_state_t old_state, + mach_msg_type_number_t old_stateCnt, + thread_state_t new_state, + mach_msg_type_number_t *new_stateCnt +); + +// Routine mach_exception_raise_state_identity +extern "C" +kern_return_t catch_mach_exception_raise_state_identity +( + mach_port_t exception_port, + mach_port_t thread, + mach_port_t task, + exception_type_t exception, + mach_exception_data_t code, + mach_msg_type_number_t codeCnt, + int *flavor, + thread_state_t old_state, + mach_msg_type_number_t old_stateCnt, + thread_state_t new_state, + mach_msg_type_number_t *new_stateCnt +); + +extern "C" boolean_t mach_exc_server( + mach_msg_header_t *InHeadP, + mach_msg_header_t *OutHeadP); + +// Any access to the g_message variable should be done by locking the +// g_message_mutex first, using the g_message variable, then unlocking +// the g_message_mutex. See MachException::Message::CatchExceptionRaise() +// for sample code. + +static MachException::Data *g_message = NULL; + + +extern "C" +kern_return_t +catch_mach_exception_raise_state +( + mach_port_t exc_port, + exception_type_t exc_type, + const mach_exception_data_t exc_data, + mach_msg_type_number_t exc_data_count, + int * flavor, + const thread_state_t old_state, + mach_msg_type_number_t old_stateCnt, + thread_state_t new_state, + mach_msg_type_number_t * new_stateCnt +) +{ + // TODO change to LIBLLDB_LOG_EXCEPTION + Log *log(GetLogIfAllCategoriesSet(LIBLLDB_LOG_PROCESS | + LIBLLDB_LOG_VERBOSE)); + if (log) + { + log->Printf("::%s(exc_port = 0x%4.4x, exc_type = %d (%s), " + "exc_data = 0x%llx, exc_data_count = %d)", + __FUNCTION__, exc_port, exc_type, + MachException::Name(exc_type), (uint64_t)exc_data, + exc_data_count); + } + return KERN_FAILURE; +} + +extern "C" +kern_return_t +catch_mach_exception_raise_state_identity +( + mach_port_t exc_port, + mach_port_t thread_port, + mach_port_t task_port, + exception_type_t exc_type, + mach_exception_data_t exc_data, + mach_msg_type_number_t exc_data_count, + int * flavor, + thread_state_t old_state, + mach_msg_type_number_t old_stateCnt, + thread_state_t new_state, + mach_msg_type_number_t *new_stateCnt +) +{ + Log *log(GetLogIfAllCategoriesSet(LIBLLDB_LOG_PROCESS | + LIBLLDB_LOG_VERBOSE)); + if (log) + { + log->Printf("::%s(exc_port = 0x%4.4x, thd_port = 0x%4.4x, " + "tsk_port = 0x%4.4x, exc_type = %d (%s), exc_data[%d] = " + "{ 0x%llx, 0x%llx })", __FUNCTION__, exc_port, thread_port, + task_port, exc_type, MachException::Name(exc_type), + exc_data_count, + (uint64_t)(exc_data_count > 0 ? exc_data[0] : 0xBADDBADD), + (uint64_t)(exc_data_count > 1 ? exc_data[1] : 0xBADDBADD)); + } + mach_port_deallocate (mach_task_self(), task_port); + mach_port_deallocate (mach_task_self(), thread_port); + + return KERN_FAILURE; +} + +extern "C" +kern_return_t +catch_mach_exception_raise +( + mach_port_t exc_port, + mach_port_t thread_port, + mach_port_t task_port, + exception_type_t exc_type, + mach_exception_data_t exc_data, + mach_msg_type_number_t exc_data_count) +{ + Log *log(GetLogIfAllCategoriesSet(LIBLLDB_LOG_PROCESS | + LIBLLDB_LOG_VERBOSE)); + if (log) + { + log->Printf("::%s(exc_port = 0x%4.4x, thd_port = 0x%4.4x, " + "tsk_port = 0x%4.4x, exc_type = %d (%s), exc_data[%d] " + "= { 0x%llx, 0x%llx })", __FUNCTION__, exc_port, + thread_port, task_port, exc_type, + MachException::Name(exc_type), exc_data_count, + (uint64_t)(exc_data_count > 0 ? exc_data[0] : 0xBADDBADD), + (uint64_t)(exc_data_count > 1 ? exc_data[1] : 0xBADDBADD)); + } + + if (task_port == g_message->task_port) + { + g_message->task_port = task_port; + g_message->thread_port = thread_port; + g_message->exc_type = exc_type; + g_message->exc_data.resize(exc_data_count); + ::memcpy(&g_message->exc_data[0], exc_data, + g_message->exc_data.size() * + sizeof (mach_exception_data_type_t)); + return KERN_SUCCESS; + } + return KERN_FAILURE; +} + +#if 0 +void +MachException::Message::Dump(Stream &stream) const +{ + stream.Printf("exc_msg { bits = 0x%8.8x size = 0x%8.8x remote-port = " + "0x%8.8x local-port = 0x%8.8x reserved = 0x%8.8x " + "id = 0x%8.8x }\n", + exc_msg.hdr.msgh_bits, + exc_msg.hdr.msgh_size, + exc_msg.hdr.msgh_remote_port, + exc_msg.hdr.msgh_local_port, + exc_msg.hdr.msgh_reserved, + exc_msg.hdr.msgh_id); + + stream.Printf("reply_msg { bits = 0x%8.8x size = 0x%8.8x remote-port " + "= 0x%8.8x local-port = 0x%8.8x reserved = 0x%8.8x " + "id = 0x%8.8x }", + reply_msg.hdr.msgh_bits, + reply_msg.hdr.msgh_size, + reply_msg.hdr.msgh_remote_port, + reply_msg.hdr.msgh_local_port, + reply_msg.hdr.msgh_reserved, + reply_msg.hdr.msgh_id); + stream.Flush(); +} +#endif + +bool +MachException::Data::GetStopInfo(struct ThreadStopInfo *stop_info, + const UnixSignals &signals, + Stream &stream) const +{ + if (!stop_info) + return false; + + // Zero out the structure. + memset(stop_info, 0, sizeof(struct ThreadStopInfo)); + + if (exc_type == 0) + { + stop_info->reason = eStopReasonInvalid; + return true; + } + + // We always stop with a mach exception. + stop_info->reason = eStopReasonException; + // Save the EXC_XXXX exception type. + stop_info->details.exception.type = exc_type; + + // Fill in a text description + const char * exc_name = MachException::Name(exc_type); + if (exc_name) + stream.Printf("%s", exc_name); + else + stream.Printf("%i", exc_type); + + stop_info->details.exception.data_count = exc_data.size(); + + int soft_signal = SoftSignal(); + if (soft_signal) + { + const char *sig_str = signals.GetSignalAsCString(soft_signal); + stream.Printf(" EXC_SOFT_SIGNAL( %i ( %s ))", soft_signal, + sig_str ? sig_str : "unknown signal"); + } + else + { + // No special disassembly for exception data, just print it. + size_t idx; + stream.Printf(" data[%llu] = {", + (uint64_t)stop_info->details.exception.data_count); + + for (idx = 0; idx < stop_info->details.exception.data_count; ++idx) + { + stream.Printf("0x%llx%c", (uint64_t)exc_data[idx], + ((idx + 1 == stop_info->details.exception.data_count) + ? '}' : ',')); + } + } + + // Copy the exception data + for (size_t i = 0; i < stop_info->details.exception.data_count; i++) + stop_info->details.exception.data[i] = exc_data[i]; + + return true; +} + +Error +MachException::Message::Receive(mach_port_t port, mach_msg_option_t options, + mach_msg_timeout_t timeout, + mach_port_t notify_port) +{ + Error error; + Log *log(GetLogIfAllCategoriesSet(LIBLLDB_LOG_PROCESS | + LIBLLDB_LOG_VERBOSE)); + + mach_msg_timeout_t mach_msg_timeout = + options & MACH_RCV_TIMEOUT ? timeout : 0; + if (log && ((options & MACH_RCV_TIMEOUT) == 0)) + { + // Dump this log message if we have no timeout in case it never returns + log->Printf("::mach_msg(msg->{bits = %#x, size = %u remote_port = %#x, " + "local_port = %#x, reserved = 0x%x, id = 0x%x}, " + "option = %#x, send_size = 0, rcv_size = %llu, " + "rcv_name = %#x, timeout = %u, notify = %#x)", + exc_msg.hdr.msgh_bits, + exc_msg.hdr.msgh_size, + exc_msg.hdr.msgh_remote_port, + exc_msg.hdr.msgh_local_port, + exc_msg.hdr.msgh_reserved, + exc_msg.hdr.msgh_id, + options, + (uint64_t)sizeof (exc_msg.data), + port, + mach_msg_timeout, + notify_port); + } + + mach_msg_return_t mach_err = + ::mach_msg (&exc_msg.hdr, + options, // options + 0, // Send size + sizeof (exc_msg.data), // Receive size + port, // exception port to watch for + // exception on + mach_msg_timeout, // timeout in msec (obeyed only + // if MACH_RCV_TIMEOUT is ORed + // into the options parameter) + notify_port); + error.SetError(mach_err, eErrorTypeMachKernel); + + // Dump any errors we get + if (error.Fail() && log) + { + log->Printf("::mach_msg(msg->{bits = %#x, size = %u remote_port = %#x, " + "local_port = %#x, reserved = 0x%x, id = 0x%x}, " + "option = %#x, send_size = %u, rcv_size = %lu, rcv_name " + "= %#x, timeout = %u, notify = %#x) failed: %s", + exc_msg.hdr.msgh_bits, + exc_msg.hdr.msgh_size, + exc_msg.hdr.msgh_remote_port, + exc_msg.hdr.msgh_local_port, + exc_msg.hdr.msgh_reserved, + exc_msg.hdr.msgh_id, + options, + 0, + sizeof(exc_msg.data), + port, + mach_msg_timeout, + notify_port, + error.AsCString()); + } + return error; +} + +void +MachException::Message::Dump(Stream &stream) const +{ + stream.Printf(" exc_msg { bits = 0x%8.8x size = 0x%8.8x remote-port = " + "0x%8.8x local-port = 0x%8.8x reserved = 0x%8.8x id = " + "0x%8.8x }\n", + exc_msg.hdr.msgh_bits, + exc_msg.hdr.msgh_size, + exc_msg.hdr.msgh_remote_port, + exc_msg.hdr.msgh_local_port, + exc_msg.hdr.msgh_reserved, + exc_msg.hdr.msgh_id); + + stream.Printf(" reply_msg { bits = 0x%8.8x size = 0x%8.8x remote-port = " + "0x%8.8x local-port = 0x%8.8x reserved = 0x%8.8x id = " + "0x%8.8x }", + reply_msg.hdr.msgh_bits, + reply_msg.hdr.msgh_size, + reply_msg.hdr.msgh_remote_port, + reply_msg.hdr.msgh_local_port, + reply_msg.hdr.msgh_reserved, + reply_msg.hdr.msgh_id); +} + +bool +MachException::Message::CatchExceptionRaise(task_t task) +{ + bool success = false; + // locker will keep a mutex locked until it goes out of scope +// PThreadMutex::Locker locker(&g_message_mutex); + // DNBLogThreaded("calling mach_exc_server"); + state.task_port = task; + g_message = &state; + // The exc_server function is the MIG generated server handling function + // to handle messages from the kernel relating to the occurrence of an + // exception in a thread. Such messages are delivered to the exception port + // set via thread_set_exception_ports or task_set_exception_ports. When an + // exception occurs in a thread, the thread sends an exception message to + // its exception port, blocking in the kernel waiting for the receipt of a + // reply. The exc_server function performs all necessary argument handling + // for this kernel message and calls catch_exception_raise, + // catch_exception_raise_state or catch_exception_raise_state_identity, + // which should handle the exception. If the called routine returns + // KERN_SUCCESS, a reply message will be sent, allowing the thread to + // continue from the point of the exception; otherwise, no reply message + // is sent and the called routine must have dealt with the exception + // thread directly. + if (mach_exc_server (&exc_msg.hdr, &reply_msg.hdr)) + { + success = true; + } + else + { + Log *log(GetLogIfAllCategoriesSet(LIBLLDB_LOG_PROCESS | + LIBLLDB_LOG_VERBOSE)); + if (log) + log->Printf("MachException::Message::%s(): mach_exc_server " + "returned zero...", __FUNCTION__); + } + g_message = NULL; + return success; +} + +Error +MachException::Message::Reply(::pid_t inferior_pid, task_t inferior_task, + int signal) +{ + // Reply to the exception... + Error error; + + Log *log(GetLogIfAllCategoriesSet(LIBLLDB_LOG_PROCESS | + LIBLLDB_LOG_VERBOSE)); + + // If we had a soft signal, we need to update the thread first so it can + // continue without signaling + int soft_signal = state.SoftSignal(); + if (soft_signal) + { + int state_pid = -1; + if (inferior_task == state.task_port) + { + // This is our task, so we can update the signal to send to it + state_pid = inferior_pid; + soft_signal = signal; + } + else + { + auto mach_err = ::pid_for_task(state.task_port, &state_pid); + if (mach_err) + { + error.SetError(mach_err, eErrorTypeMachKernel); + if (log) + log->Printf("MachException::Message::%s(): pid_for_task() " + "failed: %s", __FUNCTION__, error.AsCString()); + return error; + } + } + + lldbassert(state_pid != -1); + if (state_pid != -1) + { + errno = 0; + caddr_t thread_port_caddr = (caddr_t)(uintptr_t)state.thread_port; + if (::ptrace(PT_THUPDATE, state_pid, thread_port_caddr, soft_signal) + != 0) + error.SetError(errno, eErrorTypePOSIX); + + if (!error.Success()) + { + if (log) + log->Printf("::ptrace(request = PT_THUPDATE, pid = " + "0x%4.4x, tid = 0x%4.4x, signal = %i)", + state_pid, state.thread_port, soft_signal); + return error; + } + } + } + + if (log) + log->Printf("::mach_msg ( msg->{bits = %#x, size = %u, remote_port " + "= %#x, local_port = %#x, reserved = 0x%x, id = 0x%x}, " + "option = %#x, send_size = %u, rcv_size = %u, rcv_name " + "= %#x, timeout = %u, notify = %#x)", + reply_msg.hdr.msgh_bits, + reply_msg.hdr.msgh_size, + reply_msg.hdr.msgh_remote_port, + reply_msg.hdr.msgh_local_port, + reply_msg.hdr.msgh_reserved, + reply_msg.hdr.msgh_id, + MACH_SEND_MSG | MACH_SEND_INTERRUPT, + reply_msg.hdr.msgh_size, + 0, + MACH_PORT_NULL, + MACH_MSG_TIMEOUT_NONE, + MACH_PORT_NULL); + + auto mach_err = ::mach_msg(&reply_msg.hdr, + MACH_SEND_MSG | MACH_SEND_INTERRUPT, + reply_msg.hdr.msgh_size, + 0, + MACH_PORT_NULL, + MACH_MSG_TIMEOUT_NONE, + MACH_PORT_NULL); + if (mach_err) + error.SetError(mach_err, eErrorTypeMachKernel); + + // Log our error if we have one. + if (error.Fail() && log) + { + if (error.GetError() == MACH_SEND_INTERRUPTED) + { + log->PutCString("::mach_msg() - send interrupted"); + // TODO: keep retrying to reply??? + } + else if (state.task_port == inferior_task) + { + log->Printf("mach_msg(): returned an error when replying " + "to a mach exception: error = %u (%s)", + error.GetError(), error.AsCString()); + } + else + { + log->Printf("::mach_msg() - failed (child of task): %u (%s)", + error.GetError(), error.AsCString()); + } + } + + return error; +} + +#define PREV_EXC_MASK_ALL (EXC_MASK_BAD_ACCESS | \ + EXC_MASK_BAD_INSTRUCTION | \ + EXC_MASK_ARITHMETIC | \ + EXC_MASK_EMULATION | \ + EXC_MASK_SOFTWARE | \ + EXC_MASK_BREAKPOINT | \ + EXC_MASK_SYSCALL | \ + EXC_MASK_MACH_SYSCALL | \ + EXC_MASK_RPC_ALERT | \ + EXC_MASK_MACHINE) + +// Don't listen for EXC_RESOURCE, it should really get handled by the system handler. + +#ifndef EXC_RESOURCE +#define EXC_RESOURCE 11 +#endif + +#ifndef EXC_MASK_RESOURCE +#define EXC_MASK_RESOURCE (1 << EXC_RESOURCE) +#endif + +#define LLDB_EXC_MASK (EXC_MASK_ALL & ~EXC_MASK_RESOURCE) + +Error +MachException::PortInfo::Save(task_t task) +{ + Error error; + Log *log(GetLogIfAllCategoriesSet(LIBLLDB_LOG_PROCESS | + LIBLLDB_LOG_VERBOSE)); + + if (log) + log->Printf("MachException::PortInfo::%s(task = 0x%4.4x)", + __FUNCTION__, task); + + // Be careful to be able to have debugserver built on a newer OS than what + // it is currently running on by being able to start with all exceptions + // and back off to just what is supported on the current system + mask = LLDB_EXC_MASK; + + count = (sizeof(ports) / sizeof(ports[0])); + auto mach_err = ::task_get_exception_ports(task, mask, masks, &count, ports, + behaviors, flavors); + if (mach_err) + error.SetError(mach_err, eErrorTypeMachKernel); + + if (log) + { + if (error.Success()) + { + log->Printf("::task_get_exception_ports(task = 0x%4.4x, mask = " + "0x%x, maskCnt => %u, ports, behaviors, flavors)", + task, mask, count); + } + else + { + log->Printf("::task_get_exception_ports(task = 0x%4.4x, mask = 0x%x, " + "maskCnt => %u, ports, behaviors, flavors) error: %u (%s)", + task, mask, count, error.GetError(), error.AsCString()); + } + } + + if ((error.GetError() == KERN_INVALID_ARGUMENT) && + (mask != PREV_EXC_MASK_ALL)) + { + mask = PREV_EXC_MASK_ALL; + count = (sizeof(ports) / sizeof(ports[0])); + mach_err = ::task_get_exception_ports(task, mask, masks, &count, ports, + behaviors, flavors); + error.SetError(mach_err, eErrorTypeMachKernel); + if (log) + { + if (error.Success()) + { + log->Printf("::task_get_exception_ports(task = 0x%4.4x, " + "mask = 0x%x, maskCnt => %u, ports, behaviors, " + "flavors)", task, mask, count); + } + else + { + log->Printf("::task_get_exception_ports(task = 0x%4.4x, mask = " + "0x%x, maskCnt => %u, ports, behaviors, flavors) " + "error: %u (%s)", task, mask, count, + error.GetError(), error.AsCString()); + } + } + } + if (error.Fail()) + { + mask = 0; + count = 0; + } + return error; +} + +Error +MachException::PortInfo::Restore(task_t task) +{ + Error error; + + Log *log(GetLogIfAllCategoriesSet(LIBLLDB_LOG_PROCESS | + LIBLLDB_LOG_VERBOSE)); + + if (log) + log->Printf("MachException::PortInfo::Restore(task = 0x%4.4x)", task); + + uint32_t i = 0; + if (count > 0) + { + for (i = 0; i < count; i++) + { + auto mach_err = ::task_set_exception_ports(task, masks[i], ports[i], + behaviors[i], + flavors[i]); + if (mach_err) + error.SetError(mach_err, eErrorTypeMachKernel); + if (log) + { + if (error.Success()) + { + log->Printf("::task_set_exception_ports(task = 0x%4.4x, " + "exception_mask = 0x%8.8x, new_port = 0x%4.4x, " + "behavior = 0x%8.8x, new_flavor = 0x%8.8x)", + task, masks[i], ports[i], behaviors[i], + flavors[i]); + } + else + { + log->Printf("::task_set_exception_ports(task = 0x%4.4x, " + "exception_mask = 0x%8.8x, new_port = 0x%4.4x, " + "behavior = 0x%8.8x, new_flavor = 0x%8.8x): " + "error %u (%s)", task, masks[i], ports[i], + behaviors[i], flavors[i], error.GetError(), + error.AsCString()); + } + } + + // Bail if we encounter any errors + if (error.Fail()) + break; + } + } + + count = 0; + return error; +} + +const char * +MachException::Name(exception_type_t exc_type) +{ + switch (exc_type) + { + case EXC_BAD_ACCESS: return "EXC_BAD_ACCESS"; + case EXC_BAD_INSTRUCTION: return "EXC_BAD_INSTRUCTION"; + case EXC_ARITHMETIC: return "EXC_ARITHMETIC"; + case EXC_EMULATION: return "EXC_EMULATION"; + case EXC_SOFTWARE: return "EXC_SOFTWARE"; + case EXC_BREAKPOINT: return "EXC_BREAKPOINT"; + case EXC_SYSCALL: return "EXC_SYSCALL"; + case EXC_MACH_SYSCALL: return "EXC_MACH_SYSCALL"; + case EXC_RPC_ALERT: return "EXC_RPC_ALERT"; +#ifdef EXC_CRASH + case EXC_CRASH: return "EXC_CRASH"; +#endif + default: + break; + } + return NULL; +} diff --git a/lldb/source/Plugins/Process/Darwin/MachException.h b/lldb/source/Plugins/Process/Darwin/MachException.h new file mode 100644 index 00000000000..a96090b3bd8 --- /dev/null +++ b/lldb/source/Plugins/Process/Darwin/MachException.h @@ -0,0 +1,163 @@ +//===-- MachException.h -----------------------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// Created by Greg Clayton on 6/18/07. +// +//===----------------------------------------------------------------------===// + + +#ifndef __MachException_h__ +#define __MachException_h__ + +#include <mach/mach.h> +#include <vector> + +#include "lldb/lldb-private-forward.h" +#include "lldb/lldb-types.h" +#include "lldb/Host/Debug.h" + +namespace lldb_private +{ +namespace process_darwin +{ + +typedef union MachMessageTag +{ + mach_msg_header_t hdr; + char data[1024]; +} MachMessage; + + +class MachException +{ +public: + + struct PortInfo + { + exception_mask_t mask; // the exception mask for this device which may be a subset of EXC_MASK_ALL... + exception_mask_t masks[EXC_TYPES_COUNT]; + mach_port_t ports[EXC_TYPES_COUNT]; + exception_behavior_t behaviors[EXC_TYPES_COUNT]; + thread_state_flavor_t flavors[EXC_TYPES_COUNT]; + mach_msg_type_number_t count; + + Error + Save(task_t task); + + Error + Restore(task_t task); + }; + + struct Data + { + task_t task_port; + thread_t thread_port; + exception_type_t exc_type; + std::vector<mach_exception_data_type_t> exc_data; + Data() : + task_port(TASK_NULL), + thread_port(THREAD_NULL), + exc_type(0), + exc_data() + { + } + + void + Clear() + { + task_port = TASK_NULL; + thread_port = THREAD_NULL; + exc_type = 0; + exc_data.clear(); + } + + bool + IsValid() const + { + return task_port != TASK_NULL && + thread_port != THREAD_NULL && + exc_type != 0; + } + + // Return the SoftSignal for this MachException data, or zero if there is none + int + SoftSignal() const + { + if (exc_type == EXC_SOFTWARE && exc_data.size() == 2 && exc_data[0] == EXC_SOFT_SIGNAL) + return static_cast<int>(exc_data[1]); + return 0; + } + + bool + IsBreakpoint() const + { + return (exc_type == EXC_BREAKPOINT || ((exc_type == EXC_SOFTWARE) && exc_data[0] == 1)); + } + + bool + GetStopInfo(ThreadStopInfo *stop_info, const UnixSignals &signals, + Stream &stream) const; + }; + + struct Message + { + MachMessage exc_msg; + MachMessage reply_msg; + Data state; + + Message() : + state() + { + memset(&exc_msg, 0, sizeof(exc_msg)); + memset(&reply_msg, 0, sizeof(reply_msg)); + } + + bool + CatchExceptionRaise(task_t task); + + Error + Reply(::pid_t inferior_pid, task_t inferior_task, int signal); + + Error + Receive(mach_port_t receive_port, + mach_msg_option_t options, + mach_msg_timeout_t timeout, + mach_port_t notify_port = MACH_PORT_NULL); + + void + Dump(Stream &stream) const; + + typedef std::vector<Message> collection; + typedef collection::iterator iterator; + typedef collection::const_iterator const_iterator; + }; + + enum + { + e_actionForward, // Forward signal to inferior process + e_actionStop, // Stop when this signal is received + }; + struct Action + { + task_t task_port; // Set to TASK_NULL for any TASK + thread_t thread_port; // Set to THREAD_NULL for any thread + exception_type_t exc_mask; // Mach exception mask to watch for + std::vector<mach_exception_data_type_t> exc_data_mask; // Mask to apply to exception data, or empty to ignore exc_data value for exception + std::vector<mach_exception_data_type_t> exc_data_value; // Value to compare to exception data after masking, or empty to ignore exc_data value for exception + uint8_t flags; // Action flags describing what to do with the exception + }; + + static const char* + Name(exception_type_t exc_type); +}; + +} // namespace process_darwin +} // namespace lldb_private + +#endif diff --git a/lldb/source/Plugins/Process/Darwin/NativeProcessDarwin.cpp b/lldb/source/Plugins/Process/Darwin/NativeProcessDarwin.cpp new file mode 100644 index 00000000000..d0eaac3c793 --- /dev/null +++ b/lldb/source/Plugins/Process/Darwin/NativeProcessDarwin.cpp @@ -0,0 +1,1825 @@ +//===-- NativeProcessDarwin.cpp ---------------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "NativeProcessDarwin.h" + +// C includes +#include <mach/mach_init.h> +#include <mach/mach_traps.h> +#include <sys/ptrace.h> +#include <sys/stat.h> +#include <sys/sysctl.h> +#include <sys/types.h> + +// C++ includes +// LLDB includes +#include "lldb/Core/Log.h" +#include "lldb/Core/State.h" +#include "lldb/Core/StreamString.h" +#include "lldb/Target/ProcessLaunchInfo.h" +#include "lldb/Utility/PseudoTerminal.h" + +#include "CFBundle.h" +#include "CFString.h" +#include "DarwinProcessLauncher.h" + +#include "MachException.h" + +using namespace lldb; +using namespace lldb_private; +using namespace lldb_private::process_darwin; +using namespace lldb_private::darwin_process_launcher; + +// ----------------------------------------------------------------------------- +// Hidden Impl +// ----------------------------------------------------------------------------- + +namespace +{ + struct hack_task_dyld_info { + mach_vm_address_t all_image_info_addr; + mach_vm_size_t all_image_info_size; + }; +} + +// ----------------------------------------------------------------------------- +// Public Static Methods +// ----------------------------------------------------------------------------- + +Error +NativeProcessProtocol::Launch(ProcessLaunchInfo &launch_info, + NativeProcessProtocol::NativeDelegate + &native_delegate, + MainLoop &mainloop, + NativeProcessProtocolSP &native_process_sp) +{ + Log *log(GetLogIfAllCategoriesSet(LIBLLDB_LOG_PROCESS)); + + Error error; + + // Verify the working directory is valid if one was specified. + FileSpec working_dir(launch_info.GetWorkingDirectory()); + if (working_dir && + (!working_dir.ResolvePath() || + working_dir.GetFileType() != FileSpec::eFileTypeDirectory)) + { + error.SetErrorStringWithFormat("No such file or directory: %s", + working_dir.GetCString()); + return error; + } + + // Launch the inferior. + int pty_master_fd = -1; + LaunchFlavor launch_flavor = LaunchFlavor::Default; + + error = LaunchInferior(launch_info, &pty_master_fd, &launch_flavor); + + // Handle launch failure. + if (!error.Success()) + { + if (log) + log->Printf("NativeProcessDarwin::%s() failed to launch process: " + "%s", + __FUNCTION__, error.AsCString()); + return error; + } + + // Handle failure to return a pid. + if (launch_info.GetProcessID() == LLDB_INVALID_PROCESS_ID) + { + if (log) + log->Printf("NativeProcessDarwin::%s() launch succeeded but no " + "pid was returned! Aborting.", __FUNCTION__); + return error; + } + + // Create the Darwin native process impl. + std::shared_ptr<NativeProcessDarwin> + np_darwin_sp(new NativeProcessDarwin(launch_info.GetProcessID(), + pty_master_fd)); + if (!np_darwin_sp->RegisterNativeDelegate(native_delegate)) + { + native_process_sp.reset (); + error.SetErrorStringWithFormat ("failed to register the native delegate"); + return error; + } + + // Finalize the processing needed to debug the launched process with + // a NativeProcessDarwin instance. + error = np_darwin_sp->FinalizeLaunch(launch_flavor, mainloop); + if (!error.Success()) + { + if (log) + log->Printf("NativeProcessDarwin::%s() aborting, failed to finalize" + " the launching of the process: %s", + __FUNCTION__, error.AsCString()); + return error; + } + + // Return the process and process id to the caller through the launch args. + native_process_sp = np_darwin_sp; + return error; +} + +Error +NativeProcessProtocol::Attach(lldb::pid_t pid, + NativeProcessProtocol::NativeDelegate + &native_delegate, + MainLoop &mainloop, + NativeProcessProtocolSP &native_process_sp) +{ + Log *log(GetLogIfAllCategoriesSet(LIBLLDB_LOG_PROCESS)); + if (log) + log->Printf ("NativeProcessDarwin::%s(pid = %" PRIi64 ")", __FUNCTION__, + pid); + + // Retrieve the architecture for the running process. + ArchSpec process_arch; + Error error = ResolveProcessArchitecture(pid, process_arch); + if (!error.Success()) + return error; + + // TODO get attach to return this value. + const int pty_master_fd = -1; + std::shared_ptr<NativeProcessDarwin> native_process_darwin_sp( + new NativeProcessDarwin(pid, pty_master_fd)); + + if (!native_process_darwin_sp->RegisterNativeDelegate(native_delegate)) + { + error.SetErrorStringWithFormat("failed to register the native " + "delegate"); + return error; + } + + native_process_darwin_sp->AttachToInferior(mainloop, pid, error); + if (!error.Success()) + return error; + + native_process_sp = native_process_darwin_sp; + return error; +} + +// ----------------------------------------------------------------------------- +// ctor/dtor +// ----------------------------------------------------------------------------- + +NativeProcessDarwin::NativeProcessDarwin(lldb::pid_t pid, int pty_master_fd) : + NativeProcessProtocol(pid), + m_task(TASK_NULL), + m_did_exec(false), + m_cpu_type(0), + m_exception_port(MACH_PORT_NULL), + m_exc_port_info(), + m_exception_thread(nullptr), + m_exception_messages_mutex(), + m_sent_interrupt_signo(0), + m_auto_resume_signo(0), + m_thread_list(), + m_thread_actions(), + m_waitpid_pipe(), + m_waitpid_thread(nullptr), + m_waitpid_reader_handle() +{ + // TODO add this to the NativeProcessProtocol constructor. + m_terminal_fd = pty_master_fd; +} + +NativeProcessDarwin::~NativeProcessDarwin() +{ +} + +// ----------------------------------------------------------------------------- +// Instance methods +// ----------------------------------------------------------------------------- + +Error +NativeProcessDarwin::FinalizeLaunch(LaunchFlavor launch_flavor, + MainLoop &main_loop) +{ + Error error; + Log *log(GetLogIfAllCategoriesSet(LIBLLDB_LOG_PROCESS)); + +#if 0 + m_path = path; + size_t i; + char const *arg; + for (i=0; (arg = argv[i]) != NULL; i++) + m_args.push_back(arg); +#endif + + error = StartExceptionThread(); + if (!error.Success()) + { + if (log) + log->Printf("NativeProcessDarwin::%s(): failure starting the " + "mach exception port monitor thread: %s", + __FUNCTION__, error.AsCString()); + + // Terminate the inferior process. There's nothing meaningful we can + // do if we can't receive signals and exceptions. Since we launched + // the process, it's fair game for us to kill it. + ::ptrace(PT_KILL, m_pid, 0, 0); + SetState(eStateExited); + + return error; + } + + StartSTDIOThread(); + + if (launch_flavor == LaunchFlavor::PosixSpawn) + { + SetState(eStateAttaching); + errno = 0; + int err = ::ptrace(PT_ATTACHEXC, m_pid, 0, 0); + if (err == 0) + { + // m_flags |= eMachProcessFlagsAttached; + if (log) + log->Printf("NativeProcessDarwin::%s(): successfully spawned " + "process with pid %" PRIu64, __FUNCTION__, m_pid); + } + else + { + error.SetErrorToErrno(); + SetState(eStateExited); + if (log) + log->Printf("NativeProcessDarwin::%s(): error: failed to " + "attach to spawned pid %" PRIu64 " (error=%d (%s))", + __FUNCTION__, m_pid, (int)error.GetError(), + error.AsCString()); + return error; + } + } + + if (log) + log->Printf("NativeProcessDarwin::%s(): new pid is %" PRIu64 "...", + __FUNCTION__, m_pid); + + // Spawn a thread to reap our child inferior process... + error = StartWaitpidThread(main_loop); + if (error.Fail()) + { + if (log) + log->Printf("NativeProcessDarwin::%s(): failed to start waitpid() " + "thread: %s", __FUNCTION__, error.AsCString()); + kill(SIGKILL, static_cast<::pid_t>(m_pid)); + return error; + } + + if (TaskPortForProcessID(error) == TASK_NULL) + { + // We failed to get the task for our process ID which is bad. + // Kill our process; otherwise, it will be stopped at the entry + // point and get reparented to someone else and never go away. + if (log) + log->Printf("NativeProcessDarwin::%s(): could not get task port " + "for process, sending SIGKILL and exiting: %s", + __FUNCTION__, error.AsCString()); + kill(SIGKILL, static_cast<::pid_t>(m_pid)); + return error; + } + + // Indicate that we're stopped, as we always launch suspended. + SetState(eStateStopped); + + // Success. + return error; +} + +Error +NativeProcessDarwin::SaveExceptionPortInfo() +{ + return m_exc_port_info.Save(m_task); +} + +bool +NativeProcessDarwin::ProcessUsingSpringBoard() const +{ + // TODO implement flags + // return (m_flags & eMachProcessFlagsUsingSBS) != 0; + return false; +} + +bool +NativeProcessDarwin::ProcessUsingBackBoard() const +{ + // TODO implement flags + // return (m_flags & eMachProcessFlagsUsingBKS) != 0; + return false; +} + +// Called by the exception thread when an exception has been received from +// our process. The exception message is completely filled and the exception +// data has already been copied. +void +NativeProcessDarwin::ExceptionMessageReceived(const MachException::Message& + message) +{ + Log *log(GetLogIfAllCategoriesSet(LIBLLDB_LOG_PROCESS | + LIBLLDB_LOG_VERBOSE)); + + std::lock_guard<std::recursive_mutex> locker(m_exception_messages_mutex); + if (m_exception_messages.empty()) + { + // Suspend the task the moment we receive our first exception message. + SuspendTask(); + } + + // Use a locker to automatically unlock our mutex in case of exceptions + // Add the exception to our internal exception stack + m_exception_messages.push_back(message); + + if (log) + log->Printf("NativeProcessDarwin::%s(): new queued message count: %lu", + __FUNCTION__, m_exception_messages.size()); +} + +void* +NativeProcessDarwin::ExceptionThread(void *arg) +{ + Log *log(GetLogIfAllCategoriesSet(LIBLLDB_LOG_PROCESS | + LIBLLDB_LOG_VERBOSE)); + if (!arg) + { + if (log) + log->Printf("NativeProcessDarwin::%s(): cannot run mach exception " + "thread, mandatory process arg was null", __FUNCTION__); + return nullptr; + } + + return reinterpret_cast<NativeProcessDarwin*>(arg)->DoExceptionThread(); +} + +void* +NativeProcessDarwin::DoExceptionThread() +{ + Log *log(GetLogIfAllCategoriesSet(LIBLLDB_LOG_PROCESS | + LIBLLDB_LOG_VERBOSE)); + + if (log) + log->Printf("NativeProcessDarwin::%s(arg=%p) starting thread...", + __FUNCTION__, this); + + pthread_setname_np("exception monitoring thread"); + + // Ensure we don't get CPU starved. + MaybeRaiseThreadPriority(); + + // We keep a count of the number of consecutive exceptions received so + // we know to grab all exceptions without a timeout. We do this to get a + // bunch of related exceptions on our exception port so we can process + // then together. When we have multiple threads, we can get an exception + // per thread and they will come in consecutively. The main loop in this + // thread can stop periodically if needed to service things related to this + // process. + // + // [did we lose some words here?] + // + // flag set in the options, so we will wait forever for an exception on + //0 our exception port. After we get one exception, we then will use the + // MACH_RCV_TIMEOUT option with a zero timeout to grab all other current + // exceptions for our process. After we have received the last pending + // exception, we will get a timeout which enables us to then notify + // our main thread that we have an exception bundle available. We then wait + // for the main thread to tell this exception thread to start trying to get + // exceptions messages again and we start again with a mach_msg read with + // infinite timeout. + // + // We choose to park a thread on this, rather than polling, because the + // polling is expensive. On devices, we need to minimize overhead caused + // by the process monitor. + uint32_t num_exceptions_received = 0; + Error error; + task_t task = m_task; + mach_msg_timeout_t periodic_timeout = 0; + +#if defined (WITH_SPRINGBOARD) && !defined (WITH_BKS) + mach_msg_timeout_t watchdog_elapsed = 0; + mach_msg_timeout_t watchdog_timeout = 60 * 1000; + ::pid_t pid = (::pid_t)process->GetID(); + CFReleaser<SBSWatchdogAssertionRef> watchdog; + + if (process->ProcessUsingSpringBoard()) + { + // Request a renewal for every 60 seconds if we attached using + // SpringBoard. + watchdog.reset(::SBSWatchdogAssertionCreateForPID(nullptr, pid, 60)); + if (log) + log->Printf("::SBSWatchdogAssertionCreateForPID(NULL, %4.4x, 60) " + "=> %p", pid, watchdog.get()); + + if (watchdog.get()) + { + ::SBSWatchdogAssertionRenew (watchdog.get()); + + CFTimeInterval watchdogRenewalInterval = + ::SBSWatchdogAssertionGetRenewalInterval (watchdog.get()); + if (log) + log->Printf("::SBSWatchdogAssertionGetRenewalInterval(%p) => " + "%g seconds", watchdog.get(), + watchdogRenewalInterval); + if (watchdogRenewalInterval > 0.0) + { + watchdog_timeout = + (mach_msg_timeout_t)watchdogRenewalInterval * 1000; + if (watchdog_timeout > 3000) + { + // Give us a second to renew our timeout. + watchdog_timeout -= 1000; + } + else if (watchdog_timeout > 1000) + { + // Give us a quarter of a second to renew our timeout. + watchdog_timeout -= 250; + } + } + } + if (periodic_timeout == 0 || periodic_timeout > watchdog_timeout) + periodic_timeout = watchdog_timeout; + } +#endif // #if defined (WITH_SPRINGBOARD) && !defined (WITH_BKS) + +#ifdef WITH_BKS + CFReleaser<BKSWatchdogAssertionRef> watchdog; + if (process->ProcessUsingBackBoard()) + { + ::pid_t pid = process->GetID(); + CFAllocatorRef alloc = kCFAllocatorDefault; + watchdog.reset(::BKSWatchdogAssertionCreateForPID(alloc, pid)); + } +#endif // #ifdef WITH_BKS + + // Do we want to use a weak pointer to the NativeProcessDarwin here, in + // which case we can guarantee we don't whack the process monitor if we + // race between this thread and the main one on shutdown? + while (IsExceptionPortValid()) + { + ::pthread_testcancel(); + + MachException::Message exception_message; + + if (num_exceptions_received > 0) + { + // We don't want a timeout here, just receive as many exceptions as + // we can since we already have one. We want to get all currently + // available exceptions for this task at once. + error = exception_message.Receive(GetExceptionPort(), + MACH_RCV_MSG | MACH_RCV_INTERRUPT + | MACH_RCV_TIMEOUT, 0); + } + else if (periodic_timeout > 0) + { + // We need to stop periodically in this loop, so try and get a mach + // message with a valid timeout (ms). + error = exception_message.Receive(GetExceptionPort(), + MACH_RCV_MSG | MACH_RCV_INTERRUPT + | MACH_RCV_TIMEOUT, + periodic_timeout); + } + else + { + // We don't need to parse all current exceptions or stop + // periodically, just wait for an exception forever. + error = exception_message.Receive(GetExceptionPort(), + MACH_RCV_MSG | MACH_RCV_INTERRUPT, + 0); + } + + if (error.Success()) + { + // We successfully received an exception. + if (exception_message.CatchExceptionRaise(task)) + { + ++num_exceptions_received; + ExceptionMessageReceived(exception_message); + } + } + else + { + if (error.GetError() == MACH_RCV_INTERRUPTED) + { + // We were interrupted. + + // If we have no task port we should exit this thread, as it implies + // the inferior went down. + if (!IsExceptionPortValid()) + { + if (log) + log->Printf("NativeProcessDarwin::%s(): the inferior " + "exception port is no longer valid, " + "canceling exception thread...", __FUNCTION__); + // Should we be setting a process state here? + break; + } + + // Make sure the inferior task is still valid. + if (IsTaskValid()) + { + // Task is still ok. + if (log) + log->Printf("NativeProcessDarwin::%s(): interrupted, but " + "the inferior task iss till valid, " + "continuing...", __FUNCTION__); + continue; + } + else + { + // The inferior task is no longer valid. Time to exit as + // the process has gone away. + if (log) + log->Printf("NativeProcessDarwin::%s(): the inferior task " + "has exited, and so will we...", __FUNCTION__); + // Does this race at all with our waitpid()? + SetState(eStateExited); + break; + } + } + else if (error.GetError() == MACH_RCV_TIMED_OUT) + { + // We timed out when waiting for exceptions. + + if (num_exceptions_received > 0) + { + // We were receiving all current exceptions with a timeout of + // zero. It is time to go back to our normal looping mode. + num_exceptions_received = 0; + + // Notify our main thread we have a complete exception message + // bundle available. Get the possibly updated task port back + // from the process in case we exec'ed and our task port + // changed. + task = ExceptionMessageBundleComplete(); + + // In case we use a timeout value when getting exceptions, + // make sure our task is still valid. + if (IsTaskValid(task)) + { + // Task is still ok. + if (log) + log->Printf("NativeProcessDarwin::%s(): got a timeout, " + "continuing...", __FUNCTION__); + continue; + } + else + { + // The inferior task is no longer valid. Time to exit as + // the process has gone away. + if (log) + log->Printf("NativeProcessDarwin::%s(): the inferior " + "task has exited, and so will we...", + __FUNCTION__); + // Does this race at all with our waitpid()? + SetState(eStateExited); + break; + } + } + +#if defined (WITH_SPRINGBOARD) && !defined (WITH_BKS) + if (watchdog.get()) + { + watchdog_elapsed += periodic_timeout; + if (watchdog_elapsed >= watchdog_timeout) + { + if (log) + log->Printf("SBSWatchdogAssertionRenew(%p)", + watchdog.get()); + ::SBSWatchdogAssertionRenew (watchdog.get()); + watchdog_elapsed = 0; + } + } +#endif + } + else + { + if (log) + log->Printf("NativeProcessDarwin::%s(): continuing after " + "receiving an unexpected error: %u (%s)", + __FUNCTION__, error.GetError(), error.AsCString()); + // TODO: notify of error? + } + } + } + +#if defined (WITH_SPRINGBOARD) && !defined (WITH_BKS) + if (watchdog.get()) + { + // TODO: change SBSWatchdogAssertionRelease to SBSWatchdogAssertionCancel when we + // all are up and running on systems that support it. The SBS framework has a #define + // that will forward SBSWatchdogAssertionRelease to SBSWatchdogAssertionCancel for now + // so it should still build either way. + DNBLogThreadedIf(LOG_TASK, "::SBSWatchdogAssertionRelease(%p)", watchdog.get()); + ::SBSWatchdogAssertionRelease (watchdog.get()); + } +#endif // #if defined (WITH_SPRINGBOARD) && !defined (WITH_BKS) + + if (log) + log->Printf("NativeProcessDarwin::%s(%p): thread exiting...", + __FUNCTION__, this); + return nullptr; +} + +Error +NativeProcessDarwin::StartExceptionThread() +{ + Error error; + Log *log(GetLogIfAllCategoriesSet(LIBLLDB_LOG_PROCESS)); + if (log) + log->Printf("NativeProcessDarwin::%s() called", __FUNCTION__); + + // Make sure we've looked up the inferior port. + TaskPortForProcessID(error); + + // Ensure the inferior task is valid. + if (!IsTaskValid()) + { + error.SetErrorStringWithFormat("cannot start exception thread: " + "task 0x%4.4x is not valid", m_task); + return error; + } + + // Get the mach port for the process monitor. + mach_port_t task_self = mach_task_self(); + + // Allocate an exception port that we will use to track our child process + auto mach_err = ::mach_port_allocate(task_self, MACH_PORT_RIGHT_RECEIVE, + &m_exception_port); + error.SetError(mach_err, eErrorTypeMachKernel); + if (error.Fail()) + { + if (log) + log->Printf("NativeProcessDarwin::%s(): mach_port_allocate(" + "task_self=0x%4.4x, MACH_PORT_RIGHT_RECEIVE, " + "&m_exception_port) failed: %u (%s)", __FUNCTION__, + task_self, error.GetError(), error.AsCString()); + return error; + } + + // Add the ability to send messages on the new exception port + mach_err = ::mach_port_insert_right(task_self, m_exception_port, + m_exception_port, MACH_MSG_TYPE_MAKE_SEND); + error.SetError(mach_err, eErrorTypeMachKernel); + if (error.Fail()) + { + if (log) + log->Printf("NativeProcessDarwin::%s(): mach_port_insert_right(" + "task_self=0x%4.4x, m_exception_port=0x%4.4x, " + "m_exception_port=0x%4.4x, MACH_MSG_TYPE_MAKE_SEND) " + "failed: %u (%s)", __FUNCTION__, + task_self, m_exception_port, m_exception_port, + error.GetError(), error.AsCString()); + return error; + } + + // Save the original state of the exception ports for our child process. + error = SaveExceptionPortInfo(); + if (error.Fail() || (m_exc_port_info.mask == 0)) + { + if (log) + log->Printf("NativeProcessDarwin::%s(): SaveExceptionPortInfo() " + "failed, cannot install exception handler: %s", + __FUNCTION__, error.AsCString()); + return error; + } + + // Set the ability to get all exceptions on this port. + mach_err = ::task_set_exception_ports(m_task, m_exc_port_info.mask, + m_exception_port, + EXCEPTION_DEFAULT | + MACH_EXCEPTION_CODES, + THREAD_STATE_NONE); + error.SetError(mach_err, eErrorTypeMachKernel); + if (error.Fail()) + { + if (log) + log->Printf("::task_set_exception_ports (task = 0x%4.4x, " + "exception_mask = 0x%8.8x, new_port = 0x%4.4x, " + "behavior = 0x%8.8x, new_flavor = 0x%8.8x) failed: " + "%u (%s)", m_task, m_exc_port_info.mask, + m_exception_port, (EXCEPTION_DEFAULT | + MACH_EXCEPTION_CODES), + THREAD_STATE_NONE, error.GetError(), + error.AsCString()); + return error; + } + + // Create the exception thread. + auto pthread_err = ::pthread_create(&m_exception_thread, nullptr, + ExceptionThread, this); + error.SetError(pthread_err, eErrorTypePOSIX); + if (error.Fail()) + { + if (log) + log->Printf("NativeProcessDarwin::%s(): failed to create Mach " + "exception-handling thread: %u (%s)", __FUNCTION__, + error.GetError(), error.AsCString()); + } + + return error; +} + +lldb::addr_t +NativeProcessDarwin::GetDYLDAllImageInfosAddress(Error &error) const +{ + error.Clear(); + + struct hack_task_dyld_info dyld_info; + mach_msg_type_number_t count = TASK_DYLD_INFO_COUNT; + // Make sure that COUNT isn't bigger than our hacked up struct + // hack_task_dyld_info. If it is, then make COUNT smaller to match. + if (count > (sizeof(struct hack_task_dyld_info) / sizeof(natural_t))) + { + count = (sizeof(struct hack_task_dyld_info) / sizeof(natural_t)); + } + + TaskPortForProcessID(error); + if (error.Fail()) + return LLDB_INVALID_ADDRESS; + + auto mach_err = ::task_info(m_task, TASK_DYLD_INFO, (task_info_t)&dyld_info, + &count); + error.SetError(mach_err, eErrorTypeMachKernel); + if (error.Success()) + { + // We now have the address of the all image infos structure. + return dyld_info.all_image_info_addr; + } + + // We don't have it. + return LLDB_INVALID_ADDRESS; +} + +uint32_t +NativeProcessDarwin::GetCPUTypeForLocalProcess(::pid_t pid) +{ + int mib[CTL_MAXNAME] = {0, }; + size_t len = CTL_MAXNAME; + + if (::sysctlnametomib("sysctl.proc_cputype", mib, &len)) + return 0; + + mib[len] = pid; + len++; + + cpu_type_t cpu; + size_t cpu_len = sizeof(cpu); + if (::sysctl (mib, static_cast<u_int>(len), &cpu, &cpu_len, 0, 0)) + cpu = 0; + return cpu; +} + +uint32_t +NativeProcessDarwin::GetCPUType() const +{ + if (m_cpu_type == 0 && m_pid != 0) + m_cpu_type = GetCPUTypeForLocalProcess(m_pid); + return m_cpu_type; +} + +task_t +NativeProcessDarwin::ExceptionMessageBundleComplete() +{ + // We have a complete bundle of exceptions for our child process. + Error error; + Log *log(GetLogIfAllCategoriesSet(LIBLLDB_LOG_PROCESS | + LIBLLDB_LOG_VERBOSE)); + + std::lock_guard<std::recursive_mutex> locker(m_exception_messages_mutex); + if (log) + log->Printf("NativeProcessDarwin::%s(): processing %lu exception " + "messages.", __FUNCTION__, m_exception_messages.size()); + + if (m_exception_messages.empty()) + { + // Not particularly useful... + return m_task; + } + + bool auto_resume = false; + m_did_exec = false; + + // First check for any SIGTRAP and make sure we didn't exec + const task_t task = m_task; + size_t i; + if (m_pid != 0) + { + bool received_interrupt = false; + uint32_t num_task_exceptions = 0; + for (i = 0; i < m_exception_messages.size(); ++i) + { + if (m_exception_messages[i].state.task_port != task) + { + // This is an exception that is not for our inferior, ignore. + continue; + } + + // This is an exception for the inferior. + ++num_task_exceptions; + const int signo = m_exception_messages[i].state.SoftSignal(); + if (signo == SIGTRAP) + { + // SIGTRAP could mean that we exec'ed. We need to check the + // dyld all_image_infos.infoArray to see if it is NULL and if + // so, say that we exec'ed. + const addr_t aii_addr = + GetDYLDAllImageInfosAddress(error); + if (aii_addr == LLDB_INVALID_ADDRESS) + break; + + const addr_t info_array_count_addr = aii_addr + 4; + uint32_t info_array_count = 0; + size_t bytes_read = 0; + Error read_error; + read_error = ReadMemory(info_array_count_addr, // source addr + &info_array_count, // dest addr + 4, // byte count + bytes_read); // #bytes read + if (read_error.Success() && (bytes_read == 4)) + { + if (info_array_count == 0) + { + // We got the all infos address, and there are zero + // entries. We think we exec'd. + m_did_exec = true; + + // Force the task port to update itself in case the + // task port changed after exec + const task_t old_task = m_task; + const bool force_update = true; + const task_t new_task = + TaskPortForProcessID(error, force_update); + if (old_task != new_task) + { + if (log) + log->Printf("exec: inferior task port changed " + "from 0x%4.4x to 0x%4.4x", old_task, + new_task); + } + } + } + else + { + if (log) + log->Printf("NativeProcessDarwin::%s() warning: " + "failed to read all_image_infos." + "infoArrayCount from 0x%8.8llx", + __FUNCTION__, info_array_count_addr); + } + } + else if ((m_sent_interrupt_signo != 0) && + (signo == m_sent_interrupt_signo)) + { + // We just received the interrupt that we sent to ourselves. + received_interrupt = true; + } + } + + if (m_did_exec) + { + cpu_type_t process_cpu_type = GetCPUTypeForLocalProcess(m_pid); + if (m_cpu_type != process_cpu_type) + { + if (log) + log->Printf("NativeProcessDarwin::%s(): arch changed from " + "0x%8.8x to 0x%8.8x", __FUNCTION__, m_cpu_type, + process_cpu_type); + m_cpu_type = process_cpu_type; + // TODO figure out if we need to do something here. + // DNBArchProtocol::SetArchitecture (process_cpu_type); + } + m_thread_list.Clear(); + + // TODO hook up breakpoints. + // m_breakpoints.DisableAll(); + } + + if (m_sent_interrupt_signo != 0) + { + if (received_interrupt) + { + if (log) + log->Printf("NativeProcessDarwin::%s(): process " + "successfully interrupted with signal %i", + __FUNCTION__, m_sent_interrupt_signo); + + // Mark that we received the interrupt signal + m_sent_interrupt_signo = 0; + // Now check if we had a case where: + // 1 - We called NativeProcessDarwin::Interrupt() but we stopped + // for another reason. + // 2 - We called NativeProcessDarwin::Resume() (but still + // haven't gotten the interrupt signal). + // 3 - We are now incorrectly stopped because we are handling + // the interrupt signal we missed. + // 4 - We might need to resume if we stopped only with the + // interrupt signal that we never handled. + if (m_auto_resume_signo != 0) + { + // Only auto_resume if we stopped with _only_ the interrupt + // signal. + if (num_task_exceptions == 1) + { + auto_resume = true; + if (log) + log->Printf("NativeProcessDarwin::%s(): auto " + "resuming due to unhandled interrupt " + "signal %i", __FUNCTION__, + m_auto_resume_signo); + } + m_auto_resume_signo = 0; + } + } + else + { + if (log) + log->Printf("NativeProcessDarwin::%s(): didn't get signal " + "%i after MachProcess::Interrupt()", + __FUNCTION__, m_sent_interrupt_signo); + } + } + } + + // Let all threads recover from stopping and do any clean up based + // on the previous thread state (if any). + m_thread_list.ProcessDidStop(*this); + + // Let each thread know of any exceptions + for (i = 0; i < m_exception_messages.size(); ++i) + { + // Let the thread list forward all exceptions on down to each thread. + if (m_exception_messages[i].state.task_port == task) + { + // This exception is for our inferior. + m_thread_list.NotifyException(m_exception_messages[i].state); + } + + if (log) + { + StreamString stream; + m_exception_messages[i].Dump(stream); + stream.Flush(); + log->PutCString(stream.GetString().c_str()); + } + } + + if (log) + { + StreamString stream; + m_thread_list.Dump(stream); + stream.Flush(); + log->PutCString(stream.GetString().c_str()); + } + + bool step_more = false; + if (m_thread_list.ShouldStop(step_more) && (auto_resume == false)) + { + // TODO - need to hook up event system here. !!!! +#if 0 + // Wait for the eEventProcessRunningStateChanged event to be reset + // before changing state to stopped to avoid race condition with + // very fast start/stops. + struct timespec timeout; + + //DNBTimer::OffsetTimeOfDay(&timeout, 0, 250 * 1000); // Wait for 250 ms + DNBTimer::OffsetTimeOfDay(&timeout, 1, 0); // Wait for 250 ms + m_events.WaitForEventsToReset(eEventProcessRunningStateChanged, + &timeout); +#endif + SetState(eStateStopped); + } + else + { + // Resume without checking our current state. + PrivateResume(); + } + + return m_task; +} + +void +NativeProcessDarwin::StartSTDIOThread() +{ + // TODO implement +} + +Error +NativeProcessDarwin::StartWaitpidThread(MainLoop &main_loop) +{ + Error error; + Log *log(GetLogIfAllCategoriesSet(LIBLLDB_LOG_PROCESS)); + + // Strategy: create a thread that sits on waitpid(), waiting for the + // inferior process to die, reaping it in the process. Arrange for + // the thread to have a pipe file descriptor that it can send a byte + // over when the waitpid completes. Have the main loop have a read + // object for the other side of the pipe, and have the callback for + // the read do the process termination message sending. + + // Create a single-direction communication channel. + const bool child_inherits = false; + error = m_waitpid_pipe.CreateNew(child_inherits); + if (error.Fail()) + { + if (log) + log->Printf("NativeProcessDarwin::%s(): failed to create waitpid " + "communication pipe: %s", __FUNCTION__, + error.AsCString()); + return error; + } + + // Hook up the waitpid reader callback. + + // TODO make PipePOSIX derive from IOObject. This is goofy here. + const bool transfer_ownership = false; + auto io_sp = IOObjectSP(new File(m_waitpid_pipe.GetReadFileDescriptor(), + transfer_ownership)); + m_waitpid_reader_handle = + main_loop.RegisterReadObject( + io_sp, + [this](MainLoopBase &){ HandleWaitpidResult(); }, + error); + + // Create the thread. + auto pthread_err = ::pthread_create(&m_waitpid_thread, nullptr, + WaitpidThread, this); + error.SetError(pthread_err, eErrorTypePOSIX); + if (error.Fail()) + { + if (log) + log->Printf("NativeProcessDarwin::%s(): failed to create waitpid " + "handling thread: %u (%s)", __FUNCTION__, + error.GetError(), error.AsCString()); + return error; + } + + return error; +} + +void* +NativeProcessDarwin::WaitpidThread(void *arg) +{ + Log *log(GetLogIfAllCategoriesSet(LIBLLDB_LOG_PROCESS)); + if (!arg) + { + if (log) + log->Printf("NativeProcessDarwin::%s(): cannot run waitpid " + "thread, mandatory process arg was null", __FUNCTION__); + return nullptr; + } + + return reinterpret_cast<NativeProcessDarwin*>(arg)->DoWaitpidThread(); +} + +void +NativeProcessDarwin::MaybeRaiseThreadPriority() +{ +#if defined (__arm__) || defined (__arm64__) || defined (__aarch64__) + struct sched_param thread_param; + int thread_sched_policy; + if (pthread_getschedparam(pthread_self(), &thread_sched_policy, + &thread_param) == 0) + { + thread_param.sched_priority = 47; + pthread_setschedparam(pthread_self(), thread_sched_policy, + &thread_param); + } +#endif +} + +void* +NativeProcessDarwin::DoWaitpidThread() +{ + Log *log(GetLogIfAllCategoriesSet(LIBLLDB_LOG_PROCESS)); + + if (m_pid == LLDB_INVALID_PROCESS_ID) + { + if (log) + log->Printf("NativeProcessDarwin::%s(): inferior process ID is " + "not set, cannot waitpid on it", __FUNCTION__); + return nullptr; + } + + // Name the thread. + pthread_setname_np("waitpid thread"); + + // Ensure we don't get CPU starved. + MaybeRaiseThreadPriority(); + + Error error; + int status = -1; + + while (1) + { + // Do a waitpid. + ::pid_t child_pid = ::waitpid(m_pid, &status, 0); + if (child_pid < 0) + error.SetErrorToErrno(); + if (error.Fail()) + { + if (error.GetError() == EINTR) + { + // This is okay, we can keep going. + if (log) + log->Printf("NativeProcessDarwin::%s(): waitpid(pid = %" + PRIu64 ", &status, 0) interrupted, continuing", + __FUNCTION__, m_pid); + continue; + } + + // This error is not okay, abort. + if (log) + log->Printf("NativeProcessDarwin::%s(): waitpid(pid = %" PRIu64 + ", &status, 0) aborting due to error: %u (%s)", + __FUNCTION__, m_pid, error.GetError(), + error.AsCString()); + break; + } + + // Log the successful result. + if (log) + log->Printf("NativeProcessDarwin::%s(): waitpid(pid = %" PRIu64 + ", &status, 0) => %i, status = %i", __FUNCTION__, + m_pid, child_pid, status); + + // Handle the result. + if (WIFSTOPPED(status)) + { + if (log) + log->Printf("NativeProcessDarwin::%s(): waitpid(pid = %" PRIu64 + ") received a stop, continuing waitpid() loop", + __FUNCTION__, m_pid); + continue; + } + else // if (WIFEXITED(status) || WIFSIGNALED(status)) + { + if (log) + log->Printf("NativeProcessDarwin::%s(pid = %" PRIu64 "): " + "waitpid thread is setting exit status for pid = " + "%i to %i", __FUNCTION__, m_pid, + child_pid, status); + + error = SendInferiorExitStatusToMainLoop(child_pid, status); + return nullptr; + } + } + + // We should never exit as long as our child process is alive. If we + // get here, something completely unexpected went wrong and we should exit. + if (log) + log->Printf("NativeProcessDarwin::%s(): internal error: waitpid thread " + "exited out of its main loop in an unexpected way. pid = %" + PRIu64 ". Sending exit status of -1.", __FUNCTION__, m_pid); + + error = SendInferiorExitStatusToMainLoop((::pid_t)m_pid, -1); + return nullptr; +} + +Error +NativeProcessDarwin::SendInferiorExitStatusToMainLoop(::pid_t pid, int status) +{ + Error error; + Log *log(GetLogIfAllCategoriesSet(LIBLLDB_LOG_PROCESS)); + + size_t bytes_written = 0; + + // Send the pid. + error = m_waitpid_pipe.Write(&pid, sizeof(pid), bytes_written); + if (error.Fail() || (bytes_written < sizeof(pid))) + { + if (log) + log->Printf("NativeProcessDarwin::%s() - failed to write " + "waitpid exiting pid to the pipe. Client will not " + "hear about inferior exit status!", + __FUNCTION__); + return error; + } + + // Send the status. + bytes_written = 0; + error = m_waitpid_pipe.Write(&status, sizeof(status), bytes_written); + if (error.Fail() || (bytes_written < sizeof(status))) + { + if (log) + log->Printf("NativeProcessDarwin::%s() - failed to write " + "waitpid exit result to the pipe. Client will not " + "hear about inferior exit status!", + __FUNCTION__); + } + return error; +} + +Error +NativeProcessDarwin::HandleWaitpidResult() +{ + Error error; + Log *log(GetLogIfAllCategoriesSet(LIBLLDB_LOG_PROCESS)); + + // Read the pid. + const bool notify_status = true; + + ::pid_t pid = -1; + size_t bytes_read = 0; + error = m_waitpid_pipe.Read(&pid, sizeof(pid), bytes_read); + if (error.Fail() || (bytes_read < sizeof(pid))) + { + if (log) + log->Printf("NativeProcessDarwin::%s() - failed to read " + "waitpid exiting pid from the pipe. Will notify " + "as if parent process died with exit status -1.", + __FUNCTION__); + SetExitStatus(eExitTypeInvalid, -1, "failed to receive waitpid result", + notify_status); + return error; + } + + // Read the status. + int status = -1; + error = m_waitpid_pipe.Read(&status, sizeof(status), bytes_read); + if (error.Fail() || (bytes_read < sizeof(status))) + { + if (log) + log->Printf("NativeProcessDarwin::%s() - failed to read " + "waitpid exit status from the pipe. Will notify " + "as if parent process died with exit status -1.", + __FUNCTION__); + SetExitStatus(eExitTypeInvalid, -1, "failed to receive waitpid result", + notify_status); + return error; + } + + // Notify the monitor that our state has changed. + if (log) + log->Printf("NativeProcessDarwin::%s(): main loop received waitpid " + "exit status info: pid=%i (%s), status=%i", + __FUNCTION__, pid, + (pid == m_pid) ? "the inferior" : "not the inferior", + status); + + ExitType exit_type = eExitTypeInvalid; + int exit_status = -1; + + if (WIFEXITED(status)) + { + exit_type = eExitTypeExit; + exit_status = WEXITSTATUS(status); + } + else if (WIFSIGNALED(status)) + { + exit_type = eExitTypeSignal; + exit_status = WTERMSIG(status); + } + + SetExitStatus(exit_type, exit_status, nullptr, notify_status); + return error; +} + +task_t +NativeProcessDarwin::TaskPortForProcessID(Error &error, bool force) const +{ + if ((m_task == TASK_NULL) || force) + { + Log *log(GetLogIfAllCategoriesSet(LIBLLDB_LOG_PROCESS)); + if (m_pid == LLDB_INVALID_PROCESS_ID) + { + if (log) + log->Printf("NativeProcessDarwin::%s(): cannot get task due " + "to invalid pid", __FUNCTION__); + return TASK_NULL; + } + + const uint32_t num_retries = 10; + const uint32_t usec_interval = 10000; + + mach_port_t task_self = mach_task_self(); + task_t task = TASK_NULL; + + for (uint32_t i = 0; i < num_retries; i++) + { + kern_return_t err = ::task_for_pid(task_self, m_pid, &task); + if (err == 0) + { + // Succeeded. Save and return it. + error.Clear(); + m_task = task; + log->Printf("NativeProcessDarwin::%s(): ::task_for_pid(" + "stub_port = 0x%4.4x, pid = %llu, &task) " + "succeeded: inferior task port = 0x%4.4x", + __FUNCTION__, task_self, m_pid, m_task); + return m_task; + } + else + { + // Failed to get the task for the inferior process. + error.SetError(err, eErrorTypeMachKernel); + if (log) + { + log->Printf("NativeProcessDarwin::%s(): ::task_for_pid(" + "stub_port = 0x%4.4x, pid = %llu, &task) " + "failed, err = 0x%8.8x (%s)", + __FUNCTION__, task_self, + m_pid, + err, + error.AsCString()); + } + } + + // Sleep a bit and try again + ::usleep (usec_interval); + } + + // We failed to get the task for the inferior process. + // Ensure that it is cleared out. + m_task = TASK_NULL; + } + return m_task; +} + +void +NativeProcessDarwin::AttachToInferior(MainLoop &mainloop, lldb::pid_t pid, + Error &error) +{ + error.SetErrorString("TODO: implement"); +} + +Error +NativeProcessDarwin::PrivateResume() +{ + Error error; + Log *log(GetLogIfAllCategoriesSet(LIBLLDB_LOG_PROCESS)); + + std::lock_guard<std::recursive_mutex> locker(m_exception_messages_mutex); + m_auto_resume_signo = m_sent_interrupt_signo; + + if (log) + { + if (m_auto_resume_signo) + log->Printf("NativeProcessDarwin::%s(): task 0x%x resuming (with " + "unhandled interrupt signal %i)...", __FUNCTION__, + m_task, m_auto_resume_signo); + else + log->Printf("NativeProcessDarwin::%s(): task 0x%x resuming...", + __FUNCTION__, m_task); + } + + error = ReplyToAllExceptions(); + if (error.Fail()) + { + if (log) + log->Printf("NativeProcessDarwin::%s(): aborting, failed to " + "reply to exceptions: %s", __FUNCTION__, + error.AsCString()); + return error; + } + // bool stepOverBreakInstruction = step; + + // Let the thread prepare to resume and see if any threads want us to + // step over a breakpoint instruction (ProcessWillResume will modify + // the value of stepOverBreakInstruction). + m_thread_list.ProcessWillResume(*this, m_thread_actions); + + // Set our state accordingly + if (m_thread_actions.NumActionsWithState(eStateStepping)) + SetState(eStateStepping); + else + SetState(eStateRunning); + + // Now resume our task. + error = ResumeTask(); + return error; +} + +Error +NativeProcessDarwin::ReplyToAllExceptions() +{ + Error error; + Log *log(GetLogIfAllCategoriesSet(LIBLLDB_LOG_PROCESS | + LIBLLDB_LOG_VERBOSE)); + + TaskPortForProcessID(error); + if (error.Fail()) + { + if (log) + log->Printf("NativeProcessDarwin::%s(): no task port, aborting", + __FUNCTION__); + return error; + } + + std::lock_guard<std::recursive_mutex> locker(m_exception_messages_mutex); + if (m_exception_messages.empty()) + { + // We're done. + return error; + } + + size_t index = 0; + for (auto &message : m_exception_messages) + { + if (log) + { + log->Printf("NativeProcessDarwin::%s(): replying to exception " + "%zu...", __FUNCTION__, index++); + } + + int thread_reply_signal = 0; + + const tid_t tid = + m_thread_list.GetThreadIDByMachPortNumber(message.state + .thread_port); + const ResumeAction *action = nullptr; + if (tid != LLDB_INVALID_THREAD_ID) + action = m_thread_actions.GetActionForThread (tid, false); + + if (action) + { + thread_reply_signal = action->signal; + if (thread_reply_signal) + m_thread_actions.SetSignalHandledForThread(tid); + } + + error = message.Reply(m_pid, m_task, thread_reply_signal); + if (error.Fail() && log) + { + // We log any error here, but we don't stop the exception + // response handling. + log->Printf("NativeProcessDarwin::%s(): failed to reply to " + "exception: %s", __FUNCTION__, error.AsCString()); + error.Clear(); + } + } + + // Erase all exception message as we should have used and replied + // to them all already. + m_exception_messages.clear(); + return error; +} + +Error +NativeProcessDarwin::ResumeTask() +{ + Error error; + Log *log(GetLogIfAllCategoriesSet(LIBLLDB_LOG_PROCESS)); + + TaskPortForProcessID(error); + if (error.Fail()) + { + if (log) + log->Printf("NativeProcessDarwin::%s(): failed to get task port " + "for process when attempting to resume: %s", + __FUNCTION__, error.AsCString()); + return error; + } + if (m_task == TASK_NULL) + { + error.SetErrorString("task port retrieval succeeded but task port is " + "null when attempting to resume the task"); + return error; + } + + if (log) + log->Printf("NativeProcessDarwin::%s(): requesting resume of task " + "0x%4.4x", __FUNCTION__, m_task); + + // Get the BasicInfo struct to verify that we're suspended before we try + // to resume the task. + struct task_basic_info task_info; + error = GetTaskBasicInfo(m_task, &task_info); + if (error.Fail()) + { + if (log) + log->Printf("NativeProcessDarwin::%s(): failed to get task " + "BasicInfo when attempting to resume: %s", + __FUNCTION__, error.AsCString()); + return error; + } + + // task_resume isn't counted like task_suspend calls are, so if the + // task is not suspended, don't try and resume it since it is already + // running + if (task_info.suspend_count > 0) + { + auto mach_err = ::task_resume(m_task); + error.SetError(mach_err, eErrorTypeMachKernel); + if (log) + { + if (error.Success()) + log->Printf("::task_resume(target_task = 0x%4.4x): success", + m_task); + else + log->Printf("::task_resume(target_task = 0x%4.4x) error: %s", + m_task, error.AsCString()); + } + } + else + { + if (log) + log->Printf("::task_resume(target_task = 0x%4.4x): ignored, " + "already running", m_task); + } + + return error; +} + +bool +NativeProcessDarwin::IsTaskValid() const +{ + if (m_task == TASK_NULL) + return false; + + struct task_basic_info task_info; + return GetTaskBasicInfo(m_task, &task_info).Success(); +} + +bool +NativeProcessDarwin::IsTaskValid(task_t task) const +{ + if (task == TASK_NULL) + return false; + + struct task_basic_info task_info; + return GetTaskBasicInfo(task, &task_info).Success(); +} + +mach_port_t +NativeProcessDarwin::GetExceptionPort() const +{ + return m_exception_port; +} + +bool +NativeProcessDarwin::IsExceptionPortValid () const +{ + return MACH_PORT_VALID(m_exception_port); +} + +Error +NativeProcessDarwin::GetTaskBasicInfo(task_t task, + struct task_basic_info *info) const +{ + Error error; + Log *log(GetLogIfAllCategoriesSet(LIBLLDB_LOG_PROCESS)); + + // Validate args. + if (info == NULL) + { + error.SetErrorStringWithFormat("NativeProcessDarwin::%s(): mandatory " + "info arg is null", __FUNCTION__); + return error; + } + + // Grab the task if we don't already have it. + if (task == TASK_NULL) + { + error.SetErrorStringWithFormat("NativeProcessDarwin::%s(): given task " + "is invalid", __FUNCTION__); + } + + mach_msg_type_number_t count = TASK_BASIC_INFO_COUNT; + auto err = ::task_info(m_task, TASK_BASIC_INFO, (task_info_t)info, &count); + error.SetError(err, eErrorTypeMachKernel); + if (error.Fail()) + { + if (log) + log->Printf("::task_info(target_task = 0x%4.4x, " + "flavor = TASK_BASIC_INFO, task_info_out => %p, " + "task_info_outCnt => %u) failed: %u (%s)", m_task, info, + count, error.GetError(), error.AsCString()); + return error; + } + + Log *verbose_log(GetLogIfAllCategoriesSet(LIBLLDB_LOG_PROCESS | + LIBLLDB_LOG_VERBOSE)); + if (verbose_log) + { + float user = (float)info->user_time.seconds + + (float)info->user_time.microseconds / 1000000.0f; + float system = (float)info->user_time.seconds + + (float)info->user_time.microseconds / 1000000.0f; + verbose_log->Printf("task_basic_info = { suspend_count = %i, " + "virtual_size = 0x%8.8llx, resident_size = " + "0x%8.8llx, user_time = %f, system_time = %f }", + info->suspend_count, + (uint64_t)info->virtual_size, + (uint64_t)info->resident_size, + user, system); + } + return error; +} + +Error +NativeProcessDarwin::SuspendTask() +{ + Error error; + Log *log(GetLogIfAllCategoriesSet(LIBLLDB_LOG_PROCESS)); + + if (m_task == TASK_NULL) + { + error.SetErrorString("task port is null, cannot suspend task"); + if (log) + log->Printf("NativeProcessDarwin::%s() failed: %s", + __FUNCTION__, error.AsCString()); + return error; + } + + auto mach_err = ::task_suspend(m_task); + error.SetError(mach_err, eErrorTypeMachKernel); + if (error.Fail() && log) + log->Printf("::task_suspend(target_task = 0x%4.4x)", m_task); + + return error; +} + +Error +NativeProcessDarwin::Resume(const ResumeActionList &resume_actions) +{ + Error error; + Log *log(GetLogIfAllCategoriesSet(LIBLLDB_LOG_PROCESS)); + + if (log) + log->Printf("NativeProcessDarwin::%s() called", __FUNCTION__); + + if (CanResume()) + { + m_thread_actions = resume_actions; + error = PrivateResume(); + return error; + } + + auto state = GetState(); + if (state == eStateRunning) + { + if (log) + log->Printf("NativeProcessDarwin::%s(): task 0x%x is already " + "running, ignoring...", __FUNCTION__, + TaskPortForProcessID(error)); + return error; + } + + // We can't resume from this state. + error.SetErrorStringWithFormat("task 0x%x has state %s, can't resume", + TaskPortForProcessID(error), + StateAsCString(state)); + return error; +} + +Error +NativeProcessDarwin::Halt() +{ + Error error; + error.SetErrorString("TODO: implement"); + return error; +} + +Error +NativeProcessDarwin::Detach() +{ + Error error; + error.SetErrorString("TODO: implement"); + return error; +} + +Error +NativeProcessDarwin::Signal(int signo) +{ + Error error; + error.SetErrorString("TODO: implement"); + return error; +} + +Error +NativeProcessDarwin::Interrupt() +{ + Error error; + error.SetErrorString("TODO: implement"); + return error; +} + +Error +NativeProcessDarwin::Kill() +{ + Error error; + error.SetErrorString("TODO: implement"); + return error; +} + +Error +NativeProcessDarwin::GetMemoryRegionInfo(lldb::addr_t load_addr, + MemoryRegionInfo &range_info) +{ + Error error; + error.SetErrorString("TODO: implement"); + return error; +} + +Error +NativeProcessDarwin::ReadMemory(lldb::addr_t addr, void *buf, size_t size, + size_t &bytes_read) +{ + Error error; + error.SetErrorString("TODO: implement"); + return error; +} + +Error +NativeProcessDarwin::ReadMemoryWithoutTrap(lldb::addr_t addr, void *buf, + size_t size, size_t &bytes_read) +{ + Error error; + error.SetErrorString("TODO: implement"); + return error; +} + +Error +NativeProcessDarwin::WriteMemory(lldb::addr_t addr, const void *buf, + size_t size, size_t &bytes_written) +{ + Error error; + error.SetErrorString("TODO: implement"); + return error; +} + +Error +NativeProcessDarwin::AllocateMemory(size_t size, uint32_t permissions, + lldb::addr_t &addr) +{ + Error error; + error.SetErrorString("TODO: implement"); + return error; +} + +Error +NativeProcessDarwin::DeallocateMemory(lldb::addr_t addr) +{ + Error error; + error.SetErrorString("TODO: implement"); + return error; +} + +lldb::addr_t +NativeProcessDarwin::GetSharedLibraryInfoAddress() +{ + return LLDB_INVALID_ADDRESS; +} + +size_t +NativeProcessDarwin::UpdateThreads() +{ + return 0; +} + +bool +NativeProcessDarwin::GetArchitecture(ArchSpec &arch) const +{ + return false; +} + +Error +NativeProcessDarwin::SetBreakpoint(lldb::addr_t addr, uint32_t size, + bool hardware) +{ + Error error; + error.SetErrorString("TODO: implement"); + return error; +} + +void +NativeProcessDarwin::DoStopIDBumped(uint32_t newBumpId) +{ +} + +Error +NativeProcessDarwin::GetLoadedModuleFileSpec(const char* module_path, + FileSpec& file_spec) +{ + Error error; + error.SetErrorString("TODO: implement"); + return error; +} + +Error +NativeProcessDarwin::GetFileLoadAddress(const llvm::StringRef& file_name, + lldb::addr_t& load_addr) +{ + Error error; + error.SetErrorString("TODO: implement"); + return error; +} + +// ----------------------------------------------------------------- +// NativeProcessProtocol protected interface +// ----------------------------------------------------------------- +Error +NativeProcessDarwin::GetSoftwareBreakpointTrapOpcode(size_t + trap_opcode_size_hint, + size_t &actual_opcode_size, + const uint8_t + *&trap_opcode_bytes) +{ + Error error; + error.SetErrorString("TODO: implement"); + return error; +} diff --git a/lldb/source/Plugins/Process/Darwin/NativeProcessDarwin.h b/lldb/source/Plugins/Process/Darwin/NativeProcessDarwin.h new file mode 100644 index 00000000000..13077452245 --- /dev/null +++ b/lldb/source/Plugins/Process/Darwin/NativeProcessDarwin.h @@ -0,0 +1,479 @@ +//===-- NativeProcessDarwin.h --------------------------------- -*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef NativeProcessDarwin_h +#define NativeProcessDarwin_h + +// NOTE: this code should only be compiled on Apple Darwin systems. It is +// not cross-platform code and is not intended to build on any other platform. +// Therefore, platform-specific headers and code are okay here. + +// C includes +#include <mach/mach_types.h> + +// C++ includes +#include <mutex> +#include <unordered_set> + +// Other libraries and framework includes +#include "lldb/Core/ArchSpec.h" +#include "lldb/lldb-types.h" +#include "lldb/Host/common/NativeProcessProtocol.h" +#include "lldb/Host/Debug.h" +#include "lldb/Host/FileSpec.h" +#include "lldb/Host/HostThread.h" +#include "lldb/Host/Pipe.h" +#include "lldb/Target/MemoryRegionInfo.h" + +#include "NativeThreadListDarwin.h" +#include "LaunchFlavor.h" +#include "MachException.h" +#include "NativeThreadDarwin.h" + +namespace lldb_private { + class Error; + class Scalar; + + namespace process_darwin { + + /// @class NativeProcessDarwin + /// @brief Manages communication with the inferior (debugee) process. + /// + /// Upon construction, this class prepares and launches an inferior + /// process for debugging. + /// + /// Changes in the inferior process state are broadcasted. + class NativeProcessDarwin: public NativeProcessProtocol + { + friend Error + NativeProcessProtocol::Launch(ProcessLaunchInfo &launch_info, + NativeDelegate &native_delegate, + MainLoop &mainloop, + NativeProcessProtocolSP &process_sp); + + friend Error + NativeProcessProtocol::Attach(lldb::pid_t pid, + NativeProcessProtocol::NativeDelegate + &native_delegate, + MainLoop &mainloop, + NativeProcessProtocolSP &process_sp); + + public: + + ~NativeProcessDarwin() override; + + // ----------------------------------------------------------------- + // NativeProcessProtocol Interface + // ----------------------------------------------------------------- + Error + Resume(const ResumeActionList &resume_actions) override; + + Error + Halt() override; + + Error + Detach() override; + + Error + Signal(int signo) override; + + Error + Interrupt() override; + + Error + Kill() override; + + Error + GetMemoryRegionInfo(lldb::addr_t load_addr, MemoryRegionInfo + &range_info) override; + + Error + ReadMemory(lldb::addr_t addr, void *buf, size_t size, + size_t &bytes_read) override; + + Error + ReadMemoryWithoutTrap(lldb::addr_t addr, void *buf, size_t size, + size_t &bytes_read) override; + + Error + WriteMemory(lldb::addr_t addr, const void *buf, size_t size, + size_t &bytes_written) override; + + Error + AllocateMemory(size_t size, uint32_t permissions, + lldb::addr_t &addr) override; + + Error + DeallocateMemory(lldb::addr_t addr) override; + + lldb::addr_t + GetSharedLibraryInfoAddress() override; + + size_t + UpdateThreads() override; + + bool + GetArchitecture(ArchSpec &arch) const override; + + Error + SetBreakpoint(lldb::addr_t addr, uint32_t size, + bool hardware) override; + + void + DoStopIDBumped(uint32_t newBumpId) override; + + Error + GetLoadedModuleFileSpec(const char* module_path, + FileSpec& file_spec) override; + + Error + GetFileLoadAddress(const llvm::StringRef& file_name, + lldb::addr_t& load_addr) override; + + NativeThreadDarwinSP + GetThreadByID(lldb::tid_t id); + + task_t + GetTask() const + { + return m_task; + } + + // ----------------------------------------------------------------- + // Interface used by NativeRegisterContext-derived classes. + // ----------------------------------------------------------------- + static Error + PtraceWrapper(int req, + lldb::pid_t pid, + void *addr = nullptr, + void *data = nullptr, + size_t data_size = 0, + long *result = nullptr); + + bool + SupportHardwareSingleStepping() const; + + protected: + // ----------------------------------------------------------------- + // NativeProcessProtocol protected interface + // ----------------------------------------------------------------- + Error + GetSoftwareBreakpointTrapOpcode(size_t trap_opcode_size_hint, + size_t &actual_opcode_size, + const uint8_t *&trap_opcode_bytes) + override; + + private: + + // ----------------------------------------------------------------- + /// Mach task-related Member Variables + // ----------------------------------------------------------------- + + // The task port for the inferior process. + mutable task_t m_task; + + // True if the inferior process did an exec since we started + // monitoring it. + bool m_did_exec; + + // The CPU type of this process. + mutable cpu_type_t m_cpu_type; + + // ----------------------------------------------------------------- + /// Exception/Signal Handling Member Variables + // ----------------------------------------------------------------- + + // Exception port on which we will receive child exceptions + mach_port_t m_exception_port; + + // Saved state of the child exception port prior to us installing + // our own intercepting port. + MachException::PortInfo m_exc_port_info; + + // The thread that runs the Mach exception read and reply handler. + pthread_t m_exception_thread; + + // TODO see if we can remove this if we get the exception collection + // and distribution to happen in a single-threaded fashion. + std::recursive_mutex m_exception_messages_mutex; + + // A collection of exception messages caught when listening to the + // exception port. + MachException::Message::collection m_exception_messages; + + // When we call MachProcess::Interrupt(), we want to send this + // signal (if non-zero). + int m_sent_interrupt_signo; + + // If we resume the process and still haven't received our + // interrupt signal (if this is non-zero). + int m_auto_resume_signo; + + // ----------------------------------------------------------------- + /// Thread-related Member Variables + // ----------------------------------------------------------------- + NativeThreadListDarwin m_thread_list; + ResumeActionList m_thread_actions; + + // ----------------------------------------------------------------- + /// Process Lifetime Member Variable + // ----------------------------------------------------------------- + + // The pipe over which the waitpid thread and the main loop will + // communicate. + Pipe m_waitpid_pipe; + + // The thread that runs the waitpid handler. + pthread_t m_waitpid_thread; + + // waitpid reader callback handle. + MainLoop::ReadHandleUP m_waitpid_reader_handle; + +#if 0 + ArchSpec m_arch; + + LazyBool m_supports_mem_region; + std::vector<MemoryRegionInfo> m_mem_region_cache; + + lldb::tid_t m_pending_notification_tid; + + // List of thread ids stepping with a breakpoint with the address of + // the relevan breakpoint + std::map<lldb::tid_t, lldb::addr_t> + m_threads_stepping_with_breakpoint; +#endif + + // ----------------------------------------------------------------- + // Private Instance Methods + // ----------------------------------------------------------------- + NativeProcessDarwin(lldb::pid_t pid, int pty_master_fd); + + // ----------------------------------------------------------------- + /// Finalize the launch. + /// + /// This method associates the NativeProcessDarwin instance with + /// the host process that was just launched. It peforms actions + /// like attaching a listener to the inferior exception port, + /// ptracing the process, and the like. + /// + /// @param[in] launch_flavor + /// The launch flavor that was used to launch the process. + /// + /// @param[in] main_loop + /// The main loop that will run the process monitor. Work + /// that needs to be done (e.g. reading files) gets registered + /// here along with callbacks to process the work. + /// + /// @return + /// Any error that occurred during the aforementioned + /// operations. Failure here will force termination of the + /// launched process and debugging session. + // ----------------------------------------------------------------- + Error + FinalizeLaunch(LaunchFlavor launch_flavor, MainLoop &main_loop); + + Error + SaveExceptionPortInfo(); + + void + ExceptionMessageReceived(const MachException::Message &message); + + void + MaybeRaiseThreadPriority(); + + Error + StartExceptionThread(); + + Error + SendInferiorExitStatusToMainLoop(::pid_t pid, int status); + + Error + HandleWaitpidResult(); + + bool + ProcessUsingSpringBoard() const; + + bool + ProcessUsingBackBoard() const; + + static void* + ExceptionThread(void *arg); + + void* + DoExceptionThread(); + + lldb::addr_t + GetDYLDAllImageInfosAddress(Error &error) const; + + static uint32_t + GetCPUTypeForLocalProcess(::pid_t pid); + + uint32_t + GetCPUType() const; + + task_t + ExceptionMessageBundleComplete(); + + void + StartSTDIOThread(); + + Error + StartWaitpidThread(MainLoop &main_loop); + + static void* + WaitpidThread(void *arg); + + void* + DoWaitpidThread(); + + task_t + TaskPortForProcessID(Error &error, bool force = false) const; + + /// Attaches to an existing process. Forms the + /// implementation of Process::DoAttach. + void + AttachToInferior(MainLoop &mainloop, lldb::pid_t pid, Error &error); + + ::pid_t + Attach(lldb::pid_t pid, Error &error); + + Error + PrivateResume(); + + Error + ReplyToAllExceptions(); + + Error + ResumeTask(); + + bool + IsTaskValid() const; + + bool + IsTaskValid(task_t task) const; + + mach_port_t + GetExceptionPort() const; + + bool + IsExceptionPortValid () const; + + Error + GetTaskBasicInfo(task_t task, struct task_basic_info *info) const; + + Error + SuspendTask(); + + static Error + SetDefaultPtraceOpts(const lldb::pid_t); + + static void * + MonitorThread(void *baton); + + void + MonitorCallback(lldb::pid_t pid, bool exited, int signal, + int status); + + void + WaitForNewThread(::pid_t tid); + + void + MonitorSIGTRAP(const siginfo_t &info, NativeThreadDarwin &thread); + + void + MonitorTrace(NativeThreadDarwin &thread); + + void + MonitorBreakpoint(NativeThreadDarwin &thread); + + void + MonitorWatchpoint(NativeThreadDarwin &thread, uint32_t wp_index); + + void + MonitorSignal(const siginfo_t &info, NativeThreadDarwin &thread, + bool exited); + + Error + SetupSoftwareSingleStepping(NativeThreadDarwin &thread); + +#if 0 + static ::ProcessMessage::CrashReason + GetCrashReasonForSIGSEGV(const siginfo_t *info); + + static ::ProcessMessage::CrashReason + GetCrashReasonForSIGILL(const siginfo_t *info); + + static ::ProcessMessage::CrashReason + GetCrashReasonForSIGFPE(const siginfo_t *info); + + static ::ProcessMessage::CrashReason + GetCrashReasonForSIGBUS(const siginfo_t *info); +#endif + + bool + HasThreadNoLock(lldb::tid_t thread_id); + + bool + StopTrackingThread(lldb::tid_t thread_id); + + NativeThreadDarwinSP + AddThread(lldb::tid_t thread_id); + + Error + GetSoftwareBreakpointPCOffset(uint32_t &actual_opcode_size); + + Error + FixupBreakpointPCAsNeeded(NativeThreadDarwin &thread); + + /// Writes a siginfo_t structure corresponding to the given thread + /// ID to the memory region pointed to by @p siginfo. + Error + GetSignalInfo(lldb::tid_t tid, void *siginfo); + + /// Writes the raw event message code (vis-a-vis PTRACE_GETEVENTMSG) + /// corresponding to the given thread ID to the memory pointed to + /// by @p message. + Error + GetEventMessage(lldb::tid_t tid, unsigned long *message); + + void + NotifyThreadDeath(lldb::tid_t tid); + + Error + Detach(lldb::tid_t tid); + + + // This method is requests a stop on all threads which are still + // running. It sets up a deferred delegate notification, which will + // fire once threads report as stopped. The triggerring_tid will be + // set as the current thread (main stop reason). + void + StopRunningThreads(lldb::tid_t triggering_tid); + + // Notify the delegate if all threads have stopped. + void SignalIfAllThreadsStopped(); + + // Resume the given thread, optionally passing it the given signal. + // The type of resume operation (continue, single-step) depends on + // the state parameter. + Error + ResumeThread(NativeThreadDarwin &thread, lldb::StateType state, + int signo); + + void + ThreadWasCreated(NativeThreadDarwin &thread); + + void + SigchldHandler(); + }; + + } // namespace process_darwin +} // namespace lldb_private + +#endif /* NativeProcessDarwin_h */ diff --git a/lldb/source/Plugins/Process/Darwin/NativeThreadDarwin.cpp b/lldb/source/Plugins/Process/Darwin/NativeThreadDarwin.cpp new file mode 100644 index 00000000000..08d1a8da92b --- /dev/null +++ b/lldb/source/Plugins/Process/Darwin/NativeThreadDarwin.cpp @@ -0,0 +1,324 @@ +//===-- NativeThreadDarwin.cpp -------------------------------- -*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "NativeThreadDarwin.h" + +// C includes +#include <libproc.h> + +// LLDB includes +#include "lldb/Core/Stream.h" + +#include "NativeProcessDarwin.h" + +using namespace lldb; +using namespace lldb_private; +using namespace lldb_private::process_darwin; + +uint64_t +NativeThreadDarwin::GetGloballyUniqueThreadIDForMachPortID( + ::thread_t mach_port_id) +{ + thread_identifier_info_data_t tident; + mach_msg_type_number_t tident_count = THREAD_IDENTIFIER_INFO_COUNT; + + auto mach_err = ::thread_info(mach_port_id, THREAD_IDENTIFIER_INFO, + (thread_info_t) &tident, &tident_count); + if (mach_err != KERN_SUCCESS) + { + // When we fail to get thread info for the supposed port, assume it is + // really a globally unique thread id already, or return the best thing + // we can, which is the thread port. + return mach_port_id; + } + return tident.thread_id; +} + +NativeThreadDarwin::NativeThreadDarwin(NativeProcessDarwin *process, + bool is_64_bit, + lldb::tid_t unique_thread_id, + ::thread_t mach_thread_port) : + NativeThreadProtocol(process, unique_thread_id), + m_mach_thread_port(mach_thread_port), + m_basic_info(), + m_proc_threadinfo() +{ +} + +bool +NativeThreadDarwin::GetIdentifierInfo() +{ + // Don't try to get the thread info once and cache it for the life of the thread. It changes over time, for instance + // if the thread name changes, then the thread_handle also changes... So you have to refetch it every time. + mach_msg_type_number_t count = THREAD_IDENTIFIER_INFO_COUNT; + kern_return_t kret = ::thread_info(m_mach_thread_port, + THREAD_IDENTIFIER_INFO, + (thread_info_t) &m_ident_info, &count); + return kret == KERN_SUCCESS; + + return false; +} + +std::string +NativeThreadDarwin::GetName() +{ + std::string name; + + if (GetIdentifierInfo()) + { + auto process_sp = GetProcess(); + if (!process_sp) + { + name = "<unavailable>"; + return name; + } + + int len = ::proc_pidinfo(process_sp->GetID(), PROC_PIDTHREADINFO, + m_ident_info.thread_handle, &m_proc_threadinfo, + sizeof(m_proc_threadinfo)); + + if (len && m_proc_threadinfo.pth_name[0]) + name = m_proc_threadinfo.pth_name; + } + return name; +} + +lldb::StateType +NativeThreadDarwin::GetState() +{ + // TODO implement + return eStateInvalid; +} + +bool +NativeThreadDarwin::GetStopReason(ThreadStopInfo &stop_info, + std::string& description) +{ + // TODO implement + return false; +} + +NativeRegisterContextSP +NativeThreadDarwin::GetRegisterContext() +{ + // TODO implement + return NativeRegisterContextSP(); +} + +Error +NativeThreadDarwin::SetWatchpoint(lldb::addr_t addr, size_t size, + uint32_t watch_flags, bool hardware) +{ + Error error; + error.SetErrorString("not yet implemented"); + return error; +} + +Error +NativeThreadDarwin::RemoveWatchpoint(lldb::addr_t addr) +{ + Error error; + error.SetErrorString("not yet implemented"); + return error; +} + +void +NativeThreadDarwin::Dump(Stream &stream) const +{ + // This is what we really want once we have the thread class wired up. +#if 0 + DNBLogThreaded("[%3u] #%3u tid: 0x%8.8" PRIx64 ", pc: 0x%16.16" PRIx64 ", sp: 0x%16.16" PRIx64 ", user: %d.%6.6d, system: %d.%6.6d, cpu: %2d, policy: %2d, run_state: %2d (%s), flags: %2d, suspend_count: %2d (current %2d), sleep_time: %d", + index, + m_seq_id, + m_unique_id, + GetPC(INVALID_NUB_ADDRESS), + GetSP(INVALID_NUB_ADDRESS), + m_basic_info.user_time.seconds, m_basic_info.user_time.microseconds, + m_basic_info.system_time.seconds, m_basic_info.system_time.microseconds, + m_basic_info.cpu_usage, + m_basic_info.policy, + m_basic_info.run_state, + thread_run_state, + m_basic_info.flags, + m_basic_info.suspend_count, m_suspend_count, + m_basic_info.sleep_time); + +#else + // Here's all we have right now. + stream.Printf("tid: 0x%8.8" PRIx64 ", thread port: 0x%4.4x", + GetID(), m_mach_thread_port); +#endif +} + +bool +NativeThreadDarwin::NotifyException(MachException::Data &exc) +{ + // TODO implement this. +#if 0 + // Allow the arch specific protocol to process (MachException::Data &)exc + // first before possible reassignment of m_stop_exception with exc. + // See also MachThread::GetStopException(). + bool handled = m_arch_ap->NotifyException(exc); + + if (m_stop_exception.IsValid()) + { + // We may have more than one exception for a thread, but we need to + // only remember the one that we will say is the reason we stopped. + // We may have been single stepping and also gotten a signal exception, + // so just remember the most pertinent one. + if (m_stop_exception.IsBreakpoint()) + m_stop_exception = exc; + } + else + { + m_stop_exception = exc; + } + + return handled; +#else + // Pretend we handled it. + return true; +#endif +} + +bool +NativeThreadDarwin::ShouldStop(bool &step_more) const +{ + // TODO: implement this +#if 0 + // See if this thread is at a breakpoint? + DNBBreakpoint *bp = CurrentBreakpoint(); + + if (bp) + { + // This thread is sitting at a breakpoint, ask the breakpoint + // if we should be stopping here. + return true; + } + else + { + if (m_arch_ap->StepNotComplete()) + { + step_more = true; + return false; + } + // The thread state is used to let us know what the thread was + // trying to do. MachThread::ThreadWillResume() will set the + // thread state to various values depending if the thread was + // the current thread and if it was to be single stepped, or + // resumed. + if (GetState() == eStateRunning) + { + // If our state is running, then we should continue as we are in + // the process of stepping over a breakpoint. + return false; + } + else + { + // Stop if we have any kind of valid exception for this + // thread. + if (GetStopException().IsValid()) + return true; + } + } + return false; +#else + return false; +#endif +} + +void +NativeThreadDarwin::ThreadDidStop() +{ + // TODO implement this. +#if 0 + // This thread has existed prior to resuming under debug nub control, + // and has just been stopped. Do any cleanup that needs to be done + // after running. + + // The thread state and breakpoint will still have the same values + // as they had prior to resuming the thread, so it makes it easy to check + // if we were trying to step a thread, or we tried to resume while being + // at a breakpoint. + + // When this method gets called, the process state is still in the + // state it was in while running so we can act accordingly. + m_arch_ap->ThreadDidStop(); + + + // We may have suspended this thread so the primary thread could step + // without worrying about race conditions, so lets restore our suspend + // count. + RestoreSuspendCountAfterStop(); + + // Update the basic information for a thread + MachThread::GetBasicInfo(m_mach_port_number, &m_basic_info); + + if (m_basic_info.suspend_count > 0) + SetState(eStateSuspended); + else + SetState(eStateStopped); +#endif +} + +bool +NativeThreadDarwin::MachPortNumberIsValid(::thread_t thread) +{ + return thread != (::thread_t)(0); +} + +const struct thread_basic_info * +NativeThreadDarwin::GetBasicInfo() const +{ + if (GetBasicInfo(m_mach_thread_port, &m_basic_info)) + return &m_basic_info; + return NULL; +} + +bool +NativeThreadDarwin::GetBasicInfo(::thread_t thread, + struct thread_basic_info *basicInfoPtr) +{ + if (MachPortNumberIsValid(thread)) + { + unsigned int info_count = THREAD_BASIC_INFO_COUNT; + kern_return_t err = ::thread_info (thread, THREAD_BASIC_INFO, (thread_info_t) basicInfoPtr, &info_count); + if (err == KERN_SUCCESS) + return true; + } + ::memset (basicInfoPtr, 0, sizeof (struct thread_basic_info)); + return false; +} + +bool +NativeThreadDarwin::IsUserReady() const +{ + if (m_basic_info.run_state == 0) + GetBasicInfo(); + + switch (m_basic_info.run_state) + { + default: + case TH_STATE_UNINTERRUPTIBLE: + break; + + case TH_STATE_RUNNING: + case TH_STATE_STOPPED: + case TH_STATE_WAITING: + case TH_STATE_HALTED: + return true; + } + return false; +} + +NativeProcessDarwinSP +NativeThreadDarwin::GetNativeProcessDarwinSP() +{ + return std::static_pointer_cast<NativeProcessDarwin>(GetProcess()); +} diff --git a/lldb/source/Plugins/Process/Darwin/NativeThreadDarwin.h b/lldb/source/Plugins/Process/Darwin/NativeThreadDarwin.h new file mode 100644 index 00000000000..efdf79ac3e0 --- /dev/null +++ b/lldb/source/Plugins/Process/Darwin/NativeThreadDarwin.h @@ -0,0 +1,216 @@ +//===-- NativeThreadDarwin.h ---------------------------------- -*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef NativeThreadDarwin_H +#define NativeThreadDarwin_H + +// C includes +#include <mach/mach_types.h> +#include <sched.h> +#include <sys/proc_info.h> + +// C++ includes +#include <map> +#include <memory> +#include <string> + +// LLDB includes +#include "lldb/lldb-private-forward.h" +#include "lldb/Host/common/NativeThreadProtocol.h" + +#include "MachException.h" + +namespace lldb_private { +namespace process_darwin { + +class NativeProcessDarwin; +using NativeProcessDarwinSP = std::shared_ptr<NativeProcessDarwin>; + +class NativeThreadListDarwin; + +class NativeThreadDarwin : public NativeThreadProtocol +{ + friend class NativeProcessDarwin; + friend class NativeThreadListDarwin; + +public: + + static uint64_t + GetGloballyUniqueThreadIDForMachPortID(::thread_t mach_port_id); + + NativeThreadDarwin(NativeProcessDarwin *process, bool is_64_bit, + lldb::tid_t unique_thread_id = 0, + ::thread_t mach_thread_port = 0); + + // ----------------------------------------------------------------- + // NativeThreadProtocol Interface + // ----------------------------------------------------------------- + std::string + GetName() override; + + lldb::StateType + GetState () override; + + bool + GetStopReason(ThreadStopInfo &stop_info, + std::string& description) override; + + NativeRegisterContextSP + GetRegisterContext() override; + + Error + SetWatchpoint(lldb::addr_t addr, size_t size, + uint32_t watch_flags, bool hardware) override; + + Error + RemoveWatchpoint(lldb::addr_t addr) override; + + // ----------------------------------------------------------------- + // New methods that are fine for others to call. + // ----------------------------------------------------------------- + void + Dump(Stream &stream) const; + +private: + // ----------------------------------------------------------------- + // Interface for friend classes + // ----------------------------------------------------------------- + + /// Resumes the thread. If @p signo is anything but + /// LLDB_INVALID_SIGNAL_NUMBER, deliver that signal to the thread. + Error + Resume(uint32_t signo); + + /// Single steps the thread. If @p signo is anything but + /// LLDB_INVALID_SIGNAL_NUMBER, deliver that signal to the thread. + Error + SingleStep(uint32_t signo); + + bool + NotifyException(MachException::Data &exc); + + bool + ShouldStop(bool &step_more) const; + + void + ThreadDidStop(); + + void + SetStoppedBySignal(uint32_t signo, const siginfo_t *info = nullptr); + + /// Return true if the thread is stopped. + /// If stopped by a signal, indicate the signo in the signo + /// argument. Otherwise, return LLDB_INVALID_SIGNAL_NUMBER. + bool + IsStopped (int *signo); + + const struct thread_basic_info * + GetBasicInfo() const; + + static bool + GetBasicInfo(::thread_t thread, struct thread_basic_info *basicInfoPtr); + + bool + IsUserReady() const; + + void + SetStoppedByExec (); + + void + SetStoppedByBreakpoint (); + + void + SetStoppedByWatchpoint (uint32_t wp_index); + + bool + IsStoppedAtBreakpoint (); + + bool + IsStoppedAtWatchpoint (); + + void + SetStoppedByTrace (); + + void + SetStoppedWithNoReason (); + + void + SetExited (); + + Error + RequestStop (); + + // ------------------------------------------------------------------------- + /// Return the mach thread port number for this thread. + /// + /// @return + /// The mach port number for this thread. Returns NULL_THREAD + /// when the thread is invalid. + // ------------------------------------------------------------------------- + thread_t + GetMachPortNumber() const + { + return m_mach_thread_port; + } + + static bool + MachPortNumberIsValid(::thread_t thread); + + // --------------------------------------------------------------------- + // Private interface + // --------------------------------------------------------------------- + bool + GetIdentifierInfo(); + + void + MaybeLogStateChange (lldb::StateType new_state); + + NativeProcessDarwinSP + GetNativeProcessDarwinSP(); + + void + SetStopped(); + + inline void + MaybePrepareSingleStepWorkaround(); + + inline void + MaybeCleanupSingleStepWorkaround(); + + // ----------------------------------------------------------------- + // Member Variables + // ----------------------------------------------------------------- + + // The mach thread port for the thread. + ::thread_t m_mach_thread_port; + + // The most recently-retrieved thread basic info. + mutable ::thread_basic_info m_basic_info; + + struct proc_threadinfo m_proc_threadinfo; + + thread_identifier_info_data_t m_ident_info; + +#if 0 + lldb::StateType m_state; + ThreadStopInfo m_stop_info; + NativeRegisterContextSP m_reg_context_sp; + std::string m_stop_description; + using WatchpointIndexMap = std::map<lldb::addr_t, uint32_t>; + WatchpointIndexMap m_watchpoint_index_map; + // cpu_set_t m_original_cpu_set; // For single-step workaround. +#endif +}; + +typedef std::shared_ptr<NativeThreadDarwin> NativeThreadDarwinSP; + +} // namespace process_darwin +} // namespace lldb_private + +#endif // #ifndef NativeThreadDarwin_H diff --git a/lldb/source/Plugins/Process/Darwin/NativeThreadListDarwin.cpp b/lldb/source/Plugins/Process/Darwin/NativeThreadListDarwin.cpp new file mode 100644 index 00000000000..034c0c9f1fa --- /dev/null +++ b/lldb/source/Plugins/Process/Darwin/NativeThreadListDarwin.cpp @@ -0,0 +1,745 @@ +//===-- NativeThreadListDarwin.cpp ------------------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// Created by Greg Clayton on 6/19/07. +// +//===----------------------------------------------------------------------===// + +#include "NativeThreadListDarwin.h" + +// C includes +#include <inttypes.h> +#include <mach/vm_map.h> +#include <sys/sysctl.h> + +// LLDB includes +#include "lldb/Core/Error.h" +#include "lldb/Core/Log.h" +#include "lldb/Core/Stream.h" +#include "lldb/lldb-enumerations.h" + +#include "NativeProcessDarwin.h" +#include "NativeThreadDarwin.h" + +using namespace lldb; +using namespace lldb_private; +using namespace lldb_private::process_darwin; + +NativeThreadListDarwin::NativeThreadListDarwin() : + m_threads(), + m_threads_mutex(), + m_is_64_bit(false) +{ +} + +NativeThreadListDarwin::~NativeThreadListDarwin() +{ +} + +// These methods will be accessed directly from NativeThreadDarwin +#if 0 +nub_state_t +NativeThreadListDarwin::GetState(nub_thread_t tid) +{ + MachThreadSP thread_sp (GetThreadByID (tid)); + if (thread_sp) + return thread_sp->GetState(); + return eStateInvalid; +} + +const char * +NativeThreadListDarwin::GetName (nub_thread_t tid) +{ + MachThreadSP thread_sp (GetThreadByID (tid)); + if (thread_sp) + return thread_sp->GetName(); + return NULL; +} +#endif + +// TODO: figure out if we need to add this to NativeThreadDarwin yet. +#if 0 +ThreadInfo::QoS +NativeThreadListDarwin::GetRequestedQoS (nub_thread_t tid, nub_addr_t tsd, uint64_t dti_qos_class_index) +{ + MachThreadSP thread_sp (GetThreadByID (tid)); + if (thread_sp) + return thread_sp->GetRequestedQoS(tsd, dti_qos_class_index); + return ThreadInfo::QoS(); +} + +nub_addr_t +NativeThreadListDarwin::GetPThreadT (nub_thread_t tid) +{ + MachThreadSP thread_sp (GetThreadByID (tid)); + if (thread_sp) + return thread_sp->GetPThreadT(); + return INVALID_NUB_ADDRESS; +} + +nub_addr_t +NativeThreadListDarwin::GetDispatchQueueT (nub_thread_t tid) +{ + MachThreadSP thread_sp (GetThreadByID (tid)); + if (thread_sp) + return thread_sp->GetDispatchQueueT(); + return INVALID_NUB_ADDRESS; +} + +nub_addr_t +NativeThreadListDarwin::GetTSDAddressForThread (nub_thread_t tid, uint64_t plo_pthread_tsd_base_address_offset, uint64_t plo_pthread_tsd_base_offset, uint64_t plo_pthread_tsd_entry_size) +{ + MachThreadSP thread_sp (GetThreadByID (tid)); + if (thread_sp) + return thread_sp->GetTSDAddressForThread(plo_pthread_tsd_base_address_offset, plo_pthread_tsd_base_offset, plo_pthread_tsd_entry_size); + return INVALID_NUB_ADDRESS; +} +#endif + +// TODO implement these +#if 0 +nub_thread_t +NativeThreadListDarwin::SetCurrentThread(nub_thread_t tid) +{ + MachThreadSP thread_sp (GetThreadByID (tid)); + if (thread_sp) + { + m_current_thread = thread_sp; + return tid; + } + return INVALID_NUB_THREAD; +} + + +bool +NativeThreadListDarwin::GetThreadStoppedReason(nub_thread_t tid, struct DNBThreadStopInfo *stop_info) const +{ + MachThreadSP thread_sp (GetThreadByID (tid)); + if (thread_sp) + return thread_sp->GetStopException().GetStopInfo(stop_info); + return false; +} + +bool +NativeThreadListDarwin::GetIdentifierInfo (nub_thread_t tid, thread_identifier_info_data_t *ident_info) +{ + thread_t mach_port_number = GetMachPortNumberByThreadID (tid); + + mach_msg_type_number_t count = THREAD_IDENTIFIER_INFO_COUNT; + return ::thread_info (mach_port_number, THREAD_IDENTIFIER_INFO, (thread_info_t)ident_info, &count) == KERN_SUCCESS; +} + +void +NativeThreadListDarwin::DumpThreadStoppedReason (nub_thread_t tid) const +{ + MachThreadSP thread_sp (GetThreadByID (tid)); + if (thread_sp) + thread_sp->GetStopException().DumpStopReason(); +} + +const char * +NativeThreadListDarwin::GetThreadInfo (nub_thread_t tid) const +{ + MachThreadSP thread_sp (GetThreadByID (tid)); + if (thread_sp) + return thread_sp->GetBasicInfoAsString(); + return NULL; +} + +#endif + +NativeThreadDarwinSP +NativeThreadListDarwin::GetThreadByID(lldb::tid_t tid) const +{ + std::lock_guard<std::recursive_mutex> locker(m_threads_mutex); + for (auto thread_sp : m_threads) + { + if (thread_sp && (thread_sp->GetID() == tid)) + return thread_sp; + } + return NativeThreadDarwinSP(); +} + +NativeThreadDarwinSP +NativeThreadListDarwin::GetThreadByMachPortNumber(::thread_t mach_port_number) + const +{ + std::lock_guard<std::recursive_mutex> locker(m_threads_mutex); + for (auto thread_sp : m_threads) + { + if (thread_sp && (thread_sp->GetMachPortNumber() == mach_port_number)) + return thread_sp; + } + return NativeThreadDarwinSP(); +} + +lldb::tid_t +NativeThreadListDarwin::GetThreadIDByMachPortNumber(::thread_t mach_port_number) + const +{ + std::lock_guard<std::recursive_mutex> locker(m_threads_mutex); + for (auto thread_sp : m_threads) + { + if (thread_sp && (thread_sp->GetMachPortNumber() == mach_port_number)) + return thread_sp->GetID(); + } + return LLDB_INVALID_THREAD_ID; +} + +// TODO implement +#if 0 +thread_t +NativeThreadListDarwin::GetMachPortNumberByThreadID (nub_thread_t globally_unique_id) const +{ + PTHREAD_MUTEX_LOCKER (locker, m_threads_mutex); + MachThreadSP thread_sp; + const size_t num_threads = m_threads.size(); + for (size_t idx = 0; idx < num_threads; ++idx) + { + if (m_threads[idx]->ThreadID() == globally_unique_id) + { + return m_threads[idx]->MachPortNumber(); + } + } + return 0; +} + +bool +NativeThreadListDarwin::GetRegisterValue (nub_thread_t tid, uint32_t set, uint32_t reg, DNBRegisterValue *reg_value ) const +{ + MachThreadSP thread_sp (GetThreadByID (tid)); + if (thread_sp) + return thread_sp->GetRegisterValue(set, reg, reg_value); + + return false; +} + +bool +NativeThreadListDarwin::SetRegisterValue (nub_thread_t tid, uint32_t set, uint32_t reg, const DNBRegisterValue *reg_value ) const +{ + MachThreadSP thread_sp (GetThreadByID (tid)); + if (thread_sp) + return thread_sp->SetRegisterValue(set, reg, reg_value); + + return false; +} + +nub_size_t +NativeThreadListDarwin::GetRegisterContext (nub_thread_t tid, void *buf, size_t buf_len) +{ + MachThreadSP thread_sp (GetThreadByID (tid)); + if (thread_sp) + return thread_sp->GetRegisterContext (buf, buf_len); + return 0; +} + +nub_size_t +NativeThreadListDarwin::SetRegisterContext (nub_thread_t tid, const void *buf, size_t buf_len) +{ + MachThreadSP thread_sp (GetThreadByID (tid)); + if (thread_sp) + return thread_sp->SetRegisterContext (buf, buf_len); + return 0; +} + +uint32_t +NativeThreadListDarwin::SaveRegisterState (nub_thread_t tid) +{ + MachThreadSP thread_sp (GetThreadByID (tid)); + if (thread_sp) + return thread_sp->SaveRegisterState (); + return 0; +} + +bool +NativeThreadListDarwin::RestoreRegisterState (nub_thread_t tid, uint32_t save_id) +{ + MachThreadSP thread_sp (GetThreadByID (tid)); + if (thread_sp) + return thread_sp->RestoreRegisterState (save_id); + return 0; +} +#endif + +size_t +NativeThreadListDarwin::GetNumberOfThreads() const +{ + std::lock_guard<std::recursive_mutex> locker(m_threads_mutex); + return static_cast<size_t>(m_threads.size()); +} + +// TODO implement +#if 0 +nub_thread_t +NativeThreadListDarwin::ThreadIDAtIndex (nub_size_t idx) const +{ + PTHREAD_MUTEX_LOCKER (locker, m_threads_mutex); + if (idx < m_threads.size()) + return m_threads[idx]->ThreadID(); + return INVALID_NUB_THREAD; +} + +nub_thread_t +NativeThreadListDarwin::CurrentThreadID ( ) +{ + MachThreadSP thread_sp; + CurrentThread(thread_sp); + if (thread_sp.get()) + return thread_sp->ThreadID(); + return INVALID_NUB_THREAD; +} + +#endif + +bool +NativeThreadListDarwin::NotifyException(MachException::Data &exc) +{ + auto thread_sp = GetThreadByMachPortNumber(exc.thread_port); + if (thread_sp) + { + thread_sp->NotifyException(exc); + return true; + } + return false; +} + +void +NativeThreadListDarwin::Clear() +{ + std::lock_guard<std::recursive_mutex> locker(m_threads_mutex); + m_threads.clear(); +} + +uint32_t +NativeThreadListDarwin::UpdateThreadList(NativeProcessDarwin &process, + bool update, + collection *new_threads) +{ + Log *log(GetLogIfAllCategoriesSet(LIBLLDB_LOG_THREAD)); + + std::lock_guard<std::recursive_mutex> locker(m_threads_mutex); + if (log) + log->Printf("NativeThreadListDarwin::%s() (pid = %" PRIu64 ", update = " + "%u) process stop count = %u", __FUNCTION__, + process.GetID(), update, process.GetStopID()); + + if (process.GetStopID() == 0) + { + // On our first stop, we'll record details like 32/64 bitness and + // select the proper architecture implementation. + // + int mib[4] = { CTL_KERN, KERN_PROC, KERN_PROC_PID, + (int)process.GetID() }; + + struct kinfo_proc processInfo; + size_t bufsize = sizeof(processInfo); + if ((sysctl(mib, (unsigned)(sizeof(mib)/sizeof(int)), &processInfo, + &bufsize, NULL, 0) == 0) && (bufsize > 0)) + { + if (processInfo.kp_proc.p_flag & P_LP64) + m_is_64_bit = true; + } + +// TODO implement architecture selection and abstraction. +#if 0 +#if defined (__i386__) || defined (__x86_64__) + if (m_is_64_bit) + DNBArchProtocol::SetArchitecture(CPU_TYPE_X86_64); + else + DNBArchProtocol::SetArchitecture(CPU_TYPE_I386); +#elif defined (__arm__) || defined (__arm64__) || defined (__aarch64__) + if (m_is_64_bit) + DNBArchProtocol::SetArchitecture(CPU_TYPE_ARM64); + else + DNBArchProtocol::SetArchitecture(CPU_TYPE_ARM); +#endif +#endif + } + + if (m_threads.empty() || update) + { + thread_array_t thread_list = nullptr; + mach_msg_type_number_t thread_list_count = 0; + task_t task = process.GetTask(); + + Error error; + auto mach_err = ::task_threads(task, &thread_list, &thread_list_count); + error.SetError(mach_err, eErrorTypeMachKernel); + if (error.Fail()) + { + if (log) + log->Printf("::task_threads(task = 0x%4.4x, thread_list => %p, " + "thread_list_count => %u) failed: %u (%s)", task, + thread_list, thread_list_count, error.GetError(), + error.AsCString()); + return 0; + } + + if (thread_list_count > 0) + { + collection currThreads; + size_t idx; + // Iterator through the current thread list and see which threads + // we already have in our list (keep them), which ones we don't + // (add them), and which ones are not around anymore (remove them). + for (idx = 0; idx < thread_list_count; ++idx) + { + // Get the Mach thread port. + const ::thread_t mach_port_num = thread_list[idx]; + + // Get the unique thread id for the mach port number. + uint64_t unique_thread_id = + NativeThreadDarwin:: + GetGloballyUniqueThreadIDForMachPortID(mach_port_num); + + // Retrieve the thread if it exists. + auto thread_sp = GetThreadByID(unique_thread_id); + if (thread_sp) + { + // We are already tracking it. Keep the existing native + // thread instance. + currThreads.push_back(thread_sp); + } + else + { + // We don't have a native thread instance for this thread. + // Create it now. + thread_sp.reset(new NativeThreadDarwin(&process, + m_is_64_bit, + unique_thread_id, + mach_port_num)); + + // Add the new thread regardless of its is user ready state. + // Make sure the thread is ready to be displayed and shown + // to users before we add this thread to our list... + if (thread_sp->IsUserReady()) + { + if (new_threads) + new_threads->push_back(thread_sp); + + currThreads.push_back(thread_sp); + } + } + } + + m_threads.swap(currThreads); + m_current_thread.reset(); + + // Free the vm memory given to us by ::task_threads() + vm_size_t thread_list_size = (vm_size_t) (thread_list_count * + sizeof (::thread_t)); + ::vm_deallocate(::mach_task_self(), + (vm_address_t)thread_list, + thread_list_size); + } + } + return static_cast<uint32_t>(m_threads.size()); +} + +// TODO implement +#if 0 + +void +NativeThreadListDarwin::CurrentThread (MachThreadSP& thread_sp) +{ + // locker will keep a mutex locked until it goes out of scope + PTHREAD_MUTEX_LOCKER (locker, m_threads_mutex); + if (m_current_thread.get() == NULL) + { + // Figure out which thread is going to be our current thread. + // This is currently done by finding the first thread in the list + // that has a valid exception. + const size_t num_threads = m_threads.size(); + for (uint32_t idx = 0; idx < num_threads; ++idx) + { + if (m_threads[idx]->GetStopException().IsValid()) + { + m_current_thread = m_threads[idx]; + break; + } + } + } + thread_sp = m_current_thread; +} + +#endif + +void +NativeThreadListDarwin::Dump(Stream &stream) const +{ + bool first = true; + + std::lock_guard<std::recursive_mutex> locker(m_threads_mutex); + for (auto thread_sp : m_threads) + { + if (thread_sp) + { + // Handle newlines between thread entries. + if (first) + first = false; + else + stream.PutChar('\n'); + thread_sp->Dump(stream); + } + } +} + +void +NativeThreadListDarwin::ProcessWillResume(NativeProcessDarwin &process, + const ResumeActionList &thread_actions) +{ + std::lock_guard<std::recursive_mutex> locker(m_threads_mutex); + + // Update our thread list, because sometimes libdispatch or the kernel + // will spawn threads while a task is suspended. + NativeThreadListDarwin::collection new_threads; + + // TODO implement this. +#if 0 + // First figure out if we were planning on running only one thread, and if + // so, force that thread to resume. + bool run_one_thread; + thread_t solo_thread = THREAD_NULL; + if ((thread_actions.GetSize() > 0) && + (thread_actions.NumActionsWithState(eStateStepping) + + thread_actions.NumActionsWithState (eStateRunning) == 1)) + { + run_one_thread = true; + const DNBThreadResumeAction *action_ptr = thread_actions.GetFirst(); + size_t num_actions = thread_actions.GetSize(); + for (size_t i = 0; i < num_actions; i++, action_ptr++) + { + if (action_ptr->state == eStateStepping || action_ptr->state == eStateRunning) + { + solo_thread = action_ptr->tid; + break; + } + } + } + else + run_one_thread = false; +#endif + + UpdateThreadList(process, true, &new_threads); + +#if 0 + DNBThreadResumeAction resume_new_threads = { -1U, eStateRunning, 0, INVALID_NUB_ADDRESS }; + // If we are planning to run only one thread, any new threads should be suspended. + if (run_one_thread) + resume_new_threads.state = eStateSuspended; + + const size_t num_new_threads = new_threads.size(); + const size_t num_threads = m_threads.size(); + for (uint32_t idx = 0; idx < num_threads; ++idx) + { + MachThread *thread = m_threads[idx].get(); + bool handled = false; + for (uint32_t new_idx = 0; new_idx < num_new_threads; ++new_idx) + { + if (thread == new_threads[new_idx].get()) + { + thread->ThreadWillResume(&resume_new_threads); + handled = true; + break; + } + } + + if (!handled) + { + const DNBThreadResumeAction *thread_action = thread_actions.GetActionForThread (thread->ThreadID(), true); + // There must always be a thread action for every thread. + assert (thread_action); + bool others_stopped = false; + if (solo_thread == thread->ThreadID()) + others_stopped = true; + thread->ThreadWillResume (thread_action, others_stopped); + } + } + + if (new_threads.size()) + { + for (uint32_t idx = 0; idx < num_new_threads; ++idx) + { + DNBLogThreadedIf (LOG_THREAD, "NativeThreadListDarwin::ProcessWillResume (pid = %4.4x) stop-id=%u, resuming newly discovered thread: 0x%8.8" PRIx64 ", thread-is-user-ready=%i)", + process->ProcessID(), + process->StopCount(), + new_threads[idx]->ThreadID(), + new_threads[idx]->IsUserReady()); + } + } +#endif +} + +uint32_t +NativeThreadListDarwin::ProcessDidStop(NativeProcessDarwin &process) +{ + std::lock_guard<std::recursive_mutex> locker(m_threads_mutex); + + // Update our thread list. + UpdateThreadList(process, true); + + for (auto thread_sp : m_threads) + { + if (thread_sp) + thread_sp->ThreadDidStop(); + } + return (uint32_t)m_threads.size(); +} + +//---------------------------------------------------------------------- +// Check each thread in our thread list to see if we should notify our +// client of the current halt in execution. +// +// Breakpoints can have callback functions associated with them than +// can return true to stop, or false to continue executing the inferior. +// +// RETURNS +// true if we should stop and notify our clients +// false if we should resume our child process and skip notification +//---------------------------------------------------------------------- +bool +NativeThreadListDarwin::ShouldStop(bool &step_more) +{ + std::lock_guard<std::recursive_mutex> locker(m_threads_mutex); + for (auto thread_sp : m_threads) + { + if (thread_sp && thread_sp->ShouldStop(step_more)) + return true; + } + return false; +} + +// Implement. +#if 0 + +void +NativeThreadListDarwin::NotifyBreakpointChanged (const DNBBreakpoint *bp) +{ + PTHREAD_MUTEX_LOCKER (locker, m_threads_mutex); + const size_t num_threads = m_threads.size(); + for (uint32_t idx = 0; idx < num_threads; ++idx) + { + m_threads[idx]->NotifyBreakpointChanged(bp); + } +} + + +uint32_t +NativeThreadListDarwin::EnableHardwareBreakpoint (const DNBBreakpoint* bp) const +{ + if (bp != NULL) + { + const size_t num_threads = m_threads.size(); + for (uint32_t idx = 0; idx < num_threads; ++idx) + m_threads[idx]->EnableHardwareBreakpoint(bp); + } + return INVALID_NUB_HW_INDEX; +} + +bool +NativeThreadListDarwin::DisableHardwareBreakpoint (const DNBBreakpoint* bp) const +{ + if (bp != NULL) + { + const size_t num_threads = m_threads.size(); + for (uint32_t idx = 0; idx < num_threads; ++idx) + m_threads[idx]->DisableHardwareBreakpoint(bp); + } + return false; +} + +// DNBWatchpointSet() -> MachProcess::CreateWatchpoint() -> MachProcess::EnableWatchpoint() +// -> NativeThreadListDarwin::EnableHardwareWatchpoint(). +uint32_t +NativeThreadListDarwin::EnableHardwareWatchpoint (const DNBBreakpoint* wp) const +{ + uint32_t hw_index = INVALID_NUB_HW_INDEX; + if (wp != NULL) + { + PTHREAD_MUTEX_LOCKER (locker, m_threads_mutex); + const size_t num_threads = m_threads.size(); + // On Mac OS X we have to prime the control registers for new threads. We do this + // using the control register data for the first thread, for lack of a better way of choosing. + bool also_set_on_task = true; + for (uint32_t idx = 0; idx < num_threads; ++idx) + { + if ((hw_index = m_threads[idx]->EnableHardwareWatchpoint(wp, also_set_on_task)) == INVALID_NUB_HW_INDEX) + { + // We know that idx failed for some reason. Let's rollback the transaction for [0, idx). + for (uint32_t i = 0; i < idx; ++i) + m_threads[i]->RollbackTransForHWP(); + return INVALID_NUB_HW_INDEX; + } + also_set_on_task = false; + } + // Notify each thread to commit the pending transaction. + for (uint32_t idx = 0; idx < num_threads; ++idx) + m_threads[idx]->FinishTransForHWP(); + + } + return hw_index; +} + +bool +NativeThreadListDarwin::DisableHardwareWatchpoint (const DNBBreakpoint* wp) const +{ + if (wp != NULL) + { + PTHREAD_MUTEX_LOCKER (locker, m_threads_mutex); + const size_t num_threads = m_threads.size(); + + // On Mac OS X we have to prime the control registers for new threads. We do this + // using the control register data for the first thread, for lack of a better way of choosing. + bool also_set_on_task = true; + for (uint32_t idx = 0; idx < num_threads; ++idx) + { + if (!m_threads[idx]->DisableHardwareWatchpoint(wp, also_set_on_task)) + { + // We know that idx failed for some reason. Let's rollback the transaction for [0, idx). + for (uint32_t i = 0; i < idx; ++i) + m_threads[i]->RollbackTransForHWP(); + return false; + } + also_set_on_task = false; + } + // Notify each thread to commit the pending transaction. + for (uint32_t idx = 0; idx < num_threads; ++idx) + m_threads[idx]->FinishTransForHWP(); + + return true; + } + return false; +} + +uint32_t +NativeThreadListDarwin::NumSupportedHardwareWatchpoints () const +{ + PTHREAD_MUTEX_LOCKER (locker, m_threads_mutex); + const size_t num_threads = m_threads.size(); + // Use an arbitrary thread to retrieve the number of supported hardware watchpoints. + if (num_threads) + return m_threads[0]->NumSupportedHardwareWatchpoints(); + return 0; +} + +uint32_t +NativeThreadListDarwin::GetThreadIndexForThreadStoppedWithSignal (const int signo) const +{ + PTHREAD_MUTEX_LOCKER (locker, m_threads_mutex); + uint32_t should_stop = false; + const size_t num_threads = m_threads.size(); + for (uint32_t idx = 0; !should_stop && idx < num_threads; ++idx) + { + if (m_threads[idx]->GetStopException().SoftSignal () == signo) + return idx; + } + return UINT32_MAX; +} + +#endif diff --git a/lldb/source/Plugins/Process/Darwin/NativeThreadListDarwin.h b/lldb/source/Plugins/Process/Darwin/NativeThreadListDarwin.h new file mode 100644 index 00000000000..588013043e1 --- /dev/null +++ b/lldb/source/Plugins/Process/Darwin/NativeThreadListDarwin.h @@ -0,0 +1,162 @@ +//===-- NativeThreadListDarwin.h --------------------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// Created by Greg Clayton on 6/19/07. +// +//===----------------------------------------------------------------------===// + +#ifndef __NativeThreadListDarwin_h__ +#define __NativeThreadListDarwin_h__ + +#include <memory> +#include <mutex> +#include <vector> + +#include "lldb/lldb-private-forward.h" +#include "lldb/lldb-types.h" + +#include "MachException.h" + +// #include "ThreadInfo.h" + +namespace lldb_private { +namespace process_darwin { + +class NativeBreakpointDarwin; +class NativeProcessDarwin; + +class NativeThreadDarwin; +using NativeThreadDarwinSP = std::shared_ptr<NativeThreadDarwin>; + +class NativeThreadListDarwin +{ +public: + NativeThreadListDarwin(); + ~NativeThreadListDarwin(); + + void + Clear(); + + void + Dump(Stream &stream) const; + + // These methods will be accessed directly from NativeThreadDarwin +#if 0 + bool GetRegisterValue (nub_thread_t tid, uint32_t set, uint32_t reg, DNBRegisterValue *reg_value) const; + bool SetRegisterValue (nub_thread_t tid, uint32_t set, uint32_t reg, const DNBRegisterValue *reg_value) const; + nub_size_t GetRegisterContext (nub_thread_t tid, void *buf, size_t buf_len); + nub_size_t SetRegisterContext (nub_thread_t tid, const void *buf, size_t buf_len); + uint32_t SaveRegisterState (nub_thread_t tid); + bool RestoreRegisterState (nub_thread_t tid, uint32_t save_id); +#endif + + const char * + GetThreadInfo(lldb::tid_t tid) const; + + void + ProcessWillResume(NativeProcessDarwin &process, + const ResumeActionList &thread_actions); + + uint32_t + ProcessDidStop(NativeProcessDarwin &process); + + bool + NotifyException(MachException::Data& exc); + + bool + ShouldStop(bool &step_more); + + // These methods will be accessed directly from NativeThreadDarwin +#if 0 + const char * GetName (nub_thread_t tid); + nub_state_t GetState (nub_thread_t tid); + nub_thread_t SetCurrentThread (nub_thread_t tid); +#endif + + // TODO: figure out if we need to add this to NativeThreadDarwin yet. +#if 0 + ThreadInfo::QoS GetRequestedQoS (nub_thread_t tid, nub_addr_t tsd, uint64_t dti_qos_class_index); + nub_addr_t GetPThreadT (nub_thread_t tid); + nub_addr_t GetDispatchQueueT (nub_thread_t tid); + nub_addr_t GetTSDAddressForThread (nub_thread_t tid, uint64_t plo_pthread_tsd_base_address_offset, uint64_t plo_pthread_tsd_base_offset, uint64_t plo_pthread_tsd_entry_size); +#endif + + // These methods will be accessed directly from NativeThreadDarwin +#if 0 + bool GetThreadStoppedReason (nub_thread_t tid, struct DNBThreadStopInfo *stop_info) const; + void DumpThreadStoppedReason (nub_thread_t tid) const; + bool GetIdentifierInfo (nub_thread_t tid, thread_identifier_info_data_t *ident_info); +#endif + + size_t + GetNumberOfThreads() const; + + lldb::tid_t + ThreadIDAtIndex(size_t idx) const; + + lldb::tid_t + GetCurrentThreadID(); + + NativeThreadDarwinSP + GetCurrentThread(); + + void + NotifyBreakpointChanged(const NativeBreakpointDarwin *bp); + + uint32_t + EnableHardwareBreakpoint(const NativeBreakpointDarwin *bp) const; + + bool + DisableHardwareBreakpoint(const NativeBreakpointDarwin *bp) const; + + uint32_t + EnableHardwareWatchpoint(const NativeBreakpointDarwin *wp) const; + + bool + DisableHardwareWatchpoint(const NativeBreakpointDarwin *wp) const; + + uint32_t + GetNumberOfSupportedHardwareWatchpoints() const; + + size_t + GetThreadIndexForThreadStoppedWithSignal(const int signo) const; + + NativeThreadDarwinSP + GetThreadByID(lldb::tid_t tid) const; + + NativeThreadDarwinSP + GetThreadByMachPortNumber(::thread_t mach_port_number) const; + + lldb::tid_t + GetThreadIDByMachPortNumber(::thread_t mach_port_number) const; + + thread_t + GetMachPortNumberByThreadID(lldb::tid_t globally_unique_id) const; + +protected: + typedef std::vector<NativeThreadDarwinSP> collection; + typedef collection::iterator iterator; + typedef collection::const_iterator const_iterator; + + // Consider having this return an lldb_private::Error. + uint32_t + UpdateThreadList (NativeProcessDarwin &process, bool update, + collection *num_threads = nullptr); + + collection m_threads; + mutable std::recursive_mutex m_threads_mutex; + NativeThreadDarwinSP m_current_thread; + bool m_is_64_bit; +}; + +} // namespace process_darwin +} // namespace lldb_private + +#endif // #ifndef __NativeThreadListDarwin_h__ + diff --git a/lldb/source/Plugins/Process/Linux/NativeProcessLinux.cpp b/lldb/source/Plugins/Process/Linux/NativeProcessLinux.cpp index 49d57c0a0ad..4af5b6d1da9 100644 --- a/lldb/source/Plugins/Process/Linux/NativeProcessLinux.cpp +++ b/lldb/source/Plugins/Process/Linux/NativeProcessLinux.cpp @@ -108,27 +108,6 @@ static bool ProcessVmReadvSupported() namespace { -Error -ResolveProcessArchitecture(lldb::pid_t pid, ArchSpec &arch) -{ - // Grab process info for the running process. - ProcessInstanceInfo process_info; - if (!Host::GetProcessInfo(pid, process_info)) - return Error("failed to get process info"); - - // Resolve the executable module. - ModuleSpecList module_specs; - if (!ObjectFile::GetModuleSpecifications(process_info.GetExecutableFile(), 0, 0, module_specs)) - return Error("failed to get module specifications"); - assert(module_specs.GetSize() == 1); - - arch = module_specs.GetModuleSpecRefAtIndex(0).GetArchitecture(); - if (arch.IsValid()) - return Error(); - else - return Error("failed to retrieve a valid architecture from the exe module"); -} - void MaybeLogLaunchInfo(const ProcessLaunchInfo &info) { diff --git a/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationServerCommon.cpp b/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationServerCommon.cpp index 3361ffaeea7..5ae5627dffc 100644 --- a/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationServerCommon.cpp +++ b/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationServerCommon.cpp @@ -12,6 +12,11 @@ #include <errno.h> // C Includes + +#ifdef __APPLE__ +#include <TargetConditionals.h> +#endif + // C++ Includes #include <cstring> #include <chrono> @@ -169,8 +174,11 @@ GDBRemoteCommunicationServerCommon::Handle_qHostInfo (StringExtractorGDBRemote & response.PutCString(";"); } - // Only send out MachO info when lldb-platform/llgs is running on a MachO host. #if defined(__APPLE__) + // For parity with debugserver, we'll include the vendor key. + response.PutCString("vendor:apple;"); + + // Send out MachO info. uint32_t cpu = host_arch.GetMachOCPUType(); uint32_t sub = host_arch.GetMachOCPUSubType(); if (cpu != LLDB_INVALID_CPUTYPE) @@ -179,9 +187,26 @@ GDBRemoteCommunicationServerCommon::Handle_qHostInfo (StringExtractorGDBRemote & response.Printf ("cpusubtype:%u;", sub); if (cpu == ArchSpec::kCore_arm_any) - response.Printf("watchpoint_exceptions_received:before;"); // On armv7 we use "synchronous" watchpoints which means the exception is delivered before the instruction executes. + { + // Indicate the OS type. +#if defined (TARGET_OS_TV) && TARGET_OS_TV == 1 + response.PutCString("ostype:tvos;"); +#elif defined (TARGET_OS_WATCH) && TARGET_OS_WATCH == 1 + response.PutCString("ostype:watchos;"); +#else + response.PutCString("ostype:ios;"); +#endif + + // On arm, we use "synchronous" watchpoints which means the exception is + // delivered before the instruction executes. + response.PutCString("watchpoint_exceptions_received:before;"); + } else + { + response.PutCString("ostype:macosx;"); response.Printf("watchpoint_exceptions_received:after;"); + } + #else if (host_arch.GetMachine() == llvm::Triple::aarch64 || host_arch.GetMachine() == llvm::Triple::aarch64_be || diff --git a/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationServerLLGS.cpp b/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationServerLLGS.cpp index 81c54edd388..04720ace197 100644 --- a/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationServerLLGS.cpp +++ b/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationServerLLGS.cpp @@ -216,7 +216,8 @@ GDBRemoteCommunicationServerLLGS::LaunchProcess () Error error; { std::lock_guard<std::recursive_mutex> guard(m_debugged_process_mutex); - assert (!m_debugged_process_sp && "lldb-gdbserver creating debugged process but one already exists"); + assert (!m_debugged_process_sp && "lldb-server creating debugged " + "process but one already exists"); error = NativeProcessProtocol::Launch( m_process_launch_info, *this, diff --git a/lldb/source/Target/UnixSignals.cpp b/lldb/source/Target/UnixSignals.cpp index cf244ba8f7d..92309065a91 100644 --- a/lldb/source/Target/UnixSignals.cpp +++ b/lldb/source/Target/UnixSignals.cpp @@ -89,6 +89,8 @@ UnixSignals::Reset () // This builds one standard set of Unix Signals. If yours aren't quite in this // order, you can either subclass this class, and use Add & Remove to change them // or you can subclass and build them afresh in your constructor; + // + // Note: the signals below are the Darwin signals. Do not change these! m_signals.clear(); // SIGNO NAME SUPPRESS STOP NOTIFY DESCRIPTION // ====== ============ ======== ====== ====== =================================================== diff --git a/lldb/tools/lldb-server/Darwin/resources/lldb-server-Info.plist b/lldb/tools/lldb-server/Darwin/resources/lldb-server-Info.plist new file mode 100644 index 00000000000..58a34ca5e43 --- /dev/null +++ b/lldb/tools/lldb-server/Darwin/resources/lldb-server-Info.plist @@ -0,0 +1,21 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> +<plist version="1.0"> +<dict> + <key>CFBundleDevelopmentRegion</key> + <string>English</string> + <key>CFBundleIdentifier</key> + <string>com.apple.lldb-server</string> + <key>CFBundleInfoDictionaryVersion</key> + <string>6.0</string> + <key>CFBundleName</key> + <string>lldb-server</string> + <key>CFBundleVersion</key> + <string>2</string> + <key>SecTaskAccess</key> + <array> + <string>allowed</string> + <string>debug</string> + </array> +</dict> +</plist> diff --git a/lldb/tools/lldb-server/Darwin/resources/lldb-server-entitlements.plist b/lldb/tools/lldb-server/Darwin/resources/lldb-server-entitlements.plist new file mode 100644 index 00000000000..4134ee95861 --- /dev/null +++ b/lldb/tools/lldb-server/Darwin/resources/lldb-server-entitlements.plist @@ -0,0 +1,28 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> +<plist version="1.0"> +<dict> + <key>com.apple.springboard.debugapplications</key> + <true/> + <key>com.apple.backboardd.launchapplications</key> + <true/> + <key>com.apple.backboardd.debugapplications</key> + <true/> + <key>com.apple.frontboard.launchapplications</key> + <true/> + <key>com.apple.frontboard.debugapplications</key> + <true/> + <key>run-unsigned-code</key> + <true/> + <key>seatbelt-profiles</key> + <array> + <string>debugserver</string> + </array> + <key>com.apple.diagnosticd.diagnostic</key> + <true/> + <key>com.apple.security.network.server</key> + <true/> + <key>com.apple.security.network.client</key> + <true/> +</dict> +</plist> diff --git a/lldb/tools/lldb-server/Darwin/resources/lldb-server-macos-entitlements.plist b/lldb/tools/lldb-server/Darwin/resources/lldb-server-macos-entitlements.plist new file mode 100644 index 00000000000..eddbaa0063e --- /dev/null +++ b/lldb/tools/lldb-server/Darwin/resources/lldb-server-macos-entitlements.plist @@ -0,0 +1,8 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> +<plist version="1.0"> +<dict> + <key>com.apple.diagnosticd.diagnostic</key> + <true/> +</dict> +</plist> diff --git a/lldb/tools/lldb-server/Darwin/resources/lldb-server-mig.defs b/lldb/tools/lldb-server/Darwin/resources/lldb-server-mig.defs new file mode 100644 index 00000000000..cd5be170070 --- /dev/null +++ b/lldb/tools/lldb-server/Darwin/resources/lldb-server-mig.defs @@ -0,0 +1,5 @@ +/* + * nub.defs + */ + +#import <mach/mach_exc.defs> |