audk/BaseTools/ImageTool/PeEmit.c

689 lines
20 KiB
C

/** @file
Copyright (c) 2021, Marvin Häuser. All rights reserved.
Copyright (c) 2022, Mikhail Krichanov. All rights reserved.
SPDX-License-Identifier: BSD-3-Clause
**/
#include "ImageTool.h"
#include "DynamicBuffer.h"
#include "PeEmitCommon.h"
#if PE_ARCH == 32
typedef UINT32 EFI_IMAGE_NT_BASE_ADDRESS;
typedef EFI_IMAGE_NT_HEADERS32 EFI_IMAGE_NT_HEADERS;
#define EFI_IMAGE_NT_OPTIONAL_HDR_MAGIC EFI_IMAGE_NT_OPTIONAL_HDR32_MAGIC
#elif PE_ARCH == 64
typedef UINT64 EFI_IMAGE_NT_BASE_ADDRESS;
typedef EFI_IMAGE_NT_HEADERS64 EFI_IMAGE_NT_HEADERS;
#define EFI_IMAGE_NT_OPTIONAL_HDR_MAGIC EFI_IMAGE_NT_OPTIONAL_HDR64_MAGIC
#endif
#define PE_SUFFIX__(Name, Arch) Name##Arch
#define PE_SUFFIX_(Name, Arch) PE_SUFFIX__ (Name, Arch)
#define PE_SUFFIX(Name) PE_SUFFIX_ (Name, PE_ARCH)
typedef struct {
EFI_IMAGE_DEBUG_DIRECTORY_ENTRY Dir;
EFI_IMAGE_DEBUG_CODEVIEW_NB10_ENTRY Nb10;
} image_tool_debug_dir_t;
#define SIZE_OF_DATA_DIRECTORY \
EFI_IMAGE_NUMBER_OF_DIRECTORY_ENTRIES * sizeof (EFI_IMAGE_DATA_DIRECTORY)
#define SIZE_OF_OPTIONAL_HEADER \
sizeof (EFI_IMAGE_NT_HEADERS) - sizeof (EFI_IMAGE_NT_HEADERS_COMMON_HDR) \
+ SIZE_OF_DATA_DIRECTORY
static
bool
InternalFinalizeExtraSection (
image_tool_dynamic_buffer *Buffer,
EFI_IMAGE_NT_HEADERS *PeHdr,
EFI_IMAGE_SECTION_HEADER *SectionHeader,
const char *Name,
uint32_t Size,
uint32_t Offset
)
{
uint32_t BufferOffset;
assert (Size == ImageToolBufferGetSize (Buffer) - Offset);
strncpy (
(char *)SectionHeader->Name,
Name,
sizeof (SectionHeader->Name)
);
SectionHeader->Name[ARRAY_SIZE (SectionHeader->Name) - 1] = 0;
SectionHeader->VirtualSize = ALIGN_VALUE (Size, PeHdr->SectionAlignment);
SectionHeader->VirtualAddress = PeHdr->SizeOfImage;
SectionHeader->SizeOfRawData = ALIGN_VALUE (Size, PeHdr->FileAlignment);
SectionHeader->PointerToRawData = Offset;
SectionHeader->Characteristics = EFI_IMAGE_SCN_CNT_INITIALIZED_DATA | EFI_IMAGE_SCN_MEM_READ;
PeHdr->SizeOfInitializedData += SectionHeader->VirtualSize;
PeHdr->SizeOfImage += SectionHeader->VirtualSize;
BufferOffset = ImageToolBufferAppendReserve (
Buffer,
SectionHeader->SizeOfRawData - Size
);
return BufferOffset != MAX_UINT32;
}
#define ToolImageEmitPeHiiTable PE_SUFFIX (ToolImageEmitPeHiiTable)
static
bool
ToolImageEmitPeHiiTable (
image_tool_dynamic_buffer *Buffer,
EFI_IMAGE_NT_HEADERS *PeHdr,
EFI_IMAGE_SECTION_HEADER *SectionHeader,
const image_tool_image_info_t *Image
)
{
bool Success;
uint32_t HiiTableOffset;
uint32_t HiiTableSize;
void *HiiTable;
uint32_t Offset;
HiiTableOffset = ImageToolBufferAppendReserve (
Buffer,
mHiiResourceSectionHeaderSize
);
if (HiiTableOffset == MAX_UINT32) {
DEBUG_RAISE ();
return false;
}
assert (IS_ALIGNED (HiiTableOffset, PeHdr->FileAlignment));
HiiTable = ImageToolBufferGetPointer (Buffer, HiiTableOffset);
InitializeHiiResouceSectionHeader (
HiiTable,
SectionHeader->VirtualAddress,
Image->HiiInfo.DataSize
);
HiiTable = NULL;
Offset = ImageToolBufferAppend (
Buffer,
Image->HiiInfo.Data,
Image->HiiInfo.DataSize
);
if (Offset == MAX_UINT32) {
DEBUG_RAISE ();
return false;
}
HiiTableSize = ImageToolBufferGetSize (Buffer) - HiiTableOffset;
Success = InternalFinalizeExtraSection (
Buffer,
PeHdr,
SectionHeader,
".rsrc",
HiiTableSize,
HiiTableOffset
);
if (!Success) {
DEBUG_RAISE ();
return false;
}
PeHdr->DataDirectory[EFI_IMAGE_DIRECTORY_ENTRY_RESOURCE].VirtualAddress = SectionHeader->VirtualAddress;
PeHdr->DataDirectory[EFI_IMAGE_DIRECTORY_ENTRY_RESOURCE].Size = HiiTableSize;
return true;
}
#define ToolImageEmitPeRelocTable PE_SUFFIX (ToolImageEmitPeRelocTable)
static
bool
ToolImageEmitPeRelocTable (
image_tool_dynamic_buffer *Buffer,
EFI_IMAGE_NT_HEADERS *PeHdr,
EFI_IMAGE_SECTION_HEADER *SectionHeader,
const image_tool_image_info_t *Image
)
{
bool Success;
uint32_t RelocTableSize;
EFI_IMAGE_BASE_RELOCATION_BLOCK RelocBlock;
uint32_t BlockAddress;
uint32_t Index;
uint32_t RelocAddress;
uint16_t Relocation;
uint32_t RelocTableOffset;
uint32_t RelocTableEnd;
uint32_t RelocBlockOffset;
uint32_t NewRelocBlockOffset;
uint32_t Offset;
RelocBlockOffset = ImageToolBufferAppendReserve (Buffer, sizeof (RelocBlock));
if (RelocBlockOffset == MAX_UINT32) {
DEBUG_RAISE ();
return false;
}
RelocTableOffset = RelocBlockOffset;
assert (IS_ALIGNED (RelocTableOffset, PeHdr->FileAlignment));
BlockAddress = PAGE (Image->RelocInfo.Relocs[0].Target);
for (Index = 0; Index < Image->RelocInfo.NumRelocs; ++Index) {
RelocAddress = PAGE (Image->RelocInfo.Relocs[Index].Target);
if (RelocAddress != BlockAddress) {
Offset = ImageToolBufferAppendReserveAlign (
Buffer,
ALIGNOF (EFI_IMAGE_BASE_RELOCATION_BLOCK)
);
if (Offset == MAX_UINT32) {
DEBUG_RAISE ();
return false;
}
NewRelocBlockOffset = ImageToolBufferAppendReserve (
Buffer,
sizeof (RelocBlock)
);
if (NewRelocBlockOffset == MAX_UINT32) {
DEBUG_RAISE ();
return false;
}
RelocBlock.VirtualAddress = BlockAddress;
RelocBlock.SizeOfBlock = NewRelocBlockOffset - RelocBlockOffset;
ImageToolBufferWrite (
Buffer,
RelocBlockOffset,
&RelocBlock,
sizeof (RelocBlock)
);
RelocBlockOffset = NewRelocBlockOffset;
BlockAddress = RelocAddress;
}
Relocation = PAGE_OFF(Image->RelocInfo.Relocs[Index].Target);
Relocation |= ((uint16_t)Image->RelocInfo.Relocs[Index].Type) << 12U;
Offset = ImageToolBufferAppend (Buffer, &Relocation, sizeof (Relocation));
if (Offset == MAX_UINT32) {
DEBUG_RAISE ();
return false;
}
}
Offset = ImageToolBufferAppendReserveAlign (
Buffer,
ALIGNOF (EFI_IMAGE_BASE_RELOCATION_BLOCK)
);
if (Offset == MAX_UINT32) {
DEBUG_RAISE ();
return false;
}
RelocTableEnd = ImageToolBufferGetSize (Buffer);
RelocBlock.VirtualAddress = BlockAddress;
RelocBlock.SizeOfBlock = RelocTableEnd - RelocBlockOffset;
ImageToolBufferWrite (
Buffer,
RelocBlockOffset,
&RelocBlock,
sizeof (RelocBlock)
);
RelocTableSize = RelocTableEnd - RelocTableOffset;
Success = InternalFinalizeExtraSection (
Buffer,
PeHdr,
SectionHeader,
".reloc",
RelocTableSize,
RelocTableOffset
);
if (!Success) {
DEBUG_RAISE ();
return false;
}
SectionHeader->Characteristics |= EFI_IMAGE_SCN_MEM_DISCARDABLE;
PeHdr->DataDirectory[EFI_IMAGE_DIRECTORY_ENTRY_BASERELOC].VirtualAddress = SectionHeader->VirtualAddress;
PeHdr->DataDirectory[EFI_IMAGE_DIRECTORY_ENTRY_BASERELOC].Size = RelocTableSize;
return true;
}
#define ToolImageEmitPeDebugTable PE_SUFFIX (ToolImageEmitPeDebugTable)
static
bool
ToolImageEmitPeDebugTable (
image_tool_dynamic_buffer *Buffer,
EFI_IMAGE_NT_HEADERS *PeHdr,
EFI_IMAGE_SECTION_HEADER *SectionHeader,
const image_tool_image_info_t *Image
)
{
bool Success;
uint32_t DebugDirOffset;
uint32_t DebugDirSize;
image_tool_debug_dir_t *Data;
uint32_t Offset;
assert (Image->DebugInfo.SymbolsPath != NULL);
DebugDirOffset = ImageToolBufferAppendReserve (Buffer, sizeof (*Data));
if (DebugDirOffset == MAX_UINT32) {
DEBUG_RAISE ();
return false;
}
assert (IS_ALIGNED (DebugDirOffset, PeHdr->FileAlignment));
Offset = ImageToolBufferAppend (
Buffer,
Image->DebugInfo.SymbolsPath,
Image->DebugInfo.SymbolsPathLen + 1
);
if (Offset == MAX_UINT32) {
DEBUG_RAISE ();
return false;
}
DebugDirSize = ImageToolBufferGetSize (Buffer) - DebugDirOffset;
Success = InternalFinalizeExtraSection (
Buffer,
PeHdr,
SectionHeader,
".debug",
DebugDirSize,
DebugDirOffset
);
if (!Success) {
DEBUG_RAISE ();
return false;
}
SectionHeader->Characteristics |= EFI_IMAGE_SCN_MEM_DISCARDABLE;
PeHdr->DataDirectory[EFI_IMAGE_DIRECTORY_ENTRY_DEBUG].VirtualAddress = SectionHeader->VirtualAddress;
PeHdr->DataDirectory[EFI_IMAGE_DIRECTORY_ENTRY_DEBUG].Size = sizeof (Data->Dir);
Data = ImageToolBufferGetPointer (Buffer, DebugDirOffset);
memset (Data, 0, sizeof (*Data));
Data->Dir.Type = EFI_IMAGE_DEBUG_TYPE_CODEVIEW;
Data->Dir.SizeOfData = DebugDirSize - OFFSET_OF (image_tool_debug_dir_t, Nb10);
Data->Dir.RVA = SectionHeader->VirtualAddress + OFFSET_OF (image_tool_debug_dir_t, Nb10);
Data->Dir.FileOffset = SectionHeader->PointerToRawData + OFFSET_OF (image_tool_debug_dir_t, Nb10);
Data->Nb10.Signature = CODEVIEW_SIGNATURE_NB10;
Data = NULL;
return true;
}
static
bool
ToolImagePeHiiTableRequired (
const image_tool_image_info_t *Image
)
{
return Image->HiiInfo.DataSize != 0;
}
static
bool
ToolImagePeRelocTableRequired (
const image_tool_image_info_t *Image
)
{
return Image->RelocInfo.NumRelocs != 0;
}
static
bool
ToolImagePeDebugTableRequired (
const image_tool_image_info_t *Image
)
{
return Image->DebugInfo.SymbolsPath != NULL;
}
#define ToolImageEmitPeSection PE_SUFFIX (ToolImageEmitPeSection)
static
bool
ToolImageEmitPeSection (
image_tool_dynamic_buffer *Buffer,
EFI_IMAGE_NT_HEADERS *PeHdr,
EFI_IMAGE_SECTION_HEADER *SectionHeader,
const image_tool_image_info_t *Image,
bool Xip,
const image_tool_segment_t *Segment
)
{
uint32_t SectionOffset;
uint32_t SizeOfRawData;
uint32_t Offset;
SectionOffset = ImageToolBufferAppend (
Buffer,
Segment->Data,
Segment->UnpaddedSize
);
if (SectionOffset == MAX_UINT32) {
DEBUG_RAISE ();
return false;
}
assert (IS_ALIGNED (SectionOffset, PeHdr->FileAlignment));
if (!Xip) {
SizeOfRawData = Segment->UnpaddedSize;
} else {
SizeOfRawData = Segment->ImageSize;
}
if (Segment->Name != NULL) {
strncpy (
(char *)SectionHeader->Name,
Segment->Name,
sizeof (SectionHeader->Name)
);
SectionHeader->Name[ARRAY_SIZE (SectionHeader->Name) - 1] = 0;
}
SectionHeader->VirtualSize = Segment->ImageSize;
SectionHeader->VirtualAddress = Segment->ImageAddress;
SectionHeader->SizeOfRawData = ALIGN_VALUE (SizeOfRawData, PeHdr->FileAlignment);
SectionHeader->PointerToRawData = SectionOffset;
assert (SectionHeader->Characteristics == 0);
if (Segment->Read) {
SectionHeader->Characteristics |= EFI_IMAGE_SCN_MEM_READ;
}
if (Segment->Write) {
SectionHeader->Characteristics |= EFI_IMAGE_SCN_MEM_WRITE;
}
if (Segment->Execute) {
SectionHeader->Characteristics |= EFI_IMAGE_SCN_MEM_EXECUTE | EFI_IMAGE_SCN_CNT_CODE;
PeHdr->SizeOfCode += Segment->ImageSize;
if (PeHdr->BaseOfCode == 0) {
PeHdr->BaseOfCode = Segment->ImageAddress;
}
} else {
SectionHeader->Characteristics |= EFI_IMAGE_SCN_CNT_INITIALIZED_DATA;
PeHdr->SizeOfInitializedData += Segment->ImageSize;
#if PE_ARCH == 32
if (PeHdr->BaseOfData == 0) {
PeHdr->BaseOfData = Segment->ImageAddress;
}
#endif
}
assert (PeHdr->SizeOfImage == SectionHeader->VirtualAddress);
PeHdr->SizeOfImage += SectionHeader->VirtualSize;
Offset = ImageToolBufferAppendReserve (
Buffer,
SectionHeader->SizeOfRawData - Segment->UnpaddedSize
);
return Offset != MAX_UINT32;
}
#define ToolImageEmitPeSections PE_SUFFIX (ToolImageEmitPeSections)
static
bool
ToolImageEmitPeSections (
image_tool_dynamic_buffer *Buffer,
EFI_IMAGE_NT_HEADERS *PeHdr,
EFI_IMAGE_SECTION_HEADER *SectionHeaders,
const image_tool_image_info_t *Image,
bool Xip
)
{
bool Success;
uint16_t Index;
const image_tool_segment_t *Segment;
EFI_IMAGE_SECTION_HEADER *SectionHeader;
if (PeHdr->SizeOfImage != Image->SegmentInfo.Segments[0].ImageAddress) {
DEBUG_RAISE ();
return false;
}
for (Index = 0; Index < Image->SegmentInfo.NumSegments; ++Index) {
Segment = &Image->SegmentInfo.Segments[Index];
SectionHeader = &SectionHeaders[Index];
Success = ToolImageEmitPeSection (
Buffer,
PeHdr,
SectionHeader,
Image,
Xip,
Segment
);
if (!Success) {
DEBUG_RAISE ();
return false;
}
}
if (ToolImagePeHiiTableRequired (Image)) {
Success = ToolImageEmitPeHiiTable (Buffer, PeHdr, &SectionHeaders[Index], Image);
if (!Success) {
DEBUG_RAISE ();
return false;
}
++Index;
}
if (ToolImagePeRelocTableRequired (Image)) {
Success = ToolImageEmitPeRelocTable (Buffer, PeHdr, &SectionHeaders[Index], Image);
if (!Success) {
DEBUG_RAISE ();
return false;
}
++Index;
}
if (ToolImagePeDebugTableRequired (Image)) {
Success = ToolImageEmitPeDebugTable (Buffer, PeHdr, &SectionHeaders[Index], Image);
if (!Success) {
DEBUG_RAISE ();
return false;
}
++Index;
}
assert (Index == PeHdr->CommonHeader.FileHeader.NumberOfSections);
if (Image->HeaderInfo.FixedAddress) {
for (Index = 0; Index < PeHdr->CommonHeader.FileHeader.NumberOfSections; ++Index) {
if ((SectionHeaders[Index].Characteristics & EFI_IMAGE_SCN_MEM_EXECUTE) == 0) {
WriteUnaligned64 (
(VOID *)&SectionHeaders[Index].PointerToRelocations,
PeHdr->ImageBase
);
break;
}
}
if (Index == PeHdr->CommonHeader.FileHeader.NumberOfSections) {
DEBUG_RAISE ();
return false;
}
}
return true;
}
STATIC CONST EFI_IMAGE_DOS_HEADER mDosHdr = {
.e_magic = EFI_IMAGE_DOS_SIGNATURE,
.e_lfanew = sizeof (mDosHdr)
};
#define ToolImageEmitPeFile PE_SUFFIX (ToolImageEmitPeFile)
static
bool
ToolImageEmitPeFile (
image_tool_dynamic_buffer *Buffer,
const image_tool_image_info_t *Image,
bool Xip
)
{
bool Success;
uint32_t FileAlignment;
uint32_t PeOffset;
EFI_IMAGE_NT_HEADERS *PeHdr;
uint16_t NumSections;
EFI_IMAGE_SECTION_HEADER *SectionHeaders;
uint32_t SectionHeadersOffset;
uint32_t SectionHeadersSize;
uint32_t SizeOfPeHeaders;
uint32_t SizeOfHeaders;
uint32_t AlignedSizeOfHeaders;
void *BufferPeHdr;
uint32_t Offset;
if (!Xip) {
FileAlignment = 64;
} else {
FileAlignment = Image->SegmentInfo.SegmentAlignment;
}
NumSections = Image->SegmentInfo.NumSegments;
NumSections += ToolImagePeHiiTableRequired (Image) ? 1 : 0;
NumSections += ToolImagePeRelocTableRequired (Image) ? 1 : 0;
NumSections += ToolImagePeDebugTableRequired (Image) ? 1 : 0;
Offset = ImageToolBufferAppend (Buffer, &mDosHdr, sizeof (mDosHdr));
if (Offset == MAX_UINT32) {
DEBUG_RAISE ();
return false;
}
SectionHeadersOffset = sizeof (*PeHdr) + SIZE_OF_DATA_DIRECTORY;
SectionHeadersSize = NumSections * sizeof (EFI_IMAGE_SECTION_HEADER);
SizeOfPeHeaders = SectionHeadersOffset + SectionHeadersSize;
SizeOfHeaders = sizeof (mDosHdr) + SizeOfPeHeaders;
AlignedSizeOfHeaders = ALIGN_VALUE (SizeOfHeaders, FileAlignment);
//
// Necessary adjustment for '-D EDK2_REDUCE_FW_SIZE' build option.
//
if (AlignedSizeOfHeaders < Image->SegmentInfo.Segments[0].ImageAddress) {
AlignedSizeOfHeaders = (uint32_t)Image->SegmentInfo.Segments[0].ImageAddress;
}
PeOffset = ImageToolBufferAppendReserve (
Buffer,
AlignedSizeOfHeaders - sizeof (mDosHdr)
);
if (PeOffset == MAX_UINT32) {
DEBUG_RAISE ();
return false;
}
PeHdr = AllocateZeroPool (SizeOfPeHeaders);
if (PeHdr == NULL) {
DEBUG_RAISE ();
return false;
}
SectionHeaders = (EFI_IMAGE_SECTION_HEADER *)((UINT8 *)PeHdr + SectionHeadersOffset);
PeHdr->CommonHeader.Signature = EFI_IMAGE_NT_SIGNATURE;
PeHdr->CommonHeader.FileHeader.Machine = Image->HeaderInfo.Machine;
PeHdr->CommonHeader.FileHeader.NumberOfSections = NumSections;
PeHdr->CommonHeader.FileHeader.SizeOfOptionalHeader = SIZE_OF_OPTIONAL_HEADER;
PeHdr->CommonHeader.FileHeader.Characteristics =
EFI_IMAGE_FILE_EXECUTABLE_IMAGE | EFI_IMAGE_FILE_LINE_NUMS_STRIPPED |
EFI_IMAGE_FILE_LOCAL_SYMS_STRIPPED | EFI_IMAGE_FILE_LARGE_ADDRESS_AWARE;
if (Image->RelocInfo.RelocsStripped) {
PeHdr->CommonHeader.FileHeader.Characteristics |= EFI_IMAGE_FILE_RELOCS_STRIPPED;
}
PeHdr->Magic = EFI_IMAGE_NT_OPTIONAL_HDR_MAGIC;
PeHdr->AddressOfEntryPoint = Image->HeaderInfo.EntryPointAddress;
PeHdr->ImageBase = (EFI_IMAGE_NT_BASE_ADDRESS)Image->HeaderInfo.BaseAddress;
PeHdr->SectionAlignment = Image->SegmentInfo.SegmentAlignment;
PeHdr->FileAlignment = FileAlignment;
PeHdr->SizeOfImage = AlignedSizeOfHeaders;
PeHdr->SizeOfHeaders = AlignedSizeOfHeaders;
PeHdr->Subsystem = Image->HeaderInfo.Subsystem;
PeHdr->NumberOfRvaAndSizes = EFI_IMAGE_NUMBER_OF_DIRECTORY_ENTRIES;
Success = ToolImageEmitPeSections (Buffer, PeHdr, SectionHeaders, Image, Xip);
if (!Success) {
FreePool (PeHdr);
DEBUG_RAISE ();
return false;
}
BufferPeHdr = ImageToolBufferGetPointer (Buffer, PeOffset);
memmove (BufferPeHdr, PeHdr, SizeOfPeHeaders);
FreePool (PeHdr);
BufferPeHdr = NULL;
return true;
}
#define ToolImageEmitPe PE_SUFFIX (ToolImageEmitPe)
void *
ToolImageEmitPe (
const image_tool_image_info_t *Image,
uint32_t *FileSize,
bool Xip
)
{
bool Success;
image_tool_dynamic_buffer Buffer;
void *FileBuffer;
assert (Image != NULL);
assert (FileSize != NULL);
ImageInitUnpaddedSize (Image);
ImageToolBufferInit (&Buffer);
Success = ToolImageEmitPeFile (&Buffer, Image, Xip);
if (!Success) {
DEBUG_RAISE ();
ImageToolBufferFree (&Buffer);
return NULL;
}
FileBuffer = ImageToolBufferDump (FileSize, &Buffer);
ImageToolBufferFree (&Buffer);
if (FileBuffer == NULL) {
DEBUG_RAISE ();
return NULL;
}
return FileBuffer;
}