rocprofiler-sdk/cxx/codeobj/funcmap.hpp Source File

rocprofiler-sdk/cxx/codeobj/funcmap.hpp Source File#

ROCprofiler-SDK developer API: rocprofiler-sdk/cxx/codeobj/funcmap.hpp Source File
ROCprofiler-SDK developer API 1.4.1
ROCm Profiling API and tools
funcmap.hpp
1// MIT License
2//
3// Copyright (c) 2026 Advanced Micro Devices, Inc. All rights reserved.
4//
5// Permission is hereby granted, free of charge, to any person obtaining a copy
6// of this software and associated documentation files (the "Software"), to deal
7// in the Software without restriction, including without limitation the rights
8// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9// copies of the Software, and to permit persons to whom the Software is
10// furnished to do so, subject to the following conditions:
11//
12// The above copyright notice and this permission notice shall be included in all
13// copies or substantial portions of the Software.
14//
15// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21// SOFTWARE.
22
23#pragma once
24
25#include <elf.h>
26
27#include <cctype>
28#include <cstdint>
29#include <cstring>
30#include <iostream>
31#include <memory>
32#include <optional>
33#include <string>
34#include <string_view>
35#include <unordered_map>
36#include <utility>
37#include <vector>
38
39// Decoder-side reader for the `.sqtt_funcmap` ELF section emitted by the
40// sqtt_instrumentation LLVM pass. The pass writes one ASCII row per
41// instrumented function/marker; at runtime the matching ID surfaces in the
42// trace as `rocprofiler_thread_trace_decoder_shaderdata_t::value`
43// (see trace_decoder_types.h: bit 0 = exit_prev, bit 1 = is_enter,
44// bits [31:2] = ID -- or [7:2] when emitted via s_ttracedata_imm).
45
46namespace rocprofiler
47{
48namespace sdk
49{
50namespace codeobj
51{
52namespace funcmap
53{
54// --- Declarations -----------------------------------------------------------
55
56enum class FuncmapEntryKind
57{
58 Function, // F:ID:name[@source_loc] -- instrumented device function (entry/exit scope)
59 Kernel, // K:name[@source_loc] -- kernel name (no ID -- for vaddr lookup only)
60 UserScope, // U:ID:name -- named user scope marker
61 Point // P:ID:name[@source_loc] -- point marker (barrier, memory op, addr trace, ...)
62};
63
64struct FuncmapEntry
65{
66 FuncmapEntryKind kind{};
67 uint32_t id{0}; // 0 for Kernel rows (no ID)
68 std::string name{};
69 std::string source_loc{}; // empty if absent
70 uint64_t vaddr{0}; // resolved by CodeobjDecoderComponent; 0 if unresolved
71};
72
73struct Funcmap
74{
75 using EntryPtr = std::shared_ptr<const FuncmapEntry>;
76
77 std::vector<EntryPtr> entries{}; // owns rows, stable insertion order
78 std::unordered_map<uint32_t, EntryPtr> by_id{}; // ID -> entry; last-writer-wins on dup
79 uint32_t wave_size{0}; // 0 if no `W:` row
80
81 // Returns the entry for `marker_id` (refcount bump, no string copy), or
82 // nullptr if absent.
83 EntryPtr find(uint32_t marker_id) const;
84};
85
86struct MarkerValue
87{
88 uint32_t id;
89 bool is_enter;
90 bool exit_prev;
91};
92
93// Parse the `.sqtt_funcmap` ASCII blob. Parsing is best-effort: malformed rows
94// are echoed to std::cerr unless silent=true, and parsing continues with the
95// next row.
96inline Funcmap
97parse_funcmap_section(std::string_view blob, bool silent = false);
98
99// Extract a section's bytes from an in-memory ELF64 image. Returns nullopt
100// when the section is absent (common case -- non-instrumented binaries) OR
101// when the ELF header is rejected as malformed.
102inline std::optional<std::string_view>
103extract_elf_section(const char* elf_data, size_t elf_size, std::string_view section_name);
104
105// Decode a marker value emitted by an `s_ttracedata`/`s_ttracedata_imm`
106// instruction (see trace_decoder_types.h:210).
107constexpr MarkerValue
108decode_marker_value(uint32_t v) noexcept;
109
110// --- Inline definitions -----------------------------------------------------
111
112inline Funcmap::EntryPtr
113Funcmap::find(uint32_t marker_id) const
114{
115 auto it = by_id.find(marker_id);
116 return (it == by_id.end()) ? nullptr : it->second;
117}
118
119constexpr MarkerValue
120decode_marker_value(uint32_t v) noexcept
121{
122 return MarkerValue{v >> 2, bool((v >> 1) & 1u), bool(v & 1u)};
123}
124
125namespace detail
126{
127inline void
128emit_warning(const std::string& msg, size_t line_no, bool silent)
129{
130 if(silent) return;
131 std::cerr << "rocprofiler-sdk: .sqtt_funcmap warning";
132 if(line_no != 0) std::cerr << " (line " << line_no << ')';
133 std::cerr << ": " << msg << '\n';
134}
135
136inline std::string_view
137rstrip_ws(std::string_view s) noexcept
138{
139 while(!s.empty() &&
140 (s.back() == '\r' || s.back() == ' ' || s.back() == '\t' || s.back() == '\0'))
141 s.remove_suffix(1);
142 return s;
143}
144
145// Split ID prefix for F/U/P rows. Returns {id, name+optional@loc} or nullopt
146// if the ID failed to parse.
147inline std::optional<std::pair<uint32_t, std::string_view>>
148split_id_payload(std::string_view payload)
149{
150 size_t colon = payload.find(':');
151 if(colon == std::string_view::npos) return std::nullopt;
152
153 std::string_view id_str = payload.substr(0, colon);
154 std::string_view name_loc = payload.substr(colon + 1);
155 if(id_str.empty()) return std::nullopt;
156
157 uint64_t id = 0;
158 for(char c : id_str)
159 {
160 if(c < '0' || c > '9') return std::nullopt;
161 id = id * 10 + uint64_t(c - '0');
162 if(id > 0xFFFFFFFFull) return std::nullopt;
163 }
164 return std::make_pair(uint32_t(id), name_loc);
165}
166
167inline std::pair<std::string, std::string>
168split_name_loc(std::string_view name_loc)
169{
170 size_t at = name_loc.find('@');
171 if(at == std::string_view::npos) return {std::string(name_loc), std::string{}};
172 return {std::string(name_loc.substr(0, at)), std::string(name_loc.substr(at + 1))};
173}
174} // namespace detail
175
176inline Funcmap
177parse_funcmap_section(std::string_view blob, bool silent)
178{
179 Funcmap out;
180 size_t line_no = 0;
181 size_t pos = 0;
182
183 while(pos <= blob.size())
184 {
185 // Find next newline OR end of blob OR embedded NUL.
186 size_t end = pos;
187 while(end < blob.size() && blob[end] != '\n' && blob[end] != '\0')
188 ++end;
189 std::string_view line = detail::rstrip_ws(blob.substr(pos, end - pos));
190 ++line_no;
191
192 // Advance past the terminator (or stop if we ran off the end).
193 if(end >= blob.size())
194 {
195 pos = end + 1;
196 if(line.empty()) break;
197 }
198 else
199 {
200 pos = end + 1;
201 }
202
203 if(line.empty()) continue;
204
205 // Need at minimum a one-char prefix and a ':'.
206 if(line.size() < 2 || line[1] != ':')
207 {
208 detail::emit_warning(
209 "malformed row (no prefix:): \"" + std::string(line) + '"', line_no, silent);
210 continue;
211 }
212
213 char prefix = line[0];
214 std::string_view payload = line.substr(2);
215
216 auto record =
217 [&](FuncmapEntryKind kind, uint32_t id, std::string name, std::string source_loc) {
218 auto entry = std::make_shared<FuncmapEntry>(
219 FuncmapEntry{kind, id, std::move(name), std::move(source_loc), 0});
220 out.entries.push_back(entry);
221
222 if(kind == FuncmapEntryKind::Kernel) return; // K rows have no ID
223
224 auto inserted = out.by_id.emplace(id, entry);
225 if(!inserted.second)
226 {
227 const auto& prev = inserted.first->second;
228 std::string msg = "duplicate marker ID " + std::to_string(id) +
229 " -- previous \"" + prev->name + "\" replaced by \"" +
230 entry->name + "\"";
231 detail::emit_warning(msg, line_no, silent);
232 inserted.first->second = entry;
233 }
234 };
235
236 switch(prefix)
237 {
238 case 'W':
239 {
240 uint64_t w = 0;
241 bool ok = !payload.empty();
242 for(char c : payload)
243 {
244 if(c < '0' || c > '9')
245 {
246 ok = false;
247 break;
248 }
249 w = w * 10 + uint64_t(c - '0');
250 if(w > 0xFFFFFFFFull)
251 {
252 ok = false;
253 break;
254 }
255 }
256 if(!ok)
257 {
258 detail::emit_warning(
259 "malformed W: row: \"" + std::string(line) + '"', line_no, silent);
260 }
261 else
262 {
263 out.wave_size = uint32_t(w);
264 }
265 break;
266 }
267 case 'K':
268 {
269 auto [name, loc] = detail::split_name_loc(payload);
270 if(name.empty())
271 {
272 detail::emit_warning(
273 "K: row missing name: \"" + std::string(line) + '"', line_no, silent);
274 break;
275 }
276 record(FuncmapEntryKind::Kernel, 0, std::move(name), std::move(loc));
277 break;
278 }
279 case 'F':
280 case 'U':
281 case 'P':
282 {
283 auto split = detail::split_id_payload(payload);
284 if(!split)
285 {
286 detail::emit_warning(std::string("malformed ") + prefix + ": row (bad ID): \"" +
287 std::string(line) + '"',
288 line_no,
289 silent);
290 break;
291 }
292 auto [name, loc] = detail::split_name_loc(split->second);
293 if(name.empty())
294 {
295 detail::emit_warning(std::string("malformed ") + prefix +
296 ": row (empty name): \"" + std::string(line) + '"',
297 line_no,
298 silent);
299 break;
300 }
301 FuncmapEntryKind kind = FuncmapEntryKind::Point;
302 if(prefix == 'F')
303 kind = FuncmapEntryKind::Function;
304 else if(prefix == 'U')
305 kind = FuncmapEntryKind::UserScope;
306 record(kind, split->first, std::move(name), std::move(loc));
307 break;
308 }
309 default:
310 {
311 detail::emit_warning(std::string("unknown row prefix '") + prefix + "': \"" +
312 std::string(line) + '"',
313 line_no,
314 silent);
315 break;
316 }
317 }
318 }
319
320 return out;
321}
322
323// Locate `section_name` in an in-memory ELF64 image and return a view of its
324// raw bytes. Returns nullopt for: missing/empty section, or any malformed
325// header (non-64-bit, bad sizes, OOB offsets, integer wrap on
326// sh_offset+sh_size). Bounds checks use subtraction against `elf_size` to
327// avoid uint64 wrap on adversarial sh_size values.
328//
329// ELF64 layout (typical; section data and shdr table may be in any order):
330//
331// +-------------------------+ <- elf_data
332// | Elf64_Ehdr | e_shoff ----. shdr table offset
333// | | e_shnum = N |
334// | | e_shentsize = sizeof(Elf64_Shdr)
335// | | e_shstrndx = K --. index of .shstrtab shdr
336// +-------------------------+ | |
337// | section data | | |
338// | ... | | |
339// | .sqtt_funcmap | <- returned bytes | |
340// | .shstrtab <-----. | | |
341// +-------------------------+ | |
342// | shdr[0..N) | <-- e_shoff -------' |
343// | [0] SHT_NULL | |
344// | [1] .text name=1 | sh_name = byte |
345// | [3] .sqtt_.. name=15| offset into |
346// | [K] .shstrtab name=29| .shstrtab <---------'
347// | sh_offset -----' (located via shdr[K])
348// +-------------------------+
349//
350// .shstrtab is a packed NUL-terminated string blob; sh_name is a byte offset
351// into it (NOT an index into a string array):
352//
353// offset: 0 1 6 7 13 14 15 28 29
354// bytes: \0 . t e x t \0 . r o d a t a \0 . s q t t _ f u n c m a p \0 . s h s t r t a b \0
355// ^^^^^^^^^^^^^^^^^^^^^^^^^^
356// sh_name=15 -> ".sqtt_funcmap"
357//
358// Flow:
359// 1. Validate the ELF header (magic, ELFCLASS64, sizes, e_shoff in bounds,
360// whole shdr table fits, e_shstrndx < e_shnum).
361// 2. Read shdr[e_shstrndx] to locate the .shstrtab bytes (sh_offset/sh_size).
362// 3. Linear scan shdr[0..N): for each entry, resolve its name as
363// .shstrtab + sh_name and string-compare against `section_name`.
364// 4. On match, bounds-check sh_offset/sh_size and return a view of the
365// section's bytes; otherwise return nullopt.
366inline std::optional<std::string_view>
367extract_elf_section(const char* elf_data, size_t elf_size, std::string_view section_name)
368{
369 if(elf_data == nullptr || elf_size < sizeof(Elf64_Ehdr)) return std::nullopt;
370
371 if(std::memcmp(elf_data, ELFMAG, SELFMAG) != 0) return std::nullopt;
372
373 Elf64_Ehdr ehdr;
374 std::memcpy(&ehdr, elf_data, sizeof(ehdr));
375
376 if(ehdr.e_ident[EI_CLASS] != ELFCLASS64) return std::nullopt;
377 if(ehdr.e_shentsize != sizeof(Elf64_Shdr)) return std::nullopt;
378 if(ehdr.e_shoff == 0 || ehdr.e_shoff > elf_size) return std::nullopt;
379 if(ehdr.e_shstrndx == SHN_UNDEF) return std::nullopt;
380
381 // Whole shdr table must fit inside elf_size without overflow.
382 uint64_t shdr_table_bytes = uint64_t(ehdr.e_shnum) * sizeof(Elf64_Shdr);
383 if(shdr_table_bytes / sizeof(Elf64_Shdr) != uint64_t(ehdr.e_shnum)) return std::nullopt;
384 // Use subtraction to avoid wrap; e_shoff <= elf_size from the prior check.
385 if(shdr_table_bytes > uint64_t(elf_size) - ehdr.e_shoff) return std::nullopt;
386 if(ehdr.e_shstrndx >= ehdr.e_shnum) return std::nullopt;
387
388 // memcpy avoids alignment UB on the raw buffer.
389 auto read_shdr = [&](unsigned idx) {
390 Elf64_Shdr s;
391 std::memcpy(&s, elf_data + ehdr.e_shoff + idx * sizeof(Elf64_Shdr), sizeof(Elf64_Shdr));
392 return s;
393 };
394
395 // .shstrtab -- sh_name in every other shdr is an offset into [str_base, str_base+str_len).
396 Elf64_Shdr shstr = read_shdr(ehdr.e_shstrndx);
397 if(shstr.sh_offset > elf_size || shstr.sh_size > uint64_t(elf_size) - shstr.sh_offset)
398 return std::nullopt;
399
400 const char* str_base = elf_data + shstr.sh_offset;
401 size_t str_len = shstr.sh_size;
402
403 // Empty view on OOB so the caller's name-compare safely fails.
404 auto name_of = [&](uint32_t name_off) -> std::string_view {
405 if(name_off >= str_len) return std::string_view{};
406 size_t end = name_off;
407 while(end < str_len && str_base[end] != '\0')
408 ++end;
409 return std::string_view(str_base + name_off, end - name_off);
410 };
411
412 // Linear scan; ELF doesn't index sections by name and N is small.
413 for(unsigned i = 0; i < ehdr.e_shnum; ++i)
414 {
415 Elf64_Shdr s = read_shdr(i);
416 if(name_of(s.sh_name) != section_name) continue;
417
418 // Empty section is treated as "not present".
419 if(s.sh_size == 0) return std::nullopt;
420 if(s.sh_offset > elf_size || s.sh_size > uint64_t(elf_size) - s.sh_offset)
421 return std::nullopt;
422 return std::string_view(elf_data + s.sh_offset, s.sh_size);
423 }
424
425 return std::nullopt; // section absent -- common case, no diagnostic
426}
427
428} // namespace funcmap
429} // namespace codeobj
430} // namespace sdk
431} // namespace rocprofiler