2020-08-17 17:21:52 -07:00
|
|
|
//===-- Trace.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/Target/Trace.h"
|
|
|
|
|
|
|
|
|
|
#include "llvm/Support/Format.h"
|
|
|
|
|
|
[trace][intel-pt] Implement the basic decoding functionality
Depends on D89408.
This diff finally implements trace decoding!
The current interface is
$ trace load /path/to/trace/session/file.json
$ thread trace dump instructions
thread #1: tid = 3842849, total instructions = 22
[ 0] 0x40052d
[ 1] 0x40052d
...
[19] 0x400521
$ # simply enter, which is a repeat command
[20] 0x40052d
[21] 0x400529
...
This doesn't do any disassembly, which will be done in the next diff.
Changes:
- Added an IntelPTDecoder class, that is a wrapper for libipt, which is the actual library that performs the decoding.
- Added TraceThreadDecoder class that decodes traces and memoizes the result to avoid repeating the decoding step.
- Added a DecodedThread class, which represents the output from decoding and that for the time being only stores the list of reconstructed instructions. Later it'll contain the function call hierarchy, which will enable reconstructing backtraces.
- Added basic APIs for accessing the trace in Trace.h:
- GetInstructionCount, which counts the number of instructions traced for a given thread
- IsTraceFailed, which returns an Error if decoding a thread failed
- ForEachInstruction, which iterates on the instructions traced for a given thread, concealing the internal storage of threads, as plug-ins can decide to generate the instructions on the fly or to store them all in a vector, like I do.
- DumpTraceInstructions was updated to print the instructions or show an error message if decoding was impossible.
- Tests included
Differential Revision: https://reviews.llvm.org/D89283
2020-10-14 10:26:10 -07:00
|
|
|
#include "lldb/Core/Module.h"
|
2020-08-17 17:21:52 -07:00
|
|
|
#include "lldb/Core/PluginManager.h"
|
[trace][intel-pt] Implement the basic decoding functionality
Depends on D89408.
This diff finally implements trace decoding!
The current interface is
$ trace load /path/to/trace/session/file.json
$ thread trace dump instructions
thread #1: tid = 3842849, total instructions = 22
[ 0] 0x40052d
[ 1] 0x40052d
...
[19] 0x400521
$ # simply enter, which is a repeat command
[20] 0x40052d
[21] 0x400529
...
This doesn't do any disassembly, which will be done in the next diff.
Changes:
- Added an IntelPTDecoder class, that is a wrapper for libipt, which is the actual library that performs the decoding.
- Added TraceThreadDecoder class that decodes traces and memoizes the result to avoid repeating the decoding step.
- Added a DecodedThread class, which represents the output from decoding and that for the time being only stores the list of reconstructed instructions. Later it'll contain the function call hierarchy, which will enable reconstructing backtraces.
- Added basic APIs for accessing the trace in Trace.h:
- GetInstructionCount, which counts the number of instructions traced for a given thread
- IsTraceFailed, which returns an Error if decoding a thread failed
- ForEachInstruction, which iterates on the instructions traced for a given thread, concealing the internal storage of threads, as plug-ins can decide to generate the instructions on the fly or to store them all in a vector, like I do.
- DumpTraceInstructions was updated to print the instructions or show an error message if decoding was impossible.
- Tests included
Differential Revision: https://reviews.llvm.org/D89283
2020-10-14 10:26:10 -07:00
|
|
|
#include "lldb/Symbol/Function.h"
|
[trace] Dedup different source lines when dumping instructions + refactor
When dumping the traced instructions in a for loop, like this one
4: for (int a = 0; a < n; a++)
5: do something;
there might be multiple LineEntry objects for line 4, but with different address ranges. This was causing the dump command to dump something like this:
```
a.out`main + 11 at main.cpp:4
[1] 0x0000000000400518 movl $0x0, -0x8(%rbp)
[2] 0x000000000040051f jmp 0x400529 ; <+28> at main.cpp:4
a.out`main + 28 at main.cpp:4
[3] 0x0000000000400529 cmpl $0x3, -0x8(%rbp)
[4] 0x000000000040052d jle 0x400521 ; <+20> at main.cpp:5
```
which is confusing, as main.cpp:4 appears twice consecutively.
This diff fixes that issue by making the line entry comparison strictly about the line, column and file name. Before it was also comparing the address ranges, which we don't need because our output is strictly about what the user sees in the source.
Besides, I've noticed that the logic that traverses instructions and calculates symbols and disassemblies had too much coupling, and made my changes harder to implement, so I decided to decouple it. Now there are two methods for iterating over the instruction of a trace. The existing one does it on raw load addresses, but the one provides a SymbolContext and an InstructionSP, and does the calculations efficiently (not as efficient as possible for now though), so the caller doesn't need to care about these details. I think I'll be using that iterator to reconstruct the call stacks.
I was able to fix a test with this change.
Differential Revision: https://reviews.llvm.org/D100740
2021-05-03 07:55:35 -07:00
|
|
|
#include "lldb/Target/ExecutionContext.h"
|
[trace][intel-pt] Implement the basic decoding functionality
Depends on D89408.
This diff finally implements trace decoding!
The current interface is
$ trace load /path/to/trace/session/file.json
$ thread trace dump instructions
thread #1: tid = 3842849, total instructions = 22
[ 0] 0x40052d
[ 1] 0x40052d
...
[19] 0x400521
$ # simply enter, which is a repeat command
[20] 0x40052d
[21] 0x400529
...
This doesn't do any disassembly, which will be done in the next diff.
Changes:
- Added an IntelPTDecoder class, that is a wrapper for libipt, which is the actual library that performs the decoding.
- Added TraceThreadDecoder class that decodes traces and memoizes the result to avoid repeating the decoding step.
- Added a DecodedThread class, which represents the output from decoding and that for the time being only stores the list of reconstructed instructions. Later it'll contain the function call hierarchy, which will enable reconstructing backtraces.
- Added basic APIs for accessing the trace in Trace.h:
- GetInstructionCount, which counts the number of instructions traced for a given thread
- IsTraceFailed, which returns an Error if decoding a thread failed
- ForEachInstruction, which iterates on the instructions traced for a given thread, concealing the internal storage of threads, as plug-ins can decide to generate the instructions on the fly or to store them all in a vector, like I do.
- DumpTraceInstructions was updated to print the instructions or show an error message if decoding was impossible.
- Tests included
Differential Revision: https://reviews.llvm.org/D89283
2020-10-14 10:26:10 -07:00
|
|
|
#include "lldb/Target/Process.h"
|
|
|
|
|
#include "lldb/Target/SectionLoadList.h"
|
2020-10-02 14:32:22 -07:00
|
|
|
#include "lldb/Target/Thread.h"
|
2022-05-18 19:10:09 -07:00
|
|
|
#include "lldb/Utility/LLDBLog.h"
|
2020-10-02 14:32:22 -07:00
|
|
|
#include "lldb/Utility/Stream.h"
|
2023-01-07 13:43:00 -08:00
|
|
|
#include <optional>
|
2020-08-17 17:21:52 -07:00
|
|
|
|
|
|
|
|
using namespace lldb;
|
|
|
|
|
using namespace lldb_private;
|
|
|
|
|
using namespace llvm;
|
|
|
|
|
|
2022-06-23 17:45:24 -07:00
|
|
|
// Helper structs used to extract the type of a JSON trace bundle description
|
|
|
|
|
// object without having to parse the entire object.
|
2020-09-24 13:39:21 -07:00
|
|
|
|
2022-06-23 17:45:24 -07:00
|
|
|
struct JSONSimpleTraceBundleDescription {
|
2022-05-18 21:36:34 -07:00
|
|
|
std::string type;
|
2020-09-24 13:39:21 -07:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
namespace llvm {
|
|
|
|
|
namespace json {
|
|
|
|
|
|
2022-06-23 17:45:24 -07:00
|
|
|
bool fromJSON(const Value &value, JSONSimpleTraceBundleDescription &bundle,
|
|
|
|
|
Path path) {
|
2020-09-24 13:39:21 -07:00
|
|
|
json::ObjectMapper o(value, path);
|
2022-06-23 17:45:24 -07:00
|
|
|
return o && o.map("type", bundle.type);
|
2020-09-24 13:39:21 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
} // namespace json
|
|
|
|
|
} // namespace llvm
|
|
|
|
|
|
2022-06-12 23:36:52 -07:00
|
|
|
/// Helper functions for fetching data in maps and returning Optionals or
|
|
|
|
|
/// pointers instead of iterators for simplicity. It's worth mentioning that the
|
|
|
|
|
/// Optionals version can't return the inner data by reference because of
|
|
|
|
|
/// limitations in move constructors.
|
|
|
|
|
/// \{
|
|
|
|
|
template <typename K, typename V>
|
2023-01-07 14:18:35 -08:00
|
|
|
static std::optional<V> Lookup(DenseMap<K, V> &map, K k) {
|
2022-06-12 23:36:52 -07:00
|
|
|
auto it = map.find(k);
|
|
|
|
|
if (it == map.end())
|
2022-12-04 16:51:25 -08:00
|
|
|
return std::nullopt;
|
2022-06-12 23:36:52 -07:00
|
|
|
return it->second;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
template <typename K, typename V>
|
|
|
|
|
static V *LookupAsPtr(DenseMap<K, V> &map, K k) {
|
|
|
|
|
auto it = map.find(k);
|
|
|
|
|
if (it == map.end())
|
|
|
|
|
return nullptr;
|
|
|
|
|
return &it->second;
|
|
|
|
|
}
|
|
|
|
|
|
2022-06-15 10:35:00 -07:00
|
|
|
/// Similar to the methods above but it looks for an item in a map of maps.
|
2022-06-12 23:36:52 -07:00
|
|
|
template <typename K1, typename K2, typename V>
|
2023-01-07 14:18:35 -08:00
|
|
|
static std::optional<V> Lookup(DenseMap<K1, DenseMap<K2, V>> &map, K1 k1,
|
|
|
|
|
K2 k2) {
|
2022-06-12 23:36:52 -07:00
|
|
|
auto it = map.find(k1);
|
|
|
|
|
if (it == map.end())
|
2022-12-04 16:51:25 -08:00
|
|
|
return std::nullopt;
|
2022-06-12 23:36:52 -07:00
|
|
|
return Lookup(it->second, k2);
|
|
|
|
|
}
|
|
|
|
|
|
2022-06-15 10:35:00 -07:00
|
|
|
/// Similar to the methods above but it looks for an item in a map of maps.
|
2022-06-12 23:36:52 -07:00
|
|
|
template <typename K1, typename K2, typename V>
|
2022-06-15 10:35:00 -07:00
|
|
|
static V *LookupAsPtr(DenseMap<K1, DenseMap<K2, V>> &map, K1 k1, K2 k2) {
|
2022-06-12 23:36:52 -07:00
|
|
|
auto it = map.find(k1);
|
|
|
|
|
if (it == map.end())
|
|
|
|
|
return nullptr;
|
|
|
|
|
return LookupAsPtr(it->second, k2);
|
|
|
|
|
}
|
|
|
|
|
/// \}
|
|
|
|
|
|
[intel pt] Refactor parsing
With the feedback I was getting in different diffs, I realized that splitting the parsing logic into two classes was not easy to deal with. I do see value in doing that, but I'd rather leave that as a refactor after most of the intel-pt logic is in place. Thus, I'm merging the common parser into the intel pt one, having thus only one that is fully aware of Intel PT during parsing and object creation.
Besides, based on the feedback in https://reviews.llvm.org/D88769, I'm creating a ThreadIntelPT class that will be able to orchestrate decoding of its own trace and can handle the stop events correctly.
This leaves the TraceIntelPT class as an initialization class that glues together different components. Right now it can initialize a trace session from a json file, and in the future will be able to initialize a trace session from a live process.
Besides, I'm renaming SettingsParser to SessionParser, which I think is a better name, as the json object represents a trace session of possibly many processes.
With the current set of targets, we have the following
- Trace: main interface for dealing with trace sessions
- TraceIntelPT: plugin Trace for dealing with intel pt sessions
- TraceIntelPTSessionParser: a parser of a json trace session file that can create a corresponding TraceIntelPT instance along with Targets, ProcessTraces (to be created in https://reviews.llvm.org/D88769), and ThreadIntelPT threads.
- ProcessTrace: (to be created in https://reviews.llvm.org/D88769) can handle the correct state of the traces as the user traverses the trace. I don't think there'll be a need an intel-pt specific implementation of this class.
- ThreadIntelPT: a thread implementation that can handle the decoding of its own trace file, along with keeping track of the current position the user is looking at when doing reverse debugging.
Differential Revision: https://reviews.llvm.org/D88841
2020-10-03 12:23:12 -07:00
|
|
|
static Error createInvalidPlugInError(StringRef plugin_name) {
|
2020-08-17 17:21:52 -07:00
|
|
|
return createStringError(
|
|
|
|
|
std::errc::invalid_argument,
|
|
|
|
|
"no trace plug-in matches the specified type: \"%s\"",
|
[intel pt] Refactor parsing
With the feedback I was getting in different diffs, I realized that splitting the parsing logic into two classes was not easy to deal with. I do see value in doing that, but I'd rather leave that as a refactor after most of the intel-pt logic is in place. Thus, I'm merging the common parser into the intel pt one, having thus only one that is fully aware of Intel PT during parsing and object creation.
Besides, based on the feedback in https://reviews.llvm.org/D88769, I'm creating a ThreadIntelPT class that will be able to orchestrate decoding of its own trace and can handle the stop events correctly.
This leaves the TraceIntelPT class as an initialization class that glues together different components. Right now it can initialize a trace session from a json file, and in the future will be able to initialize a trace session from a live process.
Besides, I'm renaming SettingsParser to SessionParser, which I think is a better name, as the json object represents a trace session of possibly many processes.
With the current set of targets, we have the following
- Trace: main interface for dealing with trace sessions
- TraceIntelPT: plugin Trace for dealing with intel pt sessions
- TraceIntelPTSessionParser: a parser of a json trace session file that can create a corresponding TraceIntelPT instance along with Targets, ProcessTraces (to be created in https://reviews.llvm.org/D88769), and ThreadIntelPT threads.
- ProcessTrace: (to be created in https://reviews.llvm.org/D88769) can handle the correct state of the traces as the user traverses the trace. I don't think there'll be a need an intel-pt specific implementation of this class.
- ThreadIntelPT: a thread implementation that can handle the decoding of its own trace file, along with keeping track of the current position the user is looking at when doing reverse debugging.
Differential Revision: https://reviews.llvm.org/D88841
2020-10-03 12:23:12 -07:00
|
|
|
plugin_name.data());
|
2020-08-17 17:21:52 -07:00
|
|
|
}
|
|
|
|
|
|
2022-06-17 13:37:07 -07:00
|
|
|
Expected<lldb::TraceSP>
|
|
|
|
|
Trace::LoadPostMortemTraceFromFile(Debugger &debugger,
|
|
|
|
|
const FileSpec &trace_description_file) {
|
|
|
|
|
|
|
|
|
|
auto buffer_or_error =
|
|
|
|
|
MemoryBuffer::getFile(trace_description_file.GetPath());
|
|
|
|
|
if (!buffer_or_error) {
|
|
|
|
|
return createStringError(std::errc::invalid_argument,
|
|
|
|
|
"could not open input file: %s - %s.",
|
|
|
|
|
trace_description_file.GetPath().c_str(),
|
|
|
|
|
buffer_or_error.getError().message().c_str());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Expected<json::Value> session_file =
|
|
|
|
|
json::parse(buffer_or_error.get()->getBuffer().str());
|
|
|
|
|
if (!session_file) {
|
|
|
|
|
return session_file.takeError();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return Trace::FindPluginForPostMortemProcess(
|
|
|
|
|
debugger, *session_file,
|
|
|
|
|
trace_description_file.GetDirectory().AsCString());
|
|
|
|
|
}
|
|
|
|
|
|
2022-06-23 17:45:24 -07:00
|
|
|
Expected<lldb::TraceSP> Trace::FindPluginForPostMortemProcess(
|
|
|
|
|
Debugger &debugger, const json::Value &trace_bundle_description,
|
|
|
|
|
StringRef bundle_dir) {
|
|
|
|
|
JSONSimpleTraceBundleDescription json_bundle;
|
|
|
|
|
json::Path::Root root("traceBundle");
|
|
|
|
|
if (!json::fromJSON(trace_bundle_description, json_bundle, root))
|
[intel pt] Refactor parsing
With the feedback I was getting in different diffs, I realized that splitting the parsing logic into two classes was not easy to deal with. I do see value in doing that, but I'd rather leave that as a refactor after most of the intel-pt logic is in place. Thus, I'm merging the common parser into the intel pt one, having thus only one that is fully aware of Intel PT during parsing and object creation.
Besides, based on the feedback in https://reviews.llvm.org/D88769, I'm creating a ThreadIntelPT class that will be able to orchestrate decoding of its own trace and can handle the stop events correctly.
This leaves the TraceIntelPT class as an initialization class that glues together different components. Right now it can initialize a trace session from a json file, and in the future will be able to initialize a trace session from a live process.
Besides, I'm renaming SettingsParser to SessionParser, which I think is a better name, as the json object represents a trace session of possibly many processes.
With the current set of targets, we have the following
- Trace: main interface for dealing with trace sessions
- TraceIntelPT: plugin Trace for dealing with intel pt sessions
- TraceIntelPTSessionParser: a parser of a json trace session file that can create a corresponding TraceIntelPT instance along with Targets, ProcessTraces (to be created in https://reviews.llvm.org/D88769), and ThreadIntelPT threads.
- ProcessTrace: (to be created in https://reviews.llvm.org/D88769) can handle the correct state of the traces as the user traverses the trace. I don't think there'll be a need an intel-pt specific implementation of this class.
- ThreadIntelPT: a thread implementation that can handle the decoding of its own trace file, along with keeping track of the current position the user is looking at when doing reverse debugging.
Differential Revision: https://reviews.llvm.org/D88841
2020-10-03 12:23:12 -07:00
|
|
|
return root.getError();
|
2020-08-17 17:21:52 -07:00
|
|
|
|
2021-10-29 11:40:54 +02:00
|
|
|
if (auto create_callback =
|
2022-06-23 17:45:24 -07:00
|
|
|
PluginManager::GetTraceCreateCallback(json_bundle.type))
|
|
|
|
|
return create_callback(trace_bundle_description, bundle_dir, debugger);
|
[intel pt] Refactor parsing
With the feedback I was getting in different diffs, I realized that splitting the parsing logic into two classes was not easy to deal with. I do see value in doing that, but I'd rather leave that as a refactor after most of the intel-pt logic is in place. Thus, I'm merging the common parser into the intel pt one, having thus only one that is fully aware of Intel PT during parsing and object creation.
Besides, based on the feedback in https://reviews.llvm.org/D88769, I'm creating a ThreadIntelPT class that will be able to orchestrate decoding of its own trace and can handle the stop events correctly.
This leaves the TraceIntelPT class as an initialization class that glues together different components. Right now it can initialize a trace session from a json file, and in the future will be able to initialize a trace session from a live process.
Besides, I'm renaming SettingsParser to SessionParser, which I think is a better name, as the json object represents a trace session of possibly many processes.
With the current set of targets, we have the following
- Trace: main interface for dealing with trace sessions
- TraceIntelPT: plugin Trace for dealing with intel pt sessions
- TraceIntelPTSessionParser: a parser of a json trace session file that can create a corresponding TraceIntelPT instance along with Targets, ProcessTraces (to be created in https://reviews.llvm.org/D88769), and ThreadIntelPT threads.
- ProcessTrace: (to be created in https://reviews.llvm.org/D88769) can handle the correct state of the traces as the user traverses the trace. I don't think there'll be a need an intel-pt specific implementation of this class.
- ThreadIntelPT: a thread implementation that can handle the decoding of its own trace file, along with keeping track of the current position the user is looking at when doing reverse debugging.
Differential Revision: https://reviews.llvm.org/D88841
2020-10-03 12:23:12 -07:00
|
|
|
|
2022-06-23 17:45:24 -07:00
|
|
|
return createInvalidPlugInError(json_bundle.type);
|
2020-08-17 17:21:52 -07:00
|
|
|
}
|
|
|
|
|
|
2021-10-29 11:40:54 +02:00
|
|
|
Expected<lldb::TraceSP> Trace::FindPluginForLiveProcess(llvm::StringRef name,
|
|
|
|
|
Process &process) {
|
2020-11-09 13:36:26 -08:00
|
|
|
if (!process.IsLiveDebugSession())
|
|
|
|
|
return createStringError(inconvertibleErrorCode(),
|
|
|
|
|
"Can't trace non-live processes");
|
|
|
|
|
|
|
|
|
|
if (auto create_callback =
|
|
|
|
|
PluginManager::GetTraceCreateCallbackForLiveProcess(name))
|
|
|
|
|
return create_callback(process);
|
|
|
|
|
|
2021-10-29 11:40:54 +02:00
|
|
|
return createInvalidPlugInError(name);
|
2020-11-09 13:36:26 -08:00
|
|
|
}
|
|
|
|
|
|
[intel pt] Refactor parsing
With the feedback I was getting in different diffs, I realized that splitting the parsing logic into two classes was not easy to deal with. I do see value in doing that, but I'd rather leave that as a refactor after most of the intel-pt logic is in place. Thus, I'm merging the common parser into the intel pt one, having thus only one that is fully aware of Intel PT during parsing and object creation.
Besides, based on the feedback in https://reviews.llvm.org/D88769, I'm creating a ThreadIntelPT class that will be able to orchestrate decoding of its own trace and can handle the stop events correctly.
This leaves the TraceIntelPT class as an initialization class that glues together different components. Right now it can initialize a trace session from a json file, and in the future will be able to initialize a trace session from a live process.
Besides, I'm renaming SettingsParser to SessionParser, which I think is a better name, as the json object represents a trace session of possibly many processes.
With the current set of targets, we have the following
- Trace: main interface for dealing with trace sessions
- TraceIntelPT: plugin Trace for dealing with intel pt sessions
- TraceIntelPTSessionParser: a parser of a json trace session file that can create a corresponding TraceIntelPT instance along with Targets, ProcessTraces (to be created in https://reviews.llvm.org/D88769), and ThreadIntelPT threads.
- ProcessTrace: (to be created in https://reviews.llvm.org/D88769) can handle the correct state of the traces as the user traverses the trace. I don't think there'll be a need an intel-pt specific implementation of this class.
- ThreadIntelPT: a thread implementation that can handle the decoding of its own trace file, along with keeping track of the current position the user is looking at when doing reverse debugging.
Differential Revision: https://reviews.llvm.org/D88841
2020-10-03 12:23:12 -07:00
|
|
|
Expected<StringRef> Trace::FindPluginSchema(StringRef name) {
|
2021-10-29 11:40:54 +02:00
|
|
|
StringRef schema = PluginManager::GetTraceSchema(name);
|
[intel pt] Refactor parsing
With the feedback I was getting in different diffs, I realized that splitting the parsing logic into two classes was not easy to deal with. I do see value in doing that, but I'd rather leave that as a refactor after most of the intel-pt logic is in place. Thus, I'm merging the common parser into the intel pt one, having thus only one that is fully aware of Intel PT during parsing and object creation.
Besides, based on the feedback in https://reviews.llvm.org/D88769, I'm creating a ThreadIntelPT class that will be able to orchestrate decoding of its own trace and can handle the stop events correctly.
This leaves the TraceIntelPT class as an initialization class that glues together different components. Right now it can initialize a trace session from a json file, and in the future will be able to initialize a trace session from a live process.
Besides, I'm renaming SettingsParser to SessionParser, which I think is a better name, as the json object represents a trace session of possibly many processes.
With the current set of targets, we have the following
- Trace: main interface for dealing with trace sessions
- TraceIntelPT: plugin Trace for dealing with intel pt sessions
- TraceIntelPTSessionParser: a parser of a json trace session file that can create a corresponding TraceIntelPT instance along with Targets, ProcessTraces (to be created in https://reviews.llvm.org/D88769), and ThreadIntelPT threads.
- ProcessTrace: (to be created in https://reviews.llvm.org/D88769) can handle the correct state of the traces as the user traverses the trace. I don't think there'll be a need an intel-pt specific implementation of this class.
- ThreadIntelPT: a thread implementation that can handle the decoding of its own trace file, along with keeping track of the current position the user is looking at when doing reverse debugging.
Differential Revision: https://reviews.llvm.org/D88841
2020-10-03 12:23:12 -07:00
|
|
|
if (!schema.empty())
|
|
|
|
|
return schema;
|
2020-08-17 17:21:52 -07:00
|
|
|
|
[intel pt] Refactor parsing
With the feedback I was getting in different diffs, I realized that splitting the parsing logic into two classes was not easy to deal with. I do see value in doing that, but I'd rather leave that as a refactor after most of the intel-pt logic is in place. Thus, I'm merging the common parser into the intel pt one, having thus only one that is fully aware of Intel PT during parsing and object creation.
Besides, based on the feedback in https://reviews.llvm.org/D88769, I'm creating a ThreadIntelPT class that will be able to orchestrate decoding of its own trace and can handle the stop events correctly.
This leaves the TraceIntelPT class as an initialization class that glues together different components. Right now it can initialize a trace session from a json file, and in the future will be able to initialize a trace session from a live process.
Besides, I'm renaming SettingsParser to SessionParser, which I think is a better name, as the json object represents a trace session of possibly many processes.
With the current set of targets, we have the following
- Trace: main interface for dealing with trace sessions
- TraceIntelPT: plugin Trace for dealing with intel pt sessions
- TraceIntelPTSessionParser: a parser of a json trace session file that can create a corresponding TraceIntelPT instance along with Targets, ProcessTraces (to be created in https://reviews.llvm.org/D88769), and ThreadIntelPT threads.
- ProcessTrace: (to be created in https://reviews.llvm.org/D88769) can handle the correct state of the traces as the user traverses the trace. I don't think there'll be a need an intel-pt specific implementation of this class.
- ThreadIntelPT: a thread implementation that can handle the decoding of its own trace file, along with keeping track of the current position the user is looking at when doing reverse debugging.
Differential Revision: https://reviews.llvm.org/D88841
2020-10-03 12:23:12 -07:00
|
|
|
return createInvalidPlugInError(name);
|
2020-08-17 17:21:52 -07:00
|
|
|
}
|
2020-10-02 14:32:22 -07:00
|
|
|
|
2020-11-09 13:36:26 -08:00
|
|
|
Error Trace::Start(const llvm::json::Value &request) {
|
|
|
|
|
if (!m_live_process)
|
2022-06-12 23:36:52 -07:00
|
|
|
return createStringError(
|
|
|
|
|
inconvertibleErrorCode(),
|
|
|
|
|
"Attempted to start tracing without a live process.");
|
2020-11-09 13:36:26 -08:00
|
|
|
return m_live_process->TraceStart(request);
|
|
|
|
|
}
|
|
|
|
|
|
2021-06-01 15:34:06 -07:00
|
|
|
Error Trace::Stop() {
|
2020-11-09 13:36:26 -08:00
|
|
|
if (!m_live_process)
|
2022-06-12 23:36:52 -07:00
|
|
|
return createStringError(
|
|
|
|
|
inconvertibleErrorCode(),
|
|
|
|
|
"Attempted to stop tracing without a live process.");
|
2021-10-15 13:07:39 +02:00
|
|
|
return m_live_process->TraceStop(TraceStopRequest(GetPluginName()));
|
2020-11-09 13:36:26 -08:00
|
|
|
}
|
|
|
|
|
|
2021-06-01 15:34:06 -07:00
|
|
|
Error Trace::Stop(llvm::ArrayRef<lldb::tid_t> tids) {
|
2020-11-09 13:36:26 -08:00
|
|
|
if (!m_live_process)
|
2022-06-12 23:36:52 -07:00
|
|
|
return createStringError(
|
|
|
|
|
inconvertibleErrorCode(),
|
|
|
|
|
"Attempted to stop tracing without a live process.");
|
2021-10-15 13:07:39 +02:00
|
|
|
return m_live_process->TraceStop(TraceStopRequest(GetPluginName(), tids));
|
2020-11-09 13:36:26 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Expected<std::string> Trace::GetLiveProcessState() {
|
|
|
|
|
if (!m_live_process)
|
2022-06-12 23:36:52 -07:00
|
|
|
return createStringError(
|
|
|
|
|
inconvertibleErrorCode(),
|
|
|
|
|
"Attempted to fetch live trace information without a live process.");
|
2021-10-15 13:07:39 +02:00
|
|
|
return m_live_process->TraceGetState(GetPluginName());
|
2020-11-09 13:36:26 -08:00
|
|
|
}
|
|
|
|
|
|
2023-01-07 14:18:35 -08:00
|
|
|
std::optional<uint64_t>
|
|
|
|
|
Trace::GetLiveThreadBinaryDataSize(lldb::tid_t tid, llvm::StringRef kind) {
|
2022-06-08 11:39:31 -07:00
|
|
|
Storage &storage = GetUpdatedStorage();
|
2022-06-15 10:35:00 -07:00
|
|
|
return Lookup(storage.live_thread_data, tid, ConstString(kind));
|
2020-11-09 13:36:26 -08:00
|
|
|
}
|
|
|
|
|
|
2023-01-07 14:18:35 -08:00
|
|
|
std::optional<uint64_t> Trace::GetLiveCpuBinaryDataSize(lldb::cpu_id_t cpu_id,
|
|
|
|
|
llvm::StringRef kind) {
|
2022-06-08 11:39:31 -07:00
|
|
|
Storage &storage = GetUpdatedStorage();
|
2022-06-15 10:35:00 -07:00
|
|
|
return Lookup(storage.live_cpu_data_sizes, cpu_id, ConstString(kind));
|
2022-05-18 21:36:34 -07:00
|
|
|
}
|
|
|
|
|
|
2023-01-07 14:18:35 -08:00
|
|
|
std::optional<uint64_t>
|
|
|
|
|
Trace::GetLiveProcessBinaryDataSize(llvm::StringRef kind) {
|
2022-06-08 11:39:31 -07:00
|
|
|
Storage &storage = GetUpdatedStorage();
|
2022-06-12 23:36:52 -07:00
|
|
|
return Lookup(storage.live_process_data, ConstString(kind));
|
2020-11-09 13:36:26 -08:00
|
|
|
}
|
|
|
|
|
|
2022-03-21 15:46:19 -07:00
|
|
|
Expected<std::vector<uint8_t>>
|
2022-06-12 23:36:52 -07:00
|
|
|
Trace::GetLiveTraceBinaryData(const TraceGetBinaryDataRequest &request,
|
|
|
|
|
uint64_t expected_size) {
|
2020-11-09 13:36:26 -08:00
|
|
|
if (!m_live_process)
|
2022-06-12 23:36:52 -07:00
|
|
|
return createStringError(
|
|
|
|
|
inconvertibleErrorCode(),
|
|
|
|
|
formatv("Attempted to fetch live trace data without a live process. "
|
2022-06-14 15:53:59 -07:00
|
|
|
"Data kind = {0}, tid = {1}, cpu id = {2}.",
|
|
|
|
|
request.kind, request.tid, request.cpu_id));
|
2022-06-12 23:36:52 -07:00
|
|
|
|
|
|
|
|
Expected<std::vector<uint8_t>> data =
|
|
|
|
|
m_live_process->TraceGetBinaryData(request);
|
|
|
|
|
|
|
|
|
|
if (!data)
|
|
|
|
|
return data.takeError();
|
|
|
|
|
|
|
|
|
|
if (data->size() != expected_size)
|
|
|
|
|
return createStringError(
|
|
|
|
|
inconvertibleErrorCode(),
|
|
|
|
|
formatv("Got incomplete live trace data. Data kind = {0}, expected "
|
2022-06-14 15:53:59 -07:00
|
|
|
"size = {1}, actual size = {2}, tid = {3}, cpu id = {4}",
|
2022-06-12 23:36:52 -07:00
|
|
|
request.kind, expected_size, data->size(), request.tid,
|
2022-06-14 15:53:59 -07:00
|
|
|
request.cpu_id));
|
2022-06-12 23:36:52 -07:00
|
|
|
|
|
|
|
|
return data;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Expected<std::vector<uint8_t>>
|
|
|
|
|
Trace::GetLiveThreadBinaryData(lldb::tid_t tid, llvm::StringRef kind) {
|
2023-01-07 14:18:35 -08:00
|
|
|
std::optional<uint64_t> size = GetLiveThreadBinaryDataSize(tid, kind);
|
2020-11-09 13:36:26 -08:00
|
|
|
if (!size)
|
|
|
|
|
return createStringError(
|
|
|
|
|
inconvertibleErrorCode(),
|
|
|
|
|
"Tracing data \"%s\" is not available for thread %" PRIu64 ".",
|
|
|
|
|
kind.data(), tid);
|
|
|
|
|
|
2022-06-12 23:36:52 -07:00
|
|
|
TraceGetBinaryDataRequest request{GetPluginName().str(), kind.str(), tid,
|
2022-12-04 16:51:25 -08:00
|
|
|
/*cpu_id=*/std::nullopt};
|
2022-06-12 23:36:52 -07:00
|
|
|
return GetLiveTraceBinaryData(request, *size);
|
2022-05-18 21:36:34 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Expected<std::vector<uint8_t>>
|
2022-06-14 15:53:59 -07:00
|
|
|
Trace::GetLiveCpuBinaryData(lldb::cpu_id_t cpu_id, llvm::StringRef kind) {
|
2022-05-18 21:36:34 -07:00
|
|
|
if (!m_live_process)
|
2022-06-12 23:36:52 -07:00
|
|
|
return createStringError(
|
|
|
|
|
inconvertibleErrorCode(),
|
|
|
|
|
"Attempted to fetch live cpu data without a live process.");
|
2023-01-07 14:18:35 -08:00
|
|
|
std::optional<uint64_t> size = GetLiveCpuBinaryDataSize(cpu_id, kind);
|
2022-05-18 21:36:34 -07:00
|
|
|
if (!size)
|
|
|
|
|
return createStringError(
|
|
|
|
|
inconvertibleErrorCode(),
|
2022-06-14 15:53:59 -07:00
|
|
|
"Tracing data \"%s\" is not available for cpu_id %" PRIu64 ".",
|
|
|
|
|
kind.data(), cpu_id);
|
2022-05-18 21:36:34 -07:00
|
|
|
|
|
|
|
|
TraceGetBinaryDataRequest request{GetPluginName().str(), kind.str(),
|
2022-12-04 16:51:25 -08:00
|
|
|
/*tid=*/std::nullopt, cpu_id};
|
2020-11-09 13:36:26 -08:00
|
|
|
return m_live_process->TraceGetBinaryData(request);
|
|
|
|
|
}
|
|
|
|
|
|
2022-03-21 15:46:19 -07:00
|
|
|
Expected<std::vector<uint8_t>>
|
2020-11-09 13:36:26 -08:00
|
|
|
Trace::GetLiveProcessBinaryData(llvm::StringRef kind) {
|
2023-01-07 14:18:35 -08:00
|
|
|
std::optional<uint64_t> size = GetLiveProcessBinaryDataSize(kind);
|
2020-11-09 13:36:26 -08:00
|
|
|
if (!size)
|
|
|
|
|
return createStringError(
|
|
|
|
|
inconvertibleErrorCode(),
|
|
|
|
|
"Tracing data \"%s\" is not available for the process.", kind.data());
|
|
|
|
|
|
2022-05-18 21:36:34 -07:00
|
|
|
TraceGetBinaryDataRequest request{GetPluginName().str(), kind.str(),
|
2022-12-04 16:51:25 -08:00
|
|
|
/*tid=*/std::nullopt,
|
|
|
|
|
/*cpu_id*/ std::nullopt};
|
2022-06-12 23:36:52 -07:00
|
|
|
return GetLiveTraceBinaryData(request, *size);
|
2020-11-09 13:36:26 -08:00
|
|
|
}
|
|
|
|
|
|
2022-06-08 11:39:31 -07:00
|
|
|
Trace::Storage &Trace::GetUpdatedStorage() {
|
|
|
|
|
RefreshLiveProcessState();
|
|
|
|
|
return m_storage;
|
|
|
|
|
}
|
|
|
|
|
|
2022-05-18 19:10:09 -07:00
|
|
|
const char *Trace::RefreshLiveProcessState() {
|
2020-11-09 13:36:26 -08:00
|
|
|
if (!m_live_process)
|
2022-05-18 19:10:09 -07:00
|
|
|
return nullptr;
|
2020-11-09 13:36:26 -08:00
|
|
|
|
|
|
|
|
uint32_t new_stop_id = m_live_process->GetStopID();
|
|
|
|
|
if (new_stop_id == m_stop_id)
|
2022-05-18 19:10:09 -07:00
|
|
|
return nullptr;
|
|
|
|
|
|
|
|
|
|
Log *log = GetLog(LLDBLog::Target);
|
|
|
|
|
LLDB_LOG(log, "Trace::RefreshLiveProcessState invoked");
|
2020-11-09 13:36:26 -08:00
|
|
|
|
|
|
|
|
m_stop_id = new_stop_id;
|
2022-06-08 11:39:31 -07:00
|
|
|
m_storage = Trace::Storage();
|
2022-05-18 19:10:09 -07:00
|
|
|
|
2022-06-12 23:36:52 -07:00
|
|
|
auto do_refresh = [&]() -> Error {
|
|
|
|
|
Expected<std::string> json_string = GetLiveProcessState();
|
|
|
|
|
if (!json_string)
|
|
|
|
|
return json_string.takeError();
|
2022-05-18 19:10:09 -07:00
|
|
|
|
2022-06-12 23:36:52 -07:00
|
|
|
Expected<TraceGetStateResponse> live_process_state =
|
|
|
|
|
json::parse<TraceGetStateResponse>(*json_string,
|
|
|
|
|
"TraceGetStateResponse");
|
|
|
|
|
if (!live_process_state)
|
|
|
|
|
return live_process_state.takeError();
|
2020-11-09 13:36:26 -08:00
|
|
|
|
2022-06-12 23:36:52 -07:00
|
|
|
if (live_process_state->warnings) {
|
|
|
|
|
for (std::string &warning : *live_process_state->warnings)
|
|
|
|
|
LLDB_LOG(log, "== Warning when fetching the trace state: {0}", warning);
|
|
|
|
|
}
|
2020-11-09 13:36:26 -08:00
|
|
|
|
2022-06-12 23:36:52 -07:00
|
|
|
for (const TraceThreadState &thread_state :
|
|
|
|
|
live_process_state->traced_threads) {
|
|
|
|
|
for (const TraceBinaryData &item : thread_state.binary_data)
|
|
|
|
|
m_storage.live_thread_data[thread_state.tid].insert(
|
|
|
|
|
{ConstString(item.kind), item.size});
|
|
|
|
|
}
|
2022-05-18 21:36:34 -07:00
|
|
|
|
2022-06-12 23:36:52 -07:00
|
|
|
LLDB_LOG(log, "== Found {0} threads being traced",
|
|
|
|
|
live_process_state->traced_threads.size());
|
|
|
|
|
|
2022-06-14 15:53:59 -07:00
|
|
|
if (live_process_state->cpus) {
|
|
|
|
|
m_storage.cpus.emplace();
|
|
|
|
|
for (const TraceCpuState &cpu_state : *live_process_state->cpus) {
|
|
|
|
|
m_storage.cpus->push_back(cpu_state.id);
|
|
|
|
|
for (const TraceBinaryData &item : cpu_state.binary_data)
|
|
|
|
|
m_storage.live_cpu_data_sizes[cpu_state.id].insert(
|
2022-06-12 23:36:52 -07:00
|
|
|
{ConstString(item.kind), item.size});
|
|
|
|
|
}
|
2022-06-14 15:53:59 -07:00
|
|
|
LLDB_LOG(log, "== Found {0} cpu cpus being traced",
|
|
|
|
|
live_process_state->cpus->size());
|
2022-05-18 21:36:34 -07:00
|
|
|
}
|
|
|
|
|
|
2022-06-12 23:36:52 -07:00
|
|
|
for (const TraceBinaryData &item : live_process_state->process_binary_data)
|
|
|
|
|
m_storage.live_process_data.insert({ConstString(item.kind), item.size});
|
2022-05-18 21:36:34 -07:00
|
|
|
|
2022-06-12 23:36:52 -07:00
|
|
|
return DoRefreshLiveProcessState(std::move(*live_process_state),
|
|
|
|
|
*json_string);
|
|
|
|
|
};
|
2020-11-09 13:36:26 -08:00
|
|
|
|
2022-06-12 23:36:52 -07:00
|
|
|
if (Error err = do_refresh()) {
|
|
|
|
|
m_storage.live_refresh_error = toString(std::move(err));
|
|
|
|
|
return m_storage.live_refresh_error->c_str();
|
|
|
|
|
}
|
2022-05-18 19:10:09 -07:00
|
|
|
|
|
|
|
|
return nullptr;
|
2020-11-09 13:36:26 -08:00
|
|
|
}
|
[trace] Add a TraceCursor class
As a follow up of D103588, I'm reinitiating the discussion with a new proposal for traversing instructions in a trace which uses the feedback gotten in that diff.
See the embedded documentation in TraceCursor for more information. The idea is to offer an OOP way to traverse instructions exposing a minimal interface that makes no assumptions on:
- the number of instructions in the trace (i.e. having indices for instructions might be impractical for gigantic intel-pt traces, as it would require to decode the entire trace). This renders the use of indices to point to instructions impractical. Traces are big and expensive, and the consumer should try to do look linear lookups (forwards and/or backwards) and avoid random accesses (the API could be extended though, but for now I want to dicard that funcionality and leave the API extensible if needed).
- the way the instructions are represented internally by each Trace plug-in. They could be mmap'ed from a file, exist in plain vector or generated on the fly as the user requests the data.
- the actual data structure used internally for each plug-in. Ideas like having a struct TraceInstruction have been discarded because that would make the plug-in follow a certain data type, which might be costly. Instead, the user can ask the cursor for each independent property of the instruction it's pointing at.
The way to get a cursor is to ask Trace.h for the end or being cursor or a thread's trace.
There are some benefits of this approach:
- there's little cost to create a cursor, and this allows for lazily decoding a trace as the user requests data.
- each trace plug-in could decide how to cache the instructions it generates. For example, if a trace is small, it might decide to keep everything in memory, or if the trace is massive, it might decide to keep around the last thousands of instructions to speed up local searches.
- a cursor can outlive a stop point, which makes trace comparison for live processes feasible. An application of this is to compare profiling data of two runs of the same function, which should be doable with intel pt.
Differential Revision: https://reviews.llvm.org/D104422
2021-06-16 14:09:46 -07:00
|
|
|
|
2022-05-18 21:36:34 -07:00
|
|
|
Trace::Trace(ArrayRef<ProcessSP> postmortem_processes,
|
2023-01-07 14:18:35 -08:00
|
|
|
std::optional<std::vector<lldb::cpu_id_t>> postmortem_cpus) {
|
2022-05-18 21:36:34 -07:00
|
|
|
for (ProcessSP process_sp : postmortem_processes)
|
2022-06-08 11:39:31 -07:00
|
|
|
m_storage.postmortem_processes.push_back(process_sp.get());
|
2022-06-14 15:53:59 -07:00
|
|
|
m_storage.cpus = postmortem_cpus;
|
2022-05-18 21:36:34 -07:00
|
|
|
}
|
|
|
|
|
|
2022-05-18 19:10:09 -07:00
|
|
|
Process *Trace::GetLiveProcess() { return m_live_process; }
|
|
|
|
|
|
2022-05-18 21:36:34 -07:00
|
|
|
ArrayRef<Process *> Trace::GetPostMortemProcesses() {
|
2022-06-08 11:39:31 -07:00
|
|
|
return m_storage.postmortem_processes;
|
2022-05-18 21:36:34 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
std::vector<Process *> Trace::GetAllProcesses() {
|
|
|
|
|
if (Process *proc = GetLiveProcess())
|
|
|
|
|
return {proc};
|
|
|
|
|
return GetPostMortemProcesses();
|
|
|
|
|
}
|
|
|
|
|
|
[trace] Add a TraceCursor class
As a follow up of D103588, I'm reinitiating the discussion with a new proposal for traversing instructions in a trace which uses the feedback gotten in that diff.
See the embedded documentation in TraceCursor for more information. The idea is to offer an OOP way to traverse instructions exposing a minimal interface that makes no assumptions on:
- the number of instructions in the trace (i.e. having indices for instructions might be impractical for gigantic intel-pt traces, as it would require to decode the entire trace). This renders the use of indices to point to instructions impractical. Traces are big and expensive, and the consumer should try to do look linear lookups (forwards and/or backwards) and avoid random accesses (the API could be extended though, but for now I want to dicard that funcionality and leave the API extensible if needed).
- the way the instructions are represented internally by each Trace plug-in. They could be mmap'ed from a file, exist in plain vector or generated on the fly as the user requests the data.
- the actual data structure used internally for each plug-in. Ideas like having a struct TraceInstruction have been discarded because that would make the plug-in follow a certain data type, which might be costly. Instead, the user can ask the cursor for each independent property of the instruction it's pointing at.
The way to get a cursor is to ask Trace.h for the end or being cursor or a thread's trace.
There are some benefits of this approach:
- there's little cost to create a cursor, and this allows for lazily decoding a trace as the user requests data.
- each trace plug-in could decide how to cache the instructions it generates. For example, if a trace is small, it might decide to keep everything in memory, or if the trace is massive, it might decide to keep around the last thousands of instructions to speed up local searches.
- a cursor can outlive a stop point, which makes trace comparison for live processes feasible. An application of this is to compare profiling data of two runs of the same function, which should be doable with intel pt.
Differential Revision: https://reviews.llvm.org/D104422
2021-06-16 14:09:46 -07:00
|
|
|
uint32_t Trace::GetStopID() {
|
|
|
|
|
RefreshLiveProcessState();
|
|
|
|
|
return m_stop_id;
|
|
|
|
|
}
|
[trace][intel pt] Create a common accessor for live and postmortem data
Some parts of the code have to distinguish between live and postmortem threads
to figure out how to get some data, e.g. thread trace buffers. This makes the
code less generic and more error prone. An example of that is that we have
two different decoders: LiveThreadDecoder and PostMortemThreadDecoder. They
exist because getting the trace bufer is different for each case.
The problem doesn't stop there. Soon we'll have even more kinds of data, like
the context switch trace, whose fetching will be different for live and post-
mortem processes.
As a way to fix this, I'm creating a common API for accessing thread data,
which is able to figure out how to handle the postmortem and live cases on
behalf of the caller. As a result of that, I was able to eliminate the two
decoders and unify them into a simpler one. Not only that, our TraceSave
functionality only worked for live threads, but now it can also work for
postmortem processes, which might be useful now, but it might in the future.
This common API is OnThreadBinaryDataRead. More information in the inline
documentation.
Differential Revision: https://reviews.llvm.org/D123281
2022-04-06 15:17:23 -07:00
|
|
|
|
|
|
|
|
llvm::Expected<FileSpec>
|
|
|
|
|
Trace::GetPostMortemThreadDataFile(lldb::tid_t tid, llvm::StringRef kind) {
|
2022-06-12 23:36:52 -07:00
|
|
|
Storage &storage = GetUpdatedStorage();
|
2023-01-07 14:18:35 -08:00
|
|
|
if (std::optional<FileSpec> file =
|
2022-06-15 10:35:00 -07:00
|
|
|
Lookup(storage.postmortem_thread_data, tid, ConstString(kind)))
|
2022-06-12 23:36:52 -07:00
|
|
|
return *file;
|
|
|
|
|
else
|
[trace][intel pt] Create a common accessor for live and postmortem data
Some parts of the code have to distinguish between live and postmortem threads
to figure out how to get some data, e.g. thread trace buffers. This makes the
code less generic and more error prone. An example of that is that we have
two different decoders: LiveThreadDecoder and PostMortemThreadDecoder. They
exist because getting the trace bufer is different for each case.
The problem doesn't stop there. Soon we'll have even more kinds of data, like
the context switch trace, whose fetching will be different for live and post-
mortem processes.
As a way to fix this, I'm creating a common API for accessing thread data,
which is able to figure out how to handle the postmortem and live cases on
behalf of the caller. As a result of that, I was able to eliminate the two
decoders and unify them into a simpler one. Not only that, our TraceSave
functionality only worked for live threads, but now it can also work for
postmortem processes, which might be useful now, but it might in the future.
This common API is OnThreadBinaryDataRead. More information in the inline
documentation.
Differential Revision: https://reviews.llvm.org/D123281
2022-04-06 15:17:23 -07:00
|
|
|
return createStringError(
|
|
|
|
|
inconvertibleErrorCode(),
|
|
|
|
|
formatv("The thread with tid={0} doesn't have the tracing data {1}",
|
|
|
|
|
tid, kind));
|
|
|
|
|
}
|
|
|
|
|
|
2022-06-14 15:53:59 -07:00
|
|
|
llvm::Expected<FileSpec> Trace::GetPostMortemCpuDataFile(lldb::cpu_id_t cpu_id,
|
|
|
|
|
llvm::StringRef kind) {
|
2022-06-12 23:36:52 -07:00
|
|
|
Storage &storage = GetUpdatedStorage();
|
2023-01-07 14:18:35 -08:00
|
|
|
if (std::optional<FileSpec> file =
|
2022-06-15 10:35:00 -07:00
|
|
|
Lookup(storage.postmortem_cpu_data, cpu_id, ConstString(kind)))
|
2022-06-12 23:36:52 -07:00
|
|
|
return *file;
|
|
|
|
|
else
|
2022-05-18 21:36:34 -07:00
|
|
|
return createStringError(
|
|
|
|
|
inconvertibleErrorCode(),
|
2022-06-14 15:53:59 -07:00
|
|
|
formatv("The cpu with id={0} doesn't have the tracing data {1}", cpu_id,
|
|
|
|
|
kind));
|
2022-05-18 21:36:34 -07:00
|
|
|
}
|
|
|
|
|
|
[trace][intel pt] Create a common accessor for live and postmortem data
Some parts of the code have to distinguish between live and postmortem threads
to figure out how to get some data, e.g. thread trace buffers. This makes the
code less generic and more error prone. An example of that is that we have
two different decoders: LiveThreadDecoder and PostMortemThreadDecoder. They
exist because getting the trace bufer is different for each case.
The problem doesn't stop there. Soon we'll have even more kinds of data, like
the context switch trace, whose fetching will be different for live and post-
mortem processes.
As a way to fix this, I'm creating a common API for accessing thread data,
which is able to figure out how to handle the postmortem and live cases on
behalf of the caller. As a result of that, I was able to eliminate the two
decoders and unify them into a simpler one. Not only that, our TraceSave
functionality only worked for live threads, but now it can also work for
postmortem processes, which might be useful now, but it might in the future.
This common API is OnThreadBinaryDataRead. More information in the inline
documentation.
Differential Revision: https://reviews.llvm.org/D123281
2022-04-06 15:17:23 -07:00
|
|
|
void Trace::SetPostMortemThreadDataFile(lldb::tid_t tid, llvm::StringRef kind,
|
|
|
|
|
FileSpec file_spec) {
|
2022-06-08 11:39:31 -07:00
|
|
|
Storage &storage = GetUpdatedStorage();
|
2022-06-12 23:36:52 -07:00
|
|
|
storage.postmortem_thread_data[tid].insert({ConstString(kind), file_spec});
|
[trace][intel pt] Create a common accessor for live and postmortem data
Some parts of the code have to distinguish between live and postmortem threads
to figure out how to get some data, e.g. thread trace buffers. This makes the
code less generic and more error prone. An example of that is that we have
two different decoders: LiveThreadDecoder and PostMortemThreadDecoder. They
exist because getting the trace bufer is different for each case.
The problem doesn't stop there. Soon we'll have even more kinds of data, like
the context switch trace, whose fetching will be different for live and post-
mortem processes.
As a way to fix this, I'm creating a common API for accessing thread data,
which is able to figure out how to handle the postmortem and live cases on
behalf of the caller. As a result of that, I was able to eliminate the two
decoders and unify them into a simpler one. Not only that, our TraceSave
functionality only worked for live threads, but now it can also work for
postmortem processes, which might be useful now, but it might in the future.
This common API is OnThreadBinaryDataRead. More information in the inline
documentation.
Differential Revision: https://reviews.llvm.org/D123281
2022-04-06 15:17:23 -07:00
|
|
|
}
|
|
|
|
|
|
2022-06-14 15:53:59 -07:00
|
|
|
void Trace::SetPostMortemCpuDataFile(lldb::cpu_id_t cpu_id,
|
|
|
|
|
llvm::StringRef kind, FileSpec file_spec) {
|
2022-06-08 11:39:31 -07:00
|
|
|
Storage &storage = GetUpdatedStorage();
|
2022-06-14 15:53:59 -07:00
|
|
|
storage.postmortem_cpu_data[cpu_id].insert({ConstString(kind), file_spec});
|
2022-05-18 21:36:34 -07:00
|
|
|
}
|
|
|
|
|
|
[trace][intel pt] Create a common accessor for live and postmortem data
Some parts of the code have to distinguish between live and postmortem threads
to figure out how to get some data, e.g. thread trace buffers. This makes the
code less generic and more error prone. An example of that is that we have
two different decoders: LiveThreadDecoder and PostMortemThreadDecoder. They
exist because getting the trace bufer is different for each case.
The problem doesn't stop there. Soon we'll have even more kinds of data, like
the context switch trace, whose fetching will be different for live and post-
mortem processes.
As a way to fix this, I'm creating a common API for accessing thread data,
which is able to figure out how to handle the postmortem and live cases on
behalf of the caller. As a result of that, I was able to eliminate the two
decoders and unify them into a simpler one. Not only that, our TraceSave
functionality only worked for live threads, but now it can also work for
postmortem processes, which might be useful now, but it might in the future.
This common API is OnThreadBinaryDataRead. More information in the inline
documentation.
Differential Revision: https://reviews.llvm.org/D123281
2022-04-06 15:17:23 -07:00
|
|
|
llvm::Error
|
|
|
|
|
Trace::OnLiveThreadBinaryDataRead(lldb::tid_t tid, llvm::StringRef kind,
|
|
|
|
|
OnBinaryDataReadCallback callback) {
|
|
|
|
|
Expected<std::vector<uint8_t>> data = GetLiveThreadBinaryData(tid, kind);
|
|
|
|
|
if (!data)
|
|
|
|
|
return data.takeError();
|
|
|
|
|
return callback(*data);
|
|
|
|
|
}
|
|
|
|
|
|
2022-06-14 15:53:59 -07:00
|
|
|
llvm::Error Trace::OnLiveCpuBinaryDataRead(lldb::cpu_id_t cpu_id,
|
|
|
|
|
llvm::StringRef kind,
|
|
|
|
|
OnBinaryDataReadCallback callback) {
|
2022-06-08 11:39:31 -07:00
|
|
|
Storage &storage = GetUpdatedStorage();
|
2022-06-14 15:53:59 -07:00
|
|
|
if (std::vector<uint8_t> *cpu_data =
|
2022-06-15 10:35:00 -07:00
|
|
|
LookupAsPtr(storage.live_cpu_data, cpu_id, ConstString(kind)))
|
2022-06-14 15:53:59 -07:00
|
|
|
return callback(*cpu_data);
|
2022-05-24 12:16:25 -07:00
|
|
|
|
2022-06-14 15:53:59 -07:00
|
|
|
Expected<std::vector<uint8_t>> data = GetLiveCpuBinaryData(cpu_id, kind);
|
2022-05-18 21:36:34 -07:00
|
|
|
if (!data)
|
|
|
|
|
return data.takeError();
|
2022-06-14 15:53:59 -07:00
|
|
|
auto it = storage.live_cpu_data[cpu_id].insert(
|
2022-06-12 23:36:52 -07:00
|
|
|
{ConstString(kind), std::move(*data)});
|
2022-05-24 12:16:25 -07:00
|
|
|
return callback(it.first->second);
|
2022-05-18 21:36:34 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
llvm::Error Trace::OnDataFileRead(FileSpec file,
|
|
|
|
|
OnBinaryDataReadCallback callback) {
|
[trace][intel pt] Create a common accessor for live and postmortem data
Some parts of the code have to distinguish between live and postmortem threads
to figure out how to get some data, e.g. thread trace buffers. This makes the
code less generic and more error prone. An example of that is that we have
two different decoders: LiveThreadDecoder and PostMortemThreadDecoder. They
exist because getting the trace bufer is different for each case.
The problem doesn't stop there. Soon we'll have even more kinds of data, like
the context switch trace, whose fetching will be different for live and post-
mortem processes.
As a way to fix this, I'm creating a common API for accessing thread data,
which is able to figure out how to handle the postmortem and live cases on
behalf of the caller. As a result of that, I was able to eliminate the two
decoders and unify them into a simpler one. Not only that, our TraceSave
functionality only worked for live threads, but now it can also work for
postmortem processes, which might be useful now, but it might in the future.
This common API is OnThreadBinaryDataRead. More information in the inline
documentation.
Differential Revision: https://reviews.llvm.org/D123281
2022-04-06 15:17:23 -07:00
|
|
|
ErrorOr<std::unique_ptr<MemoryBuffer>> trace_or_error =
|
2022-05-18 21:36:34 -07:00
|
|
|
MemoryBuffer::getFile(file.GetPath());
|
[trace][intel pt] Create a common accessor for live and postmortem data
Some parts of the code have to distinguish between live and postmortem threads
to figure out how to get some data, e.g. thread trace buffers. This makes the
code less generic and more error prone. An example of that is that we have
two different decoders: LiveThreadDecoder and PostMortemThreadDecoder. They
exist because getting the trace bufer is different for each case.
The problem doesn't stop there. Soon we'll have even more kinds of data, like
the context switch trace, whose fetching will be different for live and post-
mortem processes.
As a way to fix this, I'm creating a common API for accessing thread data,
which is able to figure out how to handle the postmortem and live cases on
behalf of the caller. As a result of that, I was able to eliminate the two
decoders and unify them into a simpler one. Not only that, our TraceSave
functionality only worked for live threads, but now it can also work for
postmortem processes, which might be useful now, but it might in the future.
This common API is OnThreadBinaryDataRead. More information in the inline
documentation.
Differential Revision: https://reviews.llvm.org/D123281
2022-04-06 15:17:23 -07:00
|
|
|
if (std::error_code err = trace_or_error.getError())
|
2022-06-08 11:39:31 -07:00
|
|
|
return createStringError(
|
|
|
|
|
inconvertibleErrorCode(), "Failed fetching trace-related file %s. %s",
|
2022-07-25 23:29:30 -07:00
|
|
|
file.GetPath().c_str(), toString(errorCodeToError(err)).c_str());
|
[trace][intel pt] Create a common accessor for live and postmortem data
Some parts of the code have to distinguish between live and postmortem threads
to figure out how to get some data, e.g. thread trace buffers. This makes the
code less generic and more error prone. An example of that is that we have
two different decoders: LiveThreadDecoder and PostMortemThreadDecoder. They
exist because getting the trace bufer is different for each case.
The problem doesn't stop there. Soon we'll have even more kinds of data, like
the context switch trace, whose fetching will be different for live and post-
mortem processes.
As a way to fix this, I'm creating a common API for accessing thread data,
which is able to figure out how to handle the postmortem and live cases on
behalf of the caller. As a result of that, I was able to eliminate the two
decoders and unify them into a simpler one. Not only that, our TraceSave
functionality only worked for live threads, but now it can also work for
postmortem processes, which might be useful now, but it might in the future.
This common API is OnThreadBinaryDataRead. More information in the inline
documentation.
Differential Revision: https://reviews.llvm.org/D123281
2022-04-06 15:17:23 -07:00
|
|
|
|
|
|
|
|
MemoryBuffer &data = **trace_or_error;
|
|
|
|
|
ArrayRef<uint8_t> array_ref(
|
|
|
|
|
reinterpret_cast<const uint8_t *>(data.getBufferStart()),
|
|
|
|
|
data.getBufferSize());
|
|
|
|
|
return callback(array_ref);
|
|
|
|
|
}
|
|
|
|
|
|
2022-05-18 21:36:34 -07:00
|
|
|
llvm::Error
|
|
|
|
|
Trace::OnPostMortemThreadBinaryDataRead(lldb::tid_t tid, llvm::StringRef kind,
|
|
|
|
|
OnBinaryDataReadCallback callback) {
|
2022-06-12 23:36:52 -07:00
|
|
|
if (Expected<FileSpec> file = GetPostMortemThreadDataFile(tid, kind))
|
|
|
|
|
return OnDataFileRead(*file, callback);
|
|
|
|
|
else
|
2022-05-18 21:36:34 -07:00
|
|
|
return file.takeError();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
llvm::Error
|
2022-06-14 15:53:59 -07:00
|
|
|
Trace::OnPostMortemCpuBinaryDataRead(lldb::cpu_id_t cpu_id,
|
|
|
|
|
llvm::StringRef kind,
|
|
|
|
|
OnBinaryDataReadCallback callback) {
|
|
|
|
|
if (Expected<FileSpec> file = GetPostMortemCpuDataFile(cpu_id, kind))
|
2022-06-12 23:36:52 -07:00
|
|
|
return OnDataFileRead(*file, callback);
|
|
|
|
|
else
|
2022-05-18 21:36:34 -07:00
|
|
|
return file.takeError();
|
|
|
|
|
}
|
|
|
|
|
|
[trace][intel pt] Create a common accessor for live and postmortem data
Some parts of the code have to distinguish between live and postmortem threads
to figure out how to get some data, e.g. thread trace buffers. This makes the
code less generic and more error prone. An example of that is that we have
two different decoders: LiveThreadDecoder and PostMortemThreadDecoder. They
exist because getting the trace bufer is different for each case.
The problem doesn't stop there. Soon we'll have even more kinds of data, like
the context switch trace, whose fetching will be different for live and post-
mortem processes.
As a way to fix this, I'm creating a common API for accessing thread data,
which is able to figure out how to handle the postmortem and live cases on
behalf of the caller. As a result of that, I was able to eliminate the two
decoders and unify them into a simpler one. Not only that, our TraceSave
functionality only worked for live threads, but now it can also work for
postmortem processes, which might be useful now, but it might in the future.
This common API is OnThreadBinaryDataRead. More information in the inline
documentation.
Differential Revision: https://reviews.llvm.org/D123281
2022-04-06 15:17:23 -07:00
|
|
|
llvm::Error Trace::OnThreadBinaryDataRead(lldb::tid_t tid, llvm::StringRef kind,
|
|
|
|
|
OnBinaryDataReadCallback callback) {
|
|
|
|
|
if (m_live_process)
|
|
|
|
|
return OnLiveThreadBinaryDataRead(tid, kind, callback);
|
|
|
|
|
else
|
|
|
|
|
return OnPostMortemThreadBinaryDataRead(tid, kind, callback);
|
|
|
|
|
}
|
2022-05-18 21:36:34 -07:00
|
|
|
|
2022-05-24 12:16:25 -07:00
|
|
|
llvm::Error
|
2022-06-14 15:53:59 -07:00
|
|
|
Trace::OnAllCpusBinaryDataRead(llvm::StringRef kind,
|
|
|
|
|
OnCpusBinaryDataReadCallback callback) {
|
|
|
|
|
DenseMap<cpu_id_t, ArrayRef<uint8_t>> buffers;
|
2022-06-08 11:39:31 -07:00
|
|
|
Storage &storage = GetUpdatedStorage();
|
2022-06-14 15:53:59 -07:00
|
|
|
if (!storage.cpus)
|
2022-06-08 11:39:31 -07:00
|
|
|
return Error::success();
|
2022-05-24 12:16:25 -07:00
|
|
|
|
2022-06-14 15:53:59 -07:00
|
|
|
std::function<Error(std::vector<cpu_id_t>::iterator)> process_cpu =
|
|
|
|
|
[&](std::vector<cpu_id_t>::iterator cpu_id) -> Error {
|
|
|
|
|
if (cpu_id == storage.cpus->end())
|
2022-05-24 12:16:25 -07:00
|
|
|
return callback(buffers);
|
|
|
|
|
|
2022-06-14 15:53:59 -07:00
|
|
|
return OnCpuBinaryDataRead(*cpu_id, kind,
|
|
|
|
|
[&](ArrayRef<uint8_t> data) -> Error {
|
|
|
|
|
buffers.try_emplace(*cpu_id, data);
|
|
|
|
|
auto next_id = cpu_id;
|
|
|
|
|
next_id++;
|
|
|
|
|
return process_cpu(next_id);
|
|
|
|
|
});
|
2022-05-24 12:16:25 -07:00
|
|
|
};
|
2022-06-14 15:53:59 -07:00
|
|
|
return process_cpu(storage.cpus->begin());
|
2022-05-24 12:16:25 -07:00
|
|
|
}
|
|
|
|
|
|
2022-06-14 15:53:59 -07:00
|
|
|
llvm::Error Trace::OnCpuBinaryDataRead(lldb::cpu_id_t cpu_id,
|
|
|
|
|
llvm::StringRef kind,
|
|
|
|
|
OnBinaryDataReadCallback callback) {
|
2022-05-18 21:36:34 -07:00
|
|
|
if (m_live_process)
|
2022-06-14 15:53:59 -07:00
|
|
|
return OnLiveCpuBinaryDataRead(cpu_id, kind, callback);
|
2022-05-18 21:36:34 -07:00
|
|
|
else
|
2022-06-14 15:53:59 -07:00
|
|
|
return OnPostMortemCpuBinaryDataRead(cpu_id, kind, callback);
|
2022-05-18 21:36:34 -07:00
|
|
|
}
|
|
|
|
|
|
2022-06-14 15:53:59 -07:00
|
|
|
ArrayRef<lldb::cpu_id_t> Trace::GetTracedCpus() {
|
2022-06-08 11:39:31 -07:00
|
|
|
Storage &storage = GetUpdatedStorage();
|
2022-06-14 15:53:59 -07:00
|
|
|
if (storage.cpus)
|
|
|
|
|
return *storage.cpus;
|
2022-05-18 21:36:34 -07:00
|
|
|
return {};
|
|
|
|
|
}
|
2022-06-03 13:23:26 -07:00
|
|
|
|
2022-06-08 11:39:31 -07:00
|
|
|
std::vector<Process *> Trace::GetTracedProcesses() {
|
2022-06-03 13:23:26 -07:00
|
|
|
std::vector<Process *> processes;
|
2022-06-08 11:39:31 -07:00
|
|
|
Storage &storage = GetUpdatedStorage();
|
2022-06-03 13:23:26 -07:00
|
|
|
|
2022-06-08 11:39:31 -07:00
|
|
|
for (Process *proc : storage.postmortem_processes)
|
2022-06-03 13:23:26 -07:00
|
|
|
processes.push_back(proc);
|
|
|
|
|
|
|
|
|
|
if (m_live_process)
|
|
|
|
|
processes.push_back(m_live_process);
|
|
|
|
|
return processes;
|
|
|
|
|
}
|