…(#194695) This relands the original commite3bd61890e(#191584). The original change was reverted in the following commits: 1)ec9d7d18bd(#194087) 2)c26ae41c87(#194695) This reland incorporates the following fixes: 1) Change LLVM_ENABLE_OPENCSD to default to OFF (opt-in). 2) Parse the OpenCSD version from ocsd_if_version.h and gate OpenCSD support on a minimum version of 1.5.4 to avoid compilation errors. 3) Refactor the test feature detection to use a configure-time LLVM_HAVE_OPENCSD variable instead of runtime checks in lit.local.cfg.
252 lines
8.7 KiB
C++
252 lines
8.7 KiB
C++
//===-- ETMTraceDecoder.cpp - ETM Trace Decoder -----------------*- C++ -*-===//
|
|
//
|
|
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
|
|
// See https://llvm.org/LICENSE.txt for license information.
|
|
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
|
|
//
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
#include "llvm/ProfileData/ETMTraceDecoder.h"
|
|
#include "llvm/ADT/SmallVector.h"
|
|
#include "llvm/Object/ELFObjectFile.h"
|
|
#include "llvm/Object/ObjectFile.h"
|
|
#include "llvm/Support/Error.h"
|
|
#include "llvm/TargetParser/ARMTargetParser.h"
|
|
|
|
#ifdef HAVE_OPENCSD
|
|
#include "opencsd/c_api/opencsd_c_api.h"
|
|
|
|
namespace llvm {
|
|
|
|
namespace {
|
|
|
|
class HardwareTraceConfig {
|
|
public:
|
|
virtual ~HardwareTraceConfig() = default;
|
|
};
|
|
|
|
class ETMTraceConfig : public HardwareTraceConfig {
|
|
public:
|
|
ocsd_etmv4_cfg Cfg{};
|
|
uint8_t TraceID;
|
|
|
|
ETMTraceConfig(const Triple &TargetTriple, uint8_t TraceID)
|
|
: TraceID(TraceID) {
|
|
ocsd_arch_version_t ArchVer = ARCH_UNKNOWN;
|
|
if (TargetTriple.isArmMClass()) {
|
|
unsigned ArchVersion = ARM::parseArchVersion(TargetTriple.getArchName());
|
|
if (ArchVersion >= 8)
|
|
ArchVer = ARCH_V8;
|
|
else if (ArchVersion == 7)
|
|
ArchVer = ARCH_V7;
|
|
else
|
|
// For version 6 (Cortex-M0) and others.
|
|
ArchVer = ARCH_UNKNOWN;
|
|
}
|
|
// Initialize the decoder for Arm M-profile targets.
|
|
Cfg.arch_ver = ArchVer;
|
|
Cfg.core_prof = profile_CortexM;
|
|
|
|
// The CoreSight Trace ID (CSID) is a hardware-assigned 7-bit identifier
|
|
// used to route trace data.
|
|
Cfg.reg_traceidr = TraceID;
|
|
}
|
|
|
|
Error validate() const {
|
|
if (Cfg.arch_ver == ARCH_UNKNOWN)
|
|
return createStringError(
|
|
inconvertibleErrorCode(),
|
|
"OpenCSD: Unsupported processor architecture. Only Arm M-profile "
|
|
"(Cortex-M) with ETM support is currently supported.");
|
|
return Error::success();
|
|
}
|
|
};
|
|
|
|
class ETMDecoderImpl : public ETMDecoder {
|
|
dcd_tree_handle_t DcdTree = 0;
|
|
const object::Binary &Binary;
|
|
const Triple &TargetTriple;
|
|
|
|
// Trace processing and Callback handling.
|
|
static ocsd_datapath_resp_t
|
|
processTrace(const void *PContext, const ocsd_trc_index_t /*IndexSOP*/,
|
|
const uint8_t /*TrcChanID*/,
|
|
const ocsd_generic_trace_elem *Element) {
|
|
auto *Decoder = static_cast<ETMDecoderImpl *>(const_cast<void *>(PContext));
|
|
if (!Decoder || !Element)
|
|
return OCSD_RESP_FATAL_SYS_ERR;
|
|
|
|
// Process instruction ranges reconstructed by the decoder.
|
|
if (Element->elem_type == OCSD_GEN_TRC_ELEM_INSTR_RANGE) {
|
|
uint64_t Start = Element->st_addr;
|
|
uint64_t End = Element->en_addr;
|
|
if (End > Start) {
|
|
// OpenCSD ranges are exclusive at the end [Start, End).
|
|
// llvm-profgen range counters expect inclusive bounds [Start, End].
|
|
// Adjust the exclusive end address provided by OpenCSD to include
|
|
// the last executed instruction within the reported range.
|
|
Decoder->CurrentCallback->processInstructionRange(Start, End - 1);
|
|
}
|
|
}
|
|
return OCSD_RESP_CONT;
|
|
}
|
|
|
|
Callback *CurrentCallback = nullptr;
|
|
|
|
// Iterate through the ELF program headers to collect all executable LOAD
|
|
// segments. These are registered as a single transaction to the OpenCSD
|
|
// memory manager to prevent overlap/collision errors between different
|
|
// memory regions.
|
|
Error mapELFSegments(dcd_tree_handle_t DcdTree,
|
|
const object::Binary &SourceBin) {
|
|
SmallVector<ocsd_file_mem_region_t, 4> Regions;
|
|
auto ProcessHeaders = [&](const auto &ElfFile) {
|
|
auto ProgramHeaders = ElfFile.program_headers();
|
|
if (!ProgramHeaders)
|
|
return;
|
|
|
|
for (const auto &Phdr : *ProgramHeaders) {
|
|
if (Phdr.p_type == llvm::ELF::PT_LOAD &&
|
|
(Phdr.p_flags & llvm::ELF::PF_X)) {
|
|
ocsd_file_mem_region_t Region{};
|
|
Region.start_address = (uint64_t)Phdr.p_vaddr;
|
|
Region.file_offset = (uint64_t)Phdr.p_offset;
|
|
Region.region_size = (uint64_t)Phdr.p_filesz;
|
|
Regions.push_back(Region);
|
|
}
|
|
}
|
|
};
|
|
|
|
if (auto *O = dyn_cast<object::ELF32LEObjectFile>(&SourceBin))
|
|
ProcessHeaders(O->getELFFile());
|
|
else if (auto *O = dyn_cast<object::ELF64LEObjectFile>(&SourceBin))
|
|
ProcessHeaders(O->getELFFile());
|
|
else if (auto *O = dyn_cast<object::ELF32BEObjectFile>(&SourceBin))
|
|
ProcessHeaders(O->getELFFile());
|
|
else if (auto *O = dyn_cast<object::ELF64BEObjectFile>(&SourceBin))
|
|
ProcessHeaders(O->getELFFile());
|
|
|
|
if (!Regions.empty()) {
|
|
std::string Path = SourceBin.getFileName().str();
|
|
if (ocsd_dt_add_binfile_region_mem_acc(
|
|
DcdTree, Regions.data(), (uint32_t)Regions.size(),
|
|
OCSD_MEM_SPACE_ANY, Path.c_str()) != 0) {
|
|
return createStringError(
|
|
inconvertibleErrorCode(),
|
|
"OpenCSD: Failed to map ELF executable segments.");
|
|
}
|
|
}
|
|
return Error::success();
|
|
}
|
|
|
|
public:
|
|
uint8_t TraceID;
|
|
|
|
ETMDecoderImpl(const object::Binary &Binary, const Triple &Triple,
|
|
uint8_t TraceID)
|
|
: Binary(Binary), TargetTriple(Triple), TraceID(TraceID) {}
|
|
|
|
~ETMDecoderImpl() override {
|
|
if (DcdTree)
|
|
// Deallocate the decoder tree resources.
|
|
ocsd_destroy_dcd_tree(DcdTree);
|
|
}
|
|
|
|
// Initialize the decoder by auto-detecting the target architecture and
|
|
// configuring the OpenCSD decoder.
|
|
Error initialize() {
|
|
DcdTree = ocsd_create_dcd_tree(OCSD_TRC_SRC_SINGLE, 0);
|
|
if (!DcdTree)
|
|
return createStringError(inconvertibleErrorCode(),
|
|
"Failed to create OpenCSD decoder tree.");
|
|
|
|
// Configure and initialize the instruction-level decoder.
|
|
ETMTraceConfig Config(TargetTriple, TraceID);
|
|
if (Error E = Config.validate())
|
|
return E;
|
|
|
|
uint32_t Flags =
|
|
OCSD_CREATE_FLG_FULL_DECODER | OCSD_OPFLG_CHK_RANGE_CONTINUE;
|
|
if (ocsd_dt_create_decoder(DcdTree, OCSD_BUILTIN_DCD_ETMV4I, Flags,
|
|
(void *)&Config.Cfg, &Config.TraceID) != 0)
|
|
return createStringError(
|
|
inconvertibleErrorCode(),
|
|
"OpenCSD: Failed to initialize the instruction decoder.");
|
|
|
|
// Extract and map executable segments from the ELF binary.
|
|
if (Error E = mapELFSegments(DcdTree, Binary))
|
|
return E;
|
|
|
|
// Register the high-level packet callback. The 'processTrace' function
|
|
// will be invoked for every decoded instruction range.
|
|
ocsd_dt_set_gen_elem_outfn(DcdTree, processTrace, this);
|
|
return Error::success();
|
|
}
|
|
|
|
Error processTrace(ArrayRef<uint8_t> TraceData,
|
|
Callback &TraceCallback) override {
|
|
CurrentCallback = &TraceCallback;
|
|
// Initial reset to prime the decoder.
|
|
ocsd_dt_process_data(DcdTree, OCSD_OP_RESET, 0, 0, nullptr, nullptr);
|
|
|
|
const uint8_t *DataPtr = TraceData.data();
|
|
uint32_t TotalSize = TraceData.size();
|
|
uint32_t Processed = 0;
|
|
|
|
// Core Decoding Loop.
|
|
while (Processed < TotalSize) {
|
|
uint32_t Consumed = 0;
|
|
uint32_t Remaining = TotalSize - Processed;
|
|
ocsd_datapath_resp_t Response =
|
|
ocsd_dt_process_data(DcdTree, OCSD_OP_DATA, Processed, Remaining,
|
|
DataPtr + Processed, &Consumed);
|
|
|
|
if (Response == OCSD_RESP_WAIT) {
|
|
// Decoder buffers are full; flush to drain internal states.
|
|
ocsd_dt_process_data(DcdTree, OCSD_OP_FLUSH, 0, 0, nullptr, nullptr);
|
|
} else if (Consumed == 0 && Processed < TotalSize) {
|
|
// Decoder stalled; skip byte and reset to find next sync point.
|
|
Processed++;
|
|
ocsd_dt_process_data(DcdTree, OCSD_OP_RESET, 0, 0, nullptr, nullptr);
|
|
} else {
|
|
// Successfully consumed bytes of the bitstream.
|
|
Processed += Consumed;
|
|
}
|
|
|
|
if (Response >= OCSD_RESP_FATAL_INVALID_DATA)
|
|
return createStringError(inconvertibleErrorCode(),
|
|
"OpenCSD: Fatal decoding error.");
|
|
}
|
|
|
|
// Finalize the decoding session by flushing the EOT (End of Trace) marker.
|
|
ocsd_dt_process_data(DcdTree, OCSD_OP_EOT, 0, 0, nullptr, nullptr);
|
|
return Error::success();
|
|
}
|
|
};
|
|
} // namespace
|
|
|
|
Expected<std::unique_ptr<ETMDecoder>>
|
|
ETMDecoder::create(const object::Binary &Binary, const Triple &Triple,
|
|
uint8_t TraceID) {
|
|
auto Decoder = std::make_unique<ETMDecoderImpl>(Binary, Triple, TraceID);
|
|
if (Error E = Decoder->initialize())
|
|
return std::move(E);
|
|
return std::unique_ptr<ETMDecoder>(std::move(Decoder));
|
|
}
|
|
|
|
} // namespace llvm
|
|
|
|
#else // !HAVE_OPENCSD
|
|
|
|
namespace llvm {
|
|
|
|
Expected<std::unique_ptr<ETMDecoder>>
|
|
ETMDecoder::create(const object::Binary & /*Binary*/, const Triple & /*Triple*/,
|
|
uint8_t /*TraceID*/) {
|
|
return createStringError(inconvertibleErrorCode(), "OpenCSD not enabled.");
|
|
}
|
|
|
|
} // namespace llvm
|
|
|
|
#endif // HAVE_OPENCSD
|