Skip to content

Commit

Permalink
Add cache hash note to shader ELF
Browse files Browse the repository at this point in the history
When LLPC checks shader and pipeline caches, it uses the hash for
the identifier to check if required ELF files exist in the cache file.
Since the cache file keeps the mapping between the hashes and the ELF
files, we do not keep the hash value in the ELF file.

On the other hand, we need the cache creator proposed in
GPUOpen-Drivers/xgl#64 to build the cache
for relocatable shader ELFs, but the cache creator does not have any
information about the hash. It is because the hash is generated by
LLPC (or amdllpc) and the cache creator is a separate program.

We want to let the LLPC compiler create a new note section for the
cache hash.  It can be used for the cache creator to create the cache
file with the correct mapping between the hash and ELF.

ELF layouts before/after adding new note entries for cache hash and
LLPC version to the existing note section:

```
|-------------|             |-------------|
| ELF header  |             | ELF header  |
|-------------|             |-------------|
| Sections    |             | Sections    |
| ...         |             | ...         |
|-------------|             |-------------|
| Note section|     ==>     | Note section|
| ...         |             |             |
|-------------|<--(Will be  | + New note  |
| ...         |    Shifted) |   entries   |
|-------------| |        |  | ...         |
| Section     | |        \->|-------------|
| headers     | V           | ...         |
| ...         |             |-------------|
                            | Section     |
                            | headers     |
                            | ...         |
```

New note entries:
 1. Note name with "llpc_cache_hash" and note description with the cache hash used for the cache lookup.
 2. Note name with "llpc_version" and note description with the LLPC version - both major and minor.
    The LLPC version information will help us to understand the hash generation algorithm. We have to
    use a correct hash algorithm for the cache lookup.

For example, if the hash is "4EDBED25 ADF15238 B8C92579 423DA423" and the LLPC version is 45.4
(the major version is 45=0x2D and the minor version is 4=0x04), two new note entries will be

```
 Unknown(0)                (name = llpc_cache_hash  size = 16)
       0:4EDBED25 ADF15238 B8C92579 423DA423
 Unknown(0)                (name = llpc_version  size = 8)
       0:0000002D 00000004
```
  • Loading branch information
jaebaek committed Jan 28, 2021
1 parent 5bdcd66 commit 15c28fd
Show file tree
Hide file tree
Showing 12 changed files with 519 additions and 10 deletions.
1 change: 1 addition & 0 deletions lgc/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,7 @@ target_sources(LLVMlgc PRIVATE
target_sources(LLVMlgc PRIVATE
util/AddressExtender.cpp
util/Debug.cpp
util/ElfNoteEntryInsertionUtil.cpp
util/GfxRegHandlerBase.cpp
util/GfxRegHandler.cpp
util/Internal.cpp
Expand Down
75 changes: 75 additions & 0 deletions lgc/interface/lgc/ElfNoteEntryInsertionUtil.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
/*
***********************************************************************************************************************
*
* Copyright (c) 2021 Google LLC. All Rights Reserved.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*
**********************************************************************************************************************/
/**
***********************************************************************************************************************
* @file ElfNoteEntryInsertionUtil.h
* @brief LLPC header file: declaration of lgc::addNotesToElf interface
*
* @details The function addNotesToElf adds given note entries to the given ELF.
***********************************************************************************************************************
*/

#pragma once
#include "llvm/ADT/ArrayRef.h"
#include "llvm/ADT/SmallVector.h"
#include "llvm/ADT/StringRef.h"
#include "llvm/Support/CommandLine.h"

namespace lgc {

// Note entry that will be added to the note section.
struct NoteEntry {
llvm::StringRef name;
llvm::ArrayRef<uint8_t> desc;
unsigned type;
};

// =====================================================================================================================
// Adds the given note entries to the given ELF if the given ELF has a note section.
// Otherwise, it does nothing.
//
// ELF layouts before/after adding new "note" entries to the existing note section:
//
// |-------------| |-------------|
// | ELF header | | ELF header |
// |-------------| |-------------|
// | Sections | | Sections |
// | ... | | ... |
// |-------------| |-------------|
// | Note section| ==> | Note section|
// | ... | | |
// |-------------|<--(Will be | + New note |
// | ... | Shifted) | entries |
// |-------------| | | | ... |
// | Section | | \->|-------------|
// | headers | V | ... |
// | ... | |-------------|
// | Section |
// | headers |
// | ... |
//
void addNotesToElf(llvm::SmallVectorImpl<char> &elf, llvm::ArrayRef<NoteEntry> notes, const char *noteSectionName);

} // namespace lgc
275 changes: 275 additions & 0 deletions lgc/util/ElfNoteEntryInsertionUtil.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,275 @@
/*
***********************************************************************************************************************
*
* Copyright (c) 2021 Google LLC. All Rights Reserved.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*
**********************************************************************************************************************/
#include "lgc/ElfNoteEntryInsertionUtil.h"
#include "llvm/ADT/SmallString.h"
#include "llvm/ADT/StringExtras.h"
#include "llvm/Object/ELF.h"
#include "llvm/Support/Alignment.h"
#include "llvm/Support/BinaryByteStream.h"
#include "llvm/Support/BinaryStreamReader.h"
#include "llvm/Support/BinaryStreamWriter.h"
#include "llvm/Support/raw_ostream.h"

using namespace llvm;
using namespace lgc;

// The implementation of ELF rewriting is based on "Linux Programmer's Manual ELF(5)".
// In particular, see "Notes (Nhdr)" for the document of the note section.

namespace {

// It is similar to struct NoteHeader defined in llpc/util/vkgcElfReader.h, but
// it allows us to use Elf_Nhdr_Impl<object::ELF64LE>::Align and it does not
// have the size limitation of note name.
using NoteHeader = object::Elf_Nhdr_Impl<object::ELF64LE>;

// An array of zeros. We use it for the note alignment.
constexpr char ZerosForNoteAlign[NoteHeader::Align] = {'\0'};

// =====================================================================================================================
// Contents of a section to be shifted and its new offset.
struct SectionShiftInfo {
SmallString<64> section;
ELF::Elf64_Off newOffset;
};

// =====================================================================================================================
// Generates a StringRef from the given SmallVector.
//
// @param input : The SmallVector to be converted to StringRef.
// @returns : The generated StringRef.
template <typename T> StringRef stringRefFromSmallVector(const SmallVectorImpl<T> &input) {
return StringRef(reinterpret_cast<const char *>(input.data()), sizeof(T) * input.size());
}

// =====================================================================================================================
// Add an entry to the note section.
//
// Reference: Linux Programmer's Manual ELF(5) "Notes (Nhdr)".
//
// @param noteEntry : The note entry to be added to the note section.
// @param [out] noteEntryWriter : The .note section where the note entry will be added to.
void addNoteEntry(const NoteEntry &noteEntry, BinaryStreamWriter &noteEntryWriter) {
NoteHeader noteHeader = {};
noteHeader.n_namesz = noteEntry.name.size() + 1;
noteHeader.n_descsz = noteEntry.desc.size();
noteHeader.n_type = static_cast<ELF::Elf64_Word>(noteEntry.type);
auto error = noteEntryWriter.writeObject(noteHeader);
(void)error;
assert(!error);

// Write the note name terminated by zero and zeros for the alignment.
error = noteEntryWriter.writeCString(noteEntry.name);
assert(!error);
error = noteEntryWriter.writeFixedString(StringRef(
ZerosForNoteAlign, offsetToAlignment(noteEntryWriter.getLength(), Align::Constant<NoteHeader::Align>())));
assert(!error);

// Write the note description and zeros for the alignment.
error = noteEntryWriter.writeBytes(noteEntry.desc);
assert(!error);
error = noteEntryWriter.writeFixedString(StringRef(
ZerosForNoteAlign, offsetToAlignment(noteEntryWriter.getLength(), Align::Constant<NoteHeader::Align>())));
assert(!error);
}

// =====================================================================================================================
// Writes note entries to a byte-stream.
//
// @param notes : The array of note entries to be added to the note section.
// @param newNoteEntryOffset : The offset in ELF where the note entries will be added.
// @param [out] noteEntryStream : The byte-stream to be filled with the note entries.
void writeNoteEntriesToByteStream(ArrayRef<NoteEntry> notes, const ELF::Elf64_Off newNoteEntryOffset,
AppendingBinaryByteStream &noteEntryStream) {
BinaryStreamWriter noteEntryWriter(noteEntryStream);
auto error = noteEntryWriter.writeFixedString(
StringRef(ZerosForNoteAlign, offsetToAlignment(newNoteEntryOffset, Align::Constant<NoteHeader::Align>())));
(void)error;
assert(!error);

// Write the note entries.
for (const auto &note : notes)
addNoteEntry(note, noteEntryWriter);
}

// =====================================================================================================================
// Updates the offsets of sections to their new offsets to be shifted. After updating offsets
// it returns the contents of sections and their new offsets.
//
// @param elf : The input ELF.
// @param shiftStartingOffset : The first offset of ELF contents that will be shifted.
// All sections after this offset will be shifted.
// @param lengthToBeShifted : The length how much sections after shiftStartingOffset will be shift.
// @param sectionHeaders : The array of section headers.
// @param [out] sectionAndNewOffset : The array of the contents of sections to be shifted and their new
// offset sorted by the new offset in the increasing order.
void updateSectionOffsetsForShift(const SmallVectorImpl<char> &elf, const ELF::Elf64_Off shiftStartingOffset,
ELF::Elf64_Off lengthToBeShifted, MutableArrayRef<ELF::Elf64_Shdr> sectionHeaders,
SmallVectorImpl<SectionShiftInfo> &sectionAndNewOffset) {
// If a section is located after shiftStartingOffset, it must be shifted.
for (auto &sectionHeader : sectionHeaders) {
if (sectionHeader.sh_offset < shiftStartingOffset)
continue;
const auto newOffset = alignTo(sectionHeader.sh_offset + lengthToBeShifted, Align(sectionHeader.sh_addralign));
sectionAndNewOffset.push_back(
{SmallString<64>(StringRef(elf.data() + sectionHeader.sh_offset, sectionHeader.sh_size)), newOffset});
lengthToBeShifted = newOffset - sectionHeader.sh_offset;

// Update the offset of section pointed by the section header to its new offset.
sectionHeader.sh_offset = newOffset;
}

// Sort sectionAndNewOffset by the new offset of each section in the increasing order.
sort(sectionAndNewOffset,
[](const SectionShiftInfo &i0, const SectionShiftInfo &i1) { return i0.newOffset < i1.newOffset; });
}

// =====================================================================================================================
// Inserts the new contents to the given ELF.
//
// @param [in/out] elf : The ELF to insert the new contents.
// @param insertionOffset : The offset of the new contents to be inserted.
// @param elfContentStream : The ELF contents to be inserted.
// @param sectionAndNewOffset : The sections to be shifted and their new offsets.
void insertContentsToELF(SmallVectorImpl<char> &elf, const ELF::Elf64_Off insertionOffset,
AppendingBinaryByteStream &elfContentStream,
const SmallVectorImpl<SectionShiftInfo> &sectionAndNewOffset) {
// Strip sections after the insertion offset of the new contents.
elf.resize(insertionOffset);

// Write the new contents.
raw_svector_ostream elfStream(elf);
ArrayRef<uint8_t> contents;
auto error = elfContentStream.readBytes(0, elfContentStream.getLength(), contents);
(void)error;
assert(!error);
elfStream << toStringRef(contents);

// Write the sections after the insertion offset.
for (const auto &sectionAndNewOffsetInfo : sectionAndNewOffset) {
elfStream.write_zeros(sectionAndNewOffsetInfo.newOffset - elfStream.str().size());
elfStream << sectionAndNewOffsetInfo.section.str();
}
}

// =====================================================================================================================
// Write the section header table to the given offset.
//
// @param [in/out] elf : The ELF to write the section header table.
// @param sectionHeaderTableOffset : The offset where it will write the section header table.
// @param sectionHeaderTable : The section header table to be written to the ELF.
void writeSectionHeaderTable(SmallVectorImpl<char> &elf, const ELF::Elf64_Off sectionHeaderTableOffset,
const SmallVectorImpl<ELF::Elf64_Shdr> &sectionHeaderTable) {
if (sectionHeaderTable.size() == 0)
return;

raw_svector_ostream elfStream(elf);
const unsigned minElfSizeForSectionHeaders =
sectionHeaderTableOffset + sizeof(ELF::Elf64_Shdr) * sectionHeaderTable.size();
if (minElfSizeForSectionHeaders > elf.size())
elfStream.write_zeros(minElfSizeForSectionHeaders - elf.size());
auto sectionHeaderTableInString = stringRefFromSmallVector(sectionHeaderTable);
elfStream.pwrite(sectionHeaderTableInString.data(), sectionHeaderTableInString.size(), sectionHeaderTableOffset);
}

} // anonymous namespace

namespace lgc {

// =====================================================================================================================
// Adds the given note entries to the note section with the given section name in the given ELF.
// If the note section with the given name does not exist, it uses any other note section.
//
// @param [in/out] elf : ELF to be updated with the new note entries.
// @param notes : An array of note entries to be inserted to the existing note section.
// @param noteSectionName : The name of note section to where note entries will be inserted.
void addNotesToElf(SmallVectorImpl<char> &elf, ArrayRef<NoteEntry> notes, const char *noteSectionName) {
// Get ELF header that contains information for section header table offset
// and the number of section headers.
//
// Reference: http://www.skyfree.org/linux/references/ELF_Format.pdf
ELF::Elf64_Ehdr *ehdr = reinterpret_cast<ELF::Elf64_Ehdr *>(elf.data());

// Get the section headers and the existing note section whose name is noteSectionName.
MutableArrayRef<ELF::Elf64_Shdr> sectionHeaders(reinterpret_cast<ELF::Elf64_Shdr *>(elf.data() + ehdr->e_shoff),
ehdr->e_shnum);
auto existingNoteSection =
find_if(sectionHeaders, [&elf, ehdr, &sectionHeaders, noteSectionName](const ELF::Elf64_Shdr &sectionHeader) {
if (sectionHeader.sh_type != ELF::SHT_NOTE)
return false;
const char *stringTableForSectionNames = elf.data() + sectionHeaders[ehdr->e_shstrndx].sh_offset;
return !strcmp(noteSectionName, &stringTableForSectionNames[sectionHeader.sh_name]);
});
// If a note section with noteSectionName does not exist, use any other note section.
if (existingNoteSection == sectionHeaders.end()) {
existingNoteSection = find_if(
sectionHeaders, [](const ELF::Elf64_Shdr &sectionHeader) { return sectionHeader.sh_type == ELF::SHT_NOTE; });
}

// We assume that the given ELF already contains a note section. Since AMD GPU
// accepts only ELFs with AMD related metadata, the assumption will be satisfied.
assert(existingNoteSection != sectionHeaders.end());

// Prepare the new note entries to be added to the existing note section.
const ELF::Elf64_Off newNoteEntryOffset = existingNoteSection->sh_offset + existingNoteSection->sh_size;
AppendingBinaryByteStream noteEntryStream(support::little);
writeNoteEntriesToByteStream(notes, newNoteEntryOffset, noteEntryStream);

// Get the last section located just before the section header table.
auto sectionBeforeSectionHeaderTable =
std::max_element(sectionHeaders.begin(), sectionHeaders.end(),
[ehdr](const ELF::Elf64_Shdr &largest, const ELF::Elf64_Shdr &current) {
if (current.sh_offset > ehdr->e_shoff)
return false;
return current.sh_offset > largest.sh_offset;
});

// Update the offset information of sections after the offset of new note entries.
// The new offset should be the offset where each section will be shifted to.
SmallVector<SectionShiftInfo> sectionAndNewOffset;
updateSectionOffsetsForShift(elf, newNoteEntryOffset, noteEntryStream.getLength(), sectionHeaders,
sectionAndNewOffset);

// Increase the size of the existing note section to include new note entries.
existingNoteSection->sh_size += noteEntryStream.getLength();

// Prepare the section header table shift if we have to shift it. Note that inserting note entries requires
// rewriting sections, which results in overwriting the section header table. Therefore, the contents
// pointed by the MutableArrayRef sectionHeaders will not be the section header table. In that case, we
// have to create a backup for the section header table.
SmallVector<ELF::Elf64_Shdr> sectionHeaderTableBackup;
if (ehdr->e_shoff > newNoteEntryOffset) {
ehdr->e_shoff = sectionBeforeSectionHeaderTable->sh_offset + sectionBeforeSectionHeaderTable->sh_size;
sectionHeaderTableBackup.append(sectionHeaders.begin(), sectionHeaders.end());
}

// Insert the stream of note entries to the ELF.
insertContentsToELF(elf, newNoteEntryOffset, noteEntryStream, sectionAndNewOffset);

// Write section header table.
writeSectionHeaderTable(elf, ehdr->e_shoff, sectionHeaderTableBackup);
}

} // namespace lgc
Loading

0 comments on commit 15c28fd

Please sign in to comment.