| 1 | //===-- ETMTraceDecoder.cpp - ETM Trace Decoder -----------------*- C++ -*-===// |
| 2 | // |
| 3 | // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. |
| 4 | // See https://llvm.org/LICENSE.txt for license information. |
| 5 | // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception |
| 6 | // |
| 7 | //===----------------------------------------------------------------------===// |
| 8 | |
| 9 | #include "llvm/ProfileData/ETMTraceDecoder.h" |
| 10 | #include "llvm/ADT/SmallVector.h" |
| 11 | #include "llvm/Object/ELFObjectFile.h" |
| 12 | #include "llvm/Object/ObjectFile.h" |
| 13 | #include "llvm/Support/Error.h" |
| 14 | #include "llvm/TargetParser/ARMTargetParser.h" |
| 15 | |
| 16 | #ifdef HAVE_OPENCSD |
| 17 | #include "opencsd/c_api/opencsd_c_api.h" |
| 18 | |
| 19 | namespace llvm { |
| 20 | |
| 21 | namespace { |
| 22 | |
| 23 | class HardwareTraceConfig { |
| 24 | public: |
| 25 | virtual ~HardwareTraceConfig() = default; |
| 26 | }; |
| 27 | |
| 28 | class ETMTraceConfig : public HardwareTraceConfig { |
| 29 | public: |
| 30 | ocsd_etmv4_cfg Cfg{}; |
| 31 | uint8_t TraceID; |
| 32 | |
| 33 | ETMTraceConfig(const Triple &TargetTriple, uint8_t TraceID) |
| 34 | : TraceID(TraceID) { |
| 35 | ocsd_arch_version_t ArchVer = ARCH_UNKNOWN; |
| 36 | if (TargetTriple.isArmMClass()) { |
| 37 | unsigned ArchVersion = ARM::parseArchVersion(TargetTriple.getArchName()); |
| 38 | if (ArchVersion >= 8) |
| 39 | ArchVer = ARCH_V8; |
| 40 | else if (ArchVersion == 7) |
| 41 | ArchVer = ARCH_V7; |
| 42 | else |
| 43 | // For version 6 (Cortex-M0) and others. |
| 44 | ArchVer = ARCH_UNKNOWN; |
| 45 | } |
| 46 | // Initialize the decoder for Arm M-profile targets. |
| 47 | Cfg.arch_ver = ArchVer; |
| 48 | Cfg.core_prof = profile_CortexM; |
| 49 | |
| 50 | // The CoreSight Trace ID (CSID) is a hardware-assigned 7-bit identifier |
| 51 | // used to route trace data. |
| 52 | Cfg.reg_traceidr = TraceID; |
| 53 | } |
| 54 | |
| 55 | Error validate() const { |
| 56 | if (Cfg.arch_ver == ARCH_UNKNOWN) |
| 57 | return createStringError( |
| 58 | inconvertibleErrorCode(), |
| 59 | "OpenCSD: Unsupported processor architecture. Only Arm M-profile " |
| 60 | "(Cortex-M) with ETM support is currently supported." ); |
| 61 | return Error::success(); |
| 62 | } |
| 63 | }; |
| 64 | |
| 65 | class ETMDecoderImpl : public ETMDecoder { |
| 66 | dcd_tree_handle_t DcdTree = 0; |
| 67 | const object::Binary &Binary; |
| 68 | const Triple &TargetTriple; |
| 69 | |
| 70 | // Trace processing and Callback handling. |
| 71 | static ocsd_datapath_resp_t |
| 72 | processTrace(const void *PContext, const ocsd_trc_index_t /*IndexSOP*/, |
| 73 | const uint8_t /*TrcChanID*/, |
| 74 | const ocsd_generic_trace_elem *Element) { |
| 75 | auto *Decoder = static_cast<ETMDecoderImpl *>(const_cast<void *>(PContext)); |
| 76 | if (!Decoder || !Element) |
| 77 | return OCSD_RESP_FATAL_SYS_ERR; |
| 78 | |
| 79 | // Process instruction ranges reconstructed by the decoder. |
| 80 | if (Element->elem_type == OCSD_GEN_TRC_ELEM_INSTR_RANGE) { |
| 81 | uint64_t Start = Element->st_addr; |
| 82 | uint64_t End = Element->en_addr; |
| 83 | if (End > Start) { |
| 84 | // OpenCSD ranges are exclusive at the end [Start, End). |
| 85 | // llvm-profgen range counters expect inclusive bounds [Start, End]. |
| 86 | // Adjust the exclusive end address provided by OpenCSD to include |
| 87 | // the last executed instruction within the reported range. |
| 88 | Decoder->CurrentCallback->processInstructionRange(Start, End - 1); |
| 89 | } |
| 90 | } |
| 91 | return OCSD_RESP_CONT; |
| 92 | } |
| 93 | |
| 94 | Callback *CurrentCallback = nullptr; |
| 95 | |
| 96 | // Iterate through the ELF program headers to collect all executable LOAD |
| 97 | // segments. These are registered as a single transaction to the OpenCSD |
| 98 | // memory manager to prevent overlap/collision errors between different |
| 99 | // memory regions. |
| 100 | Error mapELFSegments(dcd_tree_handle_t DcdTree, |
| 101 | const object::Binary &SourceBin) { |
| 102 | SmallVector<ocsd_file_mem_region_t, 4> Regions; |
| 103 | auto ProcessHeaders = [&](const auto &ElfFile) { |
| 104 | auto ProgramHeaders = ElfFile.program_headers(); |
| 105 | if (!ProgramHeaders) |
| 106 | return; |
| 107 | |
| 108 | for (const auto &Phdr : *ProgramHeaders) { |
| 109 | if (Phdr.p_type == llvm::ELF::PT_LOAD && |
| 110 | (Phdr.p_flags & llvm::ELF::PF_X)) { |
| 111 | ocsd_file_mem_region_t Region{}; |
| 112 | Region.start_address = (uint64_t)Phdr.p_vaddr; |
| 113 | Region.file_offset = (uint64_t)Phdr.p_offset; |
| 114 | Region.region_size = (uint64_t)Phdr.p_filesz; |
| 115 | Regions.push_back(Region); |
| 116 | } |
| 117 | } |
| 118 | }; |
| 119 | |
| 120 | if (auto *O = dyn_cast<object::ELF32LEObjectFile>(&SourceBin)) |
| 121 | ProcessHeaders(O->getELFFile()); |
| 122 | else if (auto *O = dyn_cast<object::ELF64LEObjectFile>(&SourceBin)) |
| 123 | ProcessHeaders(O->getELFFile()); |
| 124 | else if (auto *O = dyn_cast<object::ELF32BEObjectFile>(&SourceBin)) |
| 125 | ProcessHeaders(O->getELFFile()); |
| 126 | else if (auto *O = dyn_cast<object::ELF64BEObjectFile>(&SourceBin)) |
| 127 | ProcessHeaders(O->getELFFile()); |
| 128 | |
| 129 | if (!Regions.empty()) { |
| 130 | std::string Path = SourceBin.getFileName().str(); |
| 131 | if (ocsd_dt_add_binfile_region_mem_acc( |
| 132 | DcdTree, Regions.data(), (uint32_t)Regions.size(), |
| 133 | OCSD_MEM_SPACE_ANY, Path.c_str()) != 0) { |
| 134 | return createStringError( |
| 135 | inconvertibleErrorCode(), |
| 136 | "OpenCSD: Failed to map ELF executable segments." ); |
| 137 | } |
| 138 | } |
| 139 | return Error::success(); |
| 140 | } |
| 141 | |
| 142 | public: |
| 143 | uint8_t TraceID; |
| 144 | |
| 145 | ETMDecoderImpl(const object::Binary &Binary, const Triple &Triple, |
| 146 | uint8_t TraceID) |
| 147 | : Binary(Binary), TargetTriple(Triple), TraceID(TraceID) {} |
| 148 | |
| 149 | ~ETMDecoderImpl() override { |
| 150 | if (DcdTree) |
| 151 | // Deallocate the decoder tree resources. |
| 152 | ocsd_destroy_dcd_tree(DcdTree); |
| 153 | } |
| 154 | |
| 155 | // Initialize the decoder by auto-detecting the target architecture and |
| 156 | // configuring the OpenCSD decoder. |
| 157 | Error initialize() { |
| 158 | DcdTree = ocsd_create_dcd_tree(OCSD_TRC_SRC_SINGLE, 0); |
| 159 | if (!DcdTree) |
| 160 | return createStringError(inconvertibleErrorCode(), |
| 161 | "Failed to create OpenCSD decoder tree." ); |
| 162 | |
| 163 | // Configure and initialize the instruction-level decoder. |
| 164 | ETMTraceConfig Config(TargetTriple, TraceID); |
| 165 | if (Error E = Config.validate()) |
| 166 | return E; |
| 167 | |
| 168 | uint32_t Flags = |
| 169 | OCSD_CREATE_FLG_FULL_DECODER | OCSD_OPFLG_CHK_RANGE_CONTINUE; |
| 170 | if (ocsd_dt_create_decoder(DcdTree, OCSD_BUILTIN_DCD_ETMV4I, Flags, |
| 171 | (void *)&Config.Cfg, &Config.TraceID) != 0) |
| 172 | return createStringError( |
| 173 | inconvertibleErrorCode(), |
| 174 | "OpenCSD: Failed to initialize the instruction decoder." ); |
| 175 | |
| 176 | // Extract and map executable segments from the ELF binary. |
| 177 | if (Error E = mapELFSegments(DcdTree, Binary)) |
| 178 | return E; |
| 179 | |
| 180 | // Register the high-level packet callback. The 'processTrace' function |
| 181 | // will be invoked for every decoded instruction range. |
| 182 | ocsd_dt_set_gen_elem_outfn(DcdTree, processTrace, this); |
| 183 | return Error::success(); |
| 184 | } |
| 185 | |
| 186 | Error processTrace(ArrayRef<uint8_t> TraceData, |
| 187 | Callback &TraceCallback) override { |
| 188 | CurrentCallback = &TraceCallback; |
| 189 | // Initial reset to prime the decoder. |
| 190 | ocsd_dt_process_data(DcdTree, OCSD_OP_RESET, 0, 0, nullptr, nullptr); |
| 191 | |
| 192 | const uint8_t *DataPtr = TraceData.data(); |
| 193 | uint32_t TotalSize = TraceData.size(); |
| 194 | uint32_t Processed = 0; |
| 195 | |
| 196 | // Core Decoding Loop. |
| 197 | while (Processed < TotalSize) { |
| 198 | uint32_t Consumed = 0; |
| 199 | uint32_t Remaining = TotalSize - Processed; |
| 200 | ocsd_datapath_resp_t Response = |
| 201 | ocsd_dt_process_data(DcdTree, OCSD_OP_DATA, Processed, Remaining, |
| 202 | DataPtr + Processed, &Consumed); |
| 203 | |
| 204 | if (Response == OCSD_RESP_WAIT) { |
| 205 | // Decoder buffers are full; flush to drain internal states. |
| 206 | ocsd_dt_process_data(DcdTree, OCSD_OP_FLUSH, 0, 0, nullptr, nullptr); |
| 207 | } else if (Consumed == 0 && Processed < TotalSize) { |
| 208 | // Decoder stalled; skip byte and reset to find next sync point. |
| 209 | Processed++; |
| 210 | ocsd_dt_process_data(DcdTree, OCSD_OP_RESET, 0, 0, nullptr, nullptr); |
| 211 | } else { |
| 212 | // Successfully consumed bytes of the bitstream. |
| 213 | Processed += Consumed; |
| 214 | } |
| 215 | |
| 216 | if (Response >= OCSD_RESP_FATAL_INVALID_DATA) |
| 217 | return createStringError(inconvertibleErrorCode(), |
| 218 | "OpenCSD: Fatal decoding error." ); |
| 219 | } |
| 220 | |
| 221 | // Finalize the decoding session by flushing the EOT (End of Trace) marker. |
| 222 | ocsd_dt_process_data(DcdTree, OCSD_OP_EOT, 0, 0, nullptr, nullptr); |
| 223 | return Error::success(); |
| 224 | } |
| 225 | }; |
| 226 | } // namespace |
| 227 | |
| 228 | Expected<std::unique_ptr<ETMDecoder>> |
| 229 | ETMDecoder::create(const object::Binary &Binary, const Triple &Triple, |
| 230 | uint8_t TraceID) { |
| 231 | auto Decoder = std::make_unique<ETMDecoderImpl>(Binary, Triple, TraceID); |
| 232 | if (Error E = Decoder->initialize()) |
| 233 | return std::move(E); |
| 234 | return std::unique_ptr<ETMDecoder>(std::move(Decoder)); |
| 235 | } |
| 236 | |
| 237 | } // namespace llvm |
| 238 | |
| 239 | #else // !HAVE_OPENCSD |
| 240 | |
| 241 | namespace llvm { |
| 242 | |
| 243 | Expected<std::unique_ptr<ETMDecoder>> |
| 244 | ETMDecoder::create(const object::Binary & /*Binary*/, const Triple & /*Triple*/, |
| 245 | uint8_t /*TraceID*/) { |
| 246 | return createStringError(EC: inconvertibleErrorCode(), S: "OpenCSD not enabled." ); |
| 247 | } |
| 248 | |
| 249 | } // namespace llvm |
| 250 | |
| 251 | #endif // HAVE_OPENCSD |
| 252 | |