//===- DXContainer.cpp - DXContainer object file implementation -----------===// // // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. // See https://llvm.org/LICENSE.txt for license information. // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception // //===----------------------------------------------------------------------===// #include "llvm/Object/DXContainer.h" #include "llvm/BinaryFormat/DXContainer.h" #include "llvm/Object/Error.h" #include "llvm/Support/FormatVariadic.h" using namespace llvm; using namespace llvm::object; static Error parseFailed(const Twine &Msg) { return make_error(Msg.str(), object_error::parse_failed); } template static Error readStruct(StringRef Buffer, const char *Src, T &Struct) { // Don't read before the beginning or past the end of the file if (Src < Buffer.begin() || Src + sizeof(T) > Buffer.end()) return parseFailed("Reading structure out of file bounds"); memcpy(&Struct, Src, sizeof(T)); // DXContainer is always little endian if (sys::IsBigEndianHost) Struct.swapBytes(); return Error::success(); } template static Error readInteger(StringRef Buffer, const char *Src, T &Val, Twine Str = "structure") { static_assert(std::is_integral_v, "Cannot call readInteger on non-integral type."); // Don't read before the beginning or past the end of the file if (Src < Buffer.begin() || Src + sizeof(T) > Buffer.end()) return parseFailed(Twine("Reading ") + Str + " out of file bounds"); // The DXContainer offset table is comprised of uint32_t values but not padded // to a 64-bit boundary. So Parts may start unaligned if there is an odd // number of parts and part data itself is not required to be padded. if (reinterpret_cast(Src) % alignof(T) != 0) memcpy(reinterpret_cast(&Val), Src, sizeof(T)); else Val = *reinterpret_cast(Src); // DXContainer is always little endian if (sys::IsBigEndianHost) sys::swapByteOrder(Val); return Error::success(); } DXContainer::DXContainer(MemoryBufferRef O) : Data(O) {} Error DXContainer::parseHeader() { return readStruct(Data.getBuffer(), Data.getBuffer().data(), Header); } Error DXContainer::parseDXILHeader(StringRef Part) { if (DXIL) return parseFailed("More than one DXIL part is present in the file"); const char *Current = Part.begin(); dxbc::ProgramHeader Header; if (Error Err = readStruct(Part, Current, Header)) return Err; Current += offsetof(dxbc::ProgramHeader, Bitcode) + Header.Bitcode.Offset; DXIL.emplace(std::make_pair(Header, Current)); return Error::success(); } Error DXContainer::parseShaderFlags(StringRef Part) { if (ShaderFlags) return parseFailed("More than one SFI0 part is present in the file"); uint64_t FlagValue = 0; if (Error Err = readInteger(Part, Part.begin(), FlagValue)) return Err; ShaderFlags = FlagValue; return Error::success(); } Error DXContainer::parseHash(StringRef Part) { if (Hash) return parseFailed("More than one HASH part is present in the file"); dxbc::ShaderHash ReadHash; if (Error Err = readStruct(Part, Part.begin(), ReadHash)) return Err; Hash = ReadHash; return Error::success(); } Error DXContainer::parsePartOffsets() { uint32_t LastOffset = sizeof(dxbc::Header) + (Header.PartCount * sizeof(uint32_t)); const char *Current = Data.getBuffer().data() + sizeof(dxbc::Header); for (uint32_t Part = 0; Part < Header.PartCount; ++Part) { uint32_t PartOffset; if (Error Err = readInteger(Data.getBuffer(), Current, PartOffset)) return Err; if (PartOffset < LastOffset) return parseFailed( formatv( "Part offset for part {0} begins before the previous part ends", Part) .str()); Current += sizeof(uint32_t); if (PartOffset >= Data.getBufferSize()) return parseFailed("Part offset points beyond boundary of the file"); // To prevent overflow when reading the part name, we subtract the part name // size from the buffer size, rather than adding to the offset. Since the // file header is larger than the part header we can't reach this code // unless the buffer is at least as large as a part header, so this // subtraction can't underflow. if (PartOffset >= Data.getBufferSize() - sizeof(dxbc::PartHeader::Name)) return parseFailed("File not large enough to read part name"); PartOffsets.push_back(PartOffset); dxbc::PartType PT = dxbc::parsePartType(Data.getBuffer().substr(PartOffset, 4)); uint32_t PartDataStart = PartOffset + sizeof(dxbc::PartHeader); uint32_t PartSize; if (Error Err = readInteger(Data.getBuffer(), Data.getBufferStart() + PartOffset + 4, PartSize, "part size")) return Err; StringRef PartData = Data.getBuffer().substr(PartDataStart, PartSize); LastOffset = PartOffset + PartSize; switch (PT) { case dxbc::PartType::DXIL: if (Error Err = parseDXILHeader(PartData)) return Err; break; case dxbc::PartType::SFI0: if (Error Err = parseShaderFlags(PartData)) return Err; break; case dxbc::PartType::HASH: if (Error Err = parseHash(PartData)) return Err; break; case dxbc::PartType::Unknown: break; } } return Error::success(); } Expected DXContainer::create(MemoryBufferRef Object) { DXContainer Container(Object); if (Error Err = Container.parseHeader()) return std::move(Err); if (Error Err = Container.parsePartOffsets()) return std::move(Err); return Container; } void DXContainer::PartIterator::updateIteratorImpl(const uint32_t Offset) { StringRef Buffer = Container.Data.getBuffer(); const char *Current = Buffer.data() + Offset; // Offsets are validated during parsing, so all offsets in the container are // valid and contain enough readable data to read a header. cantFail(readStruct(Buffer, Current, IteratorState.Part)); IteratorState.Data = StringRef(Current + sizeof(dxbc::PartHeader), IteratorState.Part.Size); IteratorState.Offset = Offset; }