diff --git a/lldb/docs/lldb-gdb-remote.txt b/lldb/docs/lldb-gdb-remote.txt --- a/lldb/docs/lldb-gdb-remote.txt +++ b/lldb/docs/lldb-gdb-remote.txt @@ -309,6 +309,38 @@ // Trace buffer size per thread in bytes. It must be a power of 2 // greater than or equal to 4096 (2^12) bytes. // +// "tsc": , +// 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. @@ -871,7 +903,7 @@ 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 + 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 @@ -1185,8 +1217,8 @@ // // If the stub supports identifying dirty pages within a // memory region, this key should always be present for all - // qMemoryRegionInfo replies. This key with no pages - // listed ("dirty-pages:;") indicates no dirty pages in + // qMemoryRegionInfo replies. This key with no pages + // listed ("dirty-pages:;") indicates no dirty pages in // this memory region. The *absence* of this key means // that this stub cannot determine dirty pages. diff --git a/lldb/include/lldb/Target/TraceCursor.h b/lldb/include/lldb/Target/TraceCursor.h --- a/lldb/include/lldb/Target/TraceCursor.h +++ b/lldb/include/lldb/Target/TraceCursor.h @@ -12,6 +12,7 @@ #include "lldb/lldb-private.h" #include "lldb/Target/ExecutionContext.h" +#include namespace lldb_private { @@ -180,6 +181,15 @@ /// LLDB_INVALID_ADDRESS. virtual lldb::addr_t GetLoadAddress() = 0; + /// Get the timestamp counter associated with the current instruction. + /// Modern Intel, ARM and AMD processors support this counter. However, a + /// trace plugin might decide to use a different time unit instead of an + /// actual TSC. + /// + /// \return + /// The timestamp or \b llvm::None if not available. + virtual llvm::Optional GetTimestampCounter() = 0; + /// \return /// The \a lldb::TraceInstructionControlFlowType categories the /// instruction the cursor is pointing at falls into. If the cursor points diff --git a/lldb/include/lldb/Target/TraceInstructionDumper.h b/lldb/include/lldb/Target/TraceInstructionDumper.h --- a/lldb/include/lldb/Target/TraceInstructionDumper.h +++ b/lldb/include/lldb/Target/TraceInstructionDumper.h @@ -31,7 +31,7 @@ /// Dump only instruction addresses without disassembly nor symbol /// information. TraceInstructionDumper(lldb::TraceCursorUP &&cursor_up, int initial_index = 0, - bool raw = false); + bool raw = false, bool show_tsc = false); /// Dump \a count instructions of the thread trace starting at the current /// cursor position. @@ -63,6 +63,7 @@ lldb::TraceCursorUP m_cursor_up; int m_index; bool m_raw; + bool m_show_tsc; /// If \b true, all the instructions have been traversed. bool m_no_more_data = false; }; diff --git a/lldb/include/lldb/Utility/TraceIntelPTGDBRemotePackets.h b/lldb/include/lldb/Utility/TraceIntelPTGDBRemotePackets.h --- a/lldb/include/lldb/Utility/TraceIntelPTGDBRemotePackets.h +++ b/lldb/include/lldb/Utility/TraceIntelPTGDBRemotePackets.h @@ -19,6 +19,13 @@ struct TraceIntelPTStartRequest : TraceStartRequest { /// Size in bytes to use for each thread's trace buffer. int64_t threadBufferSize; + + /// Whether to enable TSC + bool tsc; + + /// PSB packet period + llvm::Optional psb_period; + /// Required when doing "process tracing". /// /// Limit in bytes on all the thread traces started by this "process trace" diff --git a/lldb/source/Commands/CommandObjectThread.cpp b/lldb/source/Commands/CommandObjectThread.cpp --- a/lldb/source/Commands/CommandObjectThread.cpp +++ b/lldb/source/Commands/CommandObjectThread.cpp @@ -2024,6 +2024,10 @@ m_forwards = true; break; } + case 't': { + m_show_tsc = true; + break; + } default: llvm_unreachable("Unimplemented option"); } @@ -2035,6 +2039,7 @@ m_skip = 0; m_raw = false; m_forwards = false; + m_show_tsc = false; } llvm::ArrayRef GetDefinitions() override { @@ -2048,6 +2053,7 @@ size_t m_skip; bool m_raw; bool m_forwards; + bool m_show_tsc; }; CommandObjectTraceDumpInstructions(CommandInterpreter &interpreter) @@ -2109,7 +2115,8 @@ int initial_index = setUpCursor(); auto dumper = std::make_unique( - std::move(cursor_up), initial_index, m_options.m_raw); + std::move(cursor_up), initial_index, m_options.m_raw, + m_options.m_show_tsc); // This happens when the seek value was more than the number of available // instructions. diff --git a/lldb/source/Commands/Options.td b/lldb/source/Commands/Options.td --- a/lldb/source/Commands/Options.td +++ b/lldb/source/Commands/Options.td @@ -1065,6 +1065,9 @@ def thread_trace_dump_instructions_raw : Option<"raw", "r">, Group<1>, Desc<"Dump only instruction address without disassembly nor symbol information.">; + def thread_trace_dump_instructions_show_tsc : Option<"tsc", "t">, + Group<1>, + Desc<"For each instruction, print the corresponding timestamp counter if available.">; } let Command = "type summary add" in { diff --git a/lldb/source/Plugins/Process/Linux/IntelPTManager.h b/lldb/source/Plugins/Process/Linux/IntelPTManager.h --- a/lldb/source/Plugins/Process/Linux/IntelPTManager.h +++ b/lldb/source/Plugins/Process/Linux/IntelPTManager.h @@ -70,11 +70,19 @@ /// \param[in] buffer_size /// Size of the thread buffer in bytes. /// + /// \param[in] 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 llvm::Error::success if tracing was successful, or an /// \a llvm::Error otherwise. - llvm::Error StartTrace(lldb::pid_t pid, lldb::tid_t tid, - uint64_t buffer_size); + llvm::Error StartTrace(lldb::pid_t pid, lldb::tid_t tid, uint64_t buffer_size, + bool tsc, llvm::Optional psb_period); llvm::MutableArrayRef GetAuxBuffer() const; llvm::MutableArrayRef GetDataBuffer() const; @@ -95,7 +103,8 @@ /// 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); + Create(lldb::pid_t pid, lldb::tid_t tid, size_t buffer_size, bool tsc, + llvm::Optional psb_period); /// Read the trace buffer of the currently traced thread. /// diff --git a/lldb/source/Plugins/Process/Linux/IntelPTManager.cpp b/lldb/source/Plugins/Process/Linux/IntelPTManager.cpp --- a/lldb/source/Plugins/Process/Linux/IntelPTManager.cpp +++ b/lldb/source/Plugins/Process/Linux/IntelPTManager.cpp @@ -8,6 +8,7 @@ #include #include +#include #include "llvm/ADT/StringRef.h" #include "llvm/Support/Error.h" @@ -29,32 +30,149 @@ 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 +}; + +static Expected ReadIntelPTConfigFile(const char *file, + IntelPTConfigFileType type) { + auto stream = llvm::MemoryBuffer::getFileAsStream(file); + + if (!stream) + return createStringError(inconvertibleErrorCode(), + "Can't open the file '%s'", file); + + uint32_t value = 0; + StringRef buffer = stream.get()->getBuffer(); + + if (type == BitOffset) { + const char *prefix = "config:"; + if (!buffer.startswith(prefix)) + return createStringError(inconvertibleErrorCode(), + "The file '%s' contents doesn't start with '%s'", + file, prefix); + buffer = buffer.substr(strlen(prefix)); + } + + auto getRadix = [&]() { + switch (type) { + case Hex: + return 16; + case ZeroOne: + case Decimal: + case BitOffset: + return 10; + } + }; + + if (buffer.trim().consumeInteger(getRadix(), value)) { + std::ostringstream error; + error << "The file '" << file << "' has an invalid value. It should be "; + + switch (type) { + case Hex: + error << "an unsigned hexadecimal int."; + break; + case ZeroOne: + error << "0 or 1."; + break; + case Decimal: + case BitOffset: + error << "an unsigned decimal int."; + break; + } + return createStringError(inconvertibleErrorCode(), error.str().c_str()); + } + return value; +} /// Return the Linux perf event type for Intel PT. static Expected GetOSEventType() { - auto intel_pt_type_text = - llvm::MemoryBuffer::getFileAsStream(kOSEventIntelPTTypeFile); + return ReadIntelPTConfigFile(kOSEventIntelPTTypeFile, + IntelPTConfigFileType::Decimal); +} - if (!intel_pt_type_text) +static Error CheckPsbPeriod(size_t psb_period) { + Expected cap = + ReadIntelPTConfigFile(kPSBPeriodCapFile, IntelPTConfigFileType::ZeroOne); + if (!cap) + return cap.takeError(); + if (*cap == 0) return createStringError(inconvertibleErrorCode(), - "Can't open the file '%s'", - kOSEventIntelPTTypeFile); + "psb_period is unsupported in the system."); - uint32_t intel_pt_type = 0; - StringRef buffer = intel_pt_type_text.get()->getBuffer(); - if (buffer.trim().getAsInteger(10, intel_pt_type)) - return createStringError( - inconvertibleErrorCode(), - "The file '%s' has a invalid value. It should be an unsigned int.", - kOSEventIntelPTTypeFile); - return intel_pt_type; + 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 { return m_mmap_meta->aux_size; } +static Expected +GeneratePerfEventConfigValue(bool tsc, Optional psb_period) { + uint64_t config = 0; + // tsc is always supported + if (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; +} + Error IntelPTThreadTrace::StartTrace(lldb::pid_t pid, lldb::tid_t tid, - uint64_t buffer_size) { + uint64_t buffer_size, bool tsc, + Optional psb_period) { #ifndef PERF_ATTR_SIZE_VER5 llvm_unreachable("Intel PT Linux perf event not supported"); #else @@ -85,15 +203,21 @@ attr.exclude_hv = 1; attr.exclude_idle = 1; attr.mmap = 1; - attr.config = 0; - Expected intel_pt_type = GetOSEventType(); + if (Expected config_value = + GeneratePerfEventConfigValue(tsc, psb_period)) { + attr.config = *config_value; + LLDB_LOG(log, "intel pt config {0}", attr.config); + } else { + return config_value.takeError(); + } - if (!intel_pt_type) + if (Expected intel_pt_type = GetOSEventType()) { + attr.type = *intel_pt_type; + LLDB_LOG(log, "intel pt type {0}", attr.type); + } else { return intel_pt_type.takeError(); - - LLDB_LOG(log, "intel pt type {0}", *intel_pt_type); - attr.type = *intel_pt_type; + } LLDB_LOG(log, "buffer size {0} ", buffer_size); @@ -174,11 +298,12 @@ } llvm::Expected -IntelPTThreadTrace::Create(lldb::pid_t pid, lldb::tid_t tid, - size_t buffer_size) { +IntelPTThreadTrace::Create(lldb::pid_t pid, lldb::tid_t tid, size_t buffer_size, + bool tsc, Optional psb_period) { IntelPTThreadTraceUP thread_trace_up(new IntelPTThreadTrace()); - if (llvm::Error err = thread_trace_up->StartTrace(pid, tid, buffer_size)) + if (llvm::Error err = + thread_trace_up->StartTrace(pid, tid, buffer_size, tsc, psb_period)) return std::move(err); return std::move(thread_trace_up); @@ -368,8 +493,9 @@ return createStringError(inconvertibleErrorCode(), "Thread %" PRIu64 " already traced", tid); - Expected trace_up = - IntelPTThreadTrace::Create(m_pid, tid, request.threadBufferSize); + Expected trace_up = IntelPTThreadTrace::Create( + m_pid, tid, request.threadBufferSize, request.tsc, + request.psb_period.map([](int64_t period) { return (size_t)period; })); if (!trace_up) return trace_up.takeError(); diff --git a/lldb/source/Plugins/Trace/intel-pt/CommandObjectTraceStartIntelPT.h b/lldb/source/Plugins/Trace/intel-pt/CommandObjectTraceStartIntelPT.h --- a/lldb/source/Plugins/Trace/intel-pt/CommandObjectTraceStartIntelPT.h +++ b/lldb/source/Plugins/Trace/intel-pt/CommandObjectTraceStartIntelPT.h @@ -32,6 +32,8 @@ llvm::ArrayRef GetDefinitions() override; size_t m_thread_buffer_size; + bool m_tsc; + llvm::Optional m_psb_period; }; CommandObjectThreadTraceStartIntelPT(TraceIntelPT &trace, @@ -74,6 +76,8 @@ size_t m_thread_buffer_size; size_t m_process_buffer_size_limit; + bool m_tsc; + llvm::Optional m_psb_period; }; CommandObjectProcessTraceStartIntelPT(TraceIntelPT &trace, diff --git a/lldb/source/Plugins/Trace/intel-pt/CommandObjectTraceStartIntelPT.cpp b/lldb/source/Plugins/Trace/intel-pt/CommandObjectTraceStartIntelPT.cpp --- a/lldb/source/Plugins/Trace/intel-pt/CommandObjectTraceStartIntelPT.cpp +++ b/lldb/source/Plugins/Trace/intel-pt/CommandObjectTraceStartIntelPT.cpp @@ -41,6 +41,20 @@ m_thread_buffer_size = thread_buffer_size; break; } + case 't': { + m_tsc = true; + break; + } + case 'p': { + int64_t psb_period; + if (option_arg.empty() || option_arg.getAsInteger(0, psb_period) || + psb_period < 0) + error.SetErrorStringWithFormat("invalid integer value for option '%s'", + option_arg.str().c_str()); + else + m_psb_period = psb_period; + break; + } default: llvm_unreachable("Unimplemented option"); } @@ -50,6 +64,8 @@ void CommandObjectThreadTraceStartIntelPT::CommandOptions:: OptionParsingStarting(ExecutionContext *execution_context) { m_thread_buffer_size = kThreadBufferSize; + m_tsc = kTSC; + m_psb_period = kPSBPeriod; } llvm::ArrayRef @@ -60,7 +76,8 @@ bool CommandObjectThreadTraceStartIntelPT::DoExecuteOnThreads( Args &command, CommandReturnObject &result, llvm::ArrayRef tids) { - if (Error err = m_trace.Start(tids, m_options.m_thread_buffer_size)) + if (Error err = m_trace.Start(tids, m_options.m_thread_buffer_size, + m_options.m_tsc, m_options.m_psb_period)) result.SetError(Status(std::move(err))); else result.SetStatus(eReturnStatusSuccessFinishResult); @@ -101,6 +118,20 @@ m_process_buffer_size_limit = process_buffer_size_limit; break; } + case 't': { + m_tsc = true; + break; + } + case 'p': { + int64_t psb_period; + if (option_arg.empty() || option_arg.getAsInteger(0, psb_period) || + psb_period < 0) + error.SetErrorStringWithFormat("invalid integer value for option '%s'", + option_arg.str().c_str()); + else + m_psb_period = psb_period; + break; + } default: llvm_unreachable("Unimplemented option"); } @@ -111,6 +142,8 @@ OptionParsingStarting(ExecutionContext *execution_context) { m_thread_buffer_size = kThreadBufferSize; m_process_buffer_size_limit = kProcessBufferSizeLimit; + m_tsc = kTSC; + m_psb_period = kPSBPeriod; } llvm::ArrayRef @@ -121,7 +154,8 @@ bool CommandObjectProcessTraceStartIntelPT::DoExecute( Args &command, CommandReturnObject &result) { if (Error err = m_trace.Start(m_options.m_thread_buffer_size, - m_options.m_process_buffer_size_limit)) + m_options.m_process_buffer_size_limit, + m_options.m_tsc, m_options.m_psb_period)) result.SetError(Status(std::move(err))); else result.SetStatus(eReturnStatusSuccessFinishResult); diff --git a/lldb/source/Plugins/Trace/intel-pt/DecodedThread.h b/lldb/source/Plugins/Trace/intel-pt/DecodedThread.h --- a/lldb/source/Plugins/Trace/intel-pt/DecodedThread.h +++ b/lldb/source/Plugins/Trace/intel-pt/DecodedThread.h @@ -9,6 +9,7 @@ #ifndef LLDB_SOURCE_PLUGINS_TRACE_INTEL_PT_DECODEDTHREAD_H #define LLDB_SOURCE_PLUGINS_TRACE_INTEL_PT_DECODEDTHREAD_H +#include #include #include "llvm/Support/Errc.h" @@ -61,6 +62,9 @@ /// As mentioned, any gap is represented as an error in this class. class IntelPTInstruction { public: + IntelPTInstruction(const pt_insn &pt_insn, uint64_t timestamp) + : m_pt_insn(pt_insn), m_timestamp(timestamp) {} + IntelPTInstruction(const pt_insn &pt_insn) : m_pt_insn(pt_insn) {} /// Error constructor @@ -84,6 +88,13 @@ /// \a llvm::Error::success otherwise. llvm::Error ToError() const; + /// Get the timestamp associated with the current instruction. The timestamp + /// is similar to what a rdtsc instruction would return. + /// + /// \return + /// The timestamp or \b llvm::None if not available. + llvm::Optional GetTimestampCounter() const; + /// Get the \a lldb::TraceInstructionControlFlowType categories of the /// instruction. /// @@ -103,6 +114,7 @@ const IntelPTInstruction &operator=(const IntelPTInstruction &other) = delete; pt_insn m_pt_insn; + llvm::Optional m_timestamp; std::unique_ptr m_error; }; diff --git a/lldb/source/Plugins/Trace/intel-pt/DecodedThread.cpp b/lldb/source/Plugins/Trace/intel-pt/DecodedThread.cpp --- a/lldb/source/Plugins/Trace/intel-pt/DecodedThread.cpp +++ b/lldb/source/Plugins/Trace/intel-pt/DecodedThread.cpp @@ -48,6 +48,10 @@ lldb::addr_t IntelPTInstruction::GetLoadAddress() const { return m_pt_insn.ip; } +Optional IntelPTInstruction::GetTimestampCounter() const { + return m_timestamp; +} + Error IntelPTInstruction::ToError() const { if (!IsError()) return Error::success(); diff --git a/lldb/source/Plugins/Trace/intel-pt/IntelPTDecoder.cpp b/lldb/source/Plugins/Trace/intel-pt/IntelPTDecoder.cpp --- a/lldb/source/Plugins/Trace/intel-pt/IntelPTDecoder.cpp +++ b/lldb/source/Plugins/Trace/intel-pt/IntelPTDecoder.cpp @@ -9,6 +9,7 @@ #include "llvm/Support/MemoryBuffer.h" +#include "DecodedThread.h" #include "TraceIntelPT.h" #include "lldb/Core/Module.h" #include "lldb/Core/Section.h" @@ -136,7 +137,23 @@ break; } - instructions.emplace_back(insn); + uint64_t time; + int time_error = pt_insn_time(&decoder, &time, nullptr, nullptr); + if (time_error == -pte_invalid) { + // This happens if we invoke the pt_insn_time method incorrectly, + // but the instruction is good though. + instructions.emplace_back( + make_error(time_error, insn.ip)); + instructions.emplace_back(insn); + break; + } + if (time_error == -pte_no_time) { + // We simply don't have time information, i.e. None of TSC, MTC or CYC + // was enabled. + instructions.emplace_back(insn); + } else { + instructions.emplace_back(insn, time); + } } } diff --git a/lldb/source/Plugins/Trace/intel-pt/TraceCursorIntelPT.h b/lldb/source/Plugins/Trace/intel-pt/TraceCursorIntelPT.h --- a/lldb/source/Plugins/Trace/intel-pt/TraceCursorIntelPT.h +++ b/lldb/source/Plugins/Trace/intel-pt/TraceCursorIntelPT.h @@ -28,6 +28,8 @@ lldb::addr_t GetLoadAddress() override; + llvm::Optional GetTimestampCounter() override; + lldb::TraceInstructionControlFlowType GetInstructionControlFlowType() override; diff --git a/lldb/source/Plugins/Trace/intel-pt/TraceCursorIntelPT.cpp b/lldb/source/Plugins/Trace/intel-pt/TraceCursorIntelPT.cpp --- a/lldb/source/Plugins/Trace/intel-pt/TraceCursorIntelPT.cpp +++ b/lldb/source/Plugins/Trace/intel-pt/TraceCursorIntelPT.cpp @@ -85,6 +85,10 @@ return m_decoded_thread_sp->GetInstructions()[m_pos].GetLoadAddress(); } +Optional TraceCursorIntelPT::GetTimestampCounter() { + return m_decoded_thread_sp->GetInstructions()[m_pos].GetTimestampCounter(); +} + TraceInstructionControlFlowType TraceCursorIntelPT::GetInstructionControlFlowType() { lldb::addr_t next_load_address = diff --git a/lldb/source/Plugins/Trace/intel-pt/TraceIntelPT.h b/lldb/source/Plugins/Trace/intel-pt/TraceIntelPT.h --- a/lldb/source/Plugins/Trace/intel-pt/TraceIntelPT.h +++ b/lldb/source/Plugins/Trace/intel-pt/TraceIntelPT.h @@ -80,18 +80,23 @@ /// Trace size per thread in bytes. /// /// \param[in] total_buffer_size_limit - /// Maximum total trace size per process in bytes. This limit applies to - /// the sum of the sizes of all thread traces of this process, excluding - /// the threads traced explicitly. + /// Maximum total trace size per process in bytes. + /// More information in TraceIntelPT::GetStartConfigurationHelp(). /// - /// Whenever a thread is attempted to be traced due to this operation and - /// the limit would be reached, the process is stopped with a "tracing" - /// reason, so that the user can retrace the process if needed. + /// \param[in] 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 llvm::Error::success if the operation was successful, or /// \a llvm::Error otherwise. - llvm::Error Start(size_t thread_buffer_size, size_t total_buffer_size_limit); + llvm::Error Start(size_t thread_buffer_size, size_t total_buffer_size_limit, + bool tsc, llvm::Optional psb_period); /// \copydoc Trace::Start llvm::Error Start(StructuredData::ObjectSP configuration = @@ -105,11 +110,20 @@ /// \param[in] thread_buffer_size /// Trace size per thread in bytes. /// + /// \param[in] 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 llvm::Error::success if the operation was successful, or /// \a llvm::Error otherwise. - llvm::Error Start(llvm::ArrayRef tids, - size_t thread_buffer_size); + llvm::Error Start(llvm::ArrayRef tids, size_t thread_buffer_size, + bool tsc, llvm::Optional psb_period); /// \copydoc Trace::Start llvm::Error Start(llvm::ArrayRef tids, diff --git a/lldb/source/Plugins/Trace/intel-pt/TraceIntelPT.cpp b/lldb/source/Plugins/Trace/intel-pt/TraceIntelPT.cpp --- a/lldb/source/Plugins/Trace/intel-pt/TraceIntelPT.cpp +++ b/lldb/source/Plugins/Trace/intel-pt/TraceIntelPT.cpp @@ -203,6 +203,38 @@ than or equal to 4096 (2^12). The trace is circular keeping the the most recent data. + - boolean tsc (default to false): + [process and thread tracing] + Whether to use enable TSC timestamps or not. This is supported on + all devices that support intel-pt. + + - psb_period (defaults to null): + [process and thread tracing] + 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. + - int processBufferSizeLimit (defaults to 500 MB): [process tracing only] Maximum total trace size per process in bytes. This limit applies @@ -215,10 +247,13 @@ } Error TraceIntelPT::Start(size_t thread_buffer_size, - size_t total_buffer_size_limit) { + size_t total_buffer_size_limit, bool tsc, + Optional psb_period) { TraceIntelPTStartRequest request; request.threadBufferSize = thread_buffer_size; request.processBufferSizeLimit = total_buffer_size_limit; + request.tsc = tsc; + request.psb_period = psb_period.map([](size_t val) { return (int64_t)val; }); request.type = GetPluginName().AsCString(); return Trace::Start(toJSON(request)); } @@ -226,25 +261,32 @@ Error TraceIntelPT::Start(StructuredData::ObjectSP configuration) { size_t thread_buffer_size = kThreadBufferSize; size_t process_buffer_size_limit = kProcessBufferSizeLimit; + bool tsc = kTSC; + Optional psb_period = kPSBPeriod; if (configuration) { if (StructuredData::Dictionary *dict = configuration->GetAsDictionary()) { dict->GetValueForKeyAsInteger("threadBufferSize", thread_buffer_size); dict->GetValueForKeyAsInteger("processBufferSizeLimit", process_buffer_size_limit); + dict->GetValueForKeyAsBoolean("tsc", tsc); + dict->GetValueForKeyAsInteger("psb_period", psb_period); } else { return createStringError(inconvertibleErrorCode(), "configuration object is not a dictionary"); } } - return Start(thread_buffer_size, process_buffer_size_limit); + return Start(thread_buffer_size, process_buffer_size_limit, tsc, psb_period); } llvm::Error TraceIntelPT::Start(llvm::ArrayRef tids, - size_t thread_buffer_size) { + size_t thread_buffer_size, bool tsc, + Optional psb_period) { TraceIntelPTStartRequest request; request.threadBufferSize = thread_buffer_size; + request.tsc = tsc; + request.psb_period = psb_period.map([](size_t val) { return (int64_t)val; }); request.type = GetPluginName().AsCString(); request.tids.emplace(); for (lldb::tid_t tid : tids) @@ -255,17 +297,21 @@ Error TraceIntelPT::Start(llvm::ArrayRef tids, StructuredData::ObjectSP configuration) { size_t thread_buffer_size = kThreadBufferSize; + bool tsc = kTSC; + Optional psb_period = kPSBPeriod; if (configuration) { if (StructuredData::Dictionary *dict = configuration->GetAsDictionary()) { dict->GetValueForKeyAsInteger("threadBufferSize", thread_buffer_size); + dict->GetValueForKeyAsBoolean("tsc", tsc); + dict->GetValueForKeyAsInteger("psb_period", psb_period); } else { return createStringError(inconvertibleErrorCode(), "configuration object is not a dictionary"); } } - return Start(tids, thread_buffer_size); + return Start(tids, thread_buffer_size, tsc, psb_period); } Expected> diff --git a/lldb/source/Plugins/Trace/intel-pt/TraceIntelPTConstants.h b/lldb/source/Plugins/Trace/intel-pt/TraceIntelPTConstants.h --- a/lldb/source/Plugins/Trace/intel-pt/TraceIntelPTConstants.h +++ b/lldb/source/Plugins/Trace/intel-pt/TraceIntelPTConstants.h @@ -11,11 +11,15 @@ #include +#include + namespace lldb_private { namespace trace_intel_pt { const size_t kThreadBufferSize = 4 * 1024; // 4KB const size_t kProcessBufferSizeLimit = 5 * 1024 * 1024; // 500MB +const bool kTSC = false; +const llvm::Optional kPSBPeriod = llvm::None; } // namespace trace_intel_pt } // namespace lldb_private diff --git a/lldb/source/Plugins/Trace/intel-pt/TraceIntelPTOptions.td b/lldb/source/Plugins/Trace/intel-pt/TraceIntelPTOptions.td --- a/lldb/source/Plugins/Trace/intel-pt/TraceIntelPTOptions.td +++ b/lldb/source/Plugins/Trace/intel-pt/TraceIntelPTOptions.td @@ -7,6 +7,26 @@ Desc<"Trace size in bytes per thread. It must be a power of 2 greater " "than or equal to 4096 (2^12). The trace is circular keeping " "the most recent data. Defaults to 4096 bytes.">; + def thread_trace_start_intel_pt_tsc: Option<"tsc", "t">, + Group<1>, + Desc<"Enable the use of TSC timestamps. This is supported on all devices " + "that support intel-pt.">; + def thread_trace_start_intel_pt_psb_period: Option<"psb-period", "p">, + Group<1>, + Arg<"Value">, + Desc<"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.">; } let Command = "process trace start intel pt" in { @@ -26,4 +46,24 @@ "the limit would be reached, the process is stopped with a " "\"processor trace\" reason, so that the user can retrace the process " "if needed. Defaults to 500MB.">; + def process_trace_start_intel_pt_tsc: Option<"tsc", "t">, + Group<1>, + Desc<"Enable the use of TSC timestamps. This is supported on all devices " + "that support intel-pt.">; + def process_trace_start_intel_pt_psb_period: Option<"psb-period", "p">, + Group<1>, + Arg<"Value">, + Desc<"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.">; } diff --git a/lldb/source/Target/TraceInstructionDumper.cpp b/lldb/source/Target/TraceInstructionDumper.cpp --- a/lldb/source/Target/TraceInstructionDumper.cpp +++ b/lldb/source/Target/TraceInstructionDumper.cpp @@ -19,8 +19,10 @@ using namespace llvm; TraceInstructionDumper::TraceInstructionDumper(lldb::TraceCursorUP &&cursor_up, - int initial_index, bool raw) - : m_cursor_up(std::move(cursor_up)), m_index(initial_index), m_raw(raw) {} + int initial_index, bool raw, + bool show_tsc) + : m_cursor_up(std::move(cursor_up)), m_index(initial_index), m_raw(raw), + m_show_tsc(show_tsc) {} /// \return /// Return \b true if the cursor could move one step. @@ -177,6 +179,17 @@ auto printInstructionIndex = [&]() { s.Printf(" [%*d] ", digits_count, m_index); + + if (m_show_tsc) { + s.Printf("[tsc="); + + if (Optional timestamp = m_cursor_up->GetTimestampCounter()) + s.Printf("0x%016" PRIx64, *timestamp); + else + s.Printf("unavailable"); + + s.Printf("] "); + } }; InstructionSymbolInfo prev_insn_info; diff --git a/lldb/source/Utility/TraceIntelPTGDBRemotePackets.cpp b/lldb/source/Utility/TraceIntelPTGDBRemotePackets.cpp --- a/lldb/source/Utility/TraceIntelPTGDBRemotePackets.cpp +++ b/lldb/source/Utility/TraceIntelPTGDBRemotePackets.cpp @@ -17,6 +17,7 @@ Path path) { ObjectMapper o(value, path); if (!o || !fromJSON(value, (TraceStartRequest &)packet, path) || + !o.map("tsc", packet.tsc) || !o.map("psbPeriod", packet.psb_period) || !o.map("threadBufferSize", packet.threadBufferSize) || !o.map("processBufferSizeLimit", packet.processBufferSizeLimit)) return false; @@ -36,6 +37,8 @@ base.getAsObject()->try_emplace("threadBufferSize", packet.threadBufferSize); base.getAsObject()->try_emplace("processBufferSizeLimit", packet.processBufferSizeLimit); + base.getAsObject()->try_emplace("psbPeriod", packet.psb_period); + base.getAsObject()->try_emplace("tsc", packet.tsc); return base; }