Index: lldb/docs/lldb-gdb-remote.txt =================================================================== --- lldb/docs/lldb-gdb-remote.txt +++ lldb/docs/lldb-gdb-remote.txt @@ -451,7 +451,12 @@ // "size": , // Size in bytes of this thread data. // }, -// }] +// ], +// "counters"?: { +// "info_kind": {...parameters specific to the provided counter info kind}, +// Each entry includes information related to counters associated with the trace. +// They are described below. +// } // } // // NOTES @@ -463,6 +468,26 @@ // Binary data kinds: // - threadTraceBuffer: trace buffer for a thread. // - cpuInfo: contents of the /proc/cpuinfo file. +// +// Counter info kinds: +// tsc-perf-zero-conversion: +// +// This field allows converting Intel processor's TSC values to a wall time. +// It is available through the Linux perf_event API when cap_user_time and cap_user_time_zero +// are set. +// See the documentation of time_zero in +// https://man7.org/linux/man-pages/man2/perf_event_open.2.html for more information about +// the calculation and the meaning of the values in the schema below. +/// +// Sub-schema for this field: +// +// { +// "tsc-perf-zero-conversion": { +// "time_mult": , +// "time_shift": , +// "time_zero": , +// } +// } //---------------------------------------------------------------------- send packet: jLLDBTraceGetState:{"type":}] Index: lldb/include/lldb/Utility/TraceGDBRemotePackets.h =================================================================== --- lldb/include/lldb/Utility/TraceGDBRemotePackets.h +++ lldb/include/lldb/Utility/TraceGDBRemotePackets.h @@ -10,6 +10,7 @@ #define LLDB_UTILITY_TRACEGDBREMOTEPACKETS_H #include "llvm/Support/JSON.h" +#include #include "lldb/lldb-defines.h" #include "lldb/lldb-enumerations.h" @@ -116,6 +117,31 @@ llvm::json::Value toJSON(const TraceThreadState &packet); +/// Interface for different algorithms used to convert trace +/// counters into different units. +template class TraceCounterConversion { +public: + virtual ~TraceCounterConversion() = default; + + /// Convert from raw counter value to the target type. + /// + /// \param[in] raw_counter_value + /// The raw counter value to be converted. + /// + /// \return + /// The converted counter value. + virtual ToType Convert(uint64_t raw_counter_value) = 0; + + /// Serialize trace counter conversion values to JSON. + /// + /// \return + /// \a llvm::json::Value representing the trace counter conversion object. + virtual llvm::json::Value toJSON() = 0; +}; + +using TraceTscConversionUP = + std::unique_ptr>; + struct TraceGetStateResponse { std::vector tracedThreads; std::vector processBinaryData; Index: lldb/include/lldb/Utility/TraceIntelPTGDBRemotePackets.h =================================================================== --- lldb/include/lldb/Utility/TraceIntelPTGDBRemotePackets.h +++ lldb/include/lldb/Utility/TraceIntelPTGDBRemotePackets.h @@ -11,6 +11,10 @@ #include "lldb/Utility/TraceGDBRemotePackets.h" +#include "llvm/Support/JSON.h" + +#include + /// See docs/lldb-gdb-remote.txt for more information. namespace lldb_private { @@ -40,6 +44,60 @@ llvm::json::Value toJSON(const TraceIntelPTStartRequest &packet); /// \} +/// jLLDBTraceGetState gdb-remote packet +/// \{ + +/// TSC to wall time conversion values defined in the Linux perf_event_open API +/// when the capibilities cap_user_time and cap_user_time_zero are set. See the +/// See the documentation of `time_zero` in +/// https://man7.org/linux/man-pages/man2/perf_event_open.2.html for more +/// information. +class LinuxPerfZeroTscConversion + : public TraceCounterConversion { +public: + /// Create new \a LinuxPerfZeroTscConversion object from the conversion values + /// defined in the Linux perf_event_open API. + LinuxPerfZeroTscConversion(uint32_t time_mult, uint16_t time_shift, + uint64_t time_zero) + : m_time_mult(time_mult), m_time_shift(time_shift), + m_time_zero(time_zero) {} + + /// Convert TSC value to nanosecond wall time. The beginning of time (0 + /// nanoseconds) is defined by the kernel at boot time and has no particularly + /// useful meaning. On the other hand, this value is constant for an entire + /// trace session. + // See 'time_zero' section of + // https://man7.org/linux/man-pages/man2/perf_event_open.2.html + /// + /// \param[in] tsc + /// The TSC value to be converted. + /// + /// \return + /// Nanosecond wall time. + std::chrono::nanoseconds Convert(uint64_t raw_counter_value) override; + + llvm::json::Value toJSON() override; + +private: + uint32_t m_time_mult; + uint16_t m_time_shift; + uint64_t m_time_zero; +}; + +struct TraceIntelPTGetStateResponse : TraceGetStateResponse { + /// The TSC to wall time conversion if it exists, otherwise \b nullptr. + TraceTscConversionUP tsc_conversion; +}; + +bool fromJSON(const llvm::json::Value &value, + TraceTscConversionUP &tsc_conversion, llvm::json::Path path); + +bool fromJSON(const llvm::json::Value &value, + TraceIntelPTGetStateResponse &packet, llvm::json::Path path); + +llvm::json::Value toJSON(const TraceIntelPTGetStateResponse &packet); +/// \} + } // namespace lldb_private #endif // LLDB_UTILITY_TRACEINTELPTGDBREMOTEPACKETS_H Index: lldb/source/Plugins/Process/Linux/IntelPTCollector.h =================================================================== --- lldb/source/Plugins/Process/Linux/IntelPTCollector.h +++ lldb/source/Plugins/Process/Linux/IntelPTCollector.h @@ -185,7 +185,7 @@ /// Main class that manages intel-pt process and thread tracing. class IntelPTCollector { public: - IntelPTCollector(lldb::pid_t pid) : m_pid(pid), m_thread_traces(pid) {} + IntelPTCollector(lldb::pid_t pid); static bool IsSupported(); @@ -235,6 +235,8 @@ /// Threads traced due to "process tracing". Only one active "process tracing" /// instance is assumed for a single process. llvm::Optional m_process_trace; + /// TSC to wall time conversion. + TraceTscConversionUP m_tsc_conversion; }; } // namespace process_linux Index: lldb/source/Plugins/Process/Linux/IntelPTCollector.cpp =================================================================== --- lldb/source/Plugins/Process/Linux/IntelPTCollector.cpp +++ lldb/source/Plugins/Process/Linux/IntelPTCollector.cpp @@ -522,6 +522,17 @@ /// IntelPTCollector +IntelPTCollector::IntelPTCollector(lldb::pid_t pid) + : m_pid(pid), m_thread_traces(pid) { + if (Expected tsc_conversion = + LoadPerfTscConversionParameters()) + m_tsc_conversion = + std::make_unique(*tsc_conversion); + else + LLDB_LOG_ERROR(GetLog(POSIXLog::Trace), tsc_conversion.takeError(), + "unable to load TSC to wall time conversion: {0}"); +} + Error IntelPTCollector::TraceStop(lldb::tid_t tid) { if (IsProcessTracingEnabled() && m_process_trace->TracesThread(tid)) return m_process_trace->TraceStop(tid); Index: lldb/source/Plugins/Process/Linux/Perf.h =================================================================== --- lldb/source/Plugins/Process/Linux/Perf.h +++ lldb/source/Plugins/Process/Linux/Perf.h @@ -15,6 +15,7 @@ #ifndef LLDB_SOURCE_PLUGINS_PROCESS_LINUX_PERF_H #define LLDB_SOURCE_PLUGINS_PROCESS_LINUX_PERF_H +#include "lldb/Utility/TraceIntelPTGDBRemotePackets.h" #include "lldb/lldb-types.h" #include "llvm/Support/Error.h" @@ -232,29 +233,9 @@ resource_handle::MmapUP m_aux_base; }; -/// TSC to nanoseconds conversion values defined by the Linux perf_event API -/// when the capibilities cap_user_time and cap_user_time_zero are set. See the -/// documentation of `time_zero` in -/// https://man7.org/linux/man-pages/man2/perf_event_open.2.html for more -/// information. -struct PerfTscConversionParameters { - uint32_t m_time_mult; - uint16_t m_time_shift; - uint64_t m_time_zero; - - /// Convert TSC value to nanosecond wall time. - /// - /// \a param[in] tsc - /// The TSC value to be converted. - /// - /// \return - /// Nanosecond wall time. - std::chrono::nanoseconds ToWallTime(uint64_t tsc); -}; - -/// Fetch \a PerfTscConversionParameters from \a perf_event_mmap_page, if +/// Load \a PerfTscConversionParameters from \a perf_event_mmap_page, if /// available. -llvm::Expected FetchPerfTscConversionParameters(); +llvm::Expected LoadPerfTscConversionParameters(); } // namespace process_linux } // namespace lldb_private Index: lldb/source/Plugins/Process/Linux/Perf.cpp =================================================================== --- lldb/source/Plugins/Process/Linux/Perf.cpp +++ lldb/source/Plugins/Process/Linux/Perf.cpp @@ -8,15 +8,9 @@ #include "Perf.h" -#include "lldb/lldb-types.h" - -#include "llvm/Support/Error.h" #include "llvm/Support/FormatVariadic.h" #include "llvm/Support/MathExtras.h" -#include -#include -#include #include #include #include @@ -25,8 +19,8 @@ using namespace process_linux; using namespace llvm; -Expected -lldb_private::process_linux::FetchPerfTscConversionParameters() { +Expected +lldb_private::process_linux::LoadPerfTscConversionParameters() { lldb::pid_t pid = getpid(); perf_event_attr attr; memset(&attr, 0, sizeof(attr)); @@ -43,7 +37,7 @@ perf_event_mmap_page &mmap_metada = perf_event->GetMetadataPage(); if (mmap_metada.cap_user_time && mmap_metada.cap_user_time_zero) { - return PerfTscConversionParameters{ + return LinuxPerfZeroTscConversion{ mmap_metada.time_mult, mmap_metada.time_shift, mmap_metada.time_zero}; } else { auto err_cap = @@ -56,16 +50,6 @@ } } -std::chrono::nanoseconds PerfTscConversionParameters::ToWallTime(uint64_t tsc) { - // See 'time_zero' section of - // https://man7.org/linux/man-pages/man2/perf_event_open.2.html - uint64_t quot = tsc >> m_time_shift; - uint64_t rem_flag = (((uint64_t)1 << m_time_shift) - 1); - uint64_t rem = tsc & rem_flag; - return std::chrono::nanoseconds{m_time_zero + quot * m_time_mult + - ((rem * m_time_mult) >> m_time_shift)}; -} - void resource_handle::MmapDeleter::operator()(void *ptr) { if (m_bytes && ptr != nullptr) munmap(ptr, m_bytes); Index: lldb/source/Plugins/Process/POSIX/ProcessPOSIXLog.h =================================================================== --- lldb/source/Plugins/Process/POSIX/ProcessPOSIXLog.h +++ lldb/source/Plugins/Process/POSIX/ProcessPOSIXLog.h @@ -23,7 +23,8 @@ Registers = Log::ChannelFlag<4>, Thread = Log::ChannelFlag<5>, Watchpoints = Log::ChannelFlag<6>, - LLVM_MARK_AS_BITMASK_ENUM(Watchpoints) + Trace = Log::ChannelFlag<7>, + LLVM_MARK_AS_BITMASK_ENUM(Trace) }; LLVM_ENABLE_BITMASK_ENUMS_IN_NAMESPACE(); Index: lldb/source/Utility/TraceIntelPTGDBRemotePackets.cpp =================================================================== --- lldb/source/Utility/TraceIntelPTGDBRemotePackets.cpp +++ lldb/source/Utility/TraceIntelPTGDBRemotePackets.cpp @@ -43,4 +43,69 @@ return base; } +std::chrono::nanoseconds +LinuxPerfZeroTscConversion::Convert(uint64_t raw_counter_value) { + uint64_t quot = raw_counter_value >> m_time_shift; + uint64_t rem_flag = (((uint64_t)1 << m_time_shift) - 1); + uint64_t rem = raw_counter_value & rem_flag; + return std::chrono::nanoseconds{m_time_zero + quot * m_time_mult + + ((rem * m_time_mult) >> m_time_shift)}; +} + +json::Value LinuxPerfZeroTscConversion::toJSON() { + return json::Value(json::Object{ + {"kind", "tsc-perf-zero-conversion"}, + {"time_mult", static_cast(m_time_mult)}, + {"time_shift", static_cast(m_time_shift)}, + {"time_zero", static_cast(m_time_zero)}, + }); +} + +bool fromJSON(const json::Value &value, TraceTscConversionUP &tsc_conversion, + json::Path path) { + ObjectMapper o(value, path); + + int64_t time_mult, time_shift, time_zero; + if (!o || !o.map("time_mult", time_mult) || + !o.map("time_shift", time_shift) || !o.map("time_zero", time_zero)) + return false; + + tsc_conversion = + std::make_unique(LinuxPerfZeroTscConversion{ + static_cast(time_mult), static_cast(time_shift), + static_cast(time_zero)}); + + return true; +} + +bool fromJSON(const json::Value &value, TraceIntelPTGetStateResponse &packet, + json::Path path) { + ObjectMapper o(value, path); + if (!o || !fromJSON(value, (TraceGetStateResponse &)packet, path)) + return false; + + const Object &obj = *(value.getAsObject()); + if (const json::Value *counters = obj.get("counters")) { + json::Path subpath = path.field("counters"); + ObjectMapper ocounters(*counters, subpath); + if (!ocounters || !ocounters.mapOptional("tsc-perf-zero-conversion", + packet.tsc_conversion)) + return false; + } + return true; +} + +json::Value toJSON(const TraceIntelPTGetStateResponse &packet) { + json::Value base = toJSON((const TraceGetStateResponse &)packet); + + if (packet.tsc_conversion) { + std::vector counters{}; + base.getAsObject()->try_emplace( + "counters", json::Object{{"tsc-perf-zero-conversion", + packet.tsc_conversion->toJSON()}}); + } + + return base; +} + } // namespace lldb_private Index: lldb/unittests/Process/Linux/PerfTests.cpp =================================================================== --- lldb/unittests/Process/Linux/PerfTests.cpp +++ lldb/unittests/Process/Linux/PerfTests.cpp @@ -58,8 +58,8 @@ const int SLEEP_SECS = 1; std::chrono::nanoseconds SLEEP_NANOS{std::chrono::seconds(SLEEP_SECS)}; - Expected params = - FetchPerfTscConversionParameters(); + Expected params = + LoadPerfTscConversionParameters(); // Skip the test if the conversion parameters aren't available. if (!params) @@ -76,8 +76,7 @@ GTEST_SKIP() << toString(tsc_after_sleep.takeError()); std::chrono::nanoseconds converted_tsc_diff = - params->ToWallTime(*tsc_after_sleep) - - params->ToWallTime(*tsc_before_sleep); + params->Convert(*tsc_after_sleep) - params->Convert(*tsc_before_sleep); std::chrono::microseconds acceptable_overhead(500); Index: lldb/unittests/Utility/CMakeLists.txt =================================================================== --- lldb/unittests/Utility/CMakeLists.txt +++ lldb/unittests/Utility/CMakeLists.txt @@ -38,6 +38,7 @@ TildeExpressionResolverTest.cpp TimeoutTest.cpp TimerTest.cpp + TraceGDBRemotePacketsTest.cpp UriParserTest.cpp UserIDResolverTest.cpp UUIDTest.cpp Index: lldb/unittests/Utility/TraceGDBRemotePacketsTest.cpp =================================================================== --- /dev/null +++ lldb/unittests/Utility/TraceGDBRemotePacketsTest.cpp @@ -0,0 +1,104 @@ +//===-- TraceGDBRemotePacketsTest.cpp -------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#include "lldb/Utility/TraceIntelPTGDBRemotePackets.h" + +#include "gtest/gtest.h" + +#include + +using namespace lldb_private; +using namespace llvm; + +// Helper function to serialize a reponse, and then deserialize that serialized +// response. +static Expected +SerializeThenDeserialize(TraceIntelPTGetStateResponse &response) { + // Serialize + json::Value value = toJSON(response); + std::string json_string; + raw_string_ostream os(json_string); + os << value; + // Deserialize + return json::parse( + json_string, "TraceIntelPTGetStateResponse"); +} + +// Test serialization and deserialization of a non-empty +// TraceIntelPTGetStateResponse. +TEST(TraceGDBRemotePacketsTest, IntelPTGetStateResponse) { + // This test works as follows: + // 1. Create a non-empty TraceIntelPTGetStateResponse + // 2. Serialize to JSON + // 3. Deserialize the serialized JSON value + // 4. Ensure the original value and the deserialized value are equivalent + // + // Notes: + // - We intentionally set an integer value out of its signed range + // to ensure the serialization/deserialization isn't lossy since JSON + // operates on signed values + + // Choose arbitrary values for time_mult and time_shift + uint32_t test_time_mult = 1076264588; + uint16_t test_time_shift = 31; + // Intentionally set time_zero value out of the signed type's range. + uint64_t test_time_zero = + static_cast(std::numeric_limits::max()) + 1; + + // Create TraceIntelPTGetStateResponse + TraceIntelPTGetStateResponse response; + response.tsc_conversion = + std::make_unique(LinuxPerfZeroTscConversion( + test_time_mult, test_time_shift, test_time_zero)); + + Expected deserialized_response = + SerializeThenDeserialize(response); + if (!deserialized_response) + FAIL() << toString(deserialized_response.takeError()); + + // Choose arbitrary TSC value to test the Convert function. + const uint64_t TSC = std::numeric_limits::max(); + uint64_t pre_serialization_conversion = + response.tsc_conversion->Convert(TSC).count(); + uint64_t post_serialization_conversion = + deserialized_response->tsc_conversion->Convert(TSC).count(); + + // Check equality: + // Ensure that both the TraceGetStateResponse and TraceIntelPTGetStateResponse + // portions of the JSON representation are unchanged. + ASSERT_EQ(toJSON(response), toJSON(*deserialized_response)); + // Ensure the result of the Convert function is unchanged. + ASSERT_EQ(pre_serialization_conversion, post_serialization_conversion); +} + +// Test serialization and deserialization of an empty +// TraceIntelPTGetStateResponse. +TEST(TraceGDBRemotePacketsTest, IntelPTGetStateResponseEmpty) { + // This test works as follows: + // 1. Create an empty TraceIntelPTGetStateResponse + // 2. Serialize to JSON + // 3. Deserialize the serialized JSON value + // 4. Ensure the original value and the deserialized value are equivalent + + // Create TraceIntelPTGetStateResponse + TraceIntelPTGetStateResponse response; + + Expected deserialized_response = + SerializeThenDeserialize(response); + if (!deserialized_response) + FAIL() << toString(deserialized_response.takeError()); + + // Check equality: + // Ensure that both the TraceGetStateResponse and TraceIntelPTGetStateResponse + // portions of the JSON representation are unchanged. + ASSERT_EQ(toJSON(response), toJSON(*deserialized_response)); + // Ensure that the tsc_conversion's are nullptr + ASSERT_EQ(response.tsc_conversion.get(), nullptr); + ASSERT_EQ(response.tsc_conversion.get(), + deserialized_response->tsc_conversion.get()); +} Index: mypatch.patch =================================================================== --- /dev/null +++ mypatch.patch @@ -0,0 +1,4172 @@ +commit 1e27a41dd2d44c00b2f1012a99ed328aa5d73f6a +Author: Jakob Johnson +Date: Tue Mar 22 06:15:56 2022 -0700 + + [trace][intelpt] Server side changes for TSC to wall time conversion: + - add updated schema documentation to lldb-gdb-remote.txt + - Add support for counter values in trace + - Add TSC conversion logic: cache the perf + - Move conversion calculation from Perf.h to + TraceIntelPTGDBRemotePackets.h to remove dependency on + linux specific headers + - Update PerfTests to accomodate these changes + - add Trace type to PosixLog + - clean up includes + +diff --git a/lldb/docs/lldb-gdb-remote.txt b/lldb/docs/lldb-gdb-remote.txt +index 980dc77c86f5..2ddc3183b80e 100644 +--- a/lldb/docs/lldb-gdb-remote.txt ++++ b/lldb/docs/lldb-gdb-remote.txt +@@ -1,2063 +1,2088 @@ + LLDB has added new GDB server packets to better support multi-threaded and + remote debugging. Why? Normally you need to start the correct GDB and the + correct GDB server when debugging. If you have mismatch, then things go wrong + very quickly. LLDB makes extensive use of the GDB remote protocol and we + wanted to make sure that the experience was a bit more dynamic where we can + discover information about a remote target without having to know anything up + front. We also ran into performance issues with the existing GDB remote + protocol that can be overcome when using a reliable communications layer. + Some packets improve performance, others allow for remote process launching + (if you have an OS), and others allow us to dynamically figure out what + registers a thread might have. Again with GDB, both sides pre-agree on how the + registers will look (how many, their register number,name and offsets). We + prefer to be able to dynamically determine what kind of architecture, OS and + vendor we are debugging, as well as how things are laid out when it comes to + the thread register contexts. Below are the details on the new packets we have + added above and beyond the standard GDB remote protocol packets. + + //---------------------------------------------------------------------- + // "QStartNoAckMode" + // + // BRIEF + // Try to enable no ACK mode to skip sending ACKs and NACKs. + // + // PRIORITY TO IMPLEMENT + // High. Any GDB remote server that can implement this should if the + // connection is reliable. This improves packet throughput and increases + // the performance of the connection. + //---------------------------------------------------------------------- + Having to send an ACK/NACK after every packet slows things down a bit, so we + have a way to disable ACK packets to minimize the traffic for reliable + communication interfaces (like sockets). Below GDB or LLDB will send this + packet to try and disable ACKs. All lines that start with "send packet: " are + from GDB/LLDB, and all lines that start with "read packet: " are from the GDB + remote server: + + send packet: $QStartNoAckMode#b0 + read packet: + + read packet: $OK#9a + send packet: + + + + + //---------------------------------------------------------------------- + // "A" - launch args packet + // + // BRIEF + // Launch a program using the supplied arguments + // + // PRIORITY TO IMPLEMENT + // Low. Only needed if the remote target wants to launch a target after + // making a connection to a GDB server that isn't already connected to + // an inferior process. + //---------------------------------------------------------------------- + + We have added support for the "set program arguments" packet where we can + start a connection to a remote server and then later supply the path to the + executable and the arguments to use when executing: + + GDB remote docs for this: + + set program arguments(reserved) Aarglen,argnum,arg,... + + Where A is followed by the length in bytes of the hex encoded argument, + followed by an argument integer, and followed by the ASCII characters + converted into hex bytes foreach arg + + send packet: $A98,0,2f566f6c756d65732f776f726b2f67636c6179746f6e2f446f63756d656e74732f7372632f6174746163682f612e6f7574#00 + read packet: $OK#00 + + The above packet helps when you have remote debugging abilities where you + could launch a process on a remote host, this isn't needed for bare board + debugging. + + //---------------------------------------------------------------------- + // "QEnvironment:NAME=VALUE" + // + // BRIEF + // Setup the environment up for a new child process that will soon be + // launched using the "A" packet. + // + // NB: key/value pairs are sent as-is so gdb-remote protocol meta characters + // (e.g. '#' or '$') are not acceptable. If any non-printable or + // metacharacters are present in the strings, QEnvironmentHexEncoded + // should be used instead if it is available. If you don't want to + // scan the environment strings before sending, prefer + // the QEnvironmentHexEncoded packet over QEnvironment, if it is + // available. + // + // PRIORITY TO IMPLEMENT + // Low. Only needed if the remote target wants to launch a target after + // making a connection to a GDB server that isn't already connected to + // an inferior process. + //---------------------------------------------------------------------- + + Both GDB and LLDB support passing down environment variables. Is it ok to + respond with a "$#00" (unimplemented): + + send packet: $QEnvironment:ACK_COLOR_FILENAME=bold yellow#00 + read packet: $OK#00 + + This packet can be sent one or more times _prior_ to sending a "A" packet. + + //---------------------------------------------------------------------- + // "QEnvironmentHexEncoded:HEX-ENCODING(NAME=VALUE)" + // + // BRIEF + // Setup the environment up for a new child process that will soon be + // launched using the "A" packet. + // + // The only difference between this packet and QEnvironment is that the + // environment key-value pair is ascii hex encoded for transmission. + // This allows values with gdb-remote metacharacters like '#' to be sent. + // + // PRIORITY TO IMPLEMENT + // Low. Only needed if the remote target wants to launch a target after + // making a connection to a GDB server that isn't already connected to + // an inferior process. + //---------------------------------------------------------------------- + + Both GDB and LLDB support passing down environment variables. Is it ok to + respond with a "$#00" (unimplemented): + + send packet: $QEnvironment:41434b5f434f4c4f525f46494c454e414d453d626f6c642379656c6c6f77#00 + read packet: $OK#00 + + This packet can be sent one or more times _prior_ to sending a "A" packet. + + //---------------------------------------------------------------------- + // "QEnableErrorStrings" + // + // BRIEF + // This packet enables reporting of Error strings in remote packet + // replies from the server to client. If the server supports this + // feature, it should send an OK response. The client can expect the + // following error replies if this feature is enabled in the server -> + // + // EXX;AAAAAAAAA + // + // where AAAAAAAAA will be a hex encoded ASCII string. + // XX is hex encoded byte number. + // + // It must be noted that even if the client has enabled reporting + // strings in error replies, it must not expect error strings to all + // error replies. + // + // PRIORITY TO IMPLEMENT + // Low. Only needed if the remote target wants to provide strings that + // are human readable along with an error code. + //---------------------------------------------------------------------- + + send packet: $QEnableErrorStrings + read packet: $OK#00 + + //---------------------------------------------------------------------- + // "QSetSTDIN:" + // "QSetSTDOUT:" + // "QSetSTDERR:" + // + // BRIEF + // Setup where STDIN, STDOUT, and STDERR go prior to sending an "A" + // packet. + // + // PRIORITY TO IMPLEMENT + // Low. Only needed if the remote target wants to launch a target after + // making a connection to a GDB server that isn't already connected to + // an inferior process. + //---------------------------------------------------------------------- + + When launching a program through the GDB remote protocol with the "A" packet, + you might also want to specify where stdin/out/err go: + + QSetSTDIN: + QSetSTDOUT: + QSetSTDERR: + + These packets must be sent _prior_ to sending a "A" packet. + + //---------------------------------------------------------------------- + // "QSetWorkingDir:" + // + // BRIEF + // Set the working directory prior to sending an "A" packet. + // + // PRIORITY TO IMPLEMENT + // Low. Only needed if the remote target wants to launch a target after + // making a connection to a GDB server that isn't already connected to + // an inferior process. + //---------------------------------------------------------------------- + + Or specify the working directory: + + QSetWorkingDir: + + This packet must be sent _prior_ to sending a "A" packet. + + //---------------------------------------------------------------------- + // "QSetDisableASLR:" + // + // BRIEF + // Enable or disable ASLR on the next "A" packet. + // + // PRIORITY TO IMPLEMENT + // Low. Only needed if the remote target wants to launch a target after + // making a connection to a GDB server that isn't already connected to + // an inferior process and if the target supports disabling ASLR + // (Address space layout randomization). + //---------------------------------------------------------------------- + + Or control if ASLR is enabled/disabled: + + send packet: QSetDisableASLR:1 + read packet: OK + + send packet: QSetDisableASLR:0 + read packet: OK + + This packet must be sent _prior_ to sending a "A" packet. + + //---------------------------------------------------------------------- + // QListThreadsInStopReply + // + // BRIEF + // Enable the threads: and thread-pcs: data in the question-mark packet + // ("T packet") responses when the stub reports that a program has + // stopped executing. + // + // PRIORITY TO IMPLEMENT + // Performance. This is a performance benefit to lldb if the thread id's + // and thread pc values are provided to lldb in the T stop packet -- if + // they are not provided to lldb, lldb will likely need to send one to + // two packets per thread to fetch the data at every private stop. + //---------------------------------------------------------------------- + + send packet: QListThreadsInStopReply + read packet: OK + + //---------------------------------------------------------------------- + // jLLDBTraceSupported + // + // BRIEF + // Get the processor tracing type supported by the gdb-server for the current + // inferior. Responses might be different depending on the architecture and + // capabilities of the underlying OS. + // + // OUTPUT SCHEMA + // { + // "name": , + // Tracing technology name, e.g. intel-pt, arm-coresight. + // "description": , + // Description for this technology. + // } + // + // If no tracing technology is supported for the inferior, or no process is + // running, then an error message is returned. + // + // NOTE + // This packet is used by Trace plug-ins (see lldb_private::Trace.h) to + // do live tracing. Specifically, the name of the plug-in should match the name + // of the tracing technology returned by this packet. + //---------------------------------------------------------------------- + + send packet: jLLDBTraceSupported + read packet: {"name":, "description":}/E;AAAAAAAAA + + //---------------------------------------------------------------------- + // jLLDBTraceStart + // + // BRIEF + // Start tracing a process or its threads using a provided tracing technology. + // The input and output are specified as JSON objects. In case of success, an OK + // response is returned, or an error otherwise. + // + // PROCESS TRACING + // This traces existing and future threads of the current process. An error is + // returned if the process is already being traced. + // + // THREAD TRACING + // This traces specific threads. + // + // INPUT SCHEMA + // { + // "type": , + // Tracing technology name, e.g. intel-pt, arm-coresight. + // + // /* thread tracing only */ + // "tids": [], + // Individual threads to trace. + // + // ... other parameters specific to the provided tracing type + // } + // + // NOTES + // - If "tids" is not provided, then the operation is "process tracing", + // otherwise it's "thread tracing". + // - Each tracing technology can have different levels of support for "thread + // tracing" and "process tracing". + // + // INTEL-PT + // intel-pt supports both "thread tracing" and "process tracing". + // + // "Process tracing" is implemented by tracing each thread individually, but + // managed by the same "process trace" instance. + // Each actual thread trace, either from "process tracing" or "thread tracing", + // is stored in an in-memory circular buffer, which keeps the most recent data. + // + // Additional params in the input schema: + // { + // "threadBufferSize": , + // Trace buffer size per thread in bytes. It must be a power of 2 + // greater than or equal to 4096 (2^12) bytes. + // + // "enableTsc": , + // Whether to enable TSC timestamps or not. This is supported on + // all devices that support intel-pt. A TSC timestamp is generated along + // with PSB (synchronization) packets, whose frequency can be configured + // with the "psbPeriod" parameter. + // + // "psbPeriod"?: , + // This value defines the period in which PSB packets will be generated. + // A PSB packet is a synchronization packet that contains a TSC + // timestamp and the current absolute instruction pointer. + // + // This parameter can only be used if + // + // /sys/bus/event_source/devices/intel_pt/caps/psb_cyc + // + // is 1. Otherwise, the PSB period will be defined by the processor. + // + // If supported, valid values for this period can be found in + / + // /sys/bus/event_source/devices/intel_pt/caps/psb_periods + // + // which contains a hexadecimal number, whose bits represent valid + // values e.g. if bit 2 is set, then value 2 is valid. + // + // The psb_period value is converted to the approximate number of + // raw trace bytes between PSB packets as: + // + // 2 ^ (value + 11) + // + // e.g. value 3 means 16KiB between PSB packets. Defaults to + // 0 if supported. + // + // /* process tracing only */ + // "processBufferSizeLimit": , + // Maximum total buffer size per process in bytes. + // This limit applies to the sum of the sizes of all trace buffers for + // the current process, excluding the ones started with "thread tracing". + // + // Whenever a thread is attempted to be traced due to "process tracing" + // and the limit would be reached, the process is stopped with a + // "tracing" reason along with a meaningful description, so that the + // user can retrace the process if needed. + // } + // + // Notes: + // - Modifying the parameters of an existing trace is not supported. The user + // needs to stop the trace and start a new one. + // - If "process tracing" is attempted and there are individual threads + // already being traced with "thread tracing", these traces are left + // unaffected and the threads not traced twice. + // - If "thread tracing" is attempted on a thread already being traced with + // either "thread tracing" or "process tracing", it fails. + //---------------------------------------------------------------------- + + Process tracing: + send packet: jLLDBTraceStart:{"type":,...other params}] + read packet: OK/E;AAAAAAAAA + + Thread tracing: + send packet: jLLDBTraceStart:{"type":,"tids":,...other params}] + read packet: OK/E;AAAAAAAAA + + //---------------------------------------------------------------------- + // jLLDBTraceStop + // + // BRIEF + // Stop tracing a process or its threads using a provided tracing technology. + // The input and output are specified as JSON objects. In case of success, an OK + // response is returned, or an error otherwise. + // + // PROCESS TRACE STOPPING + // Stopping a process trace stops the active traces initiated with + // "thread tracing". + // + // THREAD TRACE STOPPING + // This is a best effort request, which tries to stop as many traces as + // possible. + // + // INPUT SCHEMA + // The schema for the input is + // + // { + // "type": + // Tracing technology name, e.g. intel-pt, arm-coresight. + // + // /* thread trace stopping only */ + // "tids": [] + // Individual thread traces to stop. + // } + // + // NOTES + // - If "tids" is not provided, then the operation is "process trace stopping". + // + // INTEL PT + // Stopping a specific thread trace started with "process tracing" is allowed. + //---------------------------------------------------------------------- + + Process trace stopping: + send packet: jLLDBTraceStop:{"type":}] + read packet: OK/E;AAAAAAAAA + + Thread trace stopping: + send packet: jLLDBTraceStop:{"type":,"tids":}] + read packet: OK/E;AAAAAAAAA + + //---------------------------------------------------------------------- + // jLLDBTraceGetState + // + // BRIEF + // Get the current state of the process and its threads being traced by + // a given trace technology. The response is a JSON object with custom + // information depending on the trace technology. In case of errors, an + // error message is returned. + // + // INPUT SCHEMA + // { + // "type": + // Tracing technology name, e.g. intel-pt, arm-coresight. + // } + // + // OUTPUT SCHEMA + // { + // "tracedThreads": [{ + // "tid": , + // "binaryData": [ + // { + // "kind": , + // Identifier for some binary data related to this thread to + // fetch with the jLLDBTraceGetBinaryData packet. + // "size": , + // Size in bytes of this thread data. + // }, + // ] + // }], + // "processBinaryData": [ + // { + // "kind": , + // Identifier for some binary data related to this process to + // fetch with the jLLDBTraceGetBinaryData packet. + // "size": , + // Size in bytes of this thread data. + // }, +-// }] ++// ], ++// "counters"?: { ++// "info_kind": {...parameters specific to the provided counter info kind}, ++// Each entry includes information related to counters associated with the trace. ++// They are described below. ++// } + // } + // + // NOTES + // - "traceThreads" includes all thread traced by both "process tracing" and + // "thread tracing". + // + // INTEL PT + // + // Binary data kinds: + // - threadTraceBuffer: trace buffer for a thread. + // - cpuInfo: contents of the /proc/cpuinfo file. ++// ++// Counter info kinds: ++// tsc-perf-zero-conversion: ++// ++// This field allows converting Intel processor's TSC values to a wall time. ++// It is available through the Linux perf_event API when cap_user_time and cap_user_time_zero ++// are set. ++// See the documentation of time_zero in ++// https://man7.org/linux/man-pages/man2/perf_event_open.2.html for more information about ++// the calculation and the meaning of the values in the schema below. ++/// ++// Sub-schema for this field: ++// ++// { ++// "tsc-perf-zero-conversion": { ++// "time_mult": , ++// "time_shift": , ++// "time_zero": , ++// } ++// } + //---------------------------------------------------------------------- + + send packet: jLLDBTraceGetState:{"type":}] + read packet: {...object}/E;AAAAAAAAA + + //---------------------------------------------------------------------- + // jLLDBTraceGetBinaryData + // + // BRIEF + // Get binary data given a trace technology and a data identifier. + // The input is specified as a JSON object and the response has the same format + // as the "binary memory read" (aka "x") packet. In case of failures, an error + // message is returned. + // + // SCHEMA + // The schema for the input is + // + // { + // "type": , + // Tracing technology name, e.g. intel-pt, arm-coresight. + // "kind": , + // Identifier for the data. + // "tid"?: , + // Tid in decimal if the data belongs to a thread. + // "offset": , + // Offset of the data in bytes. + // "size": , + // Number of bytes in to read starting from the offset. + // } + // + // INTEL PT + // + // Binary data kinds: + // - threadTraceBuffer: trace buffer for a thread. + // - cpuInfo: contents of the /proc/cpuinfo file. + //---------------------------------------------------------------------- + + send packet: jLLDBTraceGetBinaryData:{"type":,"kind":,"tid":,"offset":,"size":}] + read packet: /E;AAAAAAAAA + + //---------------------------------------------------------------------- + // "qRegisterInfo" + // + // BRIEF + // Discover register information from the remote GDB server. + // + // PRIORITY TO IMPLEMENT + // High. Any target that can self describe its registers, should do so. + // This means if new registers are ever added to a remote target, they + // will get picked up automatically, and allows registers to change + // depending on the actual CPU type that is used. + // + // NB: As of summer 2015, lldb can get register information from the + // "qXfer:features:read:target.xml" FSF gdb standard register packet + // where the stub provides register definitions in an XML file. + // If qXfer:features:read:target.xml is supported, qRegisterInfo does + // not need to be implemented. + //---------------------------------------------------------------------- + + With LLDB, for register information, remote GDB servers can add + support for the "qRegisterInfoN" packet where "N" is a zero based + base16 register number that must start at zero and increase by one + for each register that is supported. The response is done in typical + GDB remote fashion where a series of "KEY:VALUE;" pairs are returned. + An example for the x86_64 registers is included below: + + send packet: $qRegisterInfo0#00 + read packet: $name:rax;bitsize:64;offset:0;encoding:uint;format:hex;set:General Purpose Registers;gcc:0;dwarf:0;#00 + send packet: $qRegisterInfo1#00 + read packet: $name:rbx;bitsize:64;offset:8;encoding:uint;format:hex;set:General Purpose Registers;gcc:3;dwarf:3;#00 + send packet: $qRegisterInfo2#00 + read packet: $name:rcx;bitsize:64;offset:16;encoding:uint;format:hex;set:General Purpose Registers;gcc:2;dwarf:2;#00 + send packet: $qRegisterInfo3#00 + read packet: $name:rdx;bitsize:64;offset:24;encoding:uint;format:hex;set:General Purpose Registers;gcc:1;dwarf:1;#00 + send packet: $qRegisterInfo4#00 + read packet: $name:rdi;bitsize:64;offset:32;encoding:uint;format:hex;set:General Purpose Registers;gcc:5;dwarf:5;#00 + send packet: $qRegisterInfo5#00 + read packet: $name:rsi;bitsize:64;offset:40;encoding:uint;format:hex;set:General Purpose Registers;gcc:4;dwarf:4;#00 + send packet: $qRegisterInfo6#00 + read packet: $name:rbp;alt-name:fp;bitsize:64;offset:48;encoding:uint;format:hex;set:General Purpose Registers;gcc:6;dwarf:6;generic:fp;#00 + send packet: $qRegisterInfo7#00 + read packet: $name:rsp;alt-name:sp;bitsize:64;offset:56;encoding:uint;format:hex;set:General Purpose Registers;gcc:7;dwarf:7;generic:sp;#00 + send packet: $qRegisterInfo8#00 + read packet: $name:r8;bitsize:64;offset:64;encoding:uint;format:hex;set:General Purpose Registers;gcc:8;dwarf:8;#00 + send packet: $qRegisterInfo9#00 + read packet: $name:r9;bitsize:64;offset:72;encoding:uint;format:hex;set:General Purpose Registers;gcc:9;dwarf:9;#00 + send packet: $qRegisterInfoa#00 + read packet: $name:r10;bitsize:64;offset:80;encoding:uint;format:hex;set:General Purpose Registers;gcc:10;dwarf:10;#00 + send packet: $qRegisterInfob#00 + read packet: $name:r11;bitsize:64;offset:88;encoding:uint;format:hex;set:General Purpose Registers;gcc:11;dwarf:11;#00 + send packet: $qRegisterInfoc#00 + read packet: $name:r12;bitsize:64;offset:96;encoding:uint;format:hex;set:General Purpose Registers;gcc:12;dwarf:12;#00 + send packet: $qRegisterInfod#00 + read packet: $name:r13;bitsize:64;offset:104;encoding:uint;format:hex;set:General Purpose Registers;gcc:13;dwarf:13;#00 + send packet: $qRegisterInfoe#00 + read packet: $name:r14;bitsize:64;offset:112;encoding:uint;format:hex;set:General Purpose Registers;gcc:14;dwarf:14;#00 + send packet: $qRegisterInfof#00 + read packet: $name:r15;bitsize:64;offset:120;encoding:uint;format:hex;set:General Purpose Registers;gcc:15;dwarf:15;#00 + send packet: $qRegisterInfo10#00 + read packet: $name:rip;alt-name:pc;bitsize:64;offset:128;encoding:uint;format:hex;set:General Purpose Registers;gcc:16;dwarf:16;generic:pc;#00 + send packet: $qRegisterInfo11#00 + read packet: $name:rflags;alt-name:flags;bitsize:64;offset:136;encoding:uint;format:hex;set:General Purpose Registers;#00 + send packet: $qRegisterInfo12#00 + read packet: $name:cs;bitsize:64;offset:144;encoding:uint;format:hex;set:General Purpose Registers;#00 + send packet: $qRegisterInfo13#00 + read packet: $name:fs;bitsize:64;offset:152;encoding:uint;format:hex;set:General Purpose Registers;#00 + send packet: $qRegisterInfo14#00 + read packet: $name:gs;bitsize:64;offset:160;encoding:uint;format:hex;set:General Purpose Registers;#00 + send packet: $qRegisterInfo15#00 + read packet: $name:fctrl;bitsize:16;offset:176;encoding:uint;format:hex;set:Floating Point Registers;#00 + send packet: $qRegisterInfo16#00 + read packet: $name:fstat;bitsize:16;offset:178;encoding:uint;format:hex;set:Floating Point Registers;#00 + send packet: $qRegisterInfo17#00 + read packet: $name:ftag;bitsize:8;offset:180;encoding:uint;format:hex;set:Floating Point Registers;#00 + send packet: $qRegisterInfo18#00 + read packet: $name:fop;bitsize:16;offset:182;encoding:uint;format:hex;set:Floating Point Registers;#00 + send packet: $qRegisterInfo19#00 + read packet: $name:fioff;bitsize:32;offset:184;encoding:uint;format:hex;set:Floating Point Registers;#00 + send packet: $qRegisterInfo1a#00 + read packet: $name:fiseg;bitsize:16;offset:188;encoding:uint;format:hex;set:Floating Point Registers;#00 + send packet: $qRegisterInfo1b#00 + read packet: $name:fooff;bitsize:32;offset:192;encoding:uint;format:hex;set:Floating Point Registers;#00 + send packet: $qRegisterInfo1c#00 + read packet: $name:foseg;bitsize:16;offset:196;encoding:uint;format:hex;set:Floating Point Registers;#00 + send packet: $qRegisterInfo1d#00 + read packet: $name:mxcsr;bitsize:32;offset:200;encoding:uint;format:hex;set:Floating Point Registers;#00 + send packet: $qRegisterInfo1e#00 + read packet: $name:mxcsrmask;bitsize:32;offset:204;encoding:uint;format:hex;set:Floating Point Registers;#00 + send packet: $qRegisterInfo1f#00 + read packet: $name:stmm0;bitsize:80;offset:208;encoding:vector;format:vector-uint8;set:Floating Point Registers;gcc:33;dwarf:33;#00 + send packet: $qRegisterInfo20#00 + read packet: $name:stmm1;bitsize:80;offset:224;encoding:vector;format:vector-uint8;set:Floating Point Registers;gcc:34;dwarf:34;#00 + send packet: $qRegisterInfo21#00 + read packet: $name:stmm2;bitsize:80;offset:240;encoding:vector;format:vector-uint8;set:Floating Point Registers;gcc:35;dwarf:35;#00 + send packet: $qRegisterInfo22#00 + read packet: $name:stmm3;bitsize:80;offset:256;encoding:vector;format:vector-uint8;set:Floating Point Registers;gcc:36;dwarf:36;#00 + send packet: $qRegisterInfo23#00 + read packet: $name:stmm4;bitsize:80;offset:272;encoding:vector;format:vector-uint8;set:Floating Point Registers;gcc:37;dwarf:37;#00 + send packet: $qRegisterInfo24#00 + read packet: $name:stmm5;bitsize:80;offset:288;encoding:vector;format:vector-uint8;set:Floating Point Registers;gcc:38;dwarf:38;#00 + send packet: $qRegisterInfo25#00 + read packet: $name:stmm6;bitsize:80;offset:304;encoding:vector;format:vector-uint8;set:Floating Point Registers;gcc:39;dwarf:39;#00 + send packet: $qRegisterInfo26#00 + read packet: $name:stmm7;bitsize:80;offset:320;encoding:vector;format:vector-uint8;set:Floating Point Registers;gcc:40;dwarf:40;#00 + send packet: $qRegisterInfo27#00 + read packet: $name:xmm0;bitsize:128;offset:336;encoding:vector;format:vector-uint8;set:Floating Point Registers;gcc:17;dwarf:17;#00 + send packet: $qRegisterInfo28#00 + read packet: $name:xmm1;bitsize:128;offset:352;encoding:vector;format:vector-uint8;set:Floating Point Registers;gcc:18;dwarf:18;#00 + send packet: $qRegisterInfo29#00 + read packet: $name:xmm2;bitsize:128;offset:368;encoding:vector;format:vector-uint8;set:Floating Point Registers;gcc:19;dwarf:19;#00 + send packet: $qRegisterInfo2a#00 + read packet: $name:xmm3;bitsize:128;offset:384;encoding:vector;format:vector-uint8;set:Floating Point Registers;gcc:20;dwarf:20;#00 + send packet: $qRegisterInfo2b#00 + read packet: $name:xmm4;bitsize:128;offset:400;encoding:vector;format:vector-uint8;set:Floating Point Registers;gcc:21;dwarf:21;#00 + send packet: $qRegisterInfo2c#00 + read packet: $name:xmm5;bitsize:128;offset:416;encoding:vector;format:vector-uint8;set:Floating Point Registers;gcc:22;dwarf:22;#00 + send packet: $qRegisterInfo2d#00 + read packet: $name:xmm6;bitsize:128;offset:432;encoding:vector;format:vector-uint8;set:Floating Point Registers;gcc:23;dwarf:23;#00 + send packet: $qRegisterInfo2e#00 + read packet: $name:xmm7;bitsize:128;offset:448;encoding:vector;format:vector-uint8;set:Floating Point Registers;gcc:24;dwarf:24;#00 + send packet: $qRegisterInfo2f#00 + read packet: $name:xmm8;bitsize:128;offset:464;encoding:vector;format:vector-uint8;set:Floating Point Registers;gcc:25;dwarf:25;#00 + send packet: $qRegisterInfo30#00 + read packet: $name:xmm9;bitsize:128;offset:480;encoding:vector;format:vector-uint8;set:Floating Point Registers;gcc:26;dwarf:26;#00 + send packet: $qRegisterInfo31#00 + read packet: $name:xmm10;bitsize:128;offset:496;encoding:vector;format:vector-uint8;set:Floating Point Registers;gcc:27;dwarf:27;#00 + send packet: $qRegisterInfo32#00 + read packet: $name:xmm11;bitsize:128;offset:512;encoding:vector;format:vector-uint8;set:Floating Point Registers;gcc:28;dwarf:28;#00 + send packet: $qRegisterInfo33#00 + read packet: $name:xmm12;bitsize:128;offset:528;encoding:vector;format:vector-uint8;set:Floating Point Registers;gcc:29;dwarf:29;#00 + send packet: $qRegisterInfo34#00 + read packet: $name:xmm13;bitsize:128;offset:544;encoding:vector;format:vector-uint8;set:Floating Point Registers;gcc:30;dwarf:30;#00 + send packet: $qRegisterInfo35#00 + read packet: $name:xmm14;bitsize:128;offset:560;encoding:vector;format:vector-uint8;set:Floating Point Registers;gcc:31;dwarf:31;#00 + send packet: $qRegisterInfo36#00 + read packet: $name:xmm15;bitsize:128;offset:576;encoding:vector;format:vector-uint8;set:Floating Point Registers;gcc:32;dwarf:32;#00 + send packet: $qRegisterInfo37#00 + read packet: $name:trapno;bitsize:32;offset:696;encoding:uint;format:hex;set:Exception State Registers;#00 + send packet: $qRegisterInfo38#00 + read packet: $name:err;bitsize:32;offset:700;encoding:uint;format:hex;set:Exception State Registers;#00 + send packet: $qRegisterInfo39#00 + read packet: $name:faultvaddr;bitsize:64;offset:704;encoding:uint;format:hex;set:Exception State Registers;#00 + send packet: $qRegisterInfo3a#00 + read packet: $E45#00 + + As we see above we keep making subsequent calls to the remote server to + discover all registers by increasing the number appended to qRegisterInfo and + we get a response back that is a series of "key=value;" strings. + + The offset: fields should not leave a gap anywhere in the g/G packet -- the + register values should be appended one after another. For instance, if the + register context for a thread looks like + + struct rctx { + uint32_t gpr1; // offset 0 + uint32_t gpr2; // offset 4 + uint32_t gpr3; // offset 8 + uint64_t fp1; // offset 16 + }; + + You may end up with a 4-byte gap between gpr3 and fp1 on architectures + that align values like this. The correct offset: value for fp1 is 12 - + in the g/G packet fp1 will immediately follow gpr3, even though the + in-memory thread structure has an empty 4 bytes for alignment between + these two registers. + + The keys and values are detailed below: + + Key Value + ========== ================================================================ + name The primary register name as a string ("rbp" for example) + + alt-name An alternate name for a register as a string ("fp" for example for + the above "rbp") + + bitsize Size in bits of a register (32, 64, etc). Base 10. + + offset The offset within the "g" and "G" packet of the register data for + this register. This is the byte offset once the data has been + transformed into binary, not the character offset into the g/G + packet. Base 10. + + encoding The encoding type of the register which must be one of: + + uint (unsigned integer) + sint (signed integer) + ieee754 (IEEE 754 float) + vector (vector register) + + format The preferred format for display of this register. The value must + be one of: + + binary + decimal + hex + float + vector-sint8 + vector-uint8 + vector-sint16 + vector-uint16 + vector-sint32 + vector-uint32 + vector-float32 + vector-uint128 + + set The register set name as a string that this register belongs to. + + gcc The GCC compiler registers number for this register (used for + EH frame and other compiler information that is encoded in the + executable files). The supplied number will be decoded like a + string passed to strtoul() with a base of zero, so the number + can be decimal, or hex if it is prefixed with "0x". + + NOTE: If the compiler doesn't have a register number for this + register, this key/value pair should be omitted. + + dwarf The DWARF register number for this register that is used for this + register in the debug information. The supplied number will be decoded + like a string passed to strtoul() with a base of zero, so the number + can be decimal, or hex if it is prefixed with "0x". + + NOTE: If the compiler doesn't have a register number for this + register, this key/value pair should be omitted. + + generic If the register is a generic register that most CPUs have, classify + it correctly so the debugger knows. Valid values are one of: + pc (a program counter register. for example "name=eip;" (i386), + "name=rip;" (x86_64), "name=r15;" (32 bit arm) would + include a "generic=pc;" key value pair) + sp (a stack pointer register. for example "name=esp;" (i386), + "name=rsp;" (x86_64), "name=r13;" (32 bit arm) would + include a "generic=sp;" key value pair) + fp (a frame pointer register. for example "name=ebp;" (i386), + "name=rbp;" (x86_64), "name=r7;" (32 bit arm with macosx + ABI) would include a "generic=fp;" key value pair) + ra (a return address register. for example "name=lr;" (32 bit ARM) + would include a "generic=ra;" key value pair) + fp (a CPU flags register. for example "name=eflags;" (i386), + "name=rflags;" (x86_64), "name=cpsr;" (32 bit ARM) + would include a "generic=flags;" key value pair) + arg1 - arg8 (specified for registers that contain function + arguments when the argument fits into a register) + + container-regs + The value for this key is a comma separated list of raw hex (optional + leading "0x") register numbers. + + This specifies that this register is contained in other concrete + register values. For example "eax" is in the lower 32 bits of the + "rax" register value for x86_64, so "eax" could specify that it is + contained in "rax" by specifying the register number for "rax" (whose + register number is 0x00) + + "container-regs:00;" + + If a register is comprised of one or more registers, like "d0" is ARM + which is a 64 bit register, it might be made up of "s0" and "s1". If + the register number for "s0" is 0x20, and the register number of "s1" + is "0x21", the "container-regs" key/value pair would be: + + "container-regs:20,21;" + + This is handy for defining what GDB used to call "pseudo" registers. + These registers are never requested by LLDB via the register read + or write packets, the container registers will be requested on behalf + of this register. + + invalidate-regs + The value for this key is a comma separated list of raw hex (optional + leading "0x") register numbers. + + This specifies which register values should be invalidated when this + register is modified. For example if modifying "eax" would cause "rax", + "eax", "ax", "ah", and "al" to be modified where rax is 0x0, eax is 0x15, + ax is 0x25, ah is 0x35, and al is 0x39, the "invalidate-regs" key/value + pair would be: + + "invalidate-regs:0,15,25,35,39;" + + If there is a single register that gets invalidated, then omit the comma + and just list a single register: + + "invalidate-regs:0;" + + This is handy when modifying a specific register can cause other + register values to change. For example, when debugging an ARM target, + modifying the CPSR register can cause the r8 - r14 and cpsr value to + change depending on if the mode has changed. + + //---------------------------------------------------------------------- + // "qPlatform_shell" + // + // BRIEF + // Run a command in a shell on the connected remote machine. + // + // PRIORITY TO IMPLEMENT + // High. This command allows LLDB clients to run arbitrary shell + // commands on a remote host. + // + /---------------------------------------------------------------------- + + The request consists of the command to be executed encoded in ASCII characters + converted into hex bytes. + + The response to this packet consists of the letter F followed by the return code, + followed by the signal number (or 0 if no signal was delivered), and escaped bytes + of captured program output. + + Below is an example communication from a client sending an "ls -la" command: + + send packet: $qPlatform_shell:6c73202d6c61,00000002#ec + read packet: $F,00000000,00000000,total 4736 + drwxrwxr-x 16 username groupname 4096 Aug 15 21:36 . + drwxr-xr-x 17 username groupname 4096 Aug 10 16:39 .. + -rw-rw-r-- 1 username groupname 73875 Aug 12 16:46 notes.txt + drwxrwxr-x 5 username groupname 4096 Aug 15 21:36 source.cpp + -rw-r--r-- 1 username groupname 2792 Aug 12 16:46 a.out + -rw-r--r-- 1 username groupname 3190 Aug 12 16:46 Makefile + + //---------------------------------------------------------------------- + // "qPlatform_mkdir" + // + // BRIEF + // Creates a new directory on the connected remote machine. + // + // PRIORITY TO IMPLEMENT + // Low. This command allows LLDB clients to create new directories on + // a remote host. + // + /---------------------------------------------------------------------- + + Request: + qPlatform_mkdir:, + + Reply: + F + mkdir called successfully and returned with the given return code + Exx + An error occurred + + //---------------------------------------------------------------------- + // "qPlatform_chmod" + // + // BRIEF + // Change the permissions of a file on the connected remote machine. + // + // PRIORITY TO IMPLEMENT + // Low. This command allows LLDB clients to change the permissions of + // a file on the remote host. + // + /---------------------------------------------------------------------- + + Request: + qPlatform_chmod:, + + Reply: + F + chmod called successfully and returned with the given return code + Exx + An error occurred + + //---------------------------------------------------------------------- + // "qHostInfo" + // + // BRIEF + // Get information about the host we are remotely connected to. + // + // PRIORITY TO IMPLEMENT + // High. This packet is usually very easy to implement and can help + // LLDB select the correct plug-ins for the job based on the target + // triple information that is supplied. + //---------------------------------------------------------------------- + + LLDB supports a host info call that gets all sorts of details of the system + that is being debugged: + + send packet: $qHostInfo#00 + read packet: $cputype:16777223;cpusubtype:3;ostype:darwin;vendor:apple;endian:little;ptrsize:8;#00 + + Key value pairs are one of: + + cputype: is a number that is the mach-o CPU type that is being debugged (base 10) + cpusubtype: is a number that is the mach-o CPU subtype type that is being debugged (base 10) + triple: a string for the target triple (x86_64-apple-macosx) that can be used to specify arch + vendor + os in one entry + vendor: a string for the vendor (apple), not needed if "triple" is specified + ostype: a string for the OS being debugged (macosx, linux, freebsd, ios, watchos), not needed if "triple" is specified + endian: is one of "little", "big", or "pdp" + ptrsize: an unsigned number that represents how big pointers are in bytes on the debug target + hostname: the hostname of the host that is running the GDB server if available + os_build: a string for the OS build for the remote host as a string value + os_kernel: a string describing the kernel version + os_version: a version string that represents the current OS version (10.8.2) + watchpoint_exceptions_received: one of "before" or "after" to specify if a watchpoint is triggered before or after the pc when it stops + default_packet_timeout: an unsigned number that specifies the default timeout in seconds + distribution_id: optional. For linux, specifies distribution id (e.g. ubuntu, fedora, etc.) + osmajor: optional, specifies the major version number of the OS (e.g. for macOS 10.12.2, it would be 10) + osminor: optional, specifies the minor version number of the OS (e.g. for macOS 10.12.2, it would be 12) + ospatch: optional, specifies the patch level number of the OS (e.g. for macOS 10.12.2, it would be 2) + vm-page-size: optional, specifies the target system VM page size, base 10. + Needed for the "dirty-pages:" list in the qMemoryRegionInfo + packet, where a list of dirty pages is sent from the remote + stub. This page size tells lldb how large each dirty page is. + addressing_bits: optional, specifies how many bits in addresses are + significant for addressing, base 10. If bits 38..0 + in a 64-bit pointer are significant for addressing, + then the value is 39. This is needed on e.g. AArch64 + v8.3 ABIs that use pointer authentication, so lldb + knows which bits to clear/set to get the actual + addresses. + + //---------------------------------------------------------------------- + // "qGDBServerVersion" + // + // BRIEF + // Get version information about this implementation of the gdb-remote + // protocol. + // + // PRIORITY TO IMPLEMENT + // High. This packet is usually very easy to implement and can help + // LLDB to work around bugs in a server's implementation when they + // are found. + //---------------------------------------------------------------------- + + The goal of this packet is to provide enough information about an + implementation of the gdb-remote-protocol server that lldb can + work around implementation problems that are discovered after the + version has been released/deployed. The name and version number + should be sufficiently unique that lldb can unambiguously identify + the origin of the program (for instance, debugserver from lldb) and + the version/submission number/patch level of the program - whatever + is appropriate for your server implementation. + + The packet follows the key-value pair model, semicolon separated. + + send packet: $qGDBServerVersion#00 + read packet: $name:debugserver;version:310.2;#00 + + Other clients may find other key-value pairs to be useful for identifying + a gdb stub. Patch level, release name, build number may all be keys that + better describe your implementation's version. + Suggested key names: + + name : the name of your remote server - "debugserver" is the lldb standard + implementation + + version : identifies the version number of this server + + patch_level : the patch level of this server + + release_name : the name of this release, if your project uses names + + build_number : if you use a build system with increasing build numbers, + this may be the right key name for your server + + major_version : major version number + minor_version : minor version number + + //---------------------------------------------------------------------- + // "qProcessInfo" + // + // BRIEF + // Get information about the process we are currently debugging. + // + // PRIORITY TO IMPLEMENT + // Medium. On systems which can launch multiple different architecture processes, + // the qHostInfo may not disambiguate sufficiently to know what kind of + // process is being debugged. + // e.g. on a 64-bit x86 Mac system both 32-bit and 64-bit user processes are possible, + // and with Mach-O universal files, the executable file may contain both 32- and + // 64-bit slices so it may be impossible to know until you're attached to a real + // process to know what you're working with. + // + // All numeric fields return base-16 numbers without any "0x" prefix. + //---------------------------------------------------------------------- + + An i386 process: + + send packet: $qProcessInfo#00 + read packet: $pid:42a8;parent-pid:42bf;real-uid:ecf;real-gid:b;effective-uid:ecf;effective-gid:b;cputype:7;cpusubtype:3;ostype:macosx;vendor:apple;endian:little;ptrsize:4;#00 + + An x86_64 process: + + send packet: $qProcessInfo#00 + read packet: $pid:d22c;parent-pid:d34d;real-uid:ecf;real-gid:b;effective-uid:ecf;effective-gid:b;cputype:1000007;cpusubtype:3;ostype:macosx;vendor:apple;endian:little;ptrsize:8;#00 + + Key value pairs include: + + pid: the process id + parent-pid: the process of the parent process (often debugserver will become the parent when attaching) + real-uid: the real user id of the process + real-gid: the real group id of the process + effective-uid: the effective user id of the process + effective-gid: the effective group id of the process + cputype: the Mach-O CPU type of the process (base 16) + cpusubtype: the Mach-O CPU subtype of the process (base 16) + ostype: is a string the represents the OS being debugged (darwin, linux, freebsd) + vendor: is a string that represents the vendor (apple) + endian: is one of "little", "big", or "pdp" + ptrsize: is a number that represents how big pointers are in bytes + + main-binary-uuid: is the UUID of a firmware type binary that the gdb stub knows about + main-binary-address: is the load address of the firmware type binary + main-binary-slide: is the slide of the firmware type binary, if address isn't known + + //---------------------------------------------------------------------- + // "qShlibInfoAddr" + // + // BRIEF + // Get an address where the dynamic linker stores information about + // where shared libraries are loaded. + // + // PRIORITY TO IMPLEMENT + // High if you have a dynamic loader plug-in in LLDB for your target + // triple (see the "qHostInfo" packet) that can use this information. + // Many times address load randomization can make it hard to detect + // where the dynamic loader binary and data structures are located and + // some platforms know, or can find out where this information is. + // + // Low if you have a debug target where all object and symbol files + // contain static load addresses. + //---------------------------------------------------------------------- + + LLDB and GDB both support the "qShlibInfoAddr" packet which is a hint to each + debugger as to where to find the dynamic loader information. For darwin + binaries that run in user land this is the address of the "all_image_infos" + structure in the "/usr/lib/dyld" executable, or the result of a TASK_DYLD_INFO + call. The result is returned as big endian hex bytes that are the address + value: + + send packet: $qShlibInfoAddr#00 + read packet: $7fff5fc40040#00 + + + + //---------------------------------------------------------------------- + // "qThreadStopInfo" + // + // BRIEF + // Get information about why a thread, whose ID is "", is stopped. + // + // PRIORITY TO IMPLEMENT + // High if you need to support multi-threaded or multi-core debugging. + // Many times one thread will hit a breakpoint and while the debugger + // is in the process of suspending the other threads, other threads + // will also hit a breakpoint. This packet allows LLDB to know why all + // threads (live system debug) / cores (JTAG) in your program have + // stopped and allows LLDB to display and control your program + // correctly. + //---------------------------------------------------------------------- + + LLDB tries to use the "qThreadStopInfo" packet which is formatted as + "qThreadStopInfo%x" where %x is the hex thread ID. This requests information + about why a thread is stopped. The response is the same as the stop reply + packets and tells us what happened to the other threads. The standard GDB + remote packets love to think that there is only _one_ reason that _one_ thread + stops at a time. This allows us to see why all threads stopped and allows us + to implement better multi-threaded debugging support. + + //---------------------------------------------------------------------- + // "QThreadSuffixSupported" + // + // BRIEF + // Try to enable thread suffix support for the 'g', 'G', 'p', and 'P' + // packets. + // + // PRIORITY TO IMPLEMENT + // High. Adding a thread suffix allows us to read and write registers + // more efficiently and stops us from having to select a thread with + // one packet and then read registers with a second packet. It also + // makes sure that no errors can occur where the debugger thinks it + // already has a thread selected (see the "Hg" packet from the standard + // GDB remote protocol documentation) yet the remote GDB server actually + // has another thread selected. + //---------------------------------------------------------------------- + + When reading thread registers, you currently need to set the current + thread, then read the registers. This is kind of cumbersome, so we added the + ability to query if the remote GDB server supports adding a "thread:;" + suffix to all packets that request information for a thread. To test if the + remote GDB server supports this feature: + + send packet: $QThreadSuffixSupported#00 + read packet: OK + + If "OK" is returned, then the 'g', 'G', 'p' and 'P' packets can accept a + thread suffix. So to send a 'g' packet (read all register values): + + send packet: $g;thread:;#00 + read packet: .... + + send packet: $G;thread:;#00 + read packet: .... + + send packet: $p1a;thread:;#00 + read packet: .... + + send packet: $P1a=1234abcd;thread:;#00 + read packet: .... + + + otherwise, without this you would need to always send two packets: + + send packet: $Hg#00 + read packet: .... + send packet: $g#00 + read packet: .... + + We also added support for allocating and deallocating memory. We use this to + allocate memory so we can run JITed code. + + //---------------------------------------------------------------------- + // "_M," + // + // BRIEF + // Allocate memory on the remote target with the specified size and + // permissions. + // + // PRIORITY TO IMPLEMENT + // High if you want LLDB to be able to JIT code and run that code. JIT + // code also needs data which is also allocated and tracked. + // + // Low if you don't support running JIT'ed code. + //---------------------------------------------------------------------- + + The allocate memory packet starts with "_M,". It returns a + raw big endian address value, or "" for unimplemented, or "EXX" for an error + code. The packet is formatted as: + + char packet[256]; + int packet_len; + packet_len = ::snprintf ( + packet, + sizeof(packet), + "_M%zx,%s%s%s", + (size_t)size, + permissions & lldb::ePermissionsReadable ? "r" : "", + permissions & lldb::ePermissionsWritable ? "w" : "", + permissions & lldb::ePermissionsExecutable ? "x" : ""); + + You request a size and give the permissions. This packet does NOT need to be + implemented if you don't want to support running JITed code. The return value + is just the address of the newly allocated memory as raw big endian hex bytes. + + //---------------------------------------------------------------------- + // "_m" + // + // BRIEF + // Deallocate memory that was previously allocated using an allocate + // memory pack. + // + // PRIORITY TO IMPLEMENT + // High if you want LLDB to be able to JIT code and run that code. JIT + // code also needs data which is also allocated and tracked. + // + // Low if you don't support running JIT'ed code. + //---------------------------------------------------------------------- + + The deallocate memory packet is "_m" where you pass in the address you + got back from a previous call to the allocate memory packet. It returns "OK" + if the memory was successfully deallocated, or "EXX" for an error, or "" if + not supported. + + //---------------------------------------------------------------------- + // "qMemoryRegionInfo:" + // + // BRIEF + // Get information about the address range that contains "" + // + // PRIORITY TO IMPLEMENT + // Medium. This is nice to have, but it isn't necessary. It helps LLDB + // do stack unwinding when we branch into memory that isn't executable. + // If we can detect that the code we are stopped in isn't executable, + // then we can recover registers for stack frames above the current + // frame. Otherwise we must assume we are in some JIT'ed code (not JIT + // code that LLDB has made) and assume that no registers are available + // in higher stack frames. + //---------------------------------------------------------------------- + + We added a way to get information for a memory region. The packet is: + + qMemoryRegionInfo: + + Where is a big endian hex address. The response is returned in a series + of tuples like the data returned in a stop reply packet. The currently valid + tuples to return are: + + start:; // is a big endian hex address that is + // the start address of the range that contains + + size:; // is a big endian hex byte size of the address + // of the range that contains + + permissions:; // is a string that contains one + // or more of the characters from "rwx" + + name:; // is a hex encoded string that contains the name of + // the memory region mapped at the given address. In case of + // regions backed by a file it have to be the absolute path of + // the file while for anonymous regions it have to be the name + // associated to the region if that is available. + + flags:; // where is a space separated string + // of flag names. Currently the only supported flag + // is "mt" for AArch64 memory tagging. lldb will + // ignore any other flags in this field. + + type:[][,]; // memory types that apply to this region, e.g. + // "stack" for stack memory. + + error:; // where is + // a hex encoded string value that + // contains an error string + + dirty-pages:[][, + // QRestoreRegisterState:;thread:XXXX; + // + // BRIEF + // The QRestoreRegisterState packet tells the remote debugserver to + // restore all registers using the "save_id" which is an unsigned + // integer that was returned from a previous call to + // QSaveRegisterState. The restoration process can only be done once + // as the data backing the register state will be freed upon the + // completion of the QRestoreRegisterState command. + // + // If thread suffixes are enabled the second form of this packet is + // used, otherwise the first form is used. + // + // RESPONSE + // "OK" - if all registers were successfully restored + // "EXX" - for any errors + // + // PRIORITY TO IMPLEMENT + // Low, this is mostly a convenience packet to avoid having to send all + // registers via a g packet. It should only be implemented if support + // for the QSaveRegisterState is added. + //---------------------------------------------------------------------- + + //---------------------------------------------------------------------- + // qFileLoadAddress: + // + // BRIEF + // Get the load address of a memory mapped file. + // The load address is defined as the address of the first memory + // region what contains data mapped from the specified file. + // + // RESPONSE + // - Load address of the file in big endian encoding + // "E01" - the requested file isn't loaded + // "EXX" - for any other errors + // + // PRIORITY TO IMPLEMENT + // Low, required if dynamic linker don't fill in the load address of + // some object file in the rendezvous data structure. + //---------------------------------------------------------------------- + + //---------------------------------------------------------------------- + // qModuleInfo:; + // + // BRIEF + // Get information for a module by given module path and architecture. + // + // RESPONSE + // "(uuid|md5):...;triple:...;file_offset:...;file_size...;" + // "EXX" - for any errors + // + // PRIORITY TO IMPLEMENT + // Optional, required if dynamic loader cannot fetch module's information like + // UUID directly from inferior's memory. + //---------------------------------------------------------------------- + + //---------------------------------------------------------------------- + // jModulesInfo:[{"file":"...",triple:"..."}, ...] + // + // BRIEF + // Get information for a list of modules by given module path and + // architecture. + // + // RESPONSE + // A JSON array of dictionaries containing the following keys: uuid, + // triple, file_path, file_offset, file_size. The meaning of the fields + // is the same as in the qModuleInfo packet. The server signals the + // failure to retrieve the module info for a file by ommiting the + // corresponding array entry from the response. The server may also + // include entries the client did not ask for, if it has reason to + // the modules will be interesting to the client. + // + // PRIORITY TO IMPLEMENT + // Optional. If not implemented, qModuleInfo packet will be used, which + // may be slower if the target contains a large number of modules and + // the communication link has a non-negligible latency. + //---------------------------------------------------------------------- + + //---------------------------------------------------------------------- + // Stop reply packet extensions + // + // BRIEF + // This section describes some of the additional information you can + // specify in stop reply packets that help LLDB to know more detailed + // information about your threads. + // + // DESCRIPTION + // Standard GDB remote stop reply packets are reply packets sent in + // response to a packet that made the program run. They come in the + // following forms: + // + // "SAA" + // "S" means signal and "AA" is a hex signal number that describes why + // the thread or stopped. It doesn't specify which thread, so the "T" + // packet is recommended to use instead of the "S" packet. + // + // "TAAkey1:value1;key2:value2;..." + // "T" means a thread stopped due to a unix signal where "AA" is a hex + // signal number that describes why the program stopped. This is + // followed by a series of key/value pairs: + // - If key is a hex number, it is a register number and value is + // the hex value of the register in debuggee endian byte order. + // - If key == "thread", then the value is the big endian hex + // thread-id of the stopped thread. + // - If key == "core", then value is a hex number of the core on + // which the stop was detected. + // - If key == "watch" or key == "rwatch" or key == "awatch", then + // value is the data address in big endian hex + // - If key == "library", then value is ignore and "qXfer:libraries:read" + // packets should be used to detect any newly loaded shared libraries + // + // "WAA" + // "W" means the process exited and "AA" is the exit status. + // + // "XAA" + // "X" means the process exited and "AA" is signal that caused the program + // to exit. + // + // "O" + // "O" means STDOUT has data that was written to its console and is + // being delivered to the debugger. This packet happens asynchronously + // and the debugger is expected to continue to wait for another stop reply + // packet. + // + // LLDB EXTENSIONS + // + // We have extended the "T" packet to be able to also understand the + // following keys and values: + // + // KEY VALUE DESCRIPTION + // =========== ======== ================================================ + // "metype" unsigned mach exception type (the value of the EXC_XXX enumerations) + // as an unsigned integer. For targets with mach + // kernels only. + // + // "mecount" unsigned mach exception data count as an unsigned integer + // For targets with mach kernels only. + // + // "medata" unsigned There should be "mecount" of these and it is the data + // that goes along with a mach exception (as an unsigned + // integer). For targets with mach kernels only. + // + // "name" string The name of the thread as a plain string. The string + // must not contain an special packet characters or + // contain a ':' or a ';'. Use "hexname" if the thread + // name has special characters. + // + // "hexname" ascii-hex An ASCII hex string that contains the name of the thread + // + // "qaddr" hex Big endian hex value that contains the libdispatch + // queue address for the queue of the thread. + // + // "reason" enum The enumeration must be one of: + // "trace" the program stopped after a single instruction + // was executed on a core. Usually done when single + // stepping past a breakpoint + // "breakpoint" a breakpoint set using a 'z' packet was hit. + // "trap" stopped due to user interruption + // "signal" stopped due to an actual unix signal, not + // just the debugger using a unix signal to keep + // the GDB remote client happy. + // "watchpoint". Should be used in conjunction with + // the "watch"/"rwatch"/"awatch" key value pairs. + // "exception" an exception stop reason. Use with + // the "description" key/value pair to describe the + // exceptional event the user should see as the stop + // reason. + // "description" ascii-hex An ASCII hex string that contains a more descriptive + // reason that the thread stopped. This is only needed + // if none of the key/value pairs are enough to + // describe why something stopped. + // + // "threads" comma-sep-base16 A list of thread ids for all threads (including + // the thread that we're reporting as stopped) that + // are live in the process right now. lldb may + // request that this be included in the T packet via + // the QListThreadsInStopReply packet earlier in + // the debug session. + // + // Example: + // threads:63387,633b2,63424,63462,63486; + // + // "thread-pcs" comma-sep-base16 A list of pc values for all threads that currently + // exist in the process, including the thread that + // this T packet is reporting as stopped. + // This key-value pair will only be emitted when the + // "threads" key is already included in the T packet. + // The pc values correspond to the threads reported + // in the "threads" list. The number of pcs in the + // "thread-pcs" list will be the same as the number of + // threads in the "threads" list. + // lldb may request that this be included in the T + // packet via the QListThreadsInStopReply packet + // earlier in the debug session. + // + // Example: + // thread-pcs:dec14,2cf872b0,2cf8681c,2d02d68c,2cf716a8; + // + // BEST PRACTICES: + // Since register values can be supplied with this packet, it is often useful + // to return the PC, SP, FP, LR (if any), and FLAGS registers so that separate + // packets don't need to be sent to read each of these registers from each + // thread. + // + // If a thread is stopped for no reason (like just because another thread + // stopped, or because when one core stops all cores should stop), use a + // "T" packet with "00" as the signal number and fill in as many key values + // and registers as possible. + // + // LLDB likes to know why a thread stopped since many thread control + // operations like stepping over a source line, actually are implemented + // by running the process multiple times. If a breakpoint is hit while + // trying to step over a source line and LLDB finds out that a breakpoint + // is hit in the "reason", we will know to stop trying to do the step + // over because something happened that should stop us from trying to + // do the step. If we are at a breakpoint and we disable the breakpoint + // at the current PC and do an instruction single step, knowing that + // we stopped due to a "trace" helps us know that we can continue + // running versus stopping due to a "breakpoint" (if we have two + // breakpoint instruction on consecutive instructions). So the more info + // we can get about the reason a thread stops, the better job LLDB can + // do when controlling your process. A typical GDB server behavior is + // to send a SIGTRAP for breakpoints _and_ also when instruction single + // stepping, in this case the debugger doesn't really know why we + // stopped and it can make it hard for the debugger to control your + // program correctly. What if a real SIGTRAP was delivered to a thread + // while we were trying to single step? We wouldn't know the difference + // with a standard GDB remote server and we could do the wrong thing. + // + // PRIORITY TO IMPLEMENT + // High. Having the extra information in your stop reply packets makes + // your debug session more reliable and informative. + //---------------------------------------------------------------------- + + + //---------------------------------------------------------------------- + // PLATFORM EXTENSION - for use as a GDB remote platform + //---------------------------------------------------------------------- + // "qfProcessInfo" + // "qsProcessInfo" + // + // BRIEF + // Get the first process info (qfProcessInfo) or subsequent process + // info (qsProcessInfo) for one or more processes on the remote + // platform. The first call gets the first match and subsequent calls + // to qsProcessInfo gets the subsequent matches. Return an error EXX, + // where XX are two hex digits, when no more matches are available. + // + // PRIORITY TO IMPLEMENT + // Required. The qfProcessInfo packet can be followed by a ':' and + // some key value pairs. The key value pairs in the command are: + // + // KEY VALUE DESCRIPTION + // =========== ======== ================================================ + // "name" ascii-hex An ASCII hex string that contains the name of + // the process that will be matched. + // "name_match" enum One of: "equals", "starts_with", "ends_with", + // "contains" or "regex" + // "pid" integer A string value containing the decimal process ID + // "parent_pid" integer A string value containing the decimal parent + // process ID + // "uid" integer A string value containing the decimal user ID + // "gid" integer A string value containing the decimal group ID + // "euid" integer A string value containing the decimal effective user ID + // "egid" integer A string value containing the decimal effective group ID + // "all_users" bool A boolean value that specifies if processes should + // be listed for all users, not just the user that the + // platform is running as + // "triple" string An ASCII triple string ("x86_64", + // "x86_64-apple-macosx", "armv7-apple-ios") + // "args" string A string value containing the process arguments + // separated by the character '-', where each argument is + // hex-encoded. It includes argv[0]. + // + // The response consists of key/value pairs where the key is separated from the + // values with colons and each pair is terminated with a semi colon. For a list + // of the key/value pairs in the response see the "qProcessInfoPID" packet + // documentation. + // + // Sample packet/response: + // send packet: $qfProcessInfo#00 + // read packet: $pid:60001;ppid:59948;uid:7746;gid:11;euid:7746;egid:11;name:6c6c6462;triple:x86_64-apple-macosx;#00 + // send packet: $qsProcessInfo#00 + // read packet: $pid:59992;ppid:192;uid:7746;gid:11;euid:7746;egid:11;name:6d64776f726b6572;triple:x86_64-apple-macosx;#00 + // send packet: $qsProcessInfo#00 + // read packet: $E04#00 + //---------------------------------------------------------------------- + + + //---------------------------------------------------------------------- + // PLATFORM EXTENSION - for use as a GDB remote platform + //---------------------------------------------------------------------- + // "qLaunchGDBServer" + // + // BRIEF + // Have the remote platform launch a GDB server. + // + // PRIORITY TO IMPLEMENT + // Required. The qLaunchGDBServer packet must be followed by a ':' and + // some key value pairs. The key value pairs in the command are: + // + // KEY VALUE DESCRIPTION + // =========== ======== ================================================ + // "port" integer A string value containing the decimal port ID or + // zero if the port should be bound and returned + // + // "host" integer The host that connections should be limited to + // when the GDB server is connected to. + // + // The response consists of key/value pairs where the key is separated from the + // values with colons and each pair is terminated with a semi colon. + // + // Sample packet/response: + // send packet: $qLaunchGDBServer:port:0;host:lldb.apple.com;#00 + // read packet: $pid:60025;port:50776;#00 + // + // The "pid" key/value pair is only specified if the remote platform launched + // a separate process for the GDB remote server and can be omitted if no + // process was separately launched. + // + // The "port" key/value pair in the response lets clients know what port number + // to attach to in case zero was specified as the "port" in the sent command. + //---------------------------------------------------------------------- + + + //---------------------------------------------------------------------- + // PLATFORM EXTENSION - for use as a GDB remote platform + //---------------------------------------------------------------------- + // "qProcessInfoPID:PID" + // + // BRIEF + // Have the remote platform get detailed information on a process by + // ID. PID is specified as a decimal integer. + // + // PRIORITY TO IMPLEMENT + // Optional. + // + // The response consists of key/value pairs where the key is separated from the + // values with colons and each pair is terminated with a semi colon. + // + // The key value pairs in the response are: + // + // KEY VALUE DESCRIPTION + // =========== ======== ================================================ + // "pid" integer Process ID as a decimal integer string + // "ppid" integer Parent process ID as a decimal integer string + // "uid" integer A string value containing the decimal user ID + // "gid" integer A string value containing the decimal group ID + // "euid" integer A string value containing the decimal effective user ID + // "egid" integer A string value containing the decimal effective group ID + // "name" ascii-hex An ASCII hex string that contains the name of the process + // "triple" string A target triple ("x86_64-apple-macosx", "armv7-apple-ios") + // + // Sample packet/response: + // send packet: $qProcessInfoPID:60050#00 + // read packet: $pid:60050;ppid:59948;uid:7746;gid:11;euid:7746;egid:11;name:6c6c6462;triple:x86_64-apple-macosx;#00 + //---------------------------------------------------------------------- + + //---------------------------------------------------------------------- + // "vAttachName" + // + // BRIEF + // Same as vAttach, except instead of a "pid" you send a process name. + // + // PRIORITY TO IMPLEMENT + // Low. Only needed for "process attach -n". If the packet isn't supported + // then "process attach -n" will fail gracefully. So you need only to support + // it if attaching to a process by name makes sense for your environment. + //---------------------------------------------------------------------- + + //---------------------------------------------------------------------- + // "vAttachWait" + // + // BRIEF + // Same as vAttachName, except that the stub should wait for the next instance + // of a process by that name to be launched and attach to that. + // + // PRIORITY TO IMPLEMENT + // Low. Only needed to support "process attach -w -n" which will fail + // gracefully if the packet is not supported. + //---------------------------------------------------------------------- + + //---------------------------------------------------------------------- + // "qAttachOrWaitSupported" + // + // BRIEF + // This is a binary "is it supported" query. Return OK if you support + // vAttachOrWait + // + // PRIORITY TO IMPLEMENT + // Low. This is required if you support vAttachOrWait, otherwise no support + // is needed since the standard "I don't recognize this packet" response + // will do the right thing. + //---------------------------------------------------------------------- + + //---------------------------------------------------------------------- + // "vAttachOrWait" + // + // BRIEF + // Same as vAttachWait, except that the stub will attach to a process + // by name if it exists, and if it does not, it will wait for a process + // of that name to appear and attach to it. + // + // PRIORITY TO IMPLEMENT + // Low. Only needed to implement "process attach -w -i false -n". If + // you don't implement it but do implement -n AND lldb can somehow get + // a process list from your device, it will fall back on scanning the + // process list, and sending vAttach or vAttachWait depending on + // whether the requested process exists already. This is racy, + // however, so if you want to support this behavior it is better to + // support this packet. + //---------------------------------------------------------------------- + + //---------------------------------------------------------------------- + // "jThreadExtendedInfo" + // + // BRIEF + // This packet, which takes its arguments as JSON and sends its reply as + // JSON, allows the gdb remote stub to provide additional information + // about a given thread. + // + // PRIORITY TO IMPLEMENT + // Low. This packet is only needed if the gdb remote stub wants to + // provide interesting additional information about a thread for the + // user. + // + // This packet takes its arguments in JSON form ( http://www.json.org ). + // At a minimum, a thread must be specified, for example: + // + // jThreadExtendedInfo:{"thread":612910} + // + // Because this is a JSON string, the thread number is provided in base10. + // Additional key-value pairs may be provided by lldb to the gdb remote + // stub. For instance, on some versions of macOS, lldb can read offset + // information out of the system libraries. Using those offsets, debugserver + // is able to find the Thread Specific Address (TSD) for a thread and include + // that in the return information. So lldb will send these additional fields + // like so: + // + // jThreadExtendedInfo:{"plo_pthread_tsd_base_address_offset":0,"plo_pthread_tsd_base_offset":224,"plo_pthread_tsd_entry_size":8,"thread":612910} + // + // There are no requirements for what is included in the response. A simple + // reply on a OS X Yosemite / iOS 8 may include the pthread_t value, the + // Thread Specific Data (TSD) address, the dispatch_queue_t value if the thread + // is associated with a GCD queue, and the requested Quality of Service (QoS) + // information about that thread. For instance, a reply may look like: + // + // {"tsd_address":4371349728,"requested_qos":{"enum_value":33,"constant_name":"QOS_CLASS_USER_INTERACTIVE","printable_name":"User Interactive"},"pthread_t":4371349504,"dispatch_queue_t":140735087127872} + // + // tsd_address, pthread_t, and dispatch_queue_t are all simple key-value pairs. + // The JSON standard requires that numbers be expressed in base 10 - so all of + // these are. requested_qos is a dictionary with three key-value pairs in it - + // so the UI layer may choose the form most appropriate for displaying to the user. + // + // Sending JSON over gdb-remote protocol introduces some problems. We may be + // sending strings with arbitrary contents in them, including the '#', '$', and '*' + // characters that have special meaning in gdb-remote protocol and cannot occur + // in the middle of the string. The standard solution for this would be to require + // ascii-hex encoding of all strings, or ascii-hex encode the entire JSON payload. + // + // Instead, the binary escaping convention is used for JSON data. This convention + // (e.g. used for the X packet) says that if '#', '$', '*', or '}' are to occur in + // the payload, the character '}' (0x7d) is emitted, then the metacharacter is emitted + // xor'ed by 0x20. The '}' character occurs in every JSON payload at least once, and + // '}' ^ 0x20 happens to be ']' so the raw packet characters for a request will look + // like + // + // jThreadExtendedInfo:{"thread":612910}] + // + // on the wire. + //---------------------------------------------------------------------- + + //---------------------------------------------------------------------- + // "QEnableCompression" + // + // BRIEF + // This packet enables compression of the packets that the debug stub sends to lldb. + // If the debug stub can support compression, it indictes this in the reply of the + // "qSupported" packet. e.g. + // LLDB SENDS: qSupported:xmlRegisters=i386,arm,mips + // STUB REPLIES: qXfer:features:read+;SupportedCompressions=lzfse,zlib-deflate,lz4,lzma;DefaultCompressionMinSize=384 + // + // If lldb knows how to use any of these compression algorithms, it can ask that this + // compression mode be enabled. It may optionally change the minimum packet size + // where compression is used. Typically small packets do not benefit from compression, + // as well as compression headers -- compression is most beneficial with larger packets. + // + // QEnableCompression:type:zlib-deflate; + // or + // QEnableCompression:type:zlib-deflate;minsize:512; + // + // The debug stub should reply with an uncompressed "OK" packet to indicate that the + // request was accepted. All further packets the stub sends will use this compression. + // + // Packets are compressed as the last step before they are sent from the stub, and + // decompressed as the first step after they are received. The packet format in compressed + // mode becomes one of two: + // + // $N#00 + // + // $C:#00 + // + // Where "#00" is the actual checksum value if noack mode is not enabled. The checksum + // value is for the "N" or + // "C:" bytes in the packet. + // + // The size of the uncompressed payload in base10 is provided because it will simplify + // decompression if the final buffer size needed is known ahead of time. + // + // Compression on low-latency connections is unlikely to be an improvement. Particularly + // when the debug stub and lldb are running on the same host. It should only be used + // for slow connections, and likely only for larger packets. + // + // Example compression algorithsm that may be used include + // + // zlib-deflate + // The raw DEFLATE format as described in IETF RFC 1951. With the ZLIB library, you + // can compress to this format with an initialization like + // deflateInit2 (&stream, 5, Z_DEFLATED, -15, 8, Z_DEFAULT_STRATEGY) + // and you can decompress with an initialization like + // inflateInit2 (&stream, -15) + // + // lz4 + // https://en.wikipedia.org/wiki/LZ4_(compression_algorithm) + // https://github.com/Cyan4973/lz4 + // The libcompression APIs on darwin systems call this COMPRESSION_LZ4_RAW. + // + // lzfse + // An Apple proprietary compression algorithm implemented in libcompression. + // + // lzma + // libcompression implements "LZMA level 6", the default compression for the + // open source LZMA implementation. + //---------------------------------------------------------------------- + + //---------------------------------------------------------------------- + // "jGetLoadedDynamicLibrariesInfos" + // + // BRIEF + // This packet asks the remote debug stub to send the details about libraries + // being added/removed from the process as a performance optimization. + // + // There are three ways this packet can be used. All three return a dictionary of + // binary images formatted the same way. + // + // On OS X 10.11, iOS 9, tvOS 9, watchOS 2 and earlier, the packet is used like + // jGetLoadedDynamicLibrariesInfos:{"image_count":1,"image_list_address":140734800075128} + // where the image_list_address is an array of {void* load_addr, void* mod_date, void* pathname} + // in the inferior process memory (and image_count is the number of elements in this array). + // lldb is using information from the dyld_all_image_infos structure to make these requests to + // debugserver. This use is not supported on macOS 10.12, iOS 10, tvOS 10, watchOS 3 or newer. + // + // On macOS 10.12, iOS 10, tvOS 10, watchOS 3 and newer, there are two calls. One requests information + // on all shared libraries: + // jGetLoadedDynamicLibrariesInfos:{"fetch_all_solibs":true} + // And the second requests information about a list of shared libraries, given their load addresses: + // jGetLoadedDynamicLibrariesInfos:{"solib_addresses":[8382824135,3258302053,830202858503]} + // + // The second call is both a performance optimization (instead of having lldb read the mach-o header/load commands + // out of memory with generic read packets) but also adds additional information in the form of the + // filename of the shared libraries (which is not available in the mach-o header/load commands.) + // + // An example using the OS X 10.11 style call: + // + // LLDB SENDS: jGetLoadedDynamicLibrariesInfos:{"image_count":1,"image_list_address":140734800075128} + // STUB REPLIES: ${"images":[{"load_address":4294967296,"mod_date":0,"pathname":"/tmp/a.out","uuid":"02CF262C-ED6F-3965-9E14-63538B465CFF","mach_header":{"magic":4277009103,"cputype":16777223,"cpusubtype":18446744071562067971,"filetype":2},"segments":{"name":"__PAGEZERO","vmaddr":0,"vmsize":4294967296,"fileoff":0,"filesize":0,"maxprot":0},{"name":"__TEXT","vmaddr":4294967296,"vmsize":4096,"fileoff":0,"filesize":4096,"maxprot":7},{"name":"__LINKEDIT","vmaddr":4294971392,"vmsize":4096,"fileoff":4096,"filesize":152,"maxprot":7}}]}#00 + // + // Or pretty-printed, + // + // STUB REPLIES: ${"images": + // [ + // {"load_address":4294967296, + // "mod_date":0, + // "pathname":"/tmp/a.out", + // "uuid":"02CF262C-ED6F-3965-9E14-63538B465CFF", + // "mach_header": + // {"magic":4277009103, + // "cputype":16777223, + // "cpusubtype":18446744071562067971, + // "filetype":2 + // }, + // "segments": + // [ + // {"name":"__PAGEZERO", + // "vmaddr":0, + // "vmsize":4294967296, + // "fileoff":0, + // "filesize":0, + // "maxprot":0 + // }, + // {"name":"__TEXT", + // "vmaddr":4294967296, + // "vmsize":4096, + // "fileoff":0, + // "filesize":4096, + // "maxprot":7 + // }, + // {"name":"__LINKEDIT", + // "vmaddr":4294971392, + // "vmsize":4096, + // "fileoff":4096, + // "filesize":152, + // "maxprot":7 + // } + // ] + // } + // ] + // } + // + // + // This is similar to the qXfer:libraries:read packet, and it could + // be argued that it should be merged into that packet. A separate + // packet was created primarily because lldb needs to specify the + // number of images to be read and the address from which the initial + // information is read. Also the XML DTD would need to be extended + // quite a bit to provide all the information that the DynamicLoaderMacOSX + // would need to work correctly on this platform. + // + // PRIORITY TO IMPLEMENT + // On OS X 10.11, iOS 9, tvOS 9, watchOS 2 and older: Low. If this packet is absent, + // lldb will read the Mach-O headers/load commands out of memory. + // On macOS 10.12, iOS 10, tvOS 10, watchOS 3 and newer: High. If this packet is absent, + // lldb will not know anything about shared libraries in the inferior, or where the main + // executable loaded. + //---------------------------------------------------------------------- + + //---------------------------------------------------------------------- + // "jThreadsInfo" + // + // BRIEF + // Ask for the server for thread stop information of all threads. + // + // PRIORITY TO IMPLEMENT + // Low. This is a performance optimization, which speeds up debugging by avoiding + // multiple round-trips for retrieving thread information. The information from this + // packet can be retrieved using a combination of qThreadStopInfo and m packets. + //---------------------------------------------------------------------- + + The data in this packet is very similar to the stop reply packets, but is packaged in + JSON and uses JSON arrays where applicable. The JSON output looks like: + [ + { "tid":1580681, + "metype":6, + "medata":[2,0], + "reason":"exception", + "qaddr":140735118423168, + "registers": { + "0":"8000000000000000", + "1":"0000000000000000", + "2":"20fabf5fff7f0000", + "3":"e8f8bf5fff7f0000", + "4":"0100000000000000", + "5":"d8f8bf5fff7f0000", + "6":"b0f8bf5fff7f0000", + "7":"20f4bf5fff7f0000", + "8":"8000000000000000", + "9":"61a8db78a61500db", + "10":"3200000000000000", + "11":"4602000000000000", + "12":"0000000000000000", + "13":"0000000000000000", + "14":"0000000000000000", + "15":"0000000000000000", + "16":"960b000001000000", + "17":"0202000000000000", + "18":"2b00000000000000", + "19":"0000000000000000", + "20":"0000000000000000" + }, + "memory":[ + {"address":140734799804592,"bytes":"c8f8bf5fff7f0000c9a59e8cff7f0000"}, + {"address":140734799804616,"bytes":"00000000000000000100000000000000"} + ] + } + ] + + It contains an array of dictionaries with all of the key value pairs that are + normally in the stop reply packet, including the expedited registers. The registers are + passed as hex-encoded JSON string in debuggee-endian byte order. Note that the register + numbers are decimal numbers, unlike the stop-reply packet, where they are written in + hex. The packet also contains expedited memory in the "memory" key. This allows the + server to expedite memory that the client is likely to use (e.g., areas around the + stack pointer, which are needed for computing backtraces) and it reduces the packet + count. + + On macOS with debugserver, we expedite the frame pointer backchain for a thread + (up to 256 entries) by reading 2 pointers worth of bytes at the frame pointer (for + the previous FP and PC), and follow the backchain. Most backtraces on macOS and + iOS now don't require us to read any memory! + + //---------------------------------------------------------------------- + // "jGetSharedCacheInfo" + // + // BRIEF + // This packet asks the remote debug stub to send the details about the inferior's + // shared cache. The shared cache is a collection of common libraries/frameworks that + // are mapped into every process at the same address on Darwin systems, and can be + // identified by a load address and UUID. + // + // + // LLDB SENDS: jGetSharedCacheInfo:{} + // STUB REPLIES: ${"shared_cache_base_address":140735683125248,"shared_cache_uuid":"DDB8D70C-C9A2-3561-B2C8-BE48A4F33F96","no_shared_cache":false,"shared_cache_private_cache":false]}#00 + // + // PRIORITY TO IMPLEMENT + // Low. When both lldb and the inferior process are running on the same computer, and lldb + // and the inferior process have the same shared cache, lldb may (as an optimization) read + // the shared cache out of its own memory instead of using gdb-remote read packets to read + // them from the inferior process. + //---------------------------------------------------------------------- + + //---------------------------------------------------------------------- + // "qQueryGDBServer" + // + // BRIEF + // Ask the platform for the list of gdbservers we have to connect + // + // PRIORITY TO IMPLEMENT + // Low. The packet is required to support connecting to gdbserver started + // by the platform instance automatically. + //---------------------------------------------------------------------- + + If the remote platform automatically started one or more gdbserver instance (without + lldb asking it) then it have to return the list of port number or socket name for + each of them what can be used by lldb to connect to those instances. + + The data in this packet is a JSON array of JSON objects with the following keys: + "port": (optional) + "socket_name": (optional) + + Example packet: + [ + { "port": 1234 }, + { "port": 5432 }, + { "socket_name": "foo" } + ] +diff --git a/lldb/include/lldb/Utility/TraceGDBRemotePackets.h b/lldb/include/lldb/Utility/TraceGDBRemotePackets.h +index 1d2448b05f2a..b2669ee3d813 100644 +--- a/lldb/include/lldb/Utility/TraceGDBRemotePackets.h ++++ b/lldb/include/lldb/Utility/TraceGDBRemotePackets.h +@@ -1,154 +1,180 @@ + //===-- TraceGDBRemotePackets.h ---------------------------------*- C++ -*-===// + // + // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. + // See https://llvm.org/LICENSE.txt for license information. + // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + // + //===----------------------------------------------------------------------===// + + #ifndef LLDB_UTILITY_TRACEGDBREMOTEPACKETS_H + #define LLDB_UTILITY_TRACEGDBREMOTEPACKETS_H + + #include "llvm/Support/JSON.h" ++#include + + #include "lldb/lldb-defines.h" + #include "lldb/lldb-enumerations.h" + + /// See docs/lldb-gdb-remote.txt for more information. + namespace lldb_private { + + /// jLLDBTraceSupported gdb-remote packet + /// \{ + struct TraceSupportedResponse { + /// The name of the technology, e.g. intel-pt or arm-coresight. + /// + /// In order for a Trace plug-in (see \a lldb_private::Trace.h) to support the + /// trace technology given by this struct, it should match its name with this + /// field. + std::string name; + /// The description for the technology. + std::string description; + }; + + bool fromJSON(const llvm::json::Value &value, TraceSupportedResponse &info, + llvm::json::Path path); + + llvm::json::Value toJSON(const TraceSupportedResponse &packet); + /// \} + + /// jLLDBTraceStart gdb-remote packet + /// \{ + struct TraceStartRequest { + /// Tracing technology name, e.g. intel-pt, arm-coresight. + std::string type; + + /// If \a llvm::None, then this starts tracing the whole process. Otherwise, + /// only tracing for the specified threads is enabled. + llvm::Optional> tids; + + /// \return + /// \b true if \a tids is \a None, i.e. whole process tracing. + bool IsProcessTracing() const; + }; + + bool fromJSON(const llvm::json::Value &value, TraceStartRequest &packet, + llvm::json::Path path); + + llvm::json::Value toJSON(const TraceStartRequest &packet); + /// \} + + /// jLLDBTraceStop gdb-remote packet + /// \{ + struct TraceStopRequest { + TraceStopRequest() = default; + + TraceStopRequest(llvm::StringRef type, const std::vector &tids); + + TraceStopRequest(llvm::StringRef type) : type(type){}; + + bool IsProcessTracing() const; + + /// Tracing technology name, e.g. intel-pt, arm-coresight. + std::string type; + /// If \a llvm::None, then this stops tracing the whole process. Otherwise, + /// only tracing for the specified threads is stopped. + llvm::Optional> tids; + }; + + bool fromJSON(const llvm::json::Value &value, TraceStopRequest &packet, + llvm::json::Path path); + + llvm::json::Value toJSON(const TraceStopRequest &packet); + ///} + + /// jLLDBTraceGetState gdb-remote packet + /// \{ + struct TraceGetStateRequest { + /// Tracing technology name, e.g. intel-pt, arm-coresight. + std::string type; + }; + + bool fromJSON(const llvm::json::Value &value, TraceGetStateRequest &packet, + llvm::json::Path path); + + llvm::json::Value toJSON(const TraceGetStateRequest &packet); + + struct TraceBinaryData { + /// Identifier of data to fetch with jLLDBTraceGetBinaryData. + std::string kind; + /// Size in bytes for this data. + int64_t size; + }; + + bool fromJSON(const llvm::json::Value &value, TraceBinaryData &packet, + llvm::json::Path path); + + llvm::json::Value toJSON(const TraceBinaryData &packet); + + struct TraceThreadState { + int64_t tid; + /// List of binary data objects for this thread. + std::vector binaryData; + }; + + bool fromJSON(const llvm::json::Value &value, TraceThreadState &packet, + llvm::json::Path path); + + llvm::json::Value toJSON(const TraceThreadState &packet); + ++/// Interface for different algorithms used to convert trace ++/// counters into different units. ++template class TraceCounterConversion { ++public: ++ virtual ~TraceCounterConversion() = default; ++ ++ /// Convert from raw counter value to the target type. ++ /// ++ /// \param[in] raw_counter_value ++ /// The raw counter value to be converted. ++ /// ++ /// \return ++ /// The converted counter value. ++ virtual ToType Convert(uint64_t raw_counter_value) = 0; ++ ++ /// Serialize trace counter conversion values to JSON. ++ /// ++ /// \return ++ /// \a llvm::json::Value representing the trace counter conversion object. ++ virtual llvm::json::Value toJSON() = 0; ++}; ++ ++using TraceTscConversionUP = ++ std::unique_ptr>; ++ + struct TraceGetStateResponse { + std::vector tracedThreads; + std::vector processBinaryData; + }; + + bool fromJSON(const llvm::json::Value &value, TraceGetStateResponse &packet, + llvm::json::Path path); + + llvm::json::Value toJSON(const TraceGetStateResponse &packet); + /// \} + + /// jLLDBTraceGetBinaryData gdb-remote packet + /// \{ + struct TraceGetBinaryDataRequest { + /// Tracing technology name, e.g. intel-pt, arm-coresight. + std::string type; + /// Identifier for the data. + std::string kind; + /// Optional tid if the data is related to a thread. + llvm::Optional tid; + /// Offset in bytes from where to start reading the data. + int64_t offset; + /// Number of bytes to read. + int64_t size; + }; + + bool fromJSON(const llvm::json::Value &value, + lldb_private::TraceGetBinaryDataRequest &packet, + llvm::json::Path path); + + llvm::json::Value toJSON(const lldb_private::TraceGetBinaryDataRequest &packet); + /// \} + + } // namespace lldb_private + + #endif // LLDB_UTILITY_TRACEGDBREMOTEPACKETS_H +diff --git a/lldb/include/lldb/Utility/TraceIntelPTGDBRemotePackets.h b/lldb/include/lldb/Utility/TraceIntelPTGDBRemotePackets.h +index 8f4947b1f189..8960949f2039 100644 +--- a/lldb/include/lldb/Utility/TraceIntelPTGDBRemotePackets.h ++++ b/lldb/include/lldb/Utility/TraceIntelPTGDBRemotePackets.h +@@ -1,45 +1,103 @@ + //===-- TraceIntelPTGDBRemotePackets.h --------------------------*- C++ -*-===// + // + // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. + // See https://llvm.org/LICENSE.txt for license information. + // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + // + //===----------------------------------------------------------------------===// + + #ifndef LLDB_UTILITY_TRACEINTELPTGDBREMOTEPACKETS_H + #define LLDB_UTILITY_TRACEINTELPTGDBREMOTEPACKETS_H + + #include "lldb/Utility/TraceGDBRemotePackets.h" + ++#include "llvm/Support/JSON.h" ++ ++#include ++ + /// See docs/lldb-gdb-remote.txt for more information. + namespace lldb_private { + + /// jLLDBTraceStart gdb-remote packet + /// \{ + struct TraceIntelPTStartRequest : TraceStartRequest { + /// Size in bytes to use for each thread's trace buffer. + int64_t threadBufferSize; + + /// Whether to enable TSC + bool enableTsc; + + /// PSB packet period + llvm::Optional psbPeriod; + + /// Required when doing "process tracing". + /// + /// Limit in bytes on all the thread traces started by this "process trace" + /// instance. When a thread is about to be traced and the limit would be hit, + /// then a "tracing" stop event is triggered. + llvm::Optional processBufferSizeLimit; + }; + + bool fromJSON(const llvm::json::Value &value, TraceIntelPTStartRequest &packet, + llvm::json::Path path); + + llvm::json::Value toJSON(const TraceIntelPTStartRequest &packet); + /// \} + ++/// jLLDBTraceGetState gdb-remote packet ++/// \{ ++ ++/// TSC to wall time conversion values defined in the Linux perf_event_open API ++/// when the capibilities cap_user_time and cap_user_time_zero are set. See the ++/// See the documentation of `time_zero` in ++/// https://man7.org/linux/man-pages/man2/perf_event_open.2.html for more ++/// information. ++class LinuxPerfZeroTscConversion ++ : public TraceCounterConversion { ++public: ++ /// Create new \a LinuxPerfZeroTscConversion object from the conversion values ++ /// defined in the Linux perf_event_open API. ++ LinuxPerfZeroTscConversion(uint32_t time_mult, uint16_t time_shift, ++ uint64_t time_zero) ++ : m_time_mult(time_mult), m_time_shift(time_shift), ++ m_time_zero(time_zero) {} ++ ++ /// Convert TSC value to nanosecond wall time. The beginning of time (0 ++ /// nanoseconds) is defined by the kernel at boot time and has no particularly ++ /// useful meaning. On the other hand, this value is constant for an entire ++ /// trace session. ++ // See 'time_zero' section of ++ // https://man7.org/linux/man-pages/man2/perf_event_open.2.html ++ /// ++ /// \param[in] tsc ++ /// The TSC value to be converted. ++ /// ++ /// \return ++ /// Nanosecond wall time. ++ std::chrono::nanoseconds Convert(uint64_t raw_counter_value) override; ++ ++ llvm::json::Value toJSON() override; ++ ++private: ++ uint32_t m_time_mult; ++ uint16_t m_time_shift; ++ uint64_t m_time_zero; ++}; ++ ++struct TraceIntelPTGetStateResponse : TraceGetStateResponse { ++ /// The TSC to wall time conversion if it exists, otherwise \b nullptr. ++ TraceTscConversionUP tsc_conversion; ++}; ++ ++bool fromJSON(const llvm::json::Value &value, ++ TraceTscConversionUP &tsc_conversion, llvm::json::Path path); ++ ++bool fromJSON(const llvm::json::Value &value, ++ TraceIntelPTGetStateResponse &packet, llvm::json::Path path); ++ ++llvm::json::Value toJSON(const TraceIntelPTGetStateResponse &packet); ++/// \} ++ + } // namespace lldb_private + + #endif // LLDB_UTILITY_TRACEINTELPTGDBREMOTEPACKETS_H +diff --git a/lldb/source/Plugins/Process/Linux/IntelPTCollector.cpp b/lldb/source/Plugins/Process/Linux/IntelPTCollector.cpp +index 5f31c10f8b88..4882ab64f16a 100644 +--- a/lldb/source/Plugins/Process/Linux/IntelPTCollector.cpp ++++ b/lldb/source/Plugins/Process/Linux/IntelPTCollector.cpp +@@ -1,645 +1,656 @@ + //===-- IntelPTCollector.cpp ------------------------------------------------===// + // + // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. + // See https://llvm.org/LICENSE.txt for license information. + // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + // + //===----------------------------------------------------------------------===// + + #include "IntelPTCollector.h" + + #include "Perf.h" + + #include "Plugins/Process/POSIX/ProcessPOSIXLog.h" + #include "lldb/Host/linux/Support.h" + #include "lldb/Utility/StreamString.h" + + #include "llvm/ADT/StringRef.h" + #include "llvm/Support/Error.h" + #include "llvm/Support/MathExtras.h" + + #include + #include + #include + #include + #include + #include + #include + + using namespace lldb; + using namespace lldb_private; + using namespace process_linux; + using namespace llvm; + + const char *kOSEventIntelPTTypeFile = + "/sys/bus/event_source/devices/intel_pt/type"; + + const char *kPSBPeriodCapFile = + "/sys/bus/event_source/devices/intel_pt/caps/psb_cyc"; + + const char *kPSBPeriodValidValuesFile = + "/sys/bus/event_source/devices/intel_pt/caps/psb_periods"; + + const char *kTSCBitOffsetFile = + "/sys/bus/event_source/devices/intel_pt/format/tsc"; + + const char *kPSBPeriodBitOffsetFile = + "/sys/bus/event_source/devices/intel_pt/format/psb_period"; + + enum IntelPTConfigFileType { + Hex = 0, + // 0 or 1 + ZeroOne, + Decimal, + // a bit index file always starts with the prefix config: following by an int, + // which represents the offset of the perf_event_attr.config value where to + // store a given configuration. + BitOffset + }; + + /// Get the content of /proc/cpuinfo that can be later used to decode traces. + static Expected> GetCPUInfo() { + static llvm::Optional> cpu_info; + if (!cpu_info) { + auto buffer_or_error = errorOrToExpected(getProcFile("cpuinfo")); + if (!buffer_or_error) + return buffer_or_error.takeError(); + MemoryBuffer &buffer = **buffer_or_error; + cpu_info = std::vector( + reinterpret_cast(buffer.getBufferStart()), + reinterpret_cast(buffer.getBufferEnd())); + } + return *cpu_info; + } + + static Expected ReadIntelPTConfigFile(const char *file, + IntelPTConfigFileType type) { + ErrorOr> stream = + MemoryBuffer::getFileAsStream(file); + + if (!stream) + return createStringError(inconvertibleErrorCode(), + "Can't open the file '%s'", file); + + uint32_t value = 0; + StringRef text_buffer = stream.get()->getBuffer(); + + if (type == BitOffset) { + const char *prefix = "config:"; + if (!text_buffer.startswith(prefix)) + return createStringError(inconvertibleErrorCode(), + "The file '%s' contents doesn't start with '%s'", + file, prefix); + text_buffer = text_buffer.substr(strlen(prefix)); + } + + auto getRadix = [&]() { + switch (type) { + case Hex: + return 16; + case ZeroOne: + case Decimal: + case BitOffset: + return 10; + } + }; + + auto createError = [&](const char *expected_value_message) { + return createStringError( + inconvertibleErrorCode(), + "The file '%s' has an invalid value. It should be %s.", file, + expected_value_message); + }; + + if (text_buffer.trim().consumeInteger(getRadix(), value) || + (type == ZeroOne && value != 0 && value != 1)) { + switch (type) { + case Hex: + return createError("an unsigned hexadecimal int"); + case ZeroOne: + return createError("0 or 1"); + case Decimal: + case BitOffset: + return createError("an unsigned decimal int"); + } + } + return value; + } + + /// Return the Linux perf event type for Intel PT. + static Expected GetOSEventType() { + return ReadIntelPTConfigFile(kOSEventIntelPTTypeFile, + IntelPTConfigFileType::Decimal); + } + + static Error CheckPsbPeriod(size_t psb_period) { + Expected cap = + ReadIntelPTConfigFile(kPSBPeriodCapFile, IntelPTConfigFileType::ZeroOne); + if (!cap) + return cap.takeError(); + if (*cap == 0) + return createStringError(inconvertibleErrorCode(), + "psb_period is unsupported in the system."); + + Expected valid_values = ReadIntelPTConfigFile( + kPSBPeriodValidValuesFile, IntelPTConfigFileType::Hex); + if (!valid_values) + return valid_values.takeError(); + + if (valid_values.get() & (1 << psb_period)) + return Error::success(); + + std::ostringstream error; + // 0 is always a valid value + error << "Invalid psb_period. Valid values are: 0"; + uint32_t mask = valid_values.get(); + while (mask) { + int index = __builtin_ctz(mask); + if (index > 0) + error << ", " << index; + // clear the lowest bit + mask &= mask - 1; + } + error << "."; + return createStringError(inconvertibleErrorCode(), error.str().c_str()); + } + + size_t IntelPTThreadTrace::GetTraceBufferSize() const { + #ifndef PERF_ATTR_SIZE_VER5 + llvm_unreachable("Intel PT Linux perf event not supported"); + #else + return m_perf_event.GetAuxBuffer().size(); + #endif + } + + static Expected + GeneratePerfEventConfigValue(bool enable_tsc, Optional psb_period) { + uint64_t config = 0; + // tsc is always supported + if (enable_tsc) { + if (Expected offset = ReadIntelPTConfigFile( + kTSCBitOffsetFile, IntelPTConfigFileType::BitOffset)) + config |= 1 << *offset; + else + return offset.takeError(); + } + if (psb_period) { + if (Error error = CheckPsbPeriod(*psb_period)) + return std::move(error); + + if (Expected offset = ReadIntelPTConfigFile( + kPSBPeriodBitOffsetFile, IntelPTConfigFileType::BitOffset)) + config |= *psb_period << *offset; + else + return offset.takeError(); + } + return config; + } + + llvm::Expected + IntelPTThreadTrace::CreateIntelPTPerfEventConfiguration( + bool enable_tsc, Optional psb_period) { + perf_event_attr attr; + memset(&attr, 0, sizeof(attr)); + attr.size = sizeof(attr); + attr.exclude_kernel = 1; + attr.sample_type = PERF_SAMPLE_TIME; + attr.sample_id_all = 1; + attr.exclude_hv = 1; + attr.exclude_idle = 1; + attr.mmap = 1; + + if (Expected config_value = + GeneratePerfEventConfigValue(enable_tsc, psb_period)) { + attr.config = *config_value; + } else { + return config_value.takeError(); + } + + if (Expected intel_pt_type = GetOSEventType()) { + attr.type = *intel_pt_type; + } else { + return intel_pt_type.takeError(); + } + + return attr; + } + + llvm::Expected + IntelPTThreadTrace::Create(lldb::pid_t pid, lldb::tid_t tid, size_t buffer_size, + bool enable_tsc, Optional psb_period) { + #ifndef PERF_ATTR_SIZE_VER5 + llvm_unreachable("Intel PT Linux perf event not supported"); + #else + Log *log = GetLog(POSIXLog::Ptrace); + + LLDB_LOG(log, "called thread id {0}", tid); + + if (__builtin_popcount(buffer_size) != 1 || buffer_size < 4096) { + return createStringError( + inconvertibleErrorCode(), + "The trace buffer size must be a power of 2 greater than or equal to " + "4096 (2^12) bytes. It was %" PRIu64 ".", + buffer_size); + } + uint64_t page_size = getpagesize(); + uint64_t buffer_numpages = static_cast( + llvm::PowerOf2Floor((buffer_size + page_size - 1) / page_size)); + + Expected attr = + IntelPTThreadTrace::CreateIntelPTPerfEventConfiguration(enable_tsc, + psb_period); + if (!attr) + return attr.takeError(); + + LLDB_LOG(log, "buffer size {0} ", buffer_size); + + if (Expected perf_event = PerfEvent::Init(*attr, tid)) { + if (Error mmap_err = perf_event->MmapMetadataAndBuffers(buffer_numpages, + buffer_numpages)) { + return std::move(mmap_err); + } + return IntelPTThreadTraceUP( + new IntelPTThreadTrace(std::move(*perf_event), tid)); + } else { + return perf_event.takeError(); + } + #endif + } + + Expected> + IntelPTThreadTrace::GetIntelPTBuffer(size_t offset, size_t size) const { + std::vector data(size, 0); + MutableArrayRef buffer_ref(data); + Status error = ReadPerfTraceAux(buffer_ref, 0); + if (error.Fail()) + return error.ToError(); + return data; + } + + Status + IntelPTThreadTrace::ReadPerfTraceAux(llvm::MutableArrayRef &buffer, + size_t offset) const { + #ifndef PERF_ATTR_SIZE_VER5 + llvm_unreachable("perf event not supported"); + #else + auto fd = m_perf_event.GetFd(); + perf_event_mmap_page &mmap_metadata = m_perf_event.GetMetadataPage(); + // Disable the perf event to force a flush out of the CPU's internal buffer. + // Besides, we can guarantee that the CPU won't override any data as we are + // reading the buffer. + // + // The Intel documentation says: + // + // Packets are first buffered internally and then written out asynchronously. + // To collect packet output for postprocessing, a collector needs first to + // ensure that all packet data has been flushed from internal buffers. + // Software can ensure this by stopping packet generation by clearing + // IA32_RTIT_CTL.TraceEn (see “Disabling Packet Generation” in + // Section 35.2.7.2). + // + // This is achieved by the PERF_EVENT_IOC_DISABLE ioctl request, as mentioned + // in the man page of perf_event_open. + ioctl(fd, PERF_EVENT_IOC_DISABLE); + + Log *log = GetLog(POSIXLog::Ptrace); + Status error; + uint64_t head = mmap_metadata.aux_head; + + LLDB_LOG(log, "Aux size -{0} , Head - {1}", mmap_metadata.aux_size, head); + + /** + * When configured as ring buffer, the aux buffer keeps wrapping around + * the buffer and its not possible to detect how many times the buffer + * wrapped. Initially the buffer is filled with zeros,as shown below + * so in order to get complete buffer we first copy firstpartsize, followed + * by any left over part from beginning to aux_head + * + * aux_offset [d,d,d,d,d,d,d,d,0,0,0,0,0,0,0,0,0,0,0] aux_size + * aux_head->||<- firstpartsize ->| + * + * */ + + ReadCyclicBuffer(buffer, m_perf_event.GetAuxBuffer(), + static_cast(head), offset); + LLDB_LOG(log, "ReadCyclic Buffer Done"); + + // Reenable tracing now we have read the buffer + ioctl(fd, PERF_EVENT_IOC_ENABLE); + return error; + #endif + } + + Status + IntelPTThreadTrace::ReadPerfTraceData(llvm::MutableArrayRef &buffer, + size_t offset) const { + #ifndef PERF_ATTR_SIZE_VER5 + llvm_unreachable("perf event not supported"); + #else + Log *log = GetLog(POSIXLog::Ptrace); + uint64_t bytes_remaining = buffer.size(); + Status error; + + perf_event_mmap_page &mmap_metadata = m_perf_event.GetMetadataPage(); + uint64_t head = mmap_metadata.data_head; + + /* + * The data buffer and aux buffer have different implementations + * with respect to their definition of head pointer. In the case + * of Aux data buffer the head always wraps around the aux buffer + * and we don't need to care about it, whereas the data_head keeps + * increasing and needs to be wrapped by modulus operator + */ + + LLDB_LOG(log, "bytes_remaining - {0}", bytes_remaining); + + auto data_buffer = m_perf_event.GetDataBuffer(); + + if (head > data_buffer.size()) { + head = head % data_buffer.size(); + LLDB_LOG(log, "Data size -{0} Head - {1}", mmap_metadata.data_size, head); + + ReadCyclicBuffer(buffer, data_buffer, static_cast(head), offset); + bytes_remaining -= buffer.size(); + } else { + LLDB_LOG(log, "Head - {0}", head); + if (offset >= head) { + LLDB_LOG(log, "Invalid Offset "); + error.SetErrorString("invalid offset"); + buffer = buffer.slice(buffer.size()); + return error; + } + + auto data = data_buffer.slice(offset, (head - offset)); + auto remaining = std::copy(data.begin(), data.end(), buffer.begin()); + bytes_remaining -= (remaining - buffer.begin()); + } + buffer = buffer.drop_back(bytes_remaining); + return error; + #endif + } + + void IntelPTThreadTrace::ReadCyclicBuffer(llvm::MutableArrayRef &dst, + llvm::ArrayRef src, + size_t src_cyc_index, size_t offset) { + + Log *log = GetLog(POSIXLog::Ptrace); + + if (dst.empty() || src.empty()) { + dst = dst.drop_back(dst.size()); + return; + } + + if (dst.data() == nullptr || src.data() == nullptr) { + dst = dst.drop_back(dst.size()); + return; + } + + if (src_cyc_index > src.size()) { + dst = dst.drop_back(dst.size()); + return; + } + + if (offset >= src.size()) { + LLDB_LOG(log, "Too Big offset "); + dst = dst.drop_back(dst.size()); + return; + } + + llvm::SmallVector, 2> parts = { + src.slice(src_cyc_index), src.take_front(src_cyc_index)}; + + if (offset > parts[0].size()) { + parts[1] = parts[1].slice(offset - parts[0].size()); + parts[0] = parts[0].drop_back(parts[0].size()); + } else if (offset == parts[0].size()) { + parts[0] = parts[0].drop_back(parts[0].size()); + } else { + parts[0] = parts[0].slice(offset); + } + auto next = dst.begin(); + auto bytes_left = dst.size(); + for (auto part : parts) { + size_t chunk_size = std::min(part.size(), bytes_left); + next = std::copy_n(part.begin(), chunk_size, next); + bytes_left -= chunk_size; + } + dst = dst.drop_back(bytes_left); + } + + TraceThreadState IntelPTThreadTrace::GetState() const { + return {static_cast(m_tid), + {TraceBinaryData{"threadTraceBuffer", + static_cast(GetTraceBufferSize())}}}; + } + + /// IntelPTThreadTraceCollection + + bool IntelPTThreadTraceCollection::TracesThread(lldb::tid_t tid) const { + return m_thread_traces.count(tid); + } + + Error IntelPTThreadTraceCollection::TraceStop(lldb::tid_t tid) { + auto it = m_thread_traces.find(tid); + if (it == m_thread_traces.end()) + return createStringError(inconvertibleErrorCode(), + "Thread %" PRIu64 " not currently traced", tid); + m_total_buffer_size -= it->second->GetTraceBufferSize(); + m_thread_traces.erase(tid); + return Error::success(); + } + + Error IntelPTThreadTraceCollection::TraceStart( + lldb::tid_t tid, const TraceIntelPTStartRequest &request) { + if (TracesThread(tid)) + return createStringError(inconvertibleErrorCode(), + "Thread %" PRIu64 " already traced", tid); + + Expected trace_up = IntelPTThreadTrace::Create( + m_pid, tid, request.threadBufferSize, request.enableTsc, + request.psbPeriod.map([](int64_t period) { return (size_t)period; })); + if (!trace_up) + return trace_up.takeError(); + + m_total_buffer_size += (*trace_up)->GetTraceBufferSize(); + m_thread_traces.try_emplace(tid, std::move(*trace_up)); + return Error::success(); + } + + size_t IntelPTThreadTraceCollection::GetTotalBufferSize() const { + return m_total_buffer_size; + } + + std::vector + IntelPTThreadTraceCollection::GetThreadStates() const { + std::vector states; + for (const auto &it : m_thread_traces) + states.push_back(it.second->GetState()); + return states; + } + + Expected + IntelPTThreadTraceCollection::GetTracedThread(lldb::tid_t tid) const { + auto it = m_thread_traces.find(tid); + if (it == m_thread_traces.end()) + return createStringError(inconvertibleErrorCode(), + "Thread %" PRIu64 " not currently traced", tid); + return *it->second.get(); + } + + void IntelPTThreadTraceCollection::Clear() { + m_thread_traces.clear(); + m_total_buffer_size = 0; + } + + /// IntelPTProcessTrace + + bool IntelPTProcessTrace::TracesThread(lldb::tid_t tid) const { + return m_thread_traces.TracesThread(tid); + } + + Error IntelPTProcessTrace::TraceStop(lldb::tid_t tid) { + return m_thread_traces.TraceStop(tid); + } + + Error IntelPTProcessTrace::TraceStart(lldb::tid_t tid) { + if (m_thread_traces.GetTotalBufferSize() + m_tracing_params.threadBufferSize > + static_cast(*m_tracing_params.processBufferSizeLimit)) + return createStringError( + inconvertibleErrorCode(), + "Thread %" PRIu64 " can't be traced as the process trace size limit " + "has been reached. Consider retracing with a higher " + "limit.", + tid); + + return m_thread_traces.TraceStart(tid, m_tracing_params); + } + + const IntelPTThreadTraceCollection & + IntelPTProcessTrace::GetThreadTraces() const { + return m_thread_traces; + } + + /// IntelPTCollector + ++IntelPTCollector::IntelPTCollector(lldb::pid_t pid) ++ : m_pid(pid), m_thread_traces(pid) { ++ if (Expected tsc_conversion = ++ LoadPerfTscConversionParameters()) ++ m_tsc_conversion = ++ std::make_unique(*tsc_conversion); ++ else ++ LLDB_LOG_ERROR(GetLog(POSIXLog::Trace), tsc_conversion.takeError(), ++ "unable to load TSC to wall time conversion: {0}"); ++} ++ + Error IntelPTCollector::TraceStop(lldb::tid_t tid) { + if (IsProcessTracingEnabled() && m_process_trace->TracesThread(tid)) + return m_process_trace->TraceStop(tid); + return m_thread_traces.TraceStop(tid); + } + + Error IntelPTCollector::TraceStop(const TraceStopRequest &request) { + if (request.IsProcessTracing()) { + Clear(); + return Error::success(); + } else { + Error error = Error::success(); + for (int64_t tid : *request.tids) + error = joinErrors(std::move(error), + TraceStop(static_cast(tid))); + return error; + } + } + + Error IntelPTCollector::TraceStart( + const TraceIntelPTStartRequest &request, + const std::vector &process_threads) { + if (request.IsProcessTracing()) { + if (IsProcessTracingEnabled()) { + return createStringError( + inconvertibleErrorCode(), + "Process currently traced. Stop process tracing first"); + } + m_process_trace = IntelPTProcessTrace(m_pid, request); + + Error error = Error::success(); + for (lldb::tid_t tid : process_threads) + error = joinErrors(std::move(error), m_process_trace->TraceStart(tid)); + return error; + } else { + Error error = Error::success(); + for (int64_t tid : *request.tids) + error = joinErrors(std::move(error), + m_thread_traces.TraceStart(tid, request)); + return error; + } + } + + Error IntelPTCollector::OnThreadCreated(lldb::tid_t tid) { + if (!IsProcessTracingEnabled()) + return Error::success(); + return m_process_trace->TraceStart(tid); + } + + Error IntelPTCollector::OnThreadDestroyed(lldb::tid_t tid) { + if (IsProcessTracingEnabled() && m_process_trace->TracesThread(tid)) + return m_process_trace->TraceStop(tid); + else if (m_thread_traces.TracesThread(tid)) + return m_thread_traces.TraceStop(tid); + return Error::success(); + } + + Expected IntelPTCollector::GetState() const { + Expected> cpu_info = GetCPUInfo(); + if (!cpu_info) + return cpu_info.takeError(); + + TraceGetStateResponse state; + state.processBinaryData.push_back( + {"cpuInfo", static_cast(cpu_info->size())}); + + std::vector thread_states = + m_thread_traces.GetThreadStates(); + state.tracedThreads.insert(state.tracedThreads.end(), thread_states.begin(), + thread_states.end()); + + if (IsProcessTracingEnabled()) { + thread_states = m_process_trace->GetThreadTraces().GetThreadStates(); + state.tracedThreads.insert(state.tracedThreads.end(), thread_states.begin(), + thread_states.end()); + } + return toJSON(state); + } + + Expected + IntelPTCollector::GetTracedThread(lldb::tid_t tid) const { + if (IsProcessTracingEnabled() && m_process_trace->TracesThread(tid)) + return m_process_trace->GetThreadTraces().GetTracedThread(tid); + return m_thread_traces.GetTracedThread(tid); + } + + Expected> + IntelPTCollector::GetBinaryData(const TraceGetBinaryDataRequest &request) const { + if (request.kind == "threadTraceBuffer") { + if (Expected trace = + GetTracedThread(*request.tid)) + return trace->GetIntelPTBuffer(request.offset, request.size); + else + return trace.takeError(); + } else if (request.kind == "cpuInfo") { + return GetCPUInfo(); + } + return createStringError(inconvertibleErrorCode(), + "Unsuported trace binary data kind: %s", + request.kind.c_str()); + } + + void IntelPTCollector::ClearProcessTracing() { m_process_trace = None; } + + bool IntelPTCollector::IsSupported() { + Expected intel_pt_type = GetOSEventType(); + if (!intel_pt_type) { + llvm::consumeError(intel_pt_type.takeError()); + return false; + } + return true; + } + + bool IntelPTCollector::IsProcessTracingEnabled() const { + return (bool)m_process_trace; + } + + void IntelPTCollector::Clear() { + ClearProcessTracing(); + m_thread_traces.Clear(); + } +diff --git a/lldb/source/Plugins/Process/Linux/IntelPTCollector.h b/lldb/source/Plugins/Process/Linux/IntelPTCollector.h +index 12fa12dc299d..aa7eba4cd937 100644 +--- a/lldb/source/Plugins/Process/Linux/IntelPTCollector.h ++++ b/lldb/source/Plugins/Process/Linux/IntelPTCollector.h +@@ -1,243 +1,245 @@ + //===-- IntelPTCollector.h -------------------------------------- -*- C++ -*-===// + // + // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. + // See https://llvm.org/LICENSE.txt for license information. + // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + // + //===----------------------------------------------------------------------===// + + #ifndef liblldb_IntelPTCollector_H_ + #define liblldb_IntelPTCollector_H_ + + #include "Perf.h" + + #include "lldb/Utility/Status.h" + #include "lldb/Utility/TraceIntelPTGDBRemotePackets.h" + #include "lldb/lldb-types.h" + + #include + #include + #include + + namespace lldb_private { + + namespace process_linux { + + /// This class keeps track of one tracing instance of + /// Intel(R) Processor Trace on Linux OS at thread level. + /// + /// The kernel interface for us is the perf_event_open. + class IntelPTThreadTrace; + typedef std::unique_ptr IntelPTThreadTraceUP; + + class IntelPTThreadTrace { + public: + /// Create a new \a IntelPTThreadTrace and start tracing the thread. + /// + /// \param[in] pid + /// The pid of the process whose thread will be traced. + /// + /// \param[in] tid + /// The tid of the thread to be traced. + /// + /// \param[in] buffer_size + /// Size of the thread buffer in bytes. + /// + /// \param[in] enable_tsc + /// Whether to use enable TSC timestamps or not. + /// More information in TraceIntelPT::GetStartConfigurationHelp(). + /// + /// \param[in] psb_period + /// This value defines the period in which PSB packets will be generated. + /// More information in TraceIntelPT::GetStartConfigurationHelp(). + /// + /// \return + /// A \a IntelPTThreadTrace instance if tracing was successful, or + /// an \a llvm::Error otherwise. + static llvm::Expected + Create(lldb::pid_t pid, lldb::tid_t tid, size_t buffer_size, bool enable_tsc, + llvm::Optional psb_period); + + /// Create a \a perf_event_attr configured for + /// an IntelPT event. + /// + /// \return + /// A \a perf_event_attr if successful, + /// or an \a llvm::Error otherwise. + static llvm::Expected + CreateIntelPTPerfEventConfiguration(bool enable_tsc, + llvm::Optional psb_period); + + /// Read the trace buffer of the currently traced thread. + /// + /// \param[in] offset + /// Offset of the data to read. + /// + /// \param[in] size + /// Number of bytes to read. + /// + /// \return + /// A vector with the requested binary data. The vector will have the + /// size of the requested \a size. Non-available positions will be + /// filled with zeroes. + llvm::Expected> GetIntelPTBuffer(size_t offset, + size_t size) const; + + Status ReadPerfTraceAux(llvm::MutableArrayRef &buffer, + size_t offset = 0) const; + + Status ReadPerfTraceData(llvm::MutableArrayRef &buffer, + size_t offset = 0) const; + + /// Get the size in bytes of the aux section of the thread or process traced + /// by this object. + size_t GetTraceBufferSize() const; + + /// Read data from a cyclic buffer + /// + /// \param[in] [out] buf + /// Destination buffer, the buffer will be truncated to written size. + /// + /// \param[in] src + /// Source buffer which must be a cyclic buffer. + /// + /// \param[in] src_cyc_index + /// The index pointer (start of the valid data in the cyclic + /// buffer). + /// + /// \param[in] offset + /// The offset to begin reading the data in the cyclic buffer. + static void ReadCyclicBuffer(llvm::MutableArrayRef &dst, + llvm::ArrayRef src, + size_t src_cyc_index, size_t offset); + + /// Return the thread-specific part of the jLLDBTraceGetState packet. + TraceThreadState GetState() const; + + private: + /// Construct new \a IntelPTThreadTrace. Users are supposed to create + /// instances of this class via the \a Create() method and not invoke this one + /// directly. + /// + /// \param[in] perf_event + /// perf event configured for IntelPT. + /// + /// \param[in] tid + /// The thread being traced. + IntelPTThreadTrace(PerfEvent &&perf_event, lldb::tid_t tid) + : m_perf_event(std::move(perf_event)), m_tid(tid) {} + + /// perf event configured for IntelPT. + PerfEvent m_perf_event; + /// The thread being traced. + lldb::tid_t m_tid; + }; + + /// Manages a list of thread traces. + class IntelPTThreadTraceCollection { + public: + IntelPTThreadTraceCollection(lldb::pid_t pid) : m_pid(pid) {} + + /// Dispose of all traces + void Clear(); + + bool TracesThread(lldb::tid_t tid) const; + + size_t GetTotalBufferSize() const; + + std::vector GetThreadStates() const; + + llvm::Expected + GetTracedThread(lldb::tid_t tid) const; + + llvm::Error TraceStart(lldb::tid_t tid, + const TraceIntelPTStartRequest &request); + + llvm::Error TraceStop(lldb::tid_t tid); + + private: + lldb::pid_t m_pid; + llvm::DenseMap m_thread_traces; + /// Total actual thread buffer size in bytes + size_t m_total_buffer_size = 0; + }; + + /// Manages a "process trace" instance. + class IntelPTProcessTrace { + public: + IntelPTProcessTrace(lldb::pid_t pid, const TraceIntelPTStartRequest &request) + : m_thread_traces(pid), m_tracing_params(request) {} + + bool TracesThread(lldb::tid_t tid) const; + + const IntelPTThreadTraceCollection &GetThreadTraces() const; + + llvm::Error TraceStart(lldb::tid_t tid); + + llvm::Error TraceStop(lldb::tid_t tid); + + private: + IntelPTThreadTraceCollection m_thread_traces; + /// Params used to trace threads when the user started "process tracing". + TraceIntelPTStartRequest m_tracing_params; + }; + + /// Main class that manages intel-pt process and thread tracing. + class IntelPTCollector { + public: +- IntelPTCollector(lldb::pid_t pid) : m_pid(pid), m_thread_traces(pid) {} ++ IntelPTCollector(lldb::pid_t pid); + + static bool IsSupported(); + + /// If "process tracing" is enabled, then trace the given thread. + llvm::Error OnThreadCreated(lldb::tid_t tid); + + /// Stops tracing a tracing upon a destroy event. + llvm::Error OnThreadDestroyed(lldb::tid_t tid); + + /// Implementation of the jLLDBTraceStop packet + llvm::Error TraceStop(const TraceStopRequest &request); + + /// Implementation of the jLLDBTraceStart packet + /// + /// \param[in] process_threads + /// A list of all threads owned by the process. + llvm::Error TraceStart(const TraceIntelPTStartRequest &request, + const std::vector &process_threads); + + /// Implementation of the jLLDBTraceGetState packet + llvm::Expected GetState() const; + + /// Implementation of the jLLDBTraceGetBinaryData packet + llvm::Expected> + GetBinaryData(const TraceGetBinaryDataRequest &request) const; + + /// Dispose of all traces + void Clear(); + + private: + llvm::Error TraceStop(lldb::tid_t tid); + + /// Start tracing a specific thread. + llvm::Error TraceStart(lldb::tid_t tid, + const TraceIntelPTStartRequest &request); + + llvm::Expected + GetTracedThread(lldb::tid_t tid) const; + + bool IsProcessTracingEnabled() const; + + void ClearProcessTracing(); + + lldb::pid_t m_pid; + /// Threads traced due to "thread tracing" + IntelPTThreadTraceCollection m_thread_traces; + /// Threads traced due to "process tracing". Only one active "process tracing" + /// instance is assumed for a single process. + llvm::Optional m_process_trace; ++ /// TSC to wall time conversion. ++ TraceTscConversionUP m_tsc_conversion; + }; + + } // namespace process_linux + } // namespace lldb_private + + #endif // liblldb_IntelPTCollector_H_ +diff --git a/lldb/source/Plugins/Process/Linux/Perf.cpp b/lldb/source/Plugins/Process/Linux/Perf.cpp +index 455a81fbcbaf..ed65c61dc482 100644 +--- a/lldb/source/Plugins/Process/Linux/Perf.cpp ++++ b/lldb/source/Plugins/Process/Linux/Perf.cpp +@@ -1,181 +1,165 @@ + //===-- Perf.cpp ----------------------------------------------------------===// + // + // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. + // See https://llvm.org/LICENSE.txt for license information. + // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + // + //===----------------------------------------------------------------------===// + + #include "Perf.h" + +-#include "lldb/lldb-types.h" +- +-#include "llvm/Support/Error.h" + #include "llvm/Support/FormatVariadic.h" + #include "llvm/Support/MathExtras.h" + +-#include +-#include +-#include + #include + #include + #include + + using namespace lldb_private; + using namespace process_linux; + using namespace llvm; + +-Expected +-lldb_private::process_linux::FetchPerfTscConversionParameters() { ++Expected ++lldb_private::process_linux::LoadPerfTscConversionParameters() { + lldb::pid_t pid = getpid(); + perf_event_attr attr; + memset(&attr, 0, sizeof(attr)); + attr.size = sizeof(attr); + attr.type = PERF_TYPE_SOFTWARE; + attr.config = PERF_COUNT_SW_DUMMY; + + Expected perf_event = PerfEvent::Init(attr, pid); + if (!perf_event) + return perf_event.takeError(); + if (Error mmap_err = perf_event->MmapMetadataAndBuffers(/*num_data_pages*/ 0, + /*num_aux_pages*/ 0)) + return std::move(mmap_err); + + perf_event_mmap_page &mmap_metada = perf_event->GetMetadataPage(); + if (mmap_metada.cap_user_time && mmap_metada.cap_user_time_zero) { +- return PerfTscConversionParameters{ ++ return LinuxPerfZeroTscConversion{ + mmap_metada.time_mult, mmap_metada.time_shift, mmap_metada.time_zero}; + } else { + auto err_cap = + !mmap_metada.cap_user_time ? "cap_user_time" : "cap_user_time_zero"; + std::string err_msg = + llvm::formatv("Can't get TSC to real time conversion values. " + "perf_event capability '{0}' not supported.", + err_cap); + return llvm::createStringError(llvm::inconvertibleErrorCode(), err_msg); + } + } + +-std::chrono::nanoseconds PerfTscConversionParameters::ToWallTime(uint64_t tsc) { +- // See 'time_zero' section of +- // https://man7.org/linux/man-pages/man2/perf_event_open.2.html +- uint64_t quot = tsc >> m_time_shift; +- uint64_t rem_flag = (((uint64_t)1 << m_time_shift) - 1); +- uint64_t rem = tsc & rem_flag; +- return std::chrono::nanoseconds{m_time_zero + quot * m_time_mult + +- ((rem * m_time_mult) >> m_time_shift)}; +-} +- + void resource_handle::MmapDeleter::operator()(void *ptr) { + if (m_bytes && ptr != nullptr) + munmap(ptr, m_bytes); + } + + void resource_handle::FileDescriptorDeleter::operator()(long *ptr) { + if (ptr == nullptr) + return; + if (*ptr == -1) + return; + close(*ptr); + std::default_delete()(ptr); + } + + llvm::Expected PerfEvent::Init(perf_event_attr &attr, + lldb::pid_t pid, int cpu, + int group_fd, unsigned long flags) { + errno = 0; + long fd = syscall(SYS_perf_event_open, &attr, pid, cpu, group_fd, flags); + if (fd == -1) { + std::string err_msg = + llvm::formatv("perf event syscall failed: {0}", std::strerror(errno)); + return llvm::createStringError(llvm::inconvertibleErrorCode(), err_msg); + } + return PerfEvent{fd}; + } + + llvm::Expected PerfEvent::Init(perf_event_attr &attr, + lldb::pid_t pid) { + return Init(attr, pid, -1, -1, 0); + } + + llvm::Expected + PerfEvent::DoMmap(void *addr, size_t length, int prot, int flags, + long int offset, llvm::StringRef buffer_name) { + errno = 0; + auto mmap_result = ::mmap(nullptr, length, prot, flags, GetFd(), offset); + + if (mmap_result == MAP_FAILED) { + std::string err_msg = + llvm::formatv("perf event mmap allocation failed for {0}: {1}", + buffer_name, std::strerror(errno)); + return createStringError(inconvertibleErrorCode(), err_msg); + } + return resource_handle::MmapUP(mmap_result, length); + } + + llvm::Error PerfEvent::MmapMetadataAndDataBuffer(size_t num_data_pages) { + size_t mmap_size = (num_data_pages + 1) * getpagesize(); + if (Expected mmap_metadata_data = + DoMmap(nullptr, mmap_size, PROT_WRITE, MAP_SHARED, 0, + "metadata and data buffer")) { + m_metadata_data_base = std::move(mmap_metadata_data.get()); + return Error::success(); + } else + return mmap_metadata_data.takeError(); + } + + llvm::Error PerfEvent::MmapAuxBuffer(size_t num_aux_pages) { + if (num_aux_pages == 0) + return Error::success(); + + perf_event_mmap_page &metadata_page = GetMetadataPage(); + metadata_page.aux_offset = + metadata_page.data_offset + metadata_page.data_size; + metadata_page.aux_size = num_aux_pages * getpagesize(); + + if (Expected mmap_aux = + DoMmap(nullptr, metadata_page.aux_size, PROT_READ, MAP_SHARED, + metadata_page.aux_offset, "aux buffer")) { + m_aux_base = std::move(mmap_aux.get()); + return Error::success(); + } else + return mmap_aux.takeError(); + } + + llvm::Error PerfEvent::MmapMetadataAndBuffers(size_t num_data_pages, + size_t num_aux_pages) { + if (num_data_pages != 0 && !isPowerOf2_64(num_data_pages)) + return llvm::createStringError( + llvm::inconvertibleErrorCode(), + llvm::formatv("Number of data pages must be a power of 2, got: {0}", + num_data_pages)); + if (num_aux_pages != 0 && !isPowerOf2_64(num_aux_pages)) + return llvm::createStringError( + llvm::inconvertibleErrorCode(), + llvm::formatv("Number of aux pages must be a power of 2, got: {0}", + num_aux_pages)); + if (Error err = MmapMetadataAndDataBuffer(num_data_pages)) + return err; + if (Error err = MmapAuxBuffer(num_aux_pages)) + return err; + return Error::success(); + } + + long PerfEvent::GetFd() const { return *(m_fd.get()); } + + perf_event_mmap_page &PerfEvent::GetMetadataPage() const { + return *reinterpret_cast(m_metadata_data_base.get()); + } + + ArrayRef PerfEvent::GetDataBuffer() const { + perf_event_mmap_page &mmap_metadata = GetMetadataPage(); + return {reinterpret_cast(m_metadata_data_base.get()) + + mmap_metadata.data_offset, + static_cast(mmap_metadata.data_size)}; + } + + ArrayRef PerfEvent::GetAuxBuffer() const { + perf_event_mmap_page &mmap_metadata = GetMetadataPage(); + return {reinterpret_cast(m_aux_base.get()), + static_cast(mmap_metadata.aux_size)}; + } +diff --git a/lldb/source/Plugins/Process/Linux/Perf.h b/lldb/source/Plugins/Process/Linux/Perf.h +index 6853b58666b1..d5cb1f3b7d88 100644 +--- a/lldb/source/Plugins/Process/Linux/Perf.h ++++ b/lldb/source/Plugins/Process/Linux/Perf.h +@@ -1,262 +1,243 @@ + //===-- Perf.h --------------------------------------------------*- C++ -*-===// + // + // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. + // See https://llvm.org/LICENSE.txt for license information. + // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + // + //===----------------------------------------------------------------------===// + /// \file + /// This file contains a thin wrapper of the perf_event_open API + /// and classes to handle the destruction of file descriptors + /// and mmap pointers. + /// + //===----------------------------------------------------------------------===// + + #ifndef LLDB_SOURCE_PLUGINS_PROCESS_LINUX_PERF_H + #define LLDB_SOURCE_PLUGINS_PROCESS_LINUX_PERF_H + ++#include "lldb/Utility/TraceIntelPTGDBRemotePackets.h" + #include "lldb/lldb-types.h" + + #include "llvm/Support/Error.h" + + #include + #include + #include + + namespace lldb_private { + namespace process_linux { + namespace resource_handle { + + /// Custom deleter for the pointer returned by \a mmap. + /// + /// This functor type is provided to \a unique_ptr to properly + /// unmap the region at destruction time. + class MmapDeleter { + public: + /// Construct new \a MmapDeleter. + /// + /// \param[in] bytes + /// Size of the mmap'ed region in bytes. + MmapDeleter(size_t bytes = 0) : m_bytes(bytes) {} + + /// Unmap the mmap'ed region. + /// + /// If \a m_bytes==0 or \a ptr==nullptr, nothing is unmmapped. + /// + /// \param[in] ptr + /// pointer to the region to be unmmapped. + void operator()(void *ptr); + + private: + /// Size of the mmap'ed region, in bytes, to be unmapped. + size_t m_bytes; + }; + + /// Custom deleter for a file descriptor. + /// + /// This functor type is provided to \a unique_ptr to properly release + /// the resources associated with the file descriptor at destruction time. + class FileDescriptorDeleter { + public: + /// Close and free the memory associated with the file descriptor pointer. + /// + /// Effectively a no-op if \a ptr==nullptr or \a*ptr==-1. + /// + /// \param[in] ptr + /// Pointer to the file descriptor. + void operator()(long *ptr); + }; + + using FileDescriptorUP = + std::unique_ptr; + using MmapUP = std::unique_ptr; + + } // namespace resource_handle + + /// Thin wrapper of the perf_event_open API. + /// + /// Exposes the metadata page and data and aux buffers of a perf event. + /// Handles the management of the event's file descriptor and mmap'ed + /// regions. + class PerfEvent { + public: + /// Create a new performance monitoring event via the perf_event_open syscall. + /// + /// The parameters are directly forwarded to a perf_event_open syscall, + /// for additional information on the parameters visit + /// https://man7.org/linux/man-pages/man2/perf_event_open.2.html. + /// + /// \param[in] attr + /// Configuration information for the event. + /// + /// \param[in] pid + /// The process to be monitored by the event. + /// + /// \param[in] cpu + /// The cpu to be monitored by the event. + /// + /// \param[in] group_fd + /// File descriptor of the group leader. + /// + /// \param[in] flags + /// Bitmask of additional configuration flags. + /// + /// \return + /// If the perf_event_open syscall was successful, a minimal \a PerfEvent + /// instance, or an \a llvm::Error otherwise. + static llvm::Expected Init(perf_event_attr &attr, lldb::pid_t pid, + int cpu, int group_fd, + unsigned long flags); + + /// Create a new performance monitoring event via the perf_event_open syscall + /// with "default" values for the cpu, group_fd and flags arguments. + /// + /// Convenience method to be used when the perf event requires minimal + /// configuration. It handles the default values of all other arguments. + /// + /// \param[in] attr + /// Configuration information for the event. + /// + /// \param[in] pid + /// The process to be monitored by the event. + static llvm::Expected Init(perf_event_attr &attr, lldb::pid_t pid); + + /// Mmap the metadata page and the data and aux buffers of the perf event and + /// expose them through \a PerfEvent::GetMetadataPage() , \a + /// PerfEvent::GetDataBuffer() and \a PerfEvent::GetAuxBuffer(). + /// + /// This uses mmap underneath, which means that the number of pages mmap'ed + /// must be less than the actual data available by the kernel. The metadata + /// page is always mmap'ed. + /// + /// Mmap is needed because the underlying data might be changed by the kernel + /// dynamically. + /// + /// \param[in] num_data_pages + /// Number of pages in the data buffer to mmap, must be a power of 2. + /// A value of 0 is useful for "dummy" events that only want to access + /// the metadata, \a perf_event_mmap_page, or the aux buffer. + /// + /// \param[in] num_aux_pages + /// Number of pages in the aux buffer to mmap, must be a power of 2. + /// A value of 0 effectively is a no-op and no data is mmap'ed for this + /// buffer. + /// + /// \return + /// \a llvm::Error::success if the mmap operations succeeded, + /// or an \a llvm::Error otherwise. + llvm::Error MmapMetadataAndBuffers(size_t num_data_pages, + size_t num_aux_pages); + + /// Get the file descriptor associated with the perf event. + long GetFd() const; + + /// Get the metadata page from the data section's mmap buffer. + /// + /// The metadata page is always mmap'ed, even when \a num_data_pages is 0. + /// + /// This should be called only after \a PerfEvent::MmapMetadataAndBuffers, + /// otherwise a failure might happen. + /// + /// \return + /// The data section's \a perf_event_mmap_page. + perf_event_mmap_page &GetMetadataPage() const; + + /// Get the data buffer from the data section's mmap buffer. + /// + /// The data buffer is the region of the data section's mmap buffer where + /// perf sample data is located. + /// + /// This should be called only after \a PerfEvent::MmapMetadataAndBuffers, + /// otherwise a failure might happen. + /// + /// \return + /// \a ArrayRef extending \a data_size bytes from \a data_offset. + llvm::ArrayRef GetDataBuffer() const; + + /// Get the AUX buffer. + /// + /// AUX buffer is a region for high-bandwidth data streams + /// such as IntelPT. This is separate from the metadata and data buffer. + /// + /// This should be called only after \a PerfEvent::MmapMetadataAndBuffers, + /// otherwise a failure might happen. + /// + /// \return + /// \a ArrayRef extending \a aux_size bytes from \a aux_offset. + llvm::ArrayRef GetAuxBuffer() const; + + private: + /// Create new \a PerfEvent. + /// + /// \param[in] fd + /// File descriptor of the perf event. + PerfEvent(long fd) + : m_fd(new long(fd), resource_handle::FileDescriptorDeleter()), + m_metadata_data_base(), m_aux_base() {} + + /// Wrapper for \a mmap to provide custom error messages. + /// + /// The parameters are directly forwarded to a \a mmap syscall, + /// for information on the parameters visit + /// https://man7.org/linux/man-pages/man2/mmap.2.html. + /// + /// The value of \a GetFd() is passed as the \a fd argument to \a mmap. + llvm::Expected DoMmap(void *addr, size_t length, + int prot, int flags, + long int offset, + llvm::StringRef buffer_name); + + /// Mmap the data buffer of the perf event. + /// + /// \param[in] num_data_pages + /// Number of pages in the data buffer to mmap, must be a power of 2. + /// A value of 0 is useful for "dummy" events that only want to access + /// the metadata, \a perf_event_mmap_page, or the aux buffer. + llvm::Error MmapMetadataAndDataBuffer(size_t num_data_pages); + + /// Mmap the aux buffer of the perf event. + /// + /// \param[in] num_aux_pages + /// Number of pages in the aux buffer to mmap, must be a power of 2. + /// A value of 0 effectively is a no-op and no data is mmap'ed for this + /// buffer. + llvm::Error MmapAuxBuffer(size_t num_aux_pages); + + /// The file descriptor representing the perf event. + resource_handle::FileDescriptorUP m_fd; + /// Metadata page and data section where perf samples are stored. + resource_handle::MmapUP m_metadata_data_base; + /// AUX buffer is a separate region for high-bandwidth data streams + /// such as IntelPT. + resource_handle::MmapUP m_aux_base; + }; + +-/// TSC to nanoseconds conversion values defined by the Linux perf_event API +-/// when the capibilities cap_user_time and cap_user_time_zero are set. See the +-/// documentation of `time_zero` in +-/// https://man7.org/linux/man-pages/man2/perf_event_open.2.html for more +-/// information. +-struct PerfTscConversionParameters { +- uint32_t m_time_mult; +- uint16_t m_time_shift; +- uint64_t m_time_zero; +- +- /// Convert TSC value to nanosecond wall time. +- /// +- /// \a param[in] tsc +- /// The TSC value to be converted. +- /// +- /// \return +- /// Nanosecond wall time. +- std::chrono::nanoseconds ToWallTime(uint64_t tsc); +-}; +- +-/// Fetch \a PerfTscConversionParameters from \a perf_event_mmap_page, if ++/// Load \a PerfTscConversionParameters from \a perf_event_mmap_page, if + /// available. +-llvm::Expected FetchPerfTscConversionParameters(); ++llvm::Expected LoadPerfTscConversionParameters(); + + } // namespace process_linux + } // namespace lldb_private + + #endif // LLDB_SOURCE_PLUGINS_PROCESS_LINUX_PERF_H +diff --git a/lldb/source/Plugins/Process/POSIX/ProcessPOSIXLog.h b/lldb/source/Plugins/Process/POSIX/ProcessPOSIXLog.h +index 2d48b9db013c..88cfdfd204c0 100644 +--- a/lldb/source/Plugins/Process/POSIX/ProcessPOSIXLog.h ++++ b/lldb/source/Plugins/Process/POSIX/ProcessPOSIXLog.h +@@ -1,38 +1,39 @@ + //===-- ProcessPOSIXLog.h -----------------------------------------*- C++ + //-*-===// + // + // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. + // See https://llvm.org/LICENSE.txt for license information. + // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + // + //===----------------------------------------------------------------------===// + + #ifndef liblldb_ProcessPOSIXLog_h_ + #define liblldb_ProcessPOSIXLog_h_ + + #include "lldb/Utility/Log.h" + #include "llvm/ADT/BitmaskEnum.h" + + namespace lldb_private { + + enum class POSIXLog : Log::MaskType { + Breakpoints = Log::ChannelFlag<0>, + Memory = Log::ChannelFlag<1>, + Process = Log::ChannelFlag<2>, + Ptrace = Log::ChannelFlag<3>, + Registers = Log::ChannelFlag<4>, + Thread = Log::ChannelFlag<5>, + Watchpoints = Log::ChannelFlag<6>, +- LLVM_MARK_AS_BITMASK_ENUM(Watchpoints) ++ Trace = Log::ChannelFlag<7>, ++ LLVM_MARK_AS_BITMASK_ENUM(Trace) + }; + LLVM_ENABLE_BITMASK_ENUMS_IN_NAMESPACE(); + + class ProcessPOSIXLog { + public: + static void Initialize(); + }; + + template <> Log::Channel &LogChannelFor(); + } // namespace lldb_private + + #endif // liblldb_ProcessPOSIXLog_h_ +diff --git a/lldb/source/Utility/TraceIntelPTGDBRemotePackets.cpp b/lldb/source/Utility/TraceIntelPTGDBRemotePackets.cpp +index dbb93d8d1c5c..4dcc559f1966 100644 +--- a/lldb/source/Utility/TraceIntelPTGDBRemotePackets.cpp ++++ b/lldb/source/Utility/TraceIntelPTGDBRemotePackets.cpp +@@ -1,46 +1,111 @@ + //===-- TraceIntelPTGDBRemotePackets.cpp ------------------------*- C++ -*-===// + // + // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. + // See https://llvm.org/LICENSE.txt for license information. + // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + // + //===----------------------------------------------------------------------===// + + #include "lldb/Utility/TraceIntelPTGDBRemotePackets.h" + + using namespace llvm; + using namespace llvm::json; + + namespace lldb_private { + + bool fromJSON(const json::Value &value, TraceIntelPTStartRequest &packet, + Path path) { + ObjectMapper o(value, path); + if (!o || !fromJSON(value, (TraceStartRequest &)packet, path) || + !o.map("enableTsc", packet.enableTsc) || + !o.map("psbPeriod", packet.psbPeriod) || + !o.map("threadBufferSize", packet.threadBufferSize) || + !o.map("processBufferSizeLimit", packet.processBufferSizeLimit)) + return false; + if (packet.tids && packet.processBufferSizeLimit) { + path.report("processBufferSizeLimit must be provided"); + return false; + } + if (!packet.tids && !packet.processBufferSizeLimit) { + path.report("processBufferSizeLimit must not be provided"); + return false; + } + return true; + } + + json::Value toJSON(const TraceIntelPTStartRequest &packet) { + json::Value base = toJSON((const TraceStartRequest &)packet); + base.getAsObject()->try_emplace("threadBufferSize", packet.threadBufferSize); + base.getAsObject()->try_emplace("processBufferSizeLimit", + packet.processBufferSizeLimit); + base.getAsObject()->try_emplace("psbPeriod", packet.psbPeriod); + base.getAsObject()->try_emplace("enableTsc", packet.enableTsc); + return base; + } + ++std::chrono::nanoseconds ++LinuxPerfZeroTscConversion::Convert(uint64_t raw_counter_value) { ++ uint64_t quot = raw_counter_value >> m_time_shift; ++ uint64_t rem_flag = (((uint64_t)1 << m_time_shift) - 1); ++ uint64_t rem = raw_counter_value & rem_flag; ++ return std::chrono::nanoseconds{m_time_zero + quot * m_time_mult + ++ ((rem * m_time_mult) >> m_time_shift)}; ++} ++ ++json::Value LinuxPerfZeroTscConversion::toJSON() { ++ return json::Value(json::Object{ ++ {"kind", "tsc-perf-zero-conversion"}, ++ {"time_mult", static_cast(m_time_mult)}, ++ {"time_shift", static_cast(m_time_shift)}, ++ {"time_zero", static_cast(m_time_zero)}, ++ }); ++} ++ ++bool fromJSON(const json::Value &value, TraceTscConversionUP &tsc_conversion, ++ json::Path path) { ++ ObjectMapper o(value, path); ++ ++ int64_t time_mult, time_shift, time_zero; ++ if (!o || !o.map("time_mult", time_mult) || ++ !o.map("time_shift", time_shift) || !o.map("time_zero", time_zero)) ++ return false; ++ ++ tsc_conversion = ++ std::make_unique(LinuxPerfZeroTscConversion{ ++ static_cast(time_mult), static_cast(time_shift), ++ static_cast(time_zero)}); ++ ++ return true; ++} ++ ++bool fromJSON(const json::Value &value, TraceIntelPTGetStateResponse &packet, ++ json::Path path) { ++ ObjectMapper o(value, path); ++ if (!o || !fromJSON(value, (TraceGetStateResponse &)packet, path)) ++ return false; ++ ++ const Object &obj = *(value.getAsObject()); ++ if (const json::Value *counters = obj.get("counters")) { ++ json::Path subpath = path.field("counters"); ++ ObjectMapper ocounters(*counters, subpath); ++ if (!ocounters || !ocounters.mapOptional("tsc-perf-zero-conversion", ++ packet.tsc_conversion)) ++ return false; ++ } ++ return true; ++} ++ ++json::Value toJSON(const TraceIntelPTGetStateResponse &packet) { ++ json::Value base = toJSON((const TraceGetStateResponse &)packet); ++ ++ if (packet.tsc_conversion) { ++ std::vector counters{}; ++ base.getAsObject()->try_emplace( ++ "counters", json::Object{{"tsc-perf-zero-conversion", ++ packet.tsc_conversion->toJSON()}}); ++ } ++ ++ return base; ++} ++ + } // namespace lldb_private +diff --git a/lldb/unittests/Process/Linux/PerfTests.cpp b/lldb/unittests/Process/Linux/PerfTests.cpp +index 934d680e8c38..084b857b908f 100644 +--- a/lldb/unittests/Process/Linux/PerfTests.cpp ++++ b/lldb/unittests/Process/Linux/PerfTests.cpp +@@ -1,89 +1,88 @@ + //===-- PerfTests.cpp -----------------------------------------------------===// + // + // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. + // See https://llvm.org/LICENSE.txt for license information. + // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + // + //===----------------------------------------------------------------------===// + + #ifdef __x86_64__ + + #include "Perf.h" + + #include "llvm/Support/Error.h" + + #include "gtest/gtest.h" + #include + #include + + using namespace lldb_private; + using namespace process_linux; + using namespace llvm; + + /// Helper function to read current TSC value. + /// + /// This code is based on llvm/xray. + static Expected readTsc() { + + unsigned int eax, ebx, ecx, edx; + + // We check whether rdtscp support is enabled. According to the x86_64 manual, + // level should be set at 0x80000001, and we should have a look at bit 27 in + // EDX. That's 0x8000000 (or 1u << 27). + __asm__ __volatile__("cpuid" + : "=a"(eax), "=b"(ebx), "=c"(ecx), "=d"(edx) + : "0"(0x80000001)); + if (!(edx & (1u << 27))) { + return createStringError(inconvertibleErrorCode(), + "Missing rdtscp support."); + } + + unsigned cpu; + unsigned long rax, rdx; + + __asm__ __volatile__("rdtscp\n" : "=a"(rax), "=d"(rdx), "=c"(cpu)::); + + return (rdx << 32) + rax; + } + + // Test TSC to walltime conversion based on perf conversion values. + TEST(Perf, TscConversion) { + // This test works by first reading the TSC value directly before + // and after sleeping, then converting these values to nanoseconds, and + // finally ensuring the difference is approximately equal to the sleep time. + // + // There will be slight overhead associated with the sleep call, so it isn't + // reasonable to expect the difference to be exactly equal to the sleep time. + + const int SLEEP_SECS = 1; + std::chrono::nanoseconds SLEEP_NANOS{std::chrono::seconds(SLEEP_SECS)}; + +- Expected params = +- FetchPerfTscConversionParameters(); ++ Expected params = ++ LoadPerfTscConversionParameters(); + + // Skip the test if the conversion parameters aren't available. + if (!params) + GTEST_SKIP() << toString(params.takeError()); + + Expected tsc_before_sleep = readTsc(); + sleep(SLEEP_SECS); + Expected tsc_after_sleep = readTsc(); + + // Skip the test if we are unable to read the TSC value. + if (!tsc_before_sleep) + GTEST_SKIP() << toString(tsc_before_sleep.takeError()); + if (!tsc_after_sleep) + GTEST_SKIP() << toString(tsc_after_sleep.takeError()); + + std::chrono::nanoseconds converted_tsc_diff = +- params->ToWallTime(*tsc_after_sleep) - +- params->ToWallTime(*tsc_before_sleep); ++ params->Convert(*tsc_after_sleep) - params->Convert(*tsc_before_sleep); + + std::chrono::microseconds acceptable_overhead(500); + + ASSERT_GE(converted_tsc_diff.count(), SLEEP_NANOS.count()); + ASSERT_LT(converted_tsc_diff.count(), + (SLEEP_NANOS + acceptable_overhead).count()); + } + + #endif // __x86_64__ +diff --git a/lldb/unittests/Utility/CMakeLists.txt b/lldb/unittests/Utility/CMakeLists.txt +index 90e97f786a74..9a76b8a83099 100644 +--- a/lldb/unittests/Utility/CMakeLists.txt ++++ b/lldb/unittests/Utility/CMakeLists.txt +@@ -1,58 +1,59 @@ + add_lldb_unittest(UtilityTests + AnsiTerminalTest.cpp + ArgsTest.cpp + OptionsWithRawTest.cpp + ArchSpecTest.cpp + BroadcasterTest.cpp + ConstStringTest.cpp + CompletionRequestTest.cpp + DataEncoderTest.cpp + DataExtractorTest.cpp + EnvironmentTest.cpp + EventTest.cpp + FileSpecTest.cpp + FlagsTest.cpp + ListenerTest.cpp + LogTest.cpp + NameMatchesTest.cpp + PredicateTest.cpp + ProcessInfoTest.cpp + ProcessInstanceInfoTest.cpp + RangeMapTest.cpp + RangeTest.cpp + RegisterValueTest.cpp + RegularExpressionTest.cpp + ReproducerTest.cpp + ScalarTest.cpp + SharedClusterTest.cpp + StateTest.cpp + StatusTest.cpp + StreamTeeTest.cpp + StreamTest.cpp + StringExtractorGDBRemoteTest.cpp + StringExtractorTest.cpp + StringLexerTest.cpp + StringListTest.cpp + StructuredDataTest.cpp + SubsystemRAIITest.cpp + TildeExpressionResolverTest.cpp + TimeoutTest.cpp + TimerTest.cpp ++ TraceGDBRemotePacketsTest.cpp + UriParserTest.cpp + UserIDResolverTest.cpp + UUIDTest.cpp + VASprintfTest.cpp + VMRangeTest.cpp + XcodeSDKTest.cpp + + LINK_LIBS + lldbUtility + lldbUtilityHelpers + LLVMTestingSupport + LINK_COMPONENTS + Support + ) + + add_unittest_inputs(UtilityTests + StructuredData-basic.json + ) +diff --git a/lldb/unittests/Utility/TraceGDBRemotePacketsTest.cpp b/lldb/unittests/Utility/TraceGDBRemotePacketsTest.cpp +new file mode 100644 +index 000000000000..ff9eeddf34fa +--- /dev/null ++++ b/lldb/unittests/Utility/TraceGDBRemotePacketsTest.cpp +@@ -0,0 +1,71 @@ ++//===-- TraceGDBRemotePacketsTest.cpp -------------------------------------===// ++// ++// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. ++// See https://llvm.org/LICENSE.txt for license information. ++// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception ++// ++//===----------------------------------------------------------------------===// ++ ++#include "lldb/Utility/TraceIntelPTGDBRemotePackets.h" ++ ++#include "gtest/gtest.h" ++ ++#include ++ ++using namespace lldb_private; ++using namespace llvm; ++ ++// Test serialization and deserialization of TraceIntelPTGetStateResponse. ++TEST(TraceGDBRemotePacketsTest, IntelPTGetStateResponseTest) { ++ // This test works as follows: ++ // 1. Create TraceIntelPTGetStateResponse ++ // 2. Serialize to JSON ++ // 3. Deserialize the serialized JSON value ++ // 4. Ensure the original value and the deserialized value are equivalent ++ // ++ // Notes: ++ // - We intentionally set an integer value out of its signed range ++ // to ensure the serialization/deserialization isn't lossy since JSON ++ // operates on signed values ++ ++ // Choose arbitrary values for time_mult and time_shift ++ uint32_t test_time_mult = 1076264588; ++ uint16_t test_time_shift = 31; ++ // Intentionally set time_zero value out of the signed type's range. ++ uint64_t test_time_zero = ++ static_cast(std::numeric_limits::max()) + 1; ++ ++ // 1. Create TraceIntelPTGetStateResponse ++ TraceIntelPTGetStateResponse response; ++ response.tsc_conversion = ++ std::make_unique(LinuxPerfZeroTscConversion( ++ test_time_mult, test_time_shift, test_time_zero)); ++ ++ // 2. Serialize ++ json::Value value = lldb_private::toJSON(response); ++ std::string json_string; ++ raw_string_ostream os(json_string); ++ os << value; ++ std::cerr << json_string << std::endl; ++ ++ // 3. Deserialize ++ Expected deserialized_response = ++ json::parse(json_string, ++ "TraceIntelPTGetStateResponse"); ++ if (!deserialized_response) ++ FAIL() << toString(deserialized_response.takeError()); ++ ++ // Choose arbitrary TSC value to test the Convert function. ++ const uint64_t TSC = std::numeric_limits::max(); ++ uint64_t pre_serialization_conversion = ++ response.tsc_conversion->Convert(TSC).count(); ++ uint64_t post_serialization_conversion = ++ deserialized_response->tsc_conversion->Convert(TSC).count(); ++ ++ // 4. Check equality ++ // Ensure that both the TraceGetStateResponse and TraceIntelPTGetStateResponse ++ // portions of the JSON representation are unchanged. ++ ASSERT_EQ(toJSON(response), toJSON(*deserialized_response)); ++ // Ensure the result of the Convert function is unchanged. ++ ASSERT_EQ(pre_serialization_conversion, post_serialization_conversion); ++}