Files
llvm-project/llvm/lib/ProfileData/ETMTraceDecoder.cpp
gulfemsavrun 69c38be839 Reapply "Reland "[llvm-profgen] Add support for ETM trace decoding"" … (#194730)
…(#194695)

This relands the original commit
e3bd61890e (#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.
2026-04-28 20:46:51 -07:00

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