Index: MultiSource/UnitTests/CMakeLists.txt =================================================================== --- MultiSource/UnitTests/CMakeLists.txt +++ MultiSource/UnitTests/CMakeLists.txt @@ -1,4 +1,5 @@ add_subdirectory(C++11) +add_subdirectory(Stackmaps) if(ARCH STREQUAL "Mips") add_subdirectory(Mips) endif() Index: MultiSource/UnitTests/Stackmaps/CMakeLists.txt =================================================================== --- /dev/null +++ MultiSource/UnitTests/Stackmaps/CMakeLists.txt @@ -0,0 +1,8 @@ +file(GLOB llsources ${CMAKE_CURRENT_SOURCE_DIR}/*.ll) +SET_SOURCE_FILES_PROPERTIES(${llsources} PROPERTIES LANGUAGE CXX) + +llvm_multisource(stackmaps + ${CMAKE_CURRENT_SOURCE_DIR}/Harness.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/StackMapParser.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/input_${ARCH}.ll +) Index: MultiSource/UnitTests/Stackmaps/Harness.cpp =================================================================== --- /dev/null +++ MultiSource/UnitTests/Stackmaps/Harness.cpp @@ -0,0 +1,209 @@ +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "StackMapParser.h" + +using namespace std; + +StackMapParser *SMPRef = nullptr; +uint64_t NextID = 0; +int ExitStatus = EXIT_SUCCESS; + +// Defined in the `.ll` file for the platform under test. +extern "C" void run_tests(uintptr_t *); + +// Defined the the `.ll` file for the platform under test. It must be +// pointer-sized. +extern uintptr_t test_input; + +void dumpMem(const char *Ptr, size_t Size) { + // FIXME: should it be unsigned char everywhere? + for (size_t I = 0; I < Size; I++) { + printf("%02x", (unsigned char)Ptr[I]); + if (I != Size - 1) + printf(" "); + } + printf("\n"); +} + +char *getStackMapSectionAddr(char *Path, void **OutMap, off_t *OutMapSize) { + // First map the binary into our adress space. + struct stat St; + if (stat(Path, &St) == -1) + err(EXIT_FAILURE, "stat"); + + int Fd = open(Path, O_RDONLY); + if (Fd == -1) + err(EXIT_FAILURE, "open"); + + void *Map = mmap(nullptr, St.st_size, PROT_READ, MAP_SHARED, Fd, 0); + if (Map == MAP_FAILED) + err(EXIT_FAILURE, "mmap"); + + close(Fd); + + // Report mmap result back to caller, so it can be unmapped later. + *OutMap = Map; + *OutMapSize = St.st_size; + + // Now find the stackmap section inside what we just mapped in. + Elf64_Ehdr *Ehdr = static_cast(Map); + assert(Ehdr[0].e_ident = EI_MAG0); + assert(Ehdr[1].e_ident = EI_MAG1); + assert(Ehdr[2].e_ident = EI_MAG2); + assert(Ehdr[3].e_ident = EI_MAG3); + + Elf64_Shdr *Shdrs = static_cast( + static_cast(static_cast(Map) + Ehdr->e_shoff)); + + // Find the string table for the section names. + uint16_t StrTabShIdx = Ehdr->e_shstrndx; + assert(StrTabShIdx != SHN_UNDEF); + if (StrTabShIdx >= SHN_LORESERVE) + return nullptr; // FIXME not yet implemented. See `elf(5)` manual. + + Elf64_Shdr *StrTabSh = &Shdrs[StrTabShIdx]; + assert(StrTabSh->sh_type == ELF::SHT_STRTAB); + Elf64_Off StrTabOff = StrTabSh->sh_offset; + + void *Data = nullptr; + for (uint16_t SI = 0; SI < Ehdr->e_shentsize; SI++) { + Elf64_Shdr *Shdr = &Shdrs[SI]; + char *name = static_cast(Map) + StrTabOff + Shdr->sh_name; + if (strcmp(name, ".llvm_stackmaps") == 0) + return static_cast(Map) + Shdr->sh_offset; + } + return nullptr; +} + +// Get a pointer to the start of the storage referenced by `Loc`. +// +// This code asssumes that a register is the size of a pointer. +char *getMemForLoc(SMLoc *Loc, uintptr_t RegVals[]) { + switch (Loc->Where) { + case Reg: + // Get the saved register values off of the stack. + return reinterpret_cast(&RegVals[Loc->RegNum]); + case Direct: + errx(EXIT_FAILURE, "Direct locations unimplemented"); + case Indirect: + errx(EXIT_FAILURE, "Indirect locations unimplemented"); + case Const: + // Get the constant directly from the stackmap record. + return reinterpret_cast(&Loc->OffsetOrSmallConst); + case ConstIndex: + errx(EXIT_FAILURE, "ConstIndex locations unimplemented"); + default: + errx(EXIT_FAILURE, "unexpected storage class: %u\n", Loc->Where); + } +} + +extern "C" void do_sm_inspect(uintptr_t RegVals[]) { //, uintptr_t RetAddr) { + SMRec *Rec = SMPRef->getRecord(NextID); + if (Rec == nullptr) + errx(EXIT_FAILURE, "Failed to find stackmap record #%u\n", NextID); + + cout << "StackMap #" << NextID << "\n"; + size_t LocNum = 0; + for (auto Loc : Rec->Locs) { + char *GotPtr = getMemForLoc(&Loc, RegVals); + + // FIXME: This is arguably a bug in LLVM stackmaps. Small constants are + // reported as being 8 bytes long, but the stackmap format only allows for + // 4 bytes. + size_t GotSize = Loc.Size; + if (Loc.Where == Const) + GotSize = 4; + + cout << " Location #" << LocNum << "\n"; + cout << " Storage: " << getStorageClassName(Loc.Where) << "\n"; + cout << " Size: " << GotSize << "\n"; + cout << " Bytes: "; + dumpMem(GotPtr, GotSize); + LocNum++; + } + NextID++; + cout << "\n"; +} + +__attribute__((naked)) extern "C" void sm_inspect() { + asm volatile(".intel_syntax noprefix\n" + // Save register state onto the stack in reverse DWARF register + // number order. + "push r15\n" + "push r14\n" + "push r13\n" + "push r12\n" + "push r11\n" + "push r10\n" + "push r9\n" + "push r8\n" + "push rsp\n" + "push rbp\n" + "push rdi\n" + "push rsi\n" + "push rbx\n" + "push rcx\n" + "push rdx\n" + "push rax\n" + // Call do_sm_inspect(). + // + // Arg #0: pointer to saved registers. + "mov rdi, rsp\n" + // Arg #1: return address. + //"mov rsi, [rsp - 136]\n" + "call do_sm_inspect\n" + + // Restore registers we saved earlier. + "pop rax\n" + "pop rdx\n" + "pop rcx\n" + "pop rbx\n" + "pop rsi\n" + "pop rdi\n" + "pop rbp\n" + "pop rsp\n" + "pop r8\n" + "pop r9\n" + "pop r10\n" + "pop r11\n" + "pop r12\n" + "pop r13\n" + "pop r14\n" + "pop r15\n" + "ret"); +} + +#define NOOPT_VAL(X) asm volatile("" : "+r,m"(X) : : "memory"); + +int main(int argc, char *argv[]) { + void *Map; + off_t MapSize; + char *SMData = getStackMapSectionAddr(argv[0], &Map, &MapSize); + if (SMData == nullptr) + errx(EXIT_FAILURE, "couldn't find stackmaps"); + + StackMapParser SMP(SMData); + munmap(Map, MapSize); + + SMPRef = &SMP; + + NOOPT_VAL(test_input); + run_tests(&test_input); + + return ExitStatus; +} Index: MultiSource/UnitTests/Stackmaps/StackMapParser.h =================================================================== --- /dev/null +++ MultiSource/UnitTests/Stackmaps/StackMapParser.h @@ -0,0 +1,75 @@ +#ifndef TEST_SUITE_STACKMAP_PARSER_H +#define TEST_SUITE_STACKMAP_PARSER_H + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace std; + +const uint8_t SMVersion = 3; + +enum StorageClass { + Reg = 1, + Direct = 2, + Indirect = 3, + Const = 4, + ConstIndex = 5 +}; + +struct SMFunc { + uint64_t Addr; + uint64_t Size; + uint64_t RecCt; +}; + +struct SMLoc { + StorageClass Where; + uint16_t Size; + uint16_t RegNum; + int32_t OffsetOrSmallConst; +}; + +struct SMLiveOut { + uint16_t RegNo; + uint8_t Size; +}; + +struct SMRec { + uint32_t InstrOff; + vector Locs; + vector LiveOuts; +}; + +// A simple (copying) Stackmap parser. +// https://llvm.org/docs/StackMaps.html#stack-map-format +class StackMapParser { +private: + vector Funcs; + vector Consts; + map Recs; + + template T read(char **SMData); + void alignTo(char **SMData, size_t To); + +public: + StackMapParser(char *SMData); + SMRec *getRecord(uint64_t ID); +}; + +string getStorageClassName(StorageClass C); + +#endif // TEST_SUITE_STACKMAP_PARSER_H Index: MultiSource/UnitTests/Stackmaps/StackMapParser.cpp =================================================================== --- /dev/null +++ MultiSource/UnitTests/Stackmaps/StackMapParser.cpp @@ -0,0 +1,116 @@ +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "StackMapParser.h" + +using namespace std; + +template T StackMapParser::read(char **SMData) { + T Ret = *reinterpret_cast(*SMData); + *SMData += sizeof(T); + return Ret; +} + +void StackMapParser::alignTo(char **SMData, size_t To) { + // FIXME improve. + while (reinterpret_cast(*SMData) % To != 0) + (*SMData)++; +} + +StackMapParser::StackMapParser(char *SMData) { + // Header. + uint8_t Version = read(&SMData); + if (Version != SMVersion) + errx(EXIT_FAILURE, "expected stackmap version '%d', got version '%d'", + SMVersion, Version); + read(&SMData); // Skip reserved. + read(&SMData); // Skip reserved. + + // Multiplicities of tables. + uint32_t NumFunctions = read(&SMData); + uint32_t NumConstants = read(&SMData); + uint32_t NumRecords = read(&SMData); + + // Functions. + for (uint32_t I = 0; I < NumFunctions; I++) + Funcs.push_back({read(&SMData), read(&SMData), + read(&SMData)}); + + // Constants. + for (uint64_t I = 0; I < NumConstants; I++) + Consts.push_back(read(&SMData)); + + // Records. + for (uint64_t I = 0; I < NumRecords; I++) { + uint64_t ID = read(&SMData); + uint32_t InstrOff = read(&SMData); + read(&SMData); // skip reserved. + uint16_t NumLocs = read(&SMData); + + vector Locs; + for (uint16_t J = 0; J < NumLocs; J++) { + uint8_t Where = read(&SMData); + read(&SMData); // Skip reserved. + uint16_t Size = read(&SMData); + uint16_t RegNum = read(&SMData); + read(&SMData); // Skip reserved. + int32_t OffsetOrSmallConst = read(&SMData); + Locs.push_back({StorageClass(Where), Size, RegNum, OffsetOrSmallConst}); + } + + alignTo(&SMData, 8); // Pad if necessary. + read(&SMData); // Skip reserved. + + // Liveouts. + uint16_t NumLiveOuts = read(&SMData); + vector LiveOuts; + for (uint64_t I = 0; I < NumLiveOuts; I++) { + uint16_t RegNum = read(&SMData); + read(&SMData); // Skip reserved. + uint8_t Size = read(&SMData); + LiveOuts.push_back({RegNum, Size}); + } + + alignTo(&SMData, 8); // Pad if necessary. + + Recs.insert({ID, {InstrOff, Locs, LiveOuts}}); + } +} + +SMRec *StackMapParser::getRecord(uint64_t ID) { + map::iterator It = Recs.find(ID); + if (It == Recs.end()) + return nullptr; + return &It->second; +} + +string getStorageClassName(StorageClass C) { + switch (C) { + case Reg: + return "Register"; + case Direct: + return "Direct"; + case Indirect: + return "Indirect"; + case Const: + return "Constant"; + case ConstIndex: + return "ConstIndex"; + default: + errx(EXIT_FAILURE, "unreachable"); + } +} Index: MultiSource/UnitTests/Stackmaps/input_x86.ll =================================================================== --- /dev/null +++ MultiSource/UnitTests/Stackmaps/input_x86.ll @@ -0,0 +1,27 @@ +@test_input = global i64 9833440827789222417 ; 0x8877665544332211 + +define void @run_tests(i64* %test_input) noinline { + %arg64 = load i64, i64* %test_input + + ; Test constants. + call void (i64, i32, ...) @llvm.experimental.stackmap(i64 0, i32 0, + i8 50, i16 51, i32 52, i64 53) + call void @sm_inspect() + + ; Test non-constants. + ; This addition forces each value into a distinct register. + %arg64_plus1 = add i64 %arg64, 1 + %arg64_plus2 = add i64 %arg64, 2 + %arg64_plus3 = add i64 %arg64, 3 + %arg8 = trunc i64 %arg64_plus1 to i8 + %arg16 = trunc i64 %arg64_plus2 to i16 + %arg32 = trunc i64 %arg64_plus3 to i32 + call void (i64, i32, ...) @llvm.experimental.stackmap(i64 1, i32 0, + i8 %arg8, i16 %arg16, i32 %arg32, i64 %arg64) + notail call void @sm_inspect() + + ret void +} + +declare void @llvm.experimental.stackmap(i64, i32, ...) +declare void @sm_inspect() Index: MultiSource/UnitTests/Stackmaps/stackmaps.reference_output =================================================================== --- /dev/null +++ MultiSource/UnitTests/Stackmaps/stackmaps.reference_output @@ -0,0 +1,37 @@ +StackMap #0 + Location #0 + Storage: Constant + Size: 4 + Bytes: 32 00 00 00 + Location #1 + Storage: Constant + Size: 4 + Bytes: 33 00 00 00 + Location #2 + Storage: Constant + Size: 4 + Bytes: 34 00 00 00 + Location #3 + Storage: Constant + Size: 4 + Bytes: 35 00 00 00 + +StackMap #1 + Location #0 + Storage: Register + Size: 1 + Bytes: 12 + Location #1 + Storage: Register + Size: 2 + Bytes: 13 22 + Location #2 + Storage: Register + Size: 4 + Bytes: 14 22 33 44 + Location #3 + Storage: Register + Size: 8 + Bytes: 11 22 33 44 55 66 77 88 + +exit 0