Files
TheRock/rocm-systems/emulation/rocjitsu/tests/code/relocation_function_table_test.cpp
T
derek.holloway 7502bf64a4
CodeQL / Analyze (actions) (push) Canceled after 0s
CodeQL / Analyze (python) (push) Canceled after 0s
pre-commit / pre-commit (push) Canceled after 0s
Multi-Arch CI / setup (push) Canceled after 0s
Publish build_manylinux_x86_64 images / publish_build_manylinux_x86_64 (push) Canceled after 0s
Setup (Multi-Arch) / setup (push) Canceled after 0s
Multi-Arch CI / Linux::${{ fromJSON(needs.setup.outputs.linux_build_config || '{}').build_variant_label || 'skip' }} (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Publish no_rocm_image_ubi10 image / publish_no_rocm_image_ubi10 (push) Canceled after 0s
Multi-Arch CI / Windows::${{ fromJSON(needs.setup.outputs.windows_build_config || '{}').build_variant_label || 'skip' }} (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Multi-Arch CI / Manifest Diff (push) Canceled after 0s
TheRock Manifest Diff Report / Generate Manifest Diff Report (push) Canceled after 0s
Multi-Arch CI / CI Summary (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04 (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04_ocl_rt images / publish_no_rocm_image_ubuntu24_04_ocl_rt (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_rocgdb (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04_rocgdb images / publish_no_rocm_image_ubuntu24_04_rocgdb (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_ocl_rt (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Unit Tests / Unit Tests :: ubuntu-24.04 (push) Canceled after 0s
Unit Tests / Unit Tests :: windows-2022 (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_media (push) Canceled after 0s
Unit Tests / Unit Tests Summary (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_openmpi (push) Canceled after 0s
Flatten subprojects into a single base monorepo
2026-09-02 16:34:47 -07:00

593 lines
25 KiB
C++

// Copyright (c) 2026 Advanced Micro Devices, Inc.
// SPDX-License-Identifier: MIT
#include "rocjitsu/code/amdgpu_code_object.h"
#include "rocjitsu/code/amdgpu_elf.h"
#include "rocjitsu/code/basic_block.h"
#include "rocjitsu/code/patch/code_object_patcher.h"
#include "rocjitsu/code/relocation_function_table.h"
#include "rocjitsu/isa/arch/amdgpu/gfx1250/machine_insts.h"
#include "rocjitsu/isa/decoder.h"
#include <gtest/gtest.h>
#include <algorithm>
#include <array>
#include <bit>
#include <cstdint>
#include <cstring>
#include <functional>
#include <span>
#include <string_view>
#include <vector>
namespace rocjitsu {
namespace {
uint32_t add_name(std::vector<uint8_t> &names, std::string_view name) {
const uint32_t offset = static_cast<uint32_t>(names.size());
names.insert(names.end(), name.begin(), name.end());
names.push_back('\0');
return offset;
}
uint64_t align_up(uint64_t value, uint64_t alignment) {
return (value + alignment - 1) / alignment * alignment;
}
uint64_t relocation_info(uint32_t symbol, uint32_t type) {
return (static_cast<uint64_t>(symbol) << 32) | type;
}
std::vector<uint8_t> make_relocation_function_table_elf(std::span<const uint32_t> text_words = {}) {
constexpr uint64_t kTextVaddr = 0x1000;
constexpr uint64_t kTableVaddr = 0x2000;
constexpr uint64_t kGotVaddr = 0x3000;
constexpr uint64_t kTextOffset = 0x100;
const std::array<uint32_t, 4> default_text = {0xbf800000u, 0xbf800000u, 0xbf800000u, 0xbf800000u};
if (text_words.empty())
text_words = default_text;
const uint64_t text_size = text_words.size() * sizeof(uint32_t);
constexpr uint64_t kTableSize = 24;
constexpr uint64_t kGotSize = 8;
std::vector<uint8_t> shstrtab{'\0'};
const uint32_t text_name = add_name(shstrtab, ".text");
const uint32_t data_name = add_name(shstrtab, ".data.rel.ro");
const uint32_t got_name = add_name(shstrtab, ".got");
const uint32_t dynsym_name = add_name(shstrtab, ".dynsym");
const uint32_t rela_name = add_name(shstrtab, ".rela.dyn");
const uint32_t shstrtab_name = add_name(shstrtab, ".shstrtab");
const uint64_t data_offset = kTextOffset + text_size;
const uint64_t got_offset = data_offset + kTableSize;
const uint64_t dynsym_offset = align_up(got_offset + kGotSize, 8);
constexpr size_t kSymbolCount = 2;
const uint64_t rela_offset = dynsym_offset + kSymbolCount * sizeof(Elf64_Sym);
constexpr size_t kRelaCount = 3;
const uint64_t shstrtab_offset = rela_offset + kRelaCount * sizeof(Elf64_Rela);
const uint64_t shoff = align_up(shstrtab_offset + shstrtab.size(), 8);
constexpr uint16_t kSectionCount = 7;
std::vector<uint8_t> image(shoff + kSectionCount * sizeof(Elf64_Shdr), 0);
Elf64_Ehdr ehdr{};
std::memcpy(ehdr.e_ident, EI_MAGIC, EI_MAGIC_SIZE);
ehdr.e_ident[EI_CLASS] = ELFCLASS64;
ehdr.e_ident[EI_OSABI] = ELFOSABI_AMDGPU_HSA;
ehdr.e_type = ET_DYN;
ehdr.e_machine = EM_AMDGPU;
ehdr.e_version = 1;
ehdr.e_shoff = shoff;
ehdr.e_flags = EF_AMDGPU_MACH_AMDGCN_GFX1250;
ehdr.e_ehsize = sizeof(Elf64_Ehdr);
ehdr.e_shentsize = sizeof(Elf64_Shdr);
ehdr.e_shnum = kSectionCount;
ehdr.e_shstrndx = 6;
std::memcpy(image.data(), &ehdr, sizeof(ehdr));
std::memcpy(image.data() + kTextOffset, text_words.data(), text_size);
std::array<Elf64_Sym, kSymbolCount> symbols{};
symbols[1].st_info = elf_symbol_info(kElfSymbolBindGlobal, kElfSymbolTypeObject);
symbols[1].st_shndx = 2;
symbols[1].st_value = kTableVaddr;
symbols[1].st_size = kTableSize;
std::memcpy(image.data() + dynsym_offset, symbols.data(), sizeof(symbols));
std::array<Elf64_Rela, kRelaCount> relas{};
relas[0].r_offset = kGotVaddr;
relas[0].r_info = relocation_info(1, R_AMDGPU_ABS64);
relas[1].r_offset = kTableVaddr;
relas[1].r_info = relocation_info(0, R_AMDGPU_RELATIVE64);
relas[1].r_addend = kTextVaddr + 4;
relas[2].r_offset = kTableVaddr + 16; // middle slot is intentionally null.
relas[2].r_info = relocation_info(0, R_AMDGPU_RELATIVE64);
relas[2].r_addend = kTextVaddr + std::min<uint64_t>(12, text_size - sizeof(uint32_t));
std::memcpy(image.data() + rela_offset, relas.data(), sizeof(relas));
std::memcpy(image.data() + shstrtab_offset, shstrtab.data(), shstrtab.size());
std::array<Elf64_Shdr, kSectionCount> shdrs{};
shdrs[1] = {.sh_name = text_name,
.sh_type = SHT_PROGBITS,
.sh_flags = SHF_ALLOC | SHF_EXECINSTR,
.sh_addr = kTextVaddr,
.sh_offset = kTextOffset,
.sh_size = text_size,
.sh_link = 0,
.sh_info = 0,
.sh_addralign = 4,
.sh_entsize = 0};
shdrs[2] = {.sh_name = data_name,
.sh_type = SHT_PROGBITS,
.sh_flags = SHF_ALLOC,
.sh_addr = kTableVaddr,
.sh_offset = data_offset,
.sh_size = kTableSize,
.sh_link = 0,
.sh_info = 0,
.sh_addralign = 8,
.sh_entsize = 0};
shdrs[3] = {.sh_name = got_name,
.sh_type = SHT_PROGBITS,
.sh_flags = SHF_ALLOC,
.sh_addr = kGotVaddr,
.sh_offset = got_offset,
.sh_size = kGotSize,
.sh_link = 0,
.sh_info = 0,
.sh_addralign = 8,
.sh_entsize = 0};
shdrs[4] = {.sh_name = dynsym_name,
.sh_type = SHT_DYNSYM,
.sh_flags = 0,
.sh_addr = 0,
.sh_offset = dynsym_offset,
.sh_size = sizeof(symbols),
.sh_link = 0,
.sh_info = 1,
.sh_addralign = 8,
.sh_entsize = sizeof(Elf64_Sym)};
shdrs[5] = {.sh_name = rela_name,
.sh_type = SHT_RELA,
.sh_flags = 0,
.sh_addr = 0,
.sh_offset = rela_offset,
.sh_size = sizeof(relas),
.sh_link = 4,
.sh_info = 0,
.sh_addralign = 8,
.sh_entsize = sizeof(Elf64_Rela)};
shdrs[6] = {.sh_name = shstrtab_name,
.sh_type = SHT_STRTAB,
.sh_flags = 0,
.sh_addr = 0,
.sh_offset = shstrtab_offset,
.sh_size = shstrtab.size(),
.sh_link = 0,
.sh_info = 0,
.sh_addralign = 1,
.sh_entsize = 0};
std::memcpy(image.data() + shoff, shdrs.data(), sizeof(shdrs));
return image;
}
std::vector<uint32_t> make_table_dispatch_text() {
std::vector<uint32_t> words;
auto append32 = [&](uint32_t word) { words.push_back(word); };
auto append64 = [&](uint64_t encoding) {
words.push_back(static_cast<uint32_t>(encoding));
words.push_back(static_cast<uint32_t>(encoding >> 32));
};
auto getpc = std::bit_cast<gfx1250::Sop1MachineInst>(0xbe804700u);
getpc.sdst = 0;
append32(std::bit_cast<uint32_t>(getpc));
auto add = std::bit_cast<gfx1250::Sop2MachineInst>(0xa9800000u);
add.sdst = 0;
add.ssrc0 = 0;
add.ssrc1 = 254;
append32(std::bit_cast<uint32_t>(add));
append64(0x3000u - 0x1004u);
auto got_load = std::bit_cast<gfx1250::SmemMachineInst>(uint64_t{0xf4002000u});
got_load.sbase = 0;
got_load.sdata = 2;
append64(std::bit_cast<uint64_t>(got_load));
auto table_load = got_load;
table_load.sbase = 1; // encoded in SGPR-pair units: s[2:3].
table_load.sdata = 4;
table_load.scale_offset = 1;
table_load.soffset = 6;
append64(std::bit_cast<uint64_t>(table_load));
auto swap = std::bit_cast<gfx1250::Sop1MachineInst>(0xbe804900u);
swap.sdst = 30;
swap.ssrc0 = 4;
append32(std::bit_cast<uint32_t>(swap));
append32(0xbfb00000u); // s_endpgm continuation
auto setpc = std::bit_cast<gfx1250::Sop1MachineInst>(0xbe804800u);
setpc.ssrc0 = 30;
append32(std::bit_cast<uint32_t>(setpc));
return words;
}
// Direct indexed dispatch that reaches the table base by adding @p add_literal to
// the getpc result. Callers pick the literal to match whatever text base the
// analysis is told to assume, including a two's-complement negative value when the
// table sits below .text.
std::vector<uint32_t> make_direct_table_dispatch_text_with_addend(uint64_t add_literal) {
std::vector<uint32_t> words;
auto append32 = [&](uint32_t word) { words.push_back(word); };
auto append64 = [&](uint64_t encoding) {
words.push_back(static_cast<uint32_t>(encoding));
words.push_back(static_cast<uint32_t>(encoding >> 32));
};
auto getpc = std::bit_cast<gfx1250::Sop1MachineInst>(0xbe804700u);
getpc.sdst = 54;
append32(std::bit_cast<uint32_t>(getpc));
auto add = std::bit_cast<gfx1250::Sop2MachineInst>(0xa9800000u);
add.sdst = 54;
add.ssrc0 = 54;
add.ssrc1 = 254;
append32(std::bit_cast<uint32_t>(add));
append64(add_literal);
auto table_load = std::bit_cast<gfx1250::SmemMachineInst>(uint64_t{0xf4002000u});
table_load.sbase = 27; // encoded in SGPR-pair units: s[54:55].
table_load.sdata = 0;
table_load.scale_offset = 1;
table_load.soffset = 2;
append64(std::bit_cast<uint64_t>(table_load));
auto swap = std::bit_cast<gfx1250::Sop1MachineInst>(0xbe804900u);
swap.sdst = 30;
swap.ssrc0 = 0;
append32(std::bit_cast<uint32_t>(swap));
append32(0xbfb00000u); // s_endpgm continuation
auto setpc = std::bit_cast<gfx1250::Sop1MachineInst>(0xbe804800u);
setpc.ssrc0 = 30;
append32(std::bit_cast<uint32_t>(setpc));
return words;
}
std::vector<uint32_t> make_direct_table_dispatch_text() {
return make_direct_table_dispatch_text_with_addend(0x2000u - 0x1004u);
}
// Same direct indexed dispatch as make_direct_table_dispatch_text(), but the
// table base is built with TWO s_add_nc_u64 literal adds instead of one. The
// patcher can only rewrite a single recorded add offset, so discovery must refuse
// to resolve this (fail closed) rather than emit a dispatch whose first addend
// still executes.
std::vector<uint32_t> make_double_add_table_dispatch_text() {
std::vector<uint32_t> words;
auto append32 = [&](uint32_t word) { words.push_back(word); };
auto append64 = [&](uint64_t encoding) {
words.push_back(static_cast<uint32_t>(encoding));
words.push_back(static_cast<uint32_t>(encoding >> 32));
};
auto getpc = std::bit_cast<gfx1250::Sop1MachineInst>(0xbe804700u);
getpc.sdst = 54;
append32(std::bit_cast<uint32_t>(getpc)); // 0x00: s_get_pc_i64 s[54:55].
auto add = std::bit_cast<gfx1250::Sop2MachineInst>(0xa9800000u);
add.sdst = 54;
add.ssrc0 = 54;
add.ssrc1 = 254;
append32(std::bit_cast<uint32_t>(add)); // 0x04: s_add_nc_u64 (first add).
append64(0x800u); // 0x08: literal; getpc value 0x1004 + 0x800 = 0x1804.
append32(std::bit_cast<uint32_t>(add)); // 0x10: s_add_nc_u64 (second add).
append64(0x2000u - 0x1804u); // 0x14: literal; 0x1804 + 0x7FC = 0x2000 (table base).
auto table_load = std::bit_cast<gfx1250::SmemMachineInst>(uint64_t{0xf4002000u});
table_load.sbase = 27; // s[54:55].
table_load.sdata = 0;
table_load.scale_offset = 1;
table_load.soffset = 2;
append64(std::bit_cast<uint64_t>(table_load)); // 0x18: s_load_b64.
auto swap = std::bit_cast<gfx1250::Sop1MachineInst>(0xbe804900u);
swap.sdst = 30;
swap.ssrc0 = 0;
append32(std::bit_cast<uint32_t>(swap)); // 0x20: s_swap_pc_i64.
append32(0xbfb00000u); // 0x24: s_endpgm continuation.
auto setpc = std::bit_cast<gfx1250::Sop1MachineInst>(0xbe804800u);
setpc.ssrc0 = 30;
append32(std::bit_cast<uint32_t>(setpc)); // 0x28: s_set_pc_i64.
return words;
}
void remove_got_reference(std::vector<uint8_t> &image) {
Elf64_Ehdr ehdr{};
std::memcpy(&ehdr, image.data(), sizeof(ehdr));
std::vector<Elf64_Shdr> sections(ehdr.e_shnum);
std::memcpy(sections.data(), image.data() + ehdr.e_shoff, sections.size() * sizeof(Elf64_Shdr));
const auto rela = std::ranges::find(sections, SHT_RELA, &Elf64_Shdr::sh_type);
ASSERT_NE(rela, sections.end());
Elf64_Rela first{};
std::memcpy(&first, image.data() + rela->sh_offset, sizeof(first));
first.r_info = relocation_info(0, 0);
std::memcpy(image.data() + rela->sh_offset, &first, sizeof(first));
}
// Mutate the first R_AMDGPU_RELATIVE64 relocation in the .rela section via a
// caller-supplied editor, so negative tests can corrupt one table entry's slot
// offset or addend and confirm discovery rejects just that entry.
void edit_first_relative64(std::vector<uint8_t> &image,
const std::function<void(Elf64_Rela &)> &editor) {
Elf64_Ehdr ehdr{};
std::memcpy(&ehdr, image.data(), sizeof(ehdr));
std::vector<Elf64_Shdr> sections(ehdr.e_shnum);
std::memcpy(sections.data(), image.data() + ehdr.e_shoff, sections.size() * sizeof(Elf64_Shdr));
const auto rela_shdr = std::ranges::find(sections, SHT_RELA, &Elf64_Shdr::sh_type);
ASSERT_NE(rela_shdr, sections.end());
const size_t count = rela_shdr->sh_size / sizeof(Elf64_Rela);
for (size_t i = 0; i < count; ++i) {
const uint64_t off = rela_shdr->sh_offset + i * sizeof(Elf64_Rela);
Elf64_Rela rela{};
std::memcpy(&rela, image.data() + off, sizeof(rela));
if (elf_reloc_type(rela.r_info) != R_AMDGPU_RELATIVE64)
continue;
editor(rela);
std::memcpy(image.data() + off, &rela, sizeof(rela));
return;
}
FAIL() << "no R_AMDGPU_RELATIVE64 relocation found to edit";
}
TEST(RelocationFunctionTable, DiscoversGotReferencedRelativeTextPointers) {
const auto image = make_relocation_function_table_elf();
const AmdGpuCodeObject object(image.data(), image.size());
ASSERT_TRUE(object.is_valid());
const auto tables = discover_relocation_function_tables(object);
ASSERT_EQ(tables.size(), 1u);
EXPECT_EQ(tables[0].table_vaddr, 0x2000u);
EXPECT_EQ(tables[0].table_size, 24u);
EXPECT_EQ(tables[0].got_slot_vaddrs, std::vector<uint64_t>{0x3000u});
ASSERT_EQ(tables[0].entries.size(), 2u);
EXPECT_EQ(tables[0].entries[0].slot_vaddr, 0x2000u);
EXPECT_EQ(tables[0].entries[0].target_text_offset, 4u);
EXPECT_EQ(tables[0].entries[1].slot_vaddr, 0x2010u);
EXPECT_EQ(tables[0].entries[1].target_text_offset, 12u);
}
TEST(RelocationFunctionTable, DiscoveryRejectsMisalignedTableSlot) {
// The first table entry's RELATIVE64 relocation points at table_vaddr + 4, which
// is not a multiple of the 8-byte pointer stride from the table base. Discovery
// must drop that entry (it cannot be a valid function-pointer slot) while keeping
// the well-formed second entry.
auto image = make_relocation_function_table_elf();
edit_first_relative64(image, [](Elf64_Rela &rela) { rela.r_offset = 0x2000u + 4u; });
const AmdGpuCodeObject object(image.data(), image.size());
ASSERT_TRUE(object.is_valid());
const auto tables = discover_relocation_function_tables(object);
ASSERT_EQ(tables.size(), 1u);
ASSERT_EQ(tables[0].entries.size(), 1u);
EXPECT_EQ(tables[0].entries[0].slot_vaddr, 0x2010u);
EXPECT_EQ(tables[0].entries[0].target_text_offset, 12u);
}
TEST(RelocationFunctionTable, DiscoveryRejectsOutOfTextAddend) {
// The first table entry's RELATIVE64 addend points outside .text (into the table
// section itself). A function-pointer slot must resolve into .text, so discovery
// must drop that entry while keeping the in-text second entry.
auto image = make_relocation_function_table_elf();
edit_first_relative64(image, [](Elf64_Rela &rela) { rela.r_addend = 0x2000; });
const AmdGpuCodeObject object(image.data(), image.size());
ASSERT_TRUE(object.is_valid());
const auto tables = discover_relocation_function_tables(object);
ASSERT_EQ(tables.size(), 1u);
ASSERT_EQ(tables[0].entries.size(), 1u);
EXPECT_EQ(tables[0].entries[0].slot_vaddr, 0x2010u);
EXPECT_EQ(tables[0].entries[0].target_text_offset, 12u);
}
TEST(RelocationFunctionTable, PatcherRetargetsRelativeTextAddends) {
const auto image = make_relocation_function_table_elf();
const AmdGpuCodeObject object(image.data(), image.size());
ASSERT_TRUE(object.is_valid());
CodeObjectPatcher patcher(object);
const std::array<uint32_t, 6> expanded_text = {0xbf800000u, 0xbf800000u, 0xbf800000u,
0xbf800000u, 0xbf800000u, 0xbf800000u};
constexpr std::array<TextOffsetRelocation, 2> mappings = {
TextOffsetRelocation{.source_offset = 4, .target_offset = 8},
TextOffsetRelocation{.source_offset = 12, .target_offset = 20},
};
constexpr std::array<PcRelativeDataRelocation, 1> data_mappings = {
PcRelativeDataRelocation{
.target_getpc_offset = 0, .target_literal_offset = 8, .source_target_vaddr = 0x3000},
};
const auto bytes = std::span<const uint8_t>(
reinterpret_cast<const uint8_t *>(expanded_text.data()), sizeof(expanded_text));
ASSERT_TRUE(patcher.replace_text(bytes, mappings, data_mappings));
const auto patched = patcher.emit();
Elf64_Ehdr ehdr{};
std::memcpy(&ehdr, patched.data(), sizeof(ehdr));
ASSERT_GT(ehdr.e_shnum, 5u);
std::vector<Elf64_Shdr> shdrs(ehdr.e_shnum);
std::memcpy(shdrs.data(), patched.data() + ehdr.e_shoff, shdrs.size() * sizeof(Elf64_Shdr));
ASSERT_EQ(shdrs[5].sh_type, SHT_RELA);
ASSERT_EQ(shdrs[5].sh_size, 3 * sizeof(Elf64_Rela));
std::array<Elf64_Rela, 3> relas{};
std::memcpy(relas.data(), patched.data() + shdrs[5].sh_offset, sizeof(relas));
EXPECT_EQ(relas[1].r_addend, 0x1008);
EXPECT_EQ(relas[2].r_addend, 0x1014);
EXPECT_EQ(relas[1].r_offset, 0x2008u);
EXPECT_EQ(relas[2].r_offset, 0x2018u);
uint64_t relocated_got_delta = 0;
std::memcpy(&relocated_got_delta, patched.data() + patcher.text_offset() + 8,
sizeof(relocated_got_delta));
EXPECT_EQ(relocated_got_delta, 0x2004u);
}
TEST(RelocationFunctionTable, PatcherRejectsRelativeTextAddendWithoutExactOffsetMap) {
const auto image = make_relocation_function_table_elf();
const AmdGpuCodeObject object(image.data(), image.size());
ASSERT_TRUE(object.is_valid());
CodeObjectPatcher patcher(object);
const std::array<uint32_t, 6> expanded_text = {0xbf800000u, 0xbf800000u, 0xbf800000u,
0xbf800000u, 0xbf800000u, 0xbf800000u};
// The fixture has RELATIVE64 addends targeting source offsets 4 and 12. Omit
// the latter deliberately: accepting the partial map would leave one stale
// function-table pointer in the output code object.
constexpr std::array<TextOffsetRelocation, 1> incomplete_mapping = {
TextOffsetRelocation{.source_offset = 4, .target_offset = 8},
};
const auto bytes = std::span<const uint8_t>(
reinterpret_cast<const uint8_t *>(expanded_text.data()), sizeof(expanded_text));
EXPECT_FALSE(patcher.replace_text(bytes, incomplete_mapping));
}
TEST(RelocationFunctionTable, PatcherRejectsConflictingRelative64PlacementsAcrossScopes) {
const auto image = make_relocation_function_table_elf();
const AmdGpuCodeObject object(image.data(), image.size());
ASSERT_TRUE(object.is_valid());
CodeObjectPatcher patcher(object);
const std::array<uint32_t, 6> expanded_text = {0xbf800000u, 0xbf800000u, 0xbf800000u,
0xbf800000u, 0xbf800000u, 0xbf800000u};
// A shared source block emitted into more than one kernel-local scope produces
// the SAME source offset with DIFFERENT relocated placements. A RELATIVE64
// function pointer is dereferenced at runtime, so no single rewrite is correct:
// the patcher must fail closed rather than collapse to one arbitrary clone. The
// fixture dereferences source offset 4 as a RELATIVE64 addend; provide two
// conflicting targets for it (plus the required mapping for source offset 12).
constexpr std::array<TextOffsetRelocation, 3> conflicting = {
TextOffsetRelocation{.source_offset = 4, .target_offset = 8},
TextOffsetRelocation{.source_offset = 4, .target_offset = 16},
TextOffsetRelocation{.source_offset = 12, .target_offset = 20},
};
const auto bytes = std::span<const uint8_t>(
reinterpret_cast<const uint8_t *>(expanded_text.data()), sizeof(expanded_text));
EXPECT_FALSE(patcher.replace_text(bytes, conflicting));
}
TEST(RelocationFunctionTable, ResolvesDynamicDispatchThroughGotAndTableLoads) {
const auto text_words = make_table_dispatch_text();
const auto image = make_relocation_function_table_elf(text_words);
const AmdGpuCodeObject object(image.data(), image.size());
ASSERT_TRUE(object.is_valid());
const auto tables = discover_relocation_function_tables(object);
ASSERT_EQ(tables.size(), 1u);
auto decoder = Decoder::create(ROCJITSU_CODE_ARCH_GFX1250);
ASSERT_NE(decoder, nullptr);
std::array<uint64_t, 1> leaders{40};
const auto blocks = BasicBlock::build(object, *decoder, ROCJITSU_CODE_ARCH_GFX1250, leaders);
const auto dispatches = analyze_relocation_pairs(blocks, tables, 0x1000).dispatches;
ASSERT_EQ(dispatches.size(), 1u);
EXPECT_EQ(dispatches[0].table_index, 0u);
EXPECT_EQ(dispatches[0].source_call_offset, 32u);
EXPECT_EQ(dispatches[0].return_sreg, 30u);
EXPECT_EQ(dispatches[0].source_getpc_offset, 0u);
EXPECT_EQ(dispatches[0].source_address_add_offset, 4u);
EXPECT_EQ(dispatches[0].source_table_address_vaddr, 0x3000u);
}
TEST(RelocationFunctionTable, ResolvesRcclDirectIndexedTableDispatch) {
const auto text_words = make_direct_table_dispatch_text();
auto image = make_relocation_function_table_elf(text_words);
remove_got_reference(image);
const AmdGpuCodeObject object(image.data(), image.size());
ASSERT_TRUE(object.is_valid());
const auto tables = discover_relocation_function_tables(object);
ASSERT_EQ(tables.size(), 1u);
EXPECT_TRUE(tables[0].got_slot_vaddrs.empty());
auto decoder = Decoder::create(ROCJITSU_CODE_ARCH_GFX1250);
ASSERT_NE(decoder, nullptr);
std::array<uint64_t, 1> leaders{32};
const auto blocks = BasicBlock::build(object, *decoder, ROCJITSU_CODE_ARCH_GFX1250, leaders);
const auto analysis = analyze_relocation_pairs(blocks, tables, 0x1000);
const auto &dispatches = analysis.dispatches;
ASSERT_EQ(dispatches.size(), 1u);
EXPECT_EQ(dispatches[0].table_index, 0u);
EXPECT_EQ(dispatches[0].source_call_offset, 24u);
EXPECT_EQ(dispatches[0].return_sreg, 30u);
EXPECT_EQ(dispatches[0].source_getpc_offset, 0u);
EXPECT_EQ(dispatches[0].source_address_add_offset, 4u);
EXPECT_EQ(dispatches[0].source_table_address_vaddr, 0x2000u);
ASSERT_EQ(analysis.address_builders.size(), 1u);
EXPECT_EQ(analysis.address_builders[0].source_getpc_offset, 0u);
EXPECT_EQ(analysis.address_builders[0].source_address_add_offset, 4u);
EXPECT_EQ(analysis.address_builders[0].target_vaddr, 0x2000u);
const auto table_free_analysis = analyze_relocation_pairs(blocks, {}, 0x1000);
ASSERT_EQ(table_free_analysis.address_builders.size(), 1u);
EXPECT_EQ(table_free_analysis.address_builders[0].source_getpc_offset,
analysis.address_builders[0].source_getpc_offset);
EXPECT_EQ(table_free_analysis.address_builders[0].source_address_add_offset,
analysis.address_builders[0].source_address_add_offset);
EXPECT_EQ(table_free_analysis.address_builders[0].target_vaddr,
analysis.address_builders[0].target_vaddr);
}
TEST(RelocationFunctionTable, RejectsChainedAddressAddDispatch) {
const auto text_words = make_double_add_table_dispatch_text();
auto image = make_relocation_function_table_elf(text_words);
remove_got_reference(image);
const AmdGpuCodeObject object(image.data(), image.size());
ASSERT_TRUE(object.is_valid());
const auto tables = discover_relocation_function_tables(object);
ASSERT_EQ(tables.size(), 1u);
auto decoder = Decoder::create(ROCJITSU_CODE_ARCH_GFX1250);
ASSERT_NE(decoder, nullptr);
std::array<uint64_t, 1> leaders{44};
const auto blocks = BasicBlock::build(object, *decoder, ROCJITSU_CODE_ARCH_GFX1250, leaders);
const auto dispatches = analyze_relocation_pairs(blocks, tables, 0x1000).dispatches;
// The base is built with two literal adds; only one add offset can be relocated,
// so the dispatch must fail closed rather than resolve to a value whose first
// addend would still execute.
EXPECT_TRUE(dispatches.empty());
}
TEST(RelocationFunctionTable, ResolvesBackwardTableAddress) {
// The table sits BELOW the assumed text base, so the address is built by adding a
// two's-complement negative literal to the getpc result, which wraps modulo
// 2^64. Discovery must perform modulo arithmetic and still recognize the table,
// rather than treating the expected wraparound as overflow and rejecting a valid
// kernel. Assume text base 0x4000 (above the 0x2000 table); getpc value is
// 0x4004, so the literal is the wrapped 0x2000 - 0x4004.
constexpr uint64_t kAssumedTextVaddr = 0x4000u;
const uint64_t backward_addend = 0x2000u - (kAssumedTextVaddr + 0x4u);
const auto text_words = make_direct_table_dispatch_text_with_addend(backward_addend);
auto image = make_relocation_function_table_elf(text_words);
remove_got_reference(image);
const AmdGpuCodeObject object(image.data(), image.size());
ASSERT_TRUE(object.is_valid());
const auto tables = discover_relocation_function_tables(object);
ASSERT_EQ(tables.size(), 1u);
auto decoder = Decoder::create(ROCJITSU_CODE_ARCH_GFX1250);
ASSERT_NE(decoder, nullptr);
std::array<uint64_t, 1> leaders{32};
const auto blocks = BasicBlock::build(object, *decoder, ROCJITSU_CODE_ARCH_GFX1250, leaders);
const auto dispatches = analyze_relocation_pairs(blocks, tables, kAssumedTextVaddr).dispatches;
ASSERT_EQ(dispatches.size(), 1u);
EXPECT_EQ(dispatches[0].table_index, 0u);
EXPECT_EQ(dispatches[0].source_table_address_vaddr, 0x2000u);
}
} // namespace
} // namespace rocjitsu