| //===-- 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 |