//===-- 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(const_cast(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 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(&SourceBin)) ProcessHeaders(O->getELFFile()); else if (auto *O = dyn_cast(&SourceBin)) ProcessHeaders(O->getELFFile()); else if (auto *O = dyn_cast(&SourceBin)) ProcessHeaders(O->getELFFile()); else if (auto *O = dyn_cast(&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 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> ETMDecoder::create(const object::Binary &Binary, const Triple &Triple, uint8_t TraceID) { auto Decoder = std::make_unique(Binary, Triple, TraceID); if (Error E = Decoder->initialize()) return std::move(E); return std::unique_ptr(std::move(Decoder)); } } // namespace llvm #else // !HAVE_OPENCSD namespace llvm { Expected> ETMDecoder::create(const object::Binary & /*Binary*/, const Triple & /*Triple*/, uint8_t /*TraceID*/) { return createStringError(inconvertibleErrorCode(), "OpenCSD not enabled."); } } // namespace llvm #endif // HAVE_OPENCSD