/*++ Copyright (c) 2014 Minoca Corp. All Rights Reserved Module Name: elfconv.c Abstract: This module implements a UEFI build utility that converts an ELF image into a PE image. Author: Evan Green 7-Mar-2014 Environment: Build --*/ // // ------------------------------------------------------------------- Includes // #include #include #include #include #include #include #include #include #include "uefifw.h" #include "elfconv.h" // // ---------------------------------------------------------------- Definitions // #define ELFCONV_VERSION_MAJOR 1 #define ELFCONV_VERSION_MINOR 0 #define ELFCONV_USAGE \ "Usage: ElfConv [options] [files...]\n" \ "The ElfConv utility takes an ELF file as input and produces a PE image.\n"\ "Dynamic linking is not supported. Valid options are:\n" \ " -o, --output=File -- Specify the output image name. The default is \n" \ " the name of the input image followed by .efi\n" \ " -t, --type=type -- Specify the EFI subsystem type. Valid values are \n" \ " efiapp, efibootdriver, efiruntimedriver, efidriver, or a \n" \ " numeric value.\n" \ " -v, --verbose -- Print extra information.\n" \ " --help -- Print this help and exit.\n" \ " --version -- Print version information and exit.\n" \ #define ELFCONV_OPTIONS_STRING "o:t:v" #define ELFCONV_RELOCATION_EXPANSION_SIZE (2 * 0x1000) // // ------------------------------------------------------ Data Type Definitions // // // ----------------------------------------------- Internal Function Prototypes // int ElfconvLoadFile ( PELFCONV_CONTEXT Context ); BOOLEAN ElfconvIsElfHeader ( VOID *File, UINTN FileSize ); // // -------------------------------------------------------------------- Globals // struct option ElfconvLongOptions[] = { {"output", required_argument, 0, 'o'}, {"type", required_argument, 0, 't'}, {"verbose", no_argument, 0, 'v'}, {"help", no_argument, 0, 'h'}, {"version", no_argument, 0, 'V'}, {NULL, 0, 0, 0}, }; CHAR8 *ElfconvDebugSections[] = { ".stab", ".stabstr", ".debug_aranges", ".debug_info", ".debug_abbrev", ".debug_frame", ".debug_line", ".debug_str", ".debug_loc", ".debug_ranges", ".debug_macinfo", ".debug_pubtypes", ".eh_frame", NULL }; // // ------------------------------------------------------------------ Functions // int main ( int ArgumentCount, CHAR8 **Arguments ) /*++ Routine Description: This routine is the main entry point into the ElfConv utility, which converts and ELF binary into an EFI PE binary. Arguments: ArgumentCount - Supplies the number of command line parameters. Arguments - Supplies the array of pointers to parameter strings. Return Value: 0 on success. Non-zero on failure. --*/ { CHAR8 *AfterScan; int ArgumentIndex; ssize_t BytesWritten; ELFCONV_CONTEXT Context; CHAR8 *DefaultOutputName; UINT8 ElfClass; ELFCONV_FUNCTION_TABLE FunctionTable; size_t InputNameLength; int Option; FILE *Output; BOOLEAN Result; int Status; memset(&Context, 0, sizeof(ELFCONV_CONTEXT)); Context.SubsystemType = -1; memset(&FunctionTable, 0, sizeof(ELFCONV_FUNCTION_TABLE)); DefaultOutputName = NULL; Output = NULL; // // Process the control arguments. // while (TRUE) { Option = getopt_long(ArgumentCount, Arguments, ELFCONV_OPTIONS_STRING, ElfconvLongOptions, NULL); if (Option == -1) { break; } if ((Option == '?') || (Option == ':')) { Status = 1; goto mainEnd; } switch (Option) { case 't': if (strcasecmp(optarg, "efiapp") == 0) { Context.SubsystemType = EFI_IMAGE_SUBSYSTEM_EFI_APPLICATION; } else if (strcasecmp(optarg, "efibootdriver") == 0) { Context.SubsystemType = EFI_IMAGE_SUBSYSTEM_EFI_BOOT_SERVICE_DRIVER; } else if (strcasecmp(optarg, "efiruntimedriver") == 0) { Context.SubsystemType = EFI_IMAGE_SUBSYSTEM_EFI_RUNTIME_DRIVER; } else if (strcasecmp(optarg, "saldriver") == 0) { Context.SubsystemType = EFI_IMAGE_SUBSYSTEM_SAL_RUNTIME_DRIVER; } else { Context.SubsystemType = strtoul(optarg, &AfterScan, 0); if (AfterScan == optarg) { fprintf(stderr, "Error: Invalid PE subsystem type: %s.\n", optarg); Status = EINVAL; goto mainEnd; } } break; case 'o': Context.OutputName = optarg; break; case 'v': Context.Flags |= ELFCONV_OPTION_VERBOSE; break; case 'V': printf("ElfConv version %d.%d\n", ELFCONV_VERSION_MAJOR, ELFCONV_VERSION_MINOR); return 1; case 'h': printf(ELFCONV_USAGE); return 1; default: assert(FALSE); Status = 1; goto mainEnd; } } ArgumentIndex = optind; if (ArgumentIndex != ArgumentCount - 1) { fprintf(stderr, "ElfConv takes exactly one (non-option) argument.\n"); Status = EINVAL; goto mainEnd; } Context.InputName = Arguments[ArgumentIndex]; if (Context.OutputName == NULL) { InputNameLength = strlen(Context.InputName); DefaultOutputName = malloc(InputNameLength + sizeof(".efi")); if (DefaultOutputName == NULL) { Status = ENOMEM; goto mainEnd; } strcpy(DefaultOutputName, Context.InputName); strcpy(DefaultOutputName + InputNameLength, ".efi"); Context.OutputName = DefaultOutputName; } if (Context.SubsystemType == -1) { fprintf(stderr, "Error: -t is a required argument.\n"); Status = EINVAL; goto mainEnd; } Status = ElfconvLoadFile(&Context); if (Status != 0) { goto mainEnd; } if (ElfconvIsElfHeader(Context.InputFile, Context.InputFileSize) == FALSE) { fprintf(stderr, "Error: %s does not appear to be and ELF image.\n", Context.InputName); Status = EINVAL; goto mainEnd; } ElfClass = ((UINT8 *)(Context.InputFile))[EI_CLASS]; if (ElfClass == ELFCLASS32) { if (ElfconvInitializeElf32(&Context, &FunctionTable) == FALSE) { Status = EINVAL; goto mainEnd; } } else { fprintf(stderr, "Error: Unrecogized ei_class %d.\n", ElfClass); Status = EINVAL; goto mainEnd; } Status = EINVAL; // // Perform an initial pass and set up the destination image. // Result = FunctionTable.ScanSections(&Context); if (Result == FALSE) { fprintf(stderr, "Error: Failed to scan sections.\n"); goto mainEnd; } // // Write and relocate individual section types. // Result = FunctionTable.WriteSections(&Context, ElfconvSectionText); if (Result == FALSE) { fprintf(stderr, "Error: Failed to write text section.\n"); goto mainEnd; } Result = FunctionTable.WriteSections(&Context, ElfconvSectionData); if (Result == FALSE) { fprintf(stderr, "Error: Failed to write data section.\n"); goto mainEnd; } Result = FunctionTable.WriteSections(&Context, ElfconvSectionHii); if (Result == FALSE) { fprintf(stderr, "Error: Failed to write HII section.\n"); goto mainEnd; } // // Translate and write the relocation information. // Result = FunctionTable.WriteRelocations(&Context); if (Result == FALSE) { fprintf(stderr, "Error: Failed to translate and write relocations.\n"); goto mainEnd; } // // Write out the debug information. // Result = FunctionTable.WriteDebug(&Context); if (Result == FALSE) { fprintf(stderr, "Error: Failed to write debug data.\n"); goto mainEnd; } FunctionTable.SetImageSize(&Context); // // Write out the new file buffer. // assert(Context.OutputName != NULL); Output = fopen(Context.OutputName, "wb"); if (Output == NULL) { Status = errno; fprintf(stderr, "Error: Failed to open output %s: %s.\n", Context.OutputName, strerror(Status)); goto mainEnd; } assert((Context.CoffFile != NULL) && (Context.CoffOffset != 0)); BytesWritten = fwrite(Context.CoffFile, 1, Context.CoffOffset, Output); if ((BytesWritten != Context.CoffOffset) || (ferror(Output) != 0)) { Status = errno; if (Status == 0) { errno = EIO; } fprintf(stderr, "Error: Failed to write %s: %s.\n", Context.OutputName, strerror(Status)); goto mainEnd; } Status = 0; mainEnd: if (Output != NULL) { fclose(Output); } if (FunctionTable.CleanUp != NULL) { FunctionTable.CleanUp(&Context); } if (DefaultOutputName != NULL) { free(DefaultOutputName); } if (Context.InputFile != NULL) { free(Context.InputFile); } if (Context.CoffFile != NULL) { free(Context.CoffFile); } if (Context.StringTable != NULL) { free(Context.StringTable); } if ((Context.Flags & ELFCONV_OPTION_VERBOSE) != 0) { printf("ElfConv %s returning %d: %s.\n", Context.InputName, Status, strerror(Status)); } return Status; } VOID ElfconvSetHiiResourceHeader ( UINT8 *HiiBinData, UINT32 OffsetToFile ) /*++ Routine Description: This routine sets up the HII resource data in the destination image. Arguments: HiiBinData - Supplies a pointer to the raw resource data. OffsetToFile - Supplies the offset within the file where the data is going to reside. Return Value: None. --*/ { UINT32 Index; VOID *NextEntry; UINT32 Offset; EFI_IMAGE_RESOURCE_DATA_ENTRY *ResourceDataEntry; EFI_IMAGE_RESOURCE_DIRECTORY *ResourceDirectory; EFI_IMAGE_RESOURCE_DIRECTORY_ENTRY *ResourceDirectoryEntry; EFI_IMAGE_RESOURCE_DIRECTORY_STRING *ResourceDirectoryString; // // Fill in the resource section entry. // ResourceDirectory = (EFI_IMAGE_RESOURCE_DIRECTORY *)HiiBinData; ResourceDirectoryEntry = (EFI_IMAGE_RESOURCE_DIRECTORY_ENTRY *)(ResourceDirectory + 1); for (Index = 0; Index < ResourceDirectory->NumberOfNamedEntries; Index += 1) { if (ResourceDirectoryEntry->u1.s.NameIsString != 0) { Offset = ResourceDirectoryEntry->u1.s.NameOffset; ResourceDirectoryString = (EFI_IMAGE_RESOURCE_DIRECTORY_STRING *)(HiiBinData + Offset); if ((ResourceDirectoryString->Length == 3) && (ResourceDirectoryString->String[0] == L'H') && (ResourceDirectoryString->String[1] == L'I') && (ResourceDirectoryString->String[2] == L'I')) { // // Resource Type "HII" was found. // if (ResourceDirectoryEntry->u2.s.DataIsDirectory) { // // Move to next level - resource Name. // Offset = ResourceDirectoryEntry->u2.s.OffsetToDirectory; ResourceDirectory = (EFI_IMAGE_RESOURCE_DIRECTORY *)(HiiBinData + Offset); NextEntry = ResourceDirectory + 1; ResourceDirectoryEntry = NextEntry; if (ResourceDirectoryEntry->u2.s.DataIsDirectory) { // // Move to next level - resource Language. // Offset = ResourceDirectoryEntry->u2.s.OffsetToDirectory; ResourceDirectory = (EFI_IMAGE_RESOURCE_DIRECTORY *)(HiiBinData + Offset); NextEntry = ResourceDirectory + 1; ResourceDirectoryEntry = NextEntry; } } // // Now it ought to be resource Data. Update its "offset to // data" value. // if (ResourceDirectoryEntry->u2.s.DataIsDirectory != 0) { Offset = ResourceDirectoryEntry->u2.OffsetToData; ResourceDataEntry = (EFI_IMAGE_RESOURCE_DATA_ENTRY *)(HiiBinData + Offset); ResourceDataEntry->OffsetToData = ResourceDataEntry->OffsetToData + OffsetToFile; break; } } } ResourceDirectoryEntry += 1; } return; } VOID ElfconvCreateSectionHeader ( PELFCONV_CONTEXT Context, CHAR8 *Name, UINT32 Offset, UINT32 Size, UINT32 Flags ) /*++ Routine Description: This routine initializes a PE section header in the output file buffer. Arguments: Context - Supplies a pointer to the conversion context. Name - Supplies a pointer to a string containing the name of the section. Offset - Supplies the file offset to the start of the section. Size - Supplies the size of the section in bytes. Flags - Supplies the section flags. Return Value: None. --*/ { EFI_IMAGE_SECTION_HEADER *Header; UINT32 Length; UINT32 NewSize; CHAR8 *NewTable; Header = (EFI_IMAGE_SECTION_HEADER *)(Context->CoffFile + Context->TableOffset); Length = strlen(Name) + 1; if (Length > EFI_IMAGE_SIZEOF_SHORT_NAME) { // // Create a string table entry for it. The first 4 bytes of the string // table are its size. // NewSize = Context->StringTableSize + Length; if (Context->StringTableSize == 0) { NewSize += sizeof(UINT32); Context->StringTableSize = sizeof(UINT32); } NewTable = realloc(Context->StringTable, NewSize); if (NewTable == NULL) { assert(FALSE); return; } memcpy(NewTable + Context->StringTableSize, Name, Length); Context->StringTable = NewTable; snprintf((INT8 *)(Header->Name), EFI_IMAGE_SIZEOF_SHORT_NAME, "/%d", Context->StringTableSize); Context->StringTableSize += Length; } else { strncpy((INT8 *)(Header->Name), Name, EFI_IMAGE_SIZEOF_SHORT_NAME); } Header->Misc.VirtualSize = Size; Header->VirtualAddress = Offset; Header->SizeOfRawData = Size; Header->PointerToRawData = Offset; Header->PointerToRelocations = 0; Header->PointerToLinenumbers = 0; Header->NumberOfRelocations = 0; Header->NumberOfLinenumbers = 0; Header->Characteristics = Flags; Context->TableOffset += sizeof(EFI_IMAGE_SECTION_HEADER); if ((Context->Flags & ELFCONV_OPTION_VERBOSE) != 0) { printf("Creating section %s VA 0x%x, SizeOfRawData 0x%x, " "PointerToRawData 0x%x, Characteristics 0x%x.\n", Name, Header->VirtualAddress, Header->SizeOfRawData, Header->PointerToRawData, Header->Characteristics); } return; } BOOLEAN ElfconvCoffAddFixup ( PELFCONV_CONTEXT Context, UINT32 Offset, UINT8 Type ) /*++ Routine Description: This routine adds a COFF relocation to the destination image buffer. Arguments: Context - Supplies a pointer to the conversion context. Offset - Supplies the file offset to the relocation. Type - Supplies the relocation type. Return Value: TRUE on success. FALSE on allocation failure. --*/ { UINTN Difference; VOID *NewBuffer; UINTN NewSize; // // Create a new page entry if no relocations have been added or this one is // on a different page. // if ((Context->CoffBaseRelocation == NULL) || (Context->CoffBaseRelocation->VirtualAddress != (Offset & ~0x00000FFF))) { if (Context->CoffBaseRelocation != NULL) { // // Add a null entry, then pad for alignment. // ElfconvCoffAddFixupEntry(Context, 0); if ((Context->CoffOffset % 4) != 0) { ElfconvCoffAddFixupEntry(Context, 0); } } NewSize = Context->CoffOffset + sizeof(EFI_IMAGE_BASE_RELOCATION) + ELFCONV_RELOCATION_EXPANSION_SIZE; NewBuffer = realloc(Context->CoffFile, NewSize); if (NewBuffer == NULL) { return FALSE; } Context->CoffFile = NewBuffer; Difference = NewSize - Context->CoffOffset; memset(Context->CoffFile + Context->CoffOffset, 0, Difference); Context->CoffBaseRelocation = (EFI_IMAGE_BASE_RELOCATION *)(Context->CoffFile + Context->CoffOffset); Context->CoffBaseRelocation->VirtualAddress = Offset & ~0x00000FFF; Context->CoffBaseRelocation->SizeOfBlock = sizeof(EFI_IMAGE_BASE_RELOCATION); Context->CoffNextRelocation = (UINT16 *)(Context->CoffBaseRelocation + 1); Context->CoffOffset += sizeof(EFI_IMAGE_BASE_RELOCATION); } ElfconvCoffAddFixupEntry(Context, (UINT16)((Type << 12) | (Offset & 0xFFF))); return TRUE; } VOID ElfconvCoffAddFixupEntry ( PELFCONV_CONTEXT Context, UINT16 Value ) /*++ Routine Description: This routine adds a relocation entry to the current COFF location. Arguments: Context - Supplies a pointer to the application context. Value - Supplies the relocation value to add. Return Value: None. --*/ { *(Context->CoffNextRelocation) = Value; Context->CoffNextRelocation += 1; Context->CoffBaseRelocation->SizeOfBlock += sizeof(UINT16); Context->CoffOffset += sizeof(UINT16); return; } // // --------------------------------------------------------- Internal Functions // int ElfconvLoadFile ( PELFCONV_CONTEXT Context ) /*++ Routine Description: This routine loads the input file into memory. Arguments: Context - Supplies a pointer to the conversion context. Return Value: 0 on success. Non-zero on failure. --*/ { CHAR8 *Buffer; ssize_t BytesRead; FILE *File; struct stat Stat; int Status; Buffer = NULL; File = fopen(Context->InputName, "rb"); if (File == NULL) { Status = errno; fprintf(stderr, "Error: Failed to open %s: %s.\n", Context->InputName, strerror(Status)); goto LoadFileEnd; } Status = stat(Context->InputName, &Stat); if (Status != 0) { Status = errno; goto LoadFileEnd; } if (Stat.st_size > 0xFFFFFFFF) { Status = ERANGE; fprintf(stderr, "Error: File too big.\n"); goto LoadFileEnd; } Buffer = malloc(Stat.st_size); if (Buffer == NULL) { fprintf(stderr, "Error: Failed to allocate 0x%x for input file buffer.\n", Stat.st_size); Status = ENOMEM; goto LoadFileEnd; } BytesRead = fread(Buffer, 1, Stat.st_size, File); if (BytesRead != Stat.st_size) { Status = errno; if (Status == 0) { Status = EIO; } fprintf(stderr, "Error: Failed to read input file %s: %s.\n", Context->InputName, strerror(Status)); goto LoadFileEnd; } Context->InputFile = Buffer; Context->InputFileSize = Stat.st_size; Buffer = 0; Status = 0; LoadFileEnd: if (File != NULL) { fclose(File); } if (Buffer != NULL) { free(Buffer); } return Status; } BOOLEAN ElfconvIsElfHeader ( VOID *File, UINTN FileSize ) /*++ Routine Description: This routine determines if the given file starts with a valid ELF header. Arguments: File - Supplies a pointer to the file buffer. FileSize - Supplies the size of the file in bytes. Return Value: TRUE if the given file is ELF. FALSE if the file is not ELF or is too small. --*/ { INT8 *Buffer; if (FileSize < EI_PAD) { return FALSE; } Buffer = File; if ((Buffer[EI_MAG0] == ELFMAG0) && (Buffer[EI_MAG1] == ELFMAG1) && (Buffer[EI_MAG2] == ELFMAG2) && (Buffer[EI_MAG3] == ELFMAG3)) { return TRUE; } return FALSE; }