#!/usr/bin/env python3 #============================================================================ # #/** @file createxbl.py # # GENERAL DESCRIPTION # Concatentates XBL segments into one ELF image # # SPDX-License-Identifier: BSD-3-Clause #**/ # #---------------------------------------------------------------------------- # # EDIT HISTORY FOR FILE # # This section contains comments describing changes made to the module. # Notice that changes are listed in reverse chronological order. # # when who what, where, why # -------- --- ------------------------------------------------------ # 05/21/19 rissha Added --mbn_version to add MBN header accordingly # 03/26/18 tv Added -e to enable extended MBNV5 support # 09/04/15 et Added -x and -d to embed xbl_sec ELF # 02/11/15 ck Fixed missing elf type check in ZI OOB feature # 11/04/14 ck Updated calls to mbn_tools functions # 10/22/14 ck Added -z option to remove out of bounds ZI segments when # converting from 64 to 32 # 10/10/14 ck Added -c option and logic to enable elf type swapping # 09/12/14 ck Added single file logic # 08/29/14 ck Added no_hash option # 08/29/14 ck Refactored to use proper python arguments and cleaned code # 06/16/14 niting xbl.mbn to xbl.elf # 05/28/14 niting Initial revision # #============================================================================ from optparse import OptionParser import os import sys import shutil import mbn_tools PAGE_SIZE = 4096 SEGMENT_ALIGN = 16 ELF32_HDR_SIZE = 52 ELF32_PHDR_SIZE = 32 ELF64_HDR_SIZE = 64 ELF64_PHDR_SIZE = 56 ############################################################################## # main ############################################################################## def main(): parser = OptionParser(usage='usage: %prog [options] arguments') parser.add_option("-f", "--first_filepath", action="store", type="string", dest="elf_inp_file1", help="First ELF file to merge.") parser.add_option("-s", "--second_filepath", action="store", type="string", dest="elf_inp_file2", help="Second ELF file to merge.") parser.add_option("-x", "--xbl_sec_filepath", action="store", type="string", dest="elf_inp_xbl_sec", help="Second ELF file to merge.") parser.add_option("-o", "--output_filepath", action="store", type="string", dest="binary_out", help="Merged filename and path.") parser.add_option("-a", "--first_elf_arch", action="store", type="string", dest="elf_1_arch", help="First (and output) ELF file architecture. '32' or '64'") parser.add_option("-b", "--second_elf_arch", action="store", type="string", dest="elf_2_arch", help="Second ELF file architecture. '32' or '64'") parser.add_option("-d", "--xbl_sec_elf_arch", action="store", type="string", dest="elf_xbl_sec_arch", help="xbl_sec file architecture. '32' or '64'") parser.add_option("-c", "--output_elf_arch", action="store", type="string", dest="elf_out_arch", help="Output ELF file architecture. '32' or '64'" + \ " If not given defaults to first file arch.") parser.add_option("-n", "--no_hash", action="store_true", dest="hash_image", help="Disables hashing of image after merging.") parser.add_option("-z", "--zi_out_of_bounds", action="store_true", dest="zi_oob", help="Removes ZI segments that have addresses greater" + \ " than 32 bits when converting from a 64 to 32 bit ELF") parser.add_option("--mbn_version", action="store", type="int", dest="mbn_version", help="Add mbn header in elf image. '3', '5' or '6'") (options, args) = parser.parse_args() if not options.elf_inp_file1: parser.error('First ELF filename not given') if not options.binary_out: parser.error('Output filename not given') if not options.elf_1_arch: parser.error('First ELF architecture not given') if (not options.elf_1_arch == '64') and (not options.elf_1_arch == '32'): parser.error('Invalid First ELF architecture given') # Only evaluate elf_2_arch if two files are given for merging if options.elf_inp_file2: if (not options.elf_2_arch == '64') and (not options.elf_2_arch == '32'): parser.error('Invalid Second ELF architecture given') # Only evaluate elf_xbl_sec_arch if file is given if options.elf_inp_xbl_sec: if (not options.elf_xbl_sec_arch == '64') and (not options.elf_xbl_sec_arch == '32'): parser.error('Invalid xbl_sec ELF architecture given') # If output file architecture is given ensure it is either '32' or '64' if options.elf_out_arch: if (not options.elf_out_arch == '64') and (not options.elf_out_arch == '32'): parser.error('Invalid Output ELF architecture given') gen_dict = {} elf_inp_file1 = options.elf_inp_file1 # It is valid for only one file to be "merged". This essentially just # strips off the section names. If second file name is not given then # set elf_inp_file2 to "" if options.elf_inp_file2: elf_inp_file2 = options.elf_inp_file2 else: elf_inp_file2 = "" # Do same for xbl_sec elf_inp_xbl_sec = options.elf_inp_xbl_sec if options.elf_inp_xbl_sec else "" binary_out = options.binary_out if options.elf_1_arch == '64': is_elf1_64_bit = True else: is_elf1_64_bit = False # If second filename is not given then set is_elf2_64_bit to false so it # can be passed even though it is not used. if options.elf_inp_file2: if options.elf_2_arch == '64': is_elf2_64_bit = True else: is_elf2_64_bit = False else: is_elf2_64_bit = False if options.elf_inp_xbl_sec: if options.elf_xbl_sec_arch == '64': is_elf_xbl_sec_64_bit = True else: is_elf_xbl_sec_64_bit = False else: is_elf_xbl_sec_64_bit = False # If output ELF arch is given then set is_out_elf_64_bit accordingly. # If not then default to be input1's setting if options.elf_out_arch: if options.elf_out_arch == '64': is_out_elf_64_bit = True else: is_out_elf_64_bit = False else: is_out_elf_64_bit = is_elf1_64_bit # Store ZI Out of Bounds value if not options.zi_oob: zi_oob_enabled = False else: zi_oob_enabled = True header_version = 3 if options.elf_inp_xbl_sec: header_version = 5 if options.mbn_version: header_version = options.mbn_version mbn_type = 'elf' header_format = 'reg' gen_dict['IMAGE_KEY_IMAGE_ID'] = mbn_tools.ImageType.APPSBL_IMG #gen_dict['IMAGE_KEY_IMAGE_SOURCE'] = 0 #gen_dict['IMAGE_KEY_IMAGE_DEST'] = 0 gen_dict['IMAGE_KEY_MBN_TYPE'] = mbn_type image_header_secflag = 'non_secure' source_base = os.path.splitext(str(binary_out))[0] target_base = os.path.splitext(str(binary_out))[0] merged_elf = source_base + "_merged.elf" source_elf = source_base + "_nohash.elf" target_hash = target_base + ".hash" target_hash_hd = target_base + "_hash.hd" target_phdr_elf = target_base + "_phdr.pbn" target_nonsec = target_base + "_combined_hash.mbn" #print("Input file 1:", elf_inp_file1) #print("Input file 2:", elf_inp_file2) #print("Output file:", binary_out) merge_elfs([], elf_inp_file1, elf_inp_file2, elf_inp_xbl_sec, merged_elf, is_elf1_64_bit, is_elf2_64_bit, is_elf_xbl_sec_64_bit, is_out_elf_64_bit, zi_oob_enabled, header_version) # Hash the image if user did not explicitly say not to if options.hash_image: # Just copy the merged elf to the final output name shutil.move(merged_elf, binary_out) else: shutil.copy(merged_elf, source_elf) # Create hash table rv = mbn_tools.pboot_gen_elf([], source_elf, target_hash, elf_out_file_name = target_phdr_elf, secure_type = image_header_secflag, header_version = header_version ) if rv: raise RuntimeError("Failed to run pboot_gen_elf") # Create hash table header rv = mbn_tools.image_header([], gen_dict, target_hash, target_hash_hd, image_header_secflag, elf_file_name = source_elf, header_version = header_version) if rv: raise RuntimeError("Failed to create image header for hash segment") files_to_cat_in_order = [target_hash_hd, target_hash] mbn_tools.concat_files (target_nonsec, files_to_cat_in_order) # Add the hash segment into the ELF mbn_tools.pboot_add_hash([], target_phdr_elf, target_nonsec, binary_out) return ############################################################################## # roundup ############################################################################## def roundup(x, precision): return x if x % precision == 0 else (x + precision - (x % precision)) ############################################################################## # merge_elfs ############################################################################## def merge_elfs(env, elf_in_file_name1, elf_in_file_name2, elf_in_file_xbl_sec, elf_out_file_name, is_elf1_64_bit, is_elf2_64_bit, is_elf_xbl_sec_64_bit, is_out_elf_64_bit, zi_oob_enabled, header_version): [elf_header1, phdr_table1] = \ mbn_tools.preprocess_elf_file(elf_in_file_name1) # Check to make sure second file path exists before using if elf_in_file_name2 != "": [elf_header2, phdr_table2] = \ mbn_tools.preprocess_elf_file(elf_in_file_name2) # Check to make sure xbl_sec file path exists before using if elf_in_file_xbl_sec != "": [elf_headerxblsec, phdr_tablexblsec] = \ mbn_tools.preprocess_elf_file(elf_in_file_xbl_sec) # Open Files elf_in_fp1 = mbn_tools.OPEN(elf_in_file_name1, "rb") if elf_in_file_name2 != "": elf_in_fp2 = mbn_tools.OPEN(elf_in_file_name2, "rb") if elf_in_file_xbl_sec != "": elf_in_fpxblsec = mbn_tools.OPEN(elf_in_file_xbl_sec, "rb") if elf_out_file_name is not None: elf_out_fp = mbn_tools.OPEN(elf_out_file_name, "wb+") # Calculate the new program header size. This is dependant on the output # ELF type and number of program headers going into output. if is_out_elf_64_bit: phdr_total_size = elf_header1.e_phnum * ELF64_PHDR_SIZE phdr_total_count = elf_header1.e_phnum else: phdr_total_size = elf_header1.e_phnum * ELF32_PHDR_SIZE phdr_total_count = elf_header1.e_phnum # This logic only applies if two files are to be merged if elf_in_file_name2 != "": if is_out_elf_64_bit: phdr_total_size += elf_header2.e_phnum * ELF64_PHDR_SIZE phdr_total_count += elf_header2.e_phnum else: phdr_total_size += elf_header2.e_phnum * ELF32_PHDR_SIZE phdr_total_count += elf_header2.e_phnum # Account for xbl_sec header if included if elf_in_file_xbl_sec != "": phdr_total_count += 1 if is_out_elf_64_bit: phdr_total_size += ELF64_PHDR_SIZE else: phdr_total_size += ELF32_PHDR_SIZE # Create a new ELF header for the output file if is_out_elf_64_bit: out_elf_header = mbn_tools.Elf64_Ehdr(b'\0' * ELF64_HDR_SIZE) out_elf_header.e_phoff = ELF64_HDR_SIZE out_elf_header.e_ehsize = ELF64_HDR_SIZE out_elf_header.e_phentsize = ELF64_PHDR_SIZE out_elf_header.e_machine = 183 out_elf_header.e_ident = str('\x7f' + 'E' + 'L' + 'F' + \ '\x02' + \ '\x01' + \ '\x01' + \ '\x00' + \ '\x00' + \ ('\x00' * 7)) out_elf_header.e_entry = elf_header1.e_entry else: out_elf_header = mbn_tools.Elf32_Ehdr(b'\0' * ELF32_HDR_SIZE) out_elf_header.e_phoff = ELF32_HDR_SIZE out_elf_header.e_ehsize = ELF32_HDR_SIZE out_elf_header.e_phentsize = ELF32_PHDR_SIZE out_elf_header.e_machine = 40 out_elf_header.e_entry = elf_header1.e_entry out_elf_header.e_ident = str('\x7f' + 'E' + 'L' + 'F' + \ '\x01' + \ '\x01' + \ '\x01' + \ '\x00' + \ '\x00' + \ ('\x00' * 7)) # Address needs to be verified that it is not greater than 32 bits # as it is possible to go from a 64 bit elf to 32. if (elf_header1.e_entry > 0xFFFFFFFF): print("ERROR: File 1's entry point is too large to convert.") exit() out_elf_header.e_entry = elf_header1.e_entry # Common header entries out_elf_header.e_type = 2 out_elf_header.e_version = 1 out_elf_header.e_shoff = 0 out_elf_header.e_flags = 0 out_elf_header.e_shentsize = 0 out_elf_header.e_shnum = 0 out_elf_header.e_shstrndx = 0 # If ZI OOB is enabled then it is possible that a segment could be discarded # Scan for that instance and handle before setting e_phnum and writing header # Ensure ELF output is 32 bit if zi_oob_enabled == True and is_out_elf_64_bit == False: for i in range(len(phdr_table1)): if (phdr_table1[i].p_vaddr > 0xFFFFFFFF) or \ (phdr_table1[i].p_paddr > 0xFFFFFFFF): if phdr_table1[i].p_filesz == 0: phdr_total_count = phdr_total_count - 1 if elf_in_file_name2 != "": for i in range(len(phdr_table2)): if (phdr_table2[i].p_vaddr > 0xFFFFFFFF) or \ (phdr_table2[i].p_paddr > 0xFFFFFFFF): if phdr_table2[i].p_filesz == 0: phdr_total_count = phdr_total_count - 1 # Do not include xbl_sec in above calculation # xbl_sec is to be treated as a single blob # Now it is ok to populate the ELF header and write it out out_elf_header.e_phnum = phdr_total_count # write elf header if is_out_elf_64_bit == False: elf_out_fp.write(mbn_tools.Elf32_Ehdr.getPackedData(out_elf_header)) else: elf_out_fp.write(mbn_tools.Elf64_Ehdr.getPackedData(out_elf_header)) phdr_offset = out_elf_header.e_phoff # offset of where to put next phdr # offset the start of the segments just after the program headers segment_offset = roundup(out_elf_header.e_phoff + phdr_total_size, PAGE_SIZE) # Output first elf data for i in range(elf_header1.e_phnum): curr_phdr = phdr_table1[i] # Copy program header piece by piece to ensure possible conversion success if is_out_elf_64_bit == True: # Converting from 32 to 64 elf requires no data size validation new_phdr = mbn_tools.Elf64_Phdr(b'\0' * ELF64_PHDR_SIZE) new_phdr.p_type = curr_phdr.p_type new_phdr.p_offset = segment_offset new_phdr.p_vaddr = curr_phdr.p_vaddr new_phdr.p_paddr = curr_phdr.p_paddr new_phdr.p_filesz = curr_phdr.p_filesz new_phdr.p_memsz = curr_phdr.p_memsz new_phdr.p_flags = curr_phdr.p_flags new_phdr.p_align = curr_phdr.p_align else: # Converting from 64 to 32 elf requires data size validation # Note that there is an option to discard a segment if it is only ZI # and its address is greater than 32 bits new_phdr = mbn_tools.Elf32_Phdr(b'\0' * ELF32_PHDR_SIZE) new_phdr.p_type = curr_phdr.p_type new_phdr.p_offset = segment_offset if curr_phdr.p_vaddr > 0xFFFFFFFF: if (zi_oob_enabled == True) and (curr_phdr.p_filesz == 0): continue else: print("ERROR: File 1 VAddr is too large for conversion.") exit() new_phdr.p_vaddr = curr_phdr.p_vaddr if curr_phdr.p_paddr > 0xFFFFFFFF: if (zi_oob_enabled == True) and (curr_phdr.p_filesz == 0): continue else: print("ERROR: File 1 PAddr is too large for conversion.") exit() new_phdr.p_paddr = curr_phdr.p_paddr if curr_phdr.p_filesz > 0xFFFFFFFF: print("ERROR: File 1 Filesz is too large for conversion.") exit() new_phdr.p_filesz = curr_phdr.p_filesz if curr_phdr.p_memsz > 0xFFFFFFFF: print("ERROR: File 1 Memsz is too large for conversion.") exit() new_phdr.p_memsz = curr_phdr.p_memsz if curr_phdr.p_flags > 0xFFFFFFFF: print("ERROR: File 1 Flags is too large for conversion.") exit() new_phdr.p_flags = curr_phdr.p_flags if curr_phdr.p_align > 0xFFFFFFFF: print("ERROR: File 1 Align is too large for conversion.") exit() new_phdr.p_align = curr_phdr.p_align #print("i=",i) #print("phdr_offset=", phdr_offset) # update output file location to next phdr location elf_out_fp.seek(phdr_offset) # increment phdr_offset to next location phdr_offset += out_elf_header.e_phentsize inp_data_offset = curr_phdr.p_offset # used to read data from input file # print("inp_data_offset=") # print(inp_data_offset) # # print("curr_phdr.p_offset=") # print(curr_phdr.p_offset) # # print("curr_phdr.p_filesz=") # print(curr_phdr.p_filesz) # output current phdr if is_out_elf_64_bit == False: elf_out_fp.write(mbn_tools.Elf32_Phdr.getPackedData(new_phdr)) else: elf_out_fp.write(mbn_tools.Elf64_Phdr.getPackedData(new_phdr)) # Copy the ELF segment bytes_written = mbn_tools.file_copy_offset(elf_in_fp1, inp_data_offset, elf_out_fp, new_phdr.p_offset, new_phdr.p_filesz) # update data segment offset to be aligned after previous segment segment_offset += roundup(new_phdr.p_filesz, SEGMENT_ALIGN); elf_in_fp1.close() # Output second elf data if applicable if elf_in_file_name2 != "": for i in range(elf_header2.e_phnum): curr_phdr = phdr_table2[i] # Copy program header piece by piece to ensure possible conversion success if is_out_elf_64_bit == True: # Converting from 32 to 64 elf requires no data size validation new_phdr = mbn_tools.Elf64_Phdr(b'\0' * ELF64_PHDR_SIZE) new_phdr.p_type = curr_phdr.p_type new_phdr.p_offset = segment_offset new_phdr.p_vaddr = curr_phdr.p_vaddr new_phdr.p_paddr = curr_phdr.p_paddr new_phdr.p_filesz = curr_phdr.p_filesz new_phdr.p_memsz = curr_phdr.p_memsz new_phdr.p_flags = curr_phdr.p_flags new_phdr.p_align = curr_phdr.p_align else: # Converting from 64 to 32 elf requires data size validation # Note that there is an option to discard a segment if it is only ZI # and its address is greater than 32 bits new_phdr = mbn_tools.Elf32_Phdr(b'\0' * ELF32_PHDR_SIZE) new_phdr.p_type = curr_phdr.p_type new_phdr.p_offset = segment_offset if curr_phdr.p_vaddr > 0xFFFFFFFF: if (zi_oob_enabled == True) and (curr_phdr.p_filesz == 0): continue else: print("ERROR: File 2 VAddr is too large for conversion.") exit() new_phdr.p_vaddr = curr_phdr.p_vaddr if curr_phdr.p_paddr > 0xFFFFFFFF: if (zi_oob_enabled == True) and (curr_phdr.p_filesz == 0): continue else: print("ERROR: File 2 PAddr is too large for conversion.") exit() new_phdr.p_paddr = curr_phdr.p_paddr if curr_phdr.p_filesz > 0xFFFFFFFF: print("ERROR: File 2 Filesz is too large for conversion.") exit() new_phdr.p_filesz = curr_phdr.p_filesz if curr_phdr.p_memsz > 0xFFFFFFFF: print("ERROR: File 2 Memsz is too large for conversion.") exit() new_phdr.p_memsz = curr_phdr.p_memsz if curr_phdr.p_flags > 0xFFFFFFFF: print("ERROR: File 2 Flags is too large for conversion.") exit() new_phdr.p_flags = curr_phdr.p_flags if curr_phdr.p_align > 0xFFFFFFFF: print("ERROR: File 2 Align is too large for conversion.") exit() new_phdr.p_align = curr_phdr.p_align # print("i=",i) # print("phdr_offset=", phdr_offset) # update output file location to next phdr location elf_out_fp.seek(phdr_offset) # increment phdr_offset to next location phdr_offset += out_elf_header.e_phentsize inp_data_offset = curr_phdr.p_offset # used to read data from input file # print("inp_data_offset=") # print(inp_data_offset) # # print("curr_phdr.p_offset=") # print(curr_phdr.p_offset) # # print("curr_phdr.p_filesz=") # print(curr_phdr.p_filesz) # output current phdr if is_out_elf_64_bit == False: elf_out_fp.write(mbn_tools.Elf32_Phdr.getPackedData(new_phdr)) else: elf_out_fp.write(mbn_tools.Elf64_Phdr.getPackedData(new_phdr)) # Copy the ELF segment bytes_written = mbn_tools.file_copy_offset(elf_in_fp2, inp_data_offset, elf_out_fp, new_phdr.p_offset, new_phdr.p_filesz) # update data segment offset to be aligned after previous segment segment_offset += roundup(new_phdr.p_filesz, SEGMENT_ALIGN); elf_in_fp2.close() # Embed xbl_sec image if provided if elf_in_file_xbl_sec != "": # Scan pheaders in xbl_sec for segment that contains entry point address entry_seg_offset = -1 entry_addr = elf_headerxblsec.e_entry for i in range(elf_headerxblsec.e_phnum): phdr = phdr_tablexblsec[i] max_addr = phdr.p_vaddr + phdr.p_memsz if phdr.p_vaddr <= entry_addr <= max_addr: entry_seg_offset = phdr.p_offset break if entry_seg_offset == -1: print("Error: Failed to find entry point in any segment!") exit() # magical equation for program header's phys and virt addr phys_virt_addr = entry_addr - entry_seg_offset if is_out_elf_64_bit: # Converting from 32 to 64 elf requires no data size validation new_phdr = mbn_tools.Elf64_Phdr(b'\0' * ELF64_PHDR_SIZE) new_phdr.p_type = 0x1 new_phdr.p_offset = segment_offset new_phdr.p_vaddr = phys_virt_addr new_phdr.p_paddr = phys_virt_addr new_phdr.p_filesz = os.path.getsize(elf_in_file_xbl_sec) new_phdr.p_memsz = new_phdr.p_filesz if header_version >= 5: new_phdr.p_flags = (0x5 | (mbn_tools.MI_PBT_XBL_SEC_SEGMENT << mbn_tools.MI_PBT_FLAG_SEGMENT_TYPE_SHIFT)); else: new_phdr.p_flags = 0x5 new_phdr.p_align = 0x1000 else: # Converting from 64 to 32 elf requires data size validation # Don't discard the segment containing xbl_sec, simply error out # if the address is greater than 32 bits new_phdr = mbn_tools.Elf32_Phdr(b'\0' * ELF32_PHDR_SIZE) new_phdr.p_type = 0x1 # new_phdr.p_offset = segment_offset if header_version >= 5: new_phdr.p_flags = (0x5 | (mbn_tools.MI_PBT_XBL_SEC_SEGMENT << mbn_tools.MI_PBT_FLAG_SEGMENT_TYPE_SHIFT)); else: new_phdr.p_flags = 0x5 new_phdr.p_align = 0x1000 if phys_virt_addr > 0xFFFFFFFF: if zi_oob_enabled == False or curr_phdr.p_filesz != 0: print("ERROR: File xbl_sec VAddr or PAddr is too big for conversion.") exit() new_phdr.p_vaddr = phys_virt_addr new_phdr.p_paddr = phys_virt_addr if os.path.getsize(elf_in_file_xbl_sec) > 0xFFFFFFFF: print("ERROR: File xbl_sec Filesz is too big for conversion.") exit() new_phdr.p_filesz = os.path.getsize(elf_in_file_xbl_sec) new_phdr.p_memsz = new_phdr.p_filesz # update output file location to next phdr location elf_out_fp.seek(phdr_offset) # increment phdr_offset to next location phdr_offset += out_elf_header.e_phentsize # Copy entire xbl_sec file, so start from byte 0 inp_data_offset = 0 # Output xbl_sec's phdr elf_in_file_xbl_sec if is_out_elf_64_bit == False: elf_out_fp.write(mbn_tools.Elf32_Phdr.getPackedData(new_phdr)) else: elf_out_fp.write(mbn_tools.Elf64_Phdr.getPackedData(new_phdr)) # Copy the ENTIRE xbl_sec image bytes_written = mbn_tools.file_copy_offset(elf_in_fpxblsec, inp_data_offset, elf_out_fp, new_phdr.p_offset, new_phdr.p_filesz) # update data segment offset to be aligned after previous segment # Not necessary, unless appending more pheaders after this point segment_offset += roundup(new_phdr.p_filesz, SEGMENT_ALIGN); elf_in_fpxblsec.close() elf_out_fp.close() return 0 main()