wallace created this revision. Herald added a subscriber: dang. wallace updated this revision to Diff 359972. wallace added a comment. wallace updated this revision to Diff 359973. wallace retitled this revision from "[intel pt] Add TSC timestamps" to "[trace][intel pt] Add TSC timestamps". wallace edited the summary of this revision. wallace added a reviewer: clayborg. Herald added a subscriber: pengfei. wallace published this revision for review. Herald added a project: LLDB. Herald added a subscriber: lldb-commits.
nits wallace added a comment. nits I've just added the GetTimestampCounter method to Trace.h, which returns an Optional of a timestamp counter. Major CPU architectures support timestamp counters, which fortunately are invariant in modern chips (i.e. they are synchronized between logical CPUs and increase at a constant rate). This helps with having accurate timestamps even when the the thread is jumping around CPUs. A followup diff would be to add a GetRealTime method, which would ask the trace plugin to convert a TSC to any of ms/ns/us or just to return the real time if available in the actual trace. Tools could be built upon any of those two APIs, being GetTimestampCounter way more accurate in terms of keeping relative times. I also implemented the specific bits for Intel PT. Now the "trace start" command accepts two params: --tsc which enables the generation of timestamp counters, and --psb_period, which is the simplest way to tweak how often these timestamps are generated. There are other ways to increase the resolution of timestamps: CYC and MTC packets, but that can be left for a follow up diff to keep this one short. wallace@devbig418 ~/llvm-sand/build/Release/fbcode-x86_64/toolchain ▶ ./bin/lldb ~/a.out (lldb) target create "/home/wallace/a.out" Current executable set to '/home/wallace/a.out' (x86_64). (lldb) b main Breakpoint 1: where = a.out`main + 9 at main.cpp:6:15, address = 0x0000000000400c5f (lldb) r Process 1130363 launched: '/home/wallace/a.out' (x86_64) Process 1130363 stopped * thread #1, name = 'a.out', stop reason = breakpoint 1.1 frame #0: 0x0000000000400c5f a.out`main at main.cpp:6:15 3 using namespace std; 4 5 int main() { -> 6 vector<int> s; 7 s.push_back(1); 8 s.push_back(2); 9 cout << s.size() << endl; (lldb) thread trace start --tsc (lldb) n Process 1130363 stopped * thread #1, name = 'a.out', stop reason = step over frame #0: 0x0000000000400c6b a.out`main at main.cpp:7:15 4 5 int main() { 6 vector<int> s; -> 7 s.push_back(1); 8 s.push_back(2); 9 cout << s.size() << endl; 10 int x = 1; (lldb) thread trace dump instructions --tsc -f thread #1: tid = 1130363 a.out`main + 9 at main.cpp:6:15 [ 0] [tsc=0x005a8eadd08764ba] 0x0000000000400c5f leaq -0x30(%rbp), %rax [ 1] [tsc=0x005a8eadd09356a8] 0x0000000000400c63 movq %rax, %rdi [ 2] [tsc=0x005a8eadd09356a8] 0x0000000000400c66 callq 0x400d9c ; std::vector<int, std::allocator<int> >::vector at stl_vector.h:391:7 ... Repository: rG LLVM Github Monorepo https://reviews.llvm.org/D106328 Files: lldb/docs/lldb-gdb-remote.txt lldb/include/lldb/Target/TraceCursor.h lldb/include/lldb/Target/TraceInstructionDumper.h lldb/include/lldb/Utility/TraceIntelPTGDBRemotePackets.h lldb/source/Commands/CommandObjectThread.cpp lldb/source/Commands/Options.td lldb/source/Plugins/Process/Linux/IntelPTManager.cpp lldb/source/Plugins/Process/Linux/IntelPTManager.h lldb/source/Plugins/Trace/intel-pt/CommandObjectTraceStartIntelPT.cpp lldb/source/Plugins/Trace/intel-pt/CommandObjectTraceStartIntelPT.h lldb/source/Plugins/Trace/intel-pt/DecodedThread.cpp lldb/source/Plugins/Trace/intel-pt/DecodedThread.h lldb/source/Plugins/Trace/intel-pt/IntelPTDecoder.cpp lldb/source/Plugins/Trace/intel-pt/TraceCursorIntelPT.cpp lldb/source/Plugins/Trace/intel-pt/TraceCursorIntelPT.h lldb/source/Plugins/Trace/intel-pt/TraceIntelPT.cpp lldb/source/Plugins/Trace/intel-pt/TraceIntelPT.h lldb/source/Plugins/Trace/intel-pt/TraceIntelPTConstants.h lldb/source/Plugins/Trace/intel-pt/TraceIntelPTOptions.td lldb/source/Target/TraceInstructionDumper.cpp lldb/source/Utility/TraceIntelPTGDBRemotePackets.cpp
Index: lldb/source/Utility/TraceIntelPTGDBRemotePackets.cpp =================================================================== --- lldb/source/Utility/TraceIntelPTGDBRemotePackets.cpp +++ 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; } Index: lldb/source/Target/TraceInstructionDumper.cpp =================================================================== --- lldb/source/Target/TraceInstructionDumper.cpp +++ 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<uint64_t> timestamp = m_cursor_up->GetTimestampCounter()) + s.Printf("0x%016" PRIx64, *timestamp); + else + s.Printf("unavailable"); + + s.Printf("] "); + } }; InstructionSymbolInfo prev_insn_info; Index: lldb/source/Plugins/Trace/intel-pt/TraceIntelPTOptions.td =================================================================== --- lldb/source/Plugins/Trace/intel-pt/TraceIntelPTOptions.td +++ 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.">; } Index: lldb/source/Plugins/Trace/intel-pt/TraceIntelPTConstants.h =================================================================== --- lldb/source/Plugins/Trace/intel-pt/TraceIntelPTConstants.h +++ lldb/source/Plugins/Trace/intel-pt/TraceIntelPTConstants.h @@ -11,11 +11,15 @@ #include <cstddef> +#include <llvm/ADT/Optional.h> + 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<size_t> kPSBPeriod = llvm::None; } // namespace trace_intel_pt } // namespace lldb_private Index: lldb/source/Plugins/Trace/intel-pt/TraceIntelPT.h =================================================================== --- lldb/source/Plugins/Trace/intel-pt/TraceIntelPT.h +++ 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<size_t> 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<lldb::tid_t> tids, - size_t thread_buffer_size); + llvm::Error Start(llvm::ArrayRef<lldb::tid_t> tids, size_t thread_buffer_size, + bool tsc, llvm::Optional<size_t> psb_period); /// \copydoc Trace::Start llvm::Error Start(llvm::ArrayRef<lldb::tid_t> tids, Index: lldb/source/Plugins/Trace/intel-pt/TraceIntelPT.cpp =================================================================== --- lldb/source/Plugins/Trace/intel-pt/TraceIntelPT.cpp +++ 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<size_t> 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<size_t> 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<lldb::tid_t> tids, - size_t thread_buffer_size) { + size_t thread_buffer_size, bool tsc, + Optional<size_t> 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<lldb::tid_t> tids, StructuredData::ObjectSP configuration) { size_t thread_buffer_size = kThreadBufferSize; + bool tsc = kTSC; + Optional<size_t> 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<std::vector<uint8_t>> Index: lldb/source/Plugins/Trace/intel-pt/TraceCursorIntelPT.h =================================================================== --- lldb/source/Plugins/Trace/intel-pt/TraceCursorIntelPT.h +++ lldb/source/Plugins/Trace/intel-pt/TraceCursorIntelPT.h @@ -28,6 +28,8 @@ lldb::addr_t GetLoadAddress() override; + llvm::Optional<uint64_t> GetTimestampCounter() override; + lldb::TraceInstructionControlFlowType GetInstructionControlFlowType() override; Index: lldb/source/Plugins/Trace/intel-pt/TraceCursorIntelPT.cpp =================================================================== --- lldb/source/Plugins/Trace/intel-pt/TraceCursorIntelPT.cpp +++ lldb/source/Plugins/Trace/intel-pt/TraceCursorIntelPT.cpp @@ -85,6 +85,10 @@ return m_decoded_thread_sp->GetInstructions()[m_pos].GetLoadAddress(); } +Optional<uint64_t> TraceCursorIntelPT::GetTimestampCounter() { + return m_decoded_thread_sp->GetInstructions()[m_pos].GetTimestampCounter(); +} + TraceInstructionControlFlowType TraceCursorIntelPT::GetInstructionControlFlowType() { lldb::addr_t next_load_address = Index: lldb/source/Plugins/Trace/intel-pt/IntelPTDecoder.cpp =================================================================== --- lldb/source/Plugins/Trace/intel-pt/IntelPTDecoder.cpp +++ 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<IntelPTError>(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); + } } } Index: lldb/source/Plugins/Trace/intel-pt/DecodedThread.h =================================================================== --- lldb/source/Plugins/Trace/intel-pt/DecodedThread.h +++ 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 <cstdint> #include <vector> #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<uint64_t> 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<uint64_t> m_timestamp; std::unique_ptr<llvm::ErrorInfoBase> m_error; }; Index: lldb/source/Plugins/Trace/intel-pt/DecodedThread.cpp =================================================================== --- lldb/source/Plugins/Trace/intel-pt/DecodedThread.cpp +++ lldb/source/Plugins/Trace/intel-pt/DecodedThread.cpp @@ -48,6 +48,10 @@ lldb::addr_t IntelPTInstruction::GetLoadAddress() const { return m_pt_insn.ip; } +Optional<uint64_t> IntelPTInstruction::GetTimestampCounter() const { + return m_timestamp; +} + Error IntelPTInstruction::ToError() const { if (!IsError()) return Error::success(); Index: lldb/source/Plugins/Trace/intel-pt/CommandObjectTraceStartIntelPT.h =================================================================== --- lldb/source/Plugins/Trace/intel-pt/CommandObjectTraceStartIntelPT.h +++ lldb/source/Plugins/Trace/intel-pt/CommandObjectTraceStartIntelPT.h @@ -32,6 +32,8 @@ llvm::ArrayRef<OptionDefinition> GetDefinitions() override; size_t m_thread_buffer_size; + bool m_tsc; + llvm::Optional<size_t> 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<size_t> m_psb_period; }; CommandObjectProcessTraceStartIntelPT(TraceIntelPT &trace, Index: lldb/source/Plugins/Trace/intel-pt/CommandObjectTraceStartIntelPT.cpp =================================================================== --- lldb/source/Plugins/Trace/intel-pt/CommandObjectTraceStartIntelPT.cpp +++ 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<OptionDefinition> @@ -60,7 +76,8 @@ bool CommandObjectThreadTraceStartIntelPT::DoExecuteOnThreads( Args &command, CommandReturnObject &result, llvm::ArrayRef<lldb::tid_t> 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<OptionDefinition> @@ -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); Index: lldb/source/Plugins/Process/Linux/IntelPTManager.h =================================================================== --- lldb/source/Plugins/Process/Linux/IntelPTManager.h +++ 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<size_t> psb_period); llvm::MutableArrayRef<uint8_t> GetAuxBuffer() const; llvm::MutableArrayRef<uint8_t> GetDataBuffer() const; @@ -95,7 +103,8 @@ /// A \a IntelPTThreadTrace instance if tracing was successful, or /// an \a llvm::Error otherwise. static llvm::Expected<IntelPTThreadTraceUP> - 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<size_t> psb_period); /// Read the trace buffer of the currently traced thread. /// Index: lldb/source/Plugins/Process/Linux/IntelPTManager.cpp =================================================================== --- lldb/source/Plugins/Process/Linux/IntelPTManager.cpp +++ lldb/source/Plugins/Process/Linux/IntelPTManager.cpp @@ -8,6 +8,7 @@ #include <algorithm> #include <fstream> +#include <sstream> #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<uint32_t> 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<uint32_t> 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<uint32_t> 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<uint32_t> 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<uint64_t> +GeneratePerfEventConfigValue(bool tsc, Optional<size_t> psb_period) { + uint64_t config = 0; + // tsc is always supported + if (tsc) { + if (Expected<uint32_t> 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<uint32_t> 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<size_t> 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<uint32_t> intel_pt_type = GetOSEventType(); + if (Expected<uint64_t> 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<uint32_t> 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<IntelPTThreadTraceUP> -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<size_t> 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<IntelPTThreadTraceUP> trace_up = - IntelPTThreadTrace::Create(m_pid, tid, request.threadBufferSize); + Expected<IntelPTThreadTraceUP> 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(); Index: lldb/source/Commands/Options.td =================================================================== --- lldb/source/Commands/Options.td +++ 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 { Index: lldb/source/Commands/CommandObjectThread.cpp =================================================================== --- lldb/source/Commands/CommandObjectThread.cpp +++ 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<OptionDefinition> 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<TraceInstructionDumper>( - 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. Index: lldb/include/lldb/Utility/TraceIntelPTGDBRemotePackets.h =================================================================== --- lldb/include/lldb/Utility/TraceIntelPTGDBRemotePackets.h +++ 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<int64_t> psb_period; + /// Required when doing "process tracing". /// /// Limit in bytes on all the thread traces started by this "process trace" Index: lldb/include/lldb/Target/TraceInstructionDumper.h =================================================================== --- lldb/include/lldb/Target/TraceInstructionDumper.h +++ 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; }; Index: lldb/include/lldb/Target/TraceCursor.h =================================================================== --- lldb/include/lldb/Target/TraceCursor.h +++ lldb/include/lldb/Target/TraceCursor.h @@ -180,6 +180,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<uint64_t> GetTimestampCounter() = 0; + /// \return /// The \a lldb::TraceInstructionControlFlowType categories the /// instruction the cursor is pointing at falls into. If the cursor points Index: lldb/docs/lldb-gdb-remote.txt =================================================================== --- lldb/docs/lldb-gdb-remote.txt +++ 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": <boolean>, +// 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"?: <Optional decimal integer>, +// 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": <decimal integer>, // 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.
_______________________________________________ lldb-commits mailing list lldb-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/lldb-commits