From 8cb1b2ad5069119df238f8d17a77b3bb3b4e47db Mon Sep 17 00:00:00 2001 From: Tao Huang Date: Wed, 15 Feb 2023 16:30:35 +0800 Subject: [PATCH] rk: scripts: update mkbootimg/unpack_bootimg AOSP f85a2fd5640d ("Revert "unpack_bootimg: Export 'vendor_ramdisk' for vendor_boot v4"") Revert 7261bb083a97 ("Check DTB image size for boot image header version 2 and above") which failed to repack image without dtb. Include gki.generate_gki_certificate. Signed-off-by: Tao Huang Change-Id: Ibf9543fd4a10547f9d956b6ffc09834389fbb20f --- scripts/mkbootimg | 639 ++++++++++++++++++++++++++++++++--------- scripts/unpack_bootimg | 577 ++++++++++++++++++++++++++++--------- 2 files changed, 954 insertions(+), 262 deletions(-) diff --git a/scripts/mkbootimg b/scripts/mkbootimg index 0c6093e19416..610548fd6b58 100755 --- a/scripts/mkbootimg +++ b/scripts/mkbootimg @@ -1,4 +1,5 @@ -#!/usr/bin/env python +#!/usr/bin/env python3 +# # Copyright 2015, The Android Open Source Project # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -13,16 +14,81 @@ # See the License for the specific language governing permissions and # limitations under the License. -from __future__ import print_function +"""Creates the boot image.""" -from argparse import ArgumentParser, FileType, Action +from argparse import (ArgumentParser, ArgumentTypeError, + FileType, RawDescriptionHelpFormatter) from hashlib import sha1 from os import fstat -import re from struct import pack +import array +import collections +import os +import re +import tempfile +# from gki.generate_gki_certificate import generate_gki_certificate +def generate_gki_certificate(image, avbtool, name, algorithm, key, salt, + additional_avb_args, output): + """Shell out to avbtool to generate a GKI certificate.""" + + # Need to specify a value of --partition_size for avbtool to work. + # We use 64 MB below, but avbtool will not resize the boot image to + # this size because --do_not_append_vbmeta_image is also specified. + avbtool_cmd = [ + avbtool, 'add_hash_footer', + '--partition_name', name, + '--partition_size', str(64 * 1024 * 1024), + '--image', image, + '--algorithm', algorithm, + '--key', key, + '--do_not_append_vbmeta_image', + '--output_vbmeta_image', output, + ] + + if salt is not None: + avbtool_cmd += ['--salt', salt] + + avbtool_cmd += additional_avb_args + + subprocess.check_call(avbtool_cmd) + + +# Constant and structure definition is in +# system/tools/mkbootimg/include/bootimg/bootimg.h +BOOT_MAGIC = 'ANDROID!' +BOOT_MAGIC_SIZE = 8 +BOOT_NAME_SIZE = 16 +BOOT_ARGS_SIZE = 512 +BOOT_EXTRA_ARGS_SIZE = 1024 +BOOT_IMAGE_HEADER_V1_SIZE = 1648 +BOOT_IMAGE_HEADER_V2_SIZE = 1660 +BOOT_IMAGE_HEADER_V3_SIZE = 1580 BOOT_IMAGE_HEADER_V3_PAGESIZE = 4096 +BOOT_IMAGE_HEADER_V4_SIZE = 1584 +BOOT_IMAGE_V4_SIGNATURE_SIZE = 4096 + +VENDOR_BOOT_MAGIC = 'VNDRBOOT' +VENDOR_BOOT_MAGIC_SIZE = 8 +VENDOR_BOOT_NAME_SIZE = BOOT_NAME_SIZE +VENDOR_BOOT_ARGS_SIZE = 2048 +VENDOR_BOOT_IMAGE_HEADER_V3_SIZE = 2112 +VENDOR_BOOT_IMAGE_HEADER_V4_SIZE = 2128 + +VENDOR_RAMDISK_TYPE_NONE = 0 +VENDOR_RAMDISK_TYPE_PLATFORM = 1 +VENDOR_RAMDISK_TYPE_RECOVERY = 2 +VENDOR_RAMDISK_TYPE_DLKM = 3 +VENDOR_RAMDISK_NAME_SIZE = 32 +VENDOR_RAMDISK_TABLE_ENTRY_BOARD_ID_SIZE = 16 +VENDOR_RAMDISK_TABLE_ENTRY_V4_SIZE = 108 + +# Names with special meaning, mustn't be specified in --ramdisk_name. +VENDOR_RAMDISK_NAME_BLOCKLIST = {b'default'} + +PARSER_ARGUMENT_VENDOR_RAMDISK_FRAGMENT = '--vendor_ramdisk_fragment' + def filesize(f): if f is None: @@ -49,87 +115,141 @@ def pad_file(f, padding): def get_number_of_pages(image_size, page_size): """calculates the number of pages required for the image""" - return (image_size + page_size - 1) / page_size + return (image_size + page_size - 1) // page_size def get_recovery_dtbo_offset(args): """calculates the offset of recovery_dtbo image in the boot image""" num_header_pages = 1 # header occupies a page num_kernel_pages = get_number_of_pages(filesize(args.kernel), args.pagesize) - num_ramdisk_pages = get_number_of_pages(filesize(args.ramdisk), args.pagesize) + num_ramdisk_pages = get_number_of_pages(filesize(args.ramdisk), + args.pagesize) num_second_pages = get_number_of_pages(filesize(args.second), args.pagesize) dtbo_offset = args.pagesize * (num_header_pages + num_kernel_pages + num_ramdisk_pages + num_second_pages) return dtbo_offset -def write_header_v3(args): - BOOT_IMAGE_HEADER_V3_SIZE = 1580 - BOOT_MAGIC = 'ANDROID!'.encode() +def should_add_legacy_gki_boot_signature(args): + if args.gki_signing_key and args.gki_signing_algorithm: + return True + return False - args.output.write(pack('8s', BOOT_MAGIC)) - args.output.write(pack( - '4I', - filesize(args.kernel), # kernel size in bytes - filesize(args.ramdisk), # ramdisk size in bytes - (args.os_version << 11) | args.os_patch_level, # os version and patch level - BOOT_IMAGE_HEADER_V3_SIZE)) - args.output.write(pack('4I', 0, 0, 0, 0)) # reserved +def write_header_v3_and_above(args): + if args.header_version > 3: + boot_header_size = BOOT_IMAGE_HEADER_V4_SIZE + else: + boot_header_size = BOOT_IMAGE_HEADER_V3_SIZE - args.output.write(pack('I', args.header_version)) # version of bootimage header - args.output.write(pack('1536s', args.cmdline.encode())) + args.output.write(pack(f'{BOOT_MAGIC_SIZE}s', BOOT_MAGIC.encode())) + # kernel size in bytes + args.output.write(pack('I', filesize(args.kernel))) + # ramdisk size in bytes + args.output.write(pack('I', filesize(args.ramdisk))) + # os version and patch level + args.output.write(pack('I', (args.os_version << 11) | args.os_patch_level)) + args.output.write(pack('I', boot_header_size)) + # reserved + args.output.write(pack('4I', 0, 0, 0, 0)) + # version of boot image header + args.output.write(pack('I', args.header_version)) + args.output.write(pack(f'{BOOT_ARGS_SIZE + BOOT_EXTRA_ARGS_SIZE}s', + args.cmdline)) + if args.header_version >= 4: + # The signature used to verify boot image v4. + boot_signature_size = 0 + if should_add_legacy_gki_boot_signature(args): + boot_signature_size = BOOT_IMAGE_V4_SIGNATURE_SIZE + args.output.write(pack('I', boot_signature_size)) pad_file(args.output, BOOT_IMAGE_HEADER_V3_PAGESIZE) + def write_vendor_boot_header(args): - VENDOR_BOOT_IMAGE_HEADER_V3_SIZE = 2112 - BOOT_MAGIC = 'VNDRBOOT'.encode() + if args.header_version > 3: + vendor_ramdisk_size = args.vendor_ramdisk_total_size + vendor_boot_header_size = VENDOR_BOOT_IMAGE_HEADER_V4_SIZE + else: + vendor_ramdisk_size = filesize(args.vendor_ramdisk) + vendor_boot_header_size = VENDOR_BOOT_IMAGE_HEADER_V3_SIZE - args.vendor_boot.write(pack('8s', BOOT_MAGIC)) - args.vendor_boot.write(pack( - '5I', - args.header_version, # version of header - args.pagesize, # flash page size we assume - args.base + args.kernel_offset, # kernel physical load addr - args.base + args.ramdisk_offset, # ramdisk physical load addr - filesize(args.vendor_ramdisk))) # vendor ramdisk size in bytes - args.vendor_boot.write(pack('2048s', args.vendor_cmdline.encode())) - args.vendor_boot.write(pack('I', args.base + args.tags_offset)) # physical addr for kernel tags - args.vendor_boot.write(pack('16s', args.board.encode())) # asciiz product name - args.vendor_boot.write(pack('I', VENDOR_BOOT_IMAGE_HEADER_V3_SIZE)) # header size in bytes - if filesize(args.dtb) == 0: - raise ValueError("DTB image must not be empty.") - args.vendor_boot.write(pack('I', filesize(args.dtb))) # size in bytes - args.vendor_boot.write(pack('Q', args.base + args.dtb_offset)) # dtb physical load address - pad_file(args.vendor_boot, args.pagesize) + args.vendor_boot.write(pack(f'{VENDOR_BOOT_MAGIC_SIZE}s', + VENDOR_BOOT_MAGIC.encode())) + # version of boot image header + args.vendor_boot.write(pack('I', args.header_version)) + # flash page size + args.vendor_boot.write(pack('I', args.pagesize)) + # kernel physical load address + args.vendor_boot.write(pack('I', args.base + args.kernel_offset)) + # ramdisk physical load address + args.vendor_boot.write(pack('I', args.base + args.ramdisk_offset)) + # ramdisk size in bytes + args.vendor_boot.write(pack('I', vendor_ramdisk_size)) + args.vendor_boot.write(pack(f'{VENDOR_BOOT_ARGS_SIZE}s', + args.vendor_cmdline)) + # kernel tags physical load address + args.vendor_boot.write(pack('I', args.base + args.tags_offset)) + # asciiz product name + args.vendor_boot.write(pack(f'{VENDOR_BOOT_NAME_SIZE}s', args.board)) -def write_header(args): - BOOT_IMAGE_HEADER_V1_SIZE = 1648 - BOOT_IMAGE_HEADER_V2_SIZE = 1660 - BOOT_MAGIC = 'ANDROID!'.encode() + # header size in bytes + args.vendor_boot.write(pack('I', vendor_boot_header_size)) + + # dtb size in bytes + args.vendor_boot.write(pack('I', filesize(args.dtb))) + # dtb physical load address + args.vendor_boot.write(pack('Q', args.base + args.dtb_offset)) if args.header_version > 3: - raise ValueError('Boot header version %d not supported' % args.header_version) - elif args.header_version == 3: - return write_header_v3(args) + vendor_ramdisk_table_size = (args.vendor_ramdisk_table_entry_num * + VENDOR_RAMDISK_TABLE_ENTRY_V4_SIZE) + # vendor ramdisk table size in bytes + args.vendor_boot.write(pack('I', vendor_ramdisk_table_size)) + # number of vendor ramdisk table entries + args.vendor_boot.write(pack('I', args.vendor_ramdisk_table_entry_num)) + # vendor ramdisk table entry size in bytes + args.vendor_boot.write(pack('I', VENDOR_RAMDISK_TABLE_ENTRY_V4_SIZE)) + # bootconfig section size in bytes + args.vendor_boot.write(pack('I', filesize(args.vendor_bootconfig))) + pad_file(args.vendor_boot, args.pagesize) - args.output.write(pack('8s', BOOT_MAGIC)) - final_ramdisk_offset = (args.base + args.ramdisk_offset) if filesize(args.ramdisk) > 0 else 0 - final_second_offset = (args.base + args.second_offset) if filesize(args.second) > 0 else 0 - args.output.write(pack( - '10I', - filesize(args.kernel), # size in bytes - args.base + args.kernel_offset, # physical load addr - filesize(args.ramdisk), # size in bytes - final_ramdisk_offset, # physical load addr - filesize(args.second), # size in bytes - final_second_offset, # physical load addr - args.base + args.tags_offset, # physical addr for kernel tags - args.pagesize, # flash page size we assume - args.header_version, # version of bootimage header - (args.os_version << 11) | args.os_patch_level)) # os version and patch level - args.output.write(pack('16s', args.board.encode())) # asciiz product name - args.output.write(pack('512s', args.cmdline[:512].encode())) + +def write_header(args): + if args.header_version > 4: + raise ValueError( + f'Boot header version {args.header_version} not supported') + if args.header_version in {3, 4}: + return write_header_v3_and_above(args) + + ramdisk_load_address = ((args.base + args.ramdisk_offset) + if filesize(args.ramdisk) > 0 else 0) + second_load_address = ((args.base + args.second_offset) + if filesize(args.second) > 0 else 0) + + args.output.write(pack(f'{BOOT_MAGIC_SIZE}s', BOOT_MAGIC.encode())) + # kernel size in bytes + args.output.write(pack('I', filesize(args.kernel))) + # kernel physical load address + args.output.write(pack('I', args.base + args.kernel_offset)) + # ramdisk size in bytes + args.output.write(pack('I', filesize(args.ramdisk))) + # ramdisk physical load address + args.output.write(pack('I', ramdisk_load_address)) + # second bootloader size in bytes + args.output.write(pack('I', filesize(args.second))) + # second bootloader physical load address + args.output.write(pack('I', second_load_address)) + # kernel tags physical load address + args.output.write(pack('I', args.base + args.tags_offset)) + # flash page size + args.output.write(pack('I', args.pagesize)) + # version of boot image header + args.output.write(pack('I', args.header_version)) + # os version and patch level + args.output.write(pack('I', (args.os_version << 11) | args.os_patch_level)) + # asciiz product name + args.output.write(pack(f'{BOOT_NAME_SIZE}s', args.board)) + args.output.write(pack(f'{BOOT_ARGS_SIZE}s', args.cmdline)) sha = sha1() update_sha(sha, args.kernel) @@ -144,14 +264,18 @@ def write_header(args): img_id = pack('32s', sha.digest()) args.output.write(img_id) - args.output.write(pack('1024s', args.cmdline[512:].encode())) + args.output.write(pack(f'{BOOT_EXTRA_ARGS_SIZE}s', args.extra_cmdline)) if args.header_version > 0: - args.output.write(pack('I', filesize(args.recovery_dtbo))) # size in bytes if args.recovery_dtbo: - args.output.write(pack('Q', get_recovery_dtbo_offset(args))) # recovery dtbo offset + # recovery dtbo size in bytes + args.output.write(pack('I', filesize(args.recovery_dtbo))) + # recovert dtbo offset in the boot image + args.output.write(pack('Q', get_recovery_dtbo_offset(args))) else: - args.output.write(pack('Q', 0)) # Will be set to 0 for devices without a recovery dtbo + # Set to zero if no recovery dtbo + args.output.write(pack('I', 0)) + args.output.write(pack('Q', 0)) # Populate boot image header size for header versions 1 and 2. if args.header_version == 1: @@ -160,29 +284,101 @@ def write_header(args): args.output.write(pack('I', BOOT_IMAGE_HEADER_V2_SIZE)) if args.header_version > 1: - # if filesize(args.dtb) == 0: - # raise ValueError("DTB image must not be empty.") + # raise ValueError('DTB image must not be empty.') + + # dtb size in bytes + args.output.write(pack('I', filesize(args.dtb))) + # dtb physical load address + args.output.write(pack('Q', args.base + args.dtb_offset)) - args.output.write(pack('I', filesize(args.dtb))) # size in bytes - args.output.write(pack('Q', args.base + args.dtb_offset)) # dtb physical load address pad_file(args.output, args.pagesize) return img_id -class ValidateStrLenAction(Action): - def __init__(self, option_strings, dest, nargs=None, **kwargs): - if 'maxlen' not in kwargs: - raise ValueError('maxlen must be set') - self.maxlen = int(kwargs['maxlen']) - del kwargs['maxlen'] - super(ValidateStrLenAction, self).__init__(option_strings, dest, **kwargs) +class AsciizBytes: + """Parses a string and encodes it as an asciiz bytes object. - def __call__(self, parser, namespace, values, option_string=None): - if len(values) > self.maxlen: + >>> AsciizBytes(bufsize=4)('foo') + b'foo\\x00' + >>> AsciizBytes(bufsize=4)('foob') + Traceback (most recent call last): + ... + argparse.ArgumentTypeError: Encoded asciiz length exceeded: max 4, got 5 + """ + + def __init__(self, bufsize): + self.bufsize = bufsize + + def __call__(self, arg): + arg_bytes = arg.encode() + b'\x00' + if len(arg_bytes) > self.bufsize: + raise ArgumentTypeError( + 'Encoded asciiz length exceeded: ' + f'max {self.bufsize}, got {len(arg_bytes)}') + return arg_bytes + + +class VendorRamdiskTableBuilder: + """Vendor ramdisk table builder. + + Attributes: + entries: A list of VendorRamdiskTableEntry namedtuple. + ramdisk_total_size: Total size in bytes of all ramdisks in the table. + """ + + VendorRamdiskTableEntry = collections.namedtuple( # pylint: disable=invalid-name + 'VendorRamdiskTableEntry', + ['ramdisk_path', 'ramdisk_size', 'ramdisk_offset', 'ramdisk_type', + 'ramdisk_name', 'board_id']) + + def __init__(self): + self.entries = [] + self.ramdisk_total_size = 0 + self.ramdisk_names = set() + + def add_entry(self, ramdisk_path, ramdisk_type, ramdisk_name, board_id): + # Strip any trailing null for simple comparison. + stripped_ramdisk_name = ramdisk_name.rstrip(b'\x00') + if stripped_ramdisk_name in VENDOR_RAMDISK_NAME_BLOCKLIST: raise ValueError( - 'String argument too long: max {0:d}, got {1:d}'.format(self.maxlen, len(values))) - setattr(namespace, self.dest, values) + f'Banned vendor ramdisk name: {stripped_ramdisk_name}') + if stripped_ramdisk_name in self.ramdisk_names: + raise ValueError( + f'Duplicated vendor ramdisk name: {stripped_ramdisk_name}') + self.ramdisk_names.add(stripped_ramdisk_name) + + if board_id is None: + board_id = array.array( + 'I', [0] * VENDOR_RAMDISK_TABLE_ENTRY_BOARD_ID_SIZE) + else: + board_id = array.array('I', board_id) + if len(board_id) != VENDOR_RAMDISK_TABLE_ENTRY_BOARD_ID_SIZE: + raise ValueError('board_id size must be ' + f'{VENDOR_RAMDISK_TABLE_ENTRY_BOARD_ID_SIZE}') + + with open(ramdisk_path, 'rb') as f: + ramdisk_size = filesize(f) + self.entries.append(self.VendorRamdiskTableEntry( + ramdisk_path, ramdisk_size, self.ramdisk_total_size, ramdisk_type, + ramdisk_name, board_id)) + self.ramdisk_total_size += ramdisk_size + + def write_ramdisks_padded(self, fout, alignment): + for entry in self.entries: + with open(entry.ramdisk_path, 'rb') as f: + fout.write(f.read()) + pad_file(fout, alignment) + + def write_entries_padded(self, fout, alignment): + for entry in self.entries: + fout.write(pack('I', entry.ramdisk_size)) + fout.write(pack('I', entry.ramdisk_offset)) + fout.write(pack('I', entry.ramdisk_type)) + fout.write(pack(f'{VENDOR_RAMDISK_NAME_SIZE}s', + entry.ramdisk_name)) + fout.write(entry.board_id) + pad_file(fout, alignment) def write_padded_file(f_out, f_in, padding): @@ -225,49 +421,221 @@ def parse_os_patch_level(x): return 0 +def parse_vendor_ramdisk_type(x): + type_dict = { + 'none': VENDOR_RAMDISK_TYPE_NONE, + 'platform': VENDOR_RAMDISK_TYPE_PLATFORM, + 'recovery': VENDOR_RAMDISK_TYPE_RECOVERY, + 'dlkm': VENDOR_RAMDISK_TYPE_DLKM, + } + if x.lower() in type_dict: + return type_dict[x.lower()] + return parse_int(x) + + +def get_vendor_boot_v4_usage(): + return """vendor boot version 4 arguments: + --ramdisk_type {none,platform,recovery,dlkm} + specify the type of the ramdisk + --ramdisk_name NAME + specify the name of the ramdisk + --board_id{0..15} NUMBER + specify the value of the board_id vector, defaults to 0 + --vendor_ramdisk_fragment VENDOR_RAMDISK_FILE + path to the vendor ramdisk file + + These options can be specified multiple times, where each vendor ramdisk + option group ends with a --vendor_ramdisk_fragment option. + Each option group appends an additional ramdisk to the vendor boot image. +""" + + +def parse_vendor_ramdisk_args(args, args_list): + """Parses vendor ramdisk specific arguments. + + Args: + args: An argparse.Namespace object. Parsed results are stored into this + object. + args_list: A list of argument strings to be parsed. + + Returns: + A list argument strings that are not parsed by this method. + """ + parser = ArgumentParser(add_help=False) + parser.add_argument('--ramdisk_type', type=parse_vendor_ramdisk_type, + default=VENDOR_RAMDISK_TYPE_NONE) + parser.add_argument('--ramdisk_name', + type=AsciizBytes(bufsize=VENDOR_RAMDISK_NAME_SIZE), + required=True) + for i in range(VENDOR_RAMDISK_TABLE_ENTRY_BOARD_ID_SIZE): + parser.add_argument(f'--board_id{i}', type=parse_int, default=0) + parser.add_argument(PARSER_ARGUMENT_VENDOR_RAMDISK_FRAGMENT, required=True) + + unknown_args = [] + + vendor_ramdisk_table_builder = VendorRamdiskTableBuilder() + if args.vendor_ramdisk is not None: + vendor_ramdisk_table_builder.add_entry( + args.vendor_ramdisk.name, VENDOR_RAMDISK_TYPE_PLATFORM, b'', None) + + while PARSER_ARGUMENT_VENDOR_RAMDISK_FRAGMENT in args_list: + idx = args_list.index(PARSER_ARGUMENT_VENDOR_RAMDISK_FRAGMENT) + 2 + vendor_ramdisk_args = args_list[:idx] + args_list = args_list[idx:] + + ramdisk_args, extra_args = parser.parse_known_args(vendor_ramdisk_args) + ramdisk_args_dict = vars(ramdisk_args) + unknown_args.extend(extra_args) + + ramdisk_path = ramdisk_args.vendor_ramdisk_fragment + ramdisk_type = ramdisk_args.ramdisk_type + ramdisk_name = ramdisk_args.ramdisk_name + board_id = [ramdisk_args_dict[f'board_id{i}'] + for i in range(VENDOR_RAMDISK_TABLE_ENTRY_BOARD_ID_SIZE)] + vendor_ramdisk_table_builder.add_entry(ramdisk_path, ramdisk_type, + ramdisk_name, board_id) + + if len(args_list) > 0: + unknown_args.extend(args_list) + + args.vendor_ramdisk_total_size = (vendor_ramdisk_table_builder + .ramdisk_total_size) + args.vendor_ramdisk_table_entry_num = len(vendor_ramdisk_table_builder + .entries) + args.vendor_ramdisk_table_builder = vendor_ramdisk_table_builder + return unknown_args + + def parse_cmdline(): - parser = ArgumentParser() - parser.add_argument('--kernel', help='path to the kernel', type=FileType('rb')) - parser.add_argument('--ramdisk', help='path to the ramdisk', type=FileType('rb')) - parser.add_argument('--second', help='path to the 2nd bootloader', type=FileType('rb')) - parser.add_argument('--dtb', help='path to dtb', type=FileType('rb')) - recovery_dtbo_group = parser.add_mutually_exclusive_group() - recovery_dtbo_group.add_argument('--recovery_dtbo', help='path to the recovery DTBO', - type=FileType('rb')) - recovery_dtbo_group.add_argument('--recovery_acpio', help='path to the recovery ACPIO', - type=FileType('rb'), metavar='RECOVERY_ACPIO', - dest='recovery_dtbo') - parser.add_argument('--cmdline', help='extra arguments to be passed on the ' - 'kernel command line', default='', action=ValidateStrLenAction, maxlen=1536) + version_parser = ArgumentParser(add_help=False) + version_parser.add_argument('--header_version', type=parse_int, default=0) + if version_parser.parse_known_args()[0].header_version < 3: + # For boot header v0 to v2, the kernel commandline field is split into + # two fields, cmdline and extra_cmdline. Both fields are asciiz strings, + # so we minus one here to ensure the encoded string plus the + # null-terminator can fit in the buffer size. + cmdline_size = BOOT_ARGS_SIZE + BOOT_EXTRA_ARGS_SIZE - 1 + else: + cmdline_size = BOOT_ARGS_SIZE + BOOT_EXTRA_ARGS_SIZE + + parser = ArgumentParser(formatter_class=RawDescriptionHelpFormatter, + epilog=get_vendor_boot_v4_usage()) + parser.add_argument('--kernel', type=FileType('rb'), + help='path to the kernel') + parser.add_argument('--ramdisk', type=FileType('rb'), + help='path to the ramdisk') + parser.add_argument('--second', type=FileType('rb'), + help='path to the second bootloader') + parser.add_argument('--dtb', type=FileType('rb'), help='path to the dtb') + dtbo_group = parser.add_mutually_exclusive_group() + dtbo_group.add_argument('--recovery_dtbo', type=FileType('rb'), + help='path to the recovery DTBO') + dtbo_group.add_argument('--recovery_acpio', type=FileType('rb'), + metavar='RECOVERY_ACPIO', dest='recovery_dtbo', + help='path to the recovery ACPIO') + parser.add_argument('--cmdline', type=AsciizBytes(bufsize=cmdline_size), + default='', help='kernel command line arguments') parser.add_argument('--vendor_cmdline', - help='kernel command line arguments contained in vendor boot', - default='', action=ValidateStrLenAction, maxlen=2048) - parser.add_argument('--base', help='base address', type=parse_int, default=0x10000000) - parser.add_argument('--kernel_offset', help='kernel offset', type=parse_int, default=0x00008000) - parser.add_argument('--ramdisk_offset', help='ramdisk offset', type=parse_int, - default=0x01000000) - parser.add_argument('--second_offset', help='2nd bootloader offset', type=parse_int, - default=0x00f00000) - parser.add_argument('--dtb_offset', help='dtb offset', type=parse_int, default=0x01f00000) + type=AsciizBytes(bufsize=VENDOR_BOOT_ARGS_SIZE), + default='', + help='vendor boot kernel command line arguments') + parser.add_argument('--base', type=parse_int, default=0x10000000, + help='base address') + parser.add_argument('--kernel_offset', type=parse_int, default=0x00008000, + help='kernel offset') + parser.add_argument('--ramdisk_offset', type=parse_int, default=0x01000000, + help='ramdisk offset') + parser.add_argument('--second_offset', type=parse_int, default=0x00f00000, + help='second bootloader offset') + parser.add_argument('--dtb_offset', type=parse_int, default=0x01f00000, + help='dtb offset') - parser.add_argument('--os_version', help='operating system version', type=parse_os_version, - default=0) - parser.add_argument('--os_patch_level', help='operating system patch level', - type=parse_os_patch_level, default=0) - parser.add_argument('--tags_offset', help='tags offset', type=parse_int, default=0x00000100) - parser.add_argument('--board', help='board name', default='', action=ValidateStrLenAction, - maxlen=16) - parser.add_argument('--pagesize', help='page size', type=parse_int, - choices=[2**i for i in range(11, 15)], default=2048) - parser.add_argument('--id', help='print the image ID on standard output', - action='store_true') - parser.add_argument('--header_version', help='boot image header version', type=parse_int, - default=0) - parser.add_argument('-o', '--output', help='output file name', type=FileType('wb')) - parser.add_argument('--vendor_boot', help='vendor boot output file name', type=FileType('wb')) - parser.add_argument('--vendor_ramdisk', help='path to the vendor ramdisk', type=FileType('rb')) + parser.add_argument('--os_version', type=parse_os_version, default=0, + help='operating system version') + parser.add_argument('--os_patch_level', type=parse_os_patch_level, + default=0, help='operating system patch level') + parser.add_argument('--tags_offset', type=parse_int, default=0x00000100, + help='tags offset') + parser.add_argument('--board', type=AsciizBytes(bufsize=BOOT_NAME_SIZE), + default='', help='board name') + parser.add_argument('--pagesize', type=parse_int, + choices=[2**i for i in range(11, 15)], default=2048, + help='page size') + parser.add_argument('--id', action='store_true', + help='print the image ID on standard output') + parser.add_argument('--header_version', type=parse_int, default=0, + help='boot image header version') + parser.add_argument('-o', '--output', type=FileType('wb'), + help='output file name') + parser.add_argument('--vendor_boot', type=FileType('wb'), + help='vendor boot output file name') + parser.add_argument('--vendor_ramdisk', type=FileType('rb'), + help='path to the vendor ramdisk') + parser.add_argument('--vendor_bootconfig', type=FileType('rb'), + help='path to the vendor bootconfig file') - return parser.parse_args() + gki_2_0_signing_args = parser.add_argument_group( + '[DEPRECATED] GKI 2.0 signing arguments') + gki_2_0_signing_args.add_argument( + '--gki_signing_algorithm', help='GKI signing algorithm to use') + gki_2_0_signing_args.add_argument( + '--gki_signing_key', help='path to RSA private key file') + gki_2_0_signing_args.add_argument( + '--gki_signing_signature_args', default='', + help='other hash arguments passed to avbtool') + gki_2_0_signing_args.add_argument( + '--gki_signing_avbtool_path', default='avbtool', + help='path to avbtool for boot signature generation') + + args, extra_args = parser.parse_known_args() + if args.vendor_boot is not None and args.header_version > 3: + extra_args = parse_vendor_ramdisk_args(args, extra_args) + if len(extra_args) > 0: + raise ValueError(f'Unrecognized arguments: {extra_args}') + + if args.header_version < 3: + args.extra_cmdline = args.cmdline[BOOT_ARGS_SIZE-1:] + args.cmdline = args.cmdline[:BOOT_ARGS_SIZE-1] + b'\x00' + assert len(args.cmdline) <= BOOT_ARGS_SIZE + assert len(args.extra_cmdline) <= BOOT_EXTRA_ARGS_SIZE + + return args + + +def add_boot_image_signature(args, pagesize): + """Adds the boot image signature. + + Note that the signature will only be verified in VTS to ensure a + generic boot.img is used. It will not be used by the device + bootloader at boot time. The bootloader should only verify + the boot vbmeta at the end of the boot partition (or in the top-level + vbmeta partition) via the Android Verified Boot process, when the + device boots. + """ + # Flush the buffer for signature calculation. + args.output.flush() + + # Outputs the signed vbmeta to a separate file, then append to boot.img + # as the boot signature. + with tempfile.TemporaryDirectory() as temp_out_dir: + boot_signature_output = os.path.join(temp_out_dir, 'boot_signature') + generate_gki_certificate( + image=args.output.name, avbtool=args.gki_signing_avbtool_path, + name='boot', algorithm=args.gki_signing_algorithm, + key=args.gki_signing_key, salt='d00df00d', + additional_avb_args=args.gki_signing_signature_args.split(), + output=boot_signature_output, + ) + with open(boot_signature_output, 'rb') as boot_signature: + boot_signature_bytes = boot_signature.read() + if len(boot_signature_bytes) > BOOT_IMAGE_V4_SIGNATURE_SIZE: + raise ValueError( + f'boot sigature size is > {BOOT_IMAGE_V4_SIGNATURE_SIZE}') + boot_signature_bytes += b'\x00' * ( + BOOT_IMAGE_V4_SIGNATURE_SIZE - len(boot_signature_bytes)) + assert len(boot_signature_bytes) == BOOT_IMAGE_V4_SIGNATURE_SIZE + args.output.write(boot_signature_bytes) + pad_file(args.output, pagesize) def write_data(args, pagesize): @@ -279,37 +647,44 @@ def write_data(args, pagesize): write_padded_file(args.output, args.recovery_dtbo, pagesize) if args.header_version == 2: write_padded_file(args.output, args.dtb, pagesize) + if args.header_version >= 4 and should_add_legacy_gki_boot_signature(args): + add_boot_image_signature(args, pagesize) def write_vendor_boot_data(args): - write_padded_file(args.vendor_boot, args.vendor_ramdisk, args.pagesize) - write_padded_file(args.vendor_boot, args.dtb, args.pagesize) + if args.header_version > 3: + builder = args.vendor_ramdisk_table_builder + builder.write_ramdisks_padded(args.vendor_boot, args.pagesize) + write_padded_file(args.vendor_boot, args.dtb, args.pagesize) + builder.write_entries_padded(args.vendor_boot, args.pagesize) + write_padded_file(args.vendor_boot, args.vendor_bootconfig, + args.pagesize) + else: + write_padded_file(args.vendor_boot, args.vendor_ramdisk, args.pagesize) + write_padded_file(args.vendor_boot, args.dtb, args.pagesize) def main(): args = parse_cmdline() if args.vendor_boot is not None: - if args.header_version < 3: - raise ValueError('--vendor_boot not compatible with given header version') - if args.vendor_ramdisk is None: + if args.header_version not in {3, 4}: + raise ValueError( + '--vendor_boot not compatible with given header version') + if args.header_version == 3 and args.vendor_ramdisk is None: raise ValueError('--vendor_ramdisk missing or invalid') write_vendor_boot_header(args) write_vendor_boot_data(args) if args.output is not None: - if args.kernel is None: - raise ValueError('kernel must be supplied when creating a boot image') if args.second is not None and args.header_version > 2: - raise ValueError('--second not compatible with given header version') + raise ValueError( + '--second not compatible with given header version') img_id = write_header(args) if args.header_version > 2: write_data(args, BOOT_IMAGE_HEADER_V3_PAGESIZE) else: write_data(args, args.pagesize) if args.id and img_id is not None: - # Python 2's struct.pack returns a string, but py3 returns bytes. - if isinstance(img_id, str): - img_id = [ord(x) for x in img_id] - print('0x' + ''.join('{:02x}'.format(c) for c in img_id)) + print('0x' + ''.join(f'{octet:02x}' for octet in img_id)) if __name__ == '__main__': diff --git a/scripts/unpack_bootimg b/scripts/unpack_bootimg index 83c2bbe33d7d..a3f1a508796b 100755 --- a/scripts/unpack_bootimg +++ b/scripts/unpack_bootimg @@ -1,4 +1,5 @@ -#!/usr/bin/env python +#!/usr/bin/env python3 +# # Copyright 2018, The Android Open Source Project # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -13,18 +14,20 @@ # See the License for the specific language governing permissions and # limitations under the License. -"""unpacks the bootimage. +"""Unpacks the boot image. Extracts the kernel, ramdisk, second bootloader, dtb and recovery dtbo images. """ -from __future__ import print_function -from argparse import ArgumentParser, FileType +from argparse import ArgumentParser, RawDescriptionHelpFormatter from struct import unpack import os +import shlex BOOT_IMAGE_HEADER_V3_PAGESIZE = 4096 -VENDOR_BOOT_IMAGE_HEADER_V3_SIZE = 2112 +VENDOR_RAMDISK_NAME_SIZE = 32 +VENDOR_RAMDISK_TABLE_ENTRY_BOARD_ID_SIZE = 16 + def create_out_dir(dir_path): """creates a directory 'dir_path' if it does not exist""" @@ -50,196 +53,510 @@ def cstr(s): def format_os_version(os_version): + if os_version == 0: + return None a = os_version >> 14 b = os_version >> 7 & ((1<<7) - 1) c = os_version & ((1<<7) - 1) - return '{}.{}.{}'.format(a, b, c) + return f'{a}.{b}.{c}' def format_os_patch_level(os_patch_level): + if os_patch_level == 0: + return None y = os_patch_level >> 4 y += 2000 m = os_patch_level & ((1<<4) - 1) - return '{:04d}-{:02d}'.format(y, m) + return f'{y:04d}-{m:02d}' -def print_os_version_patch_level(value): - os_version = value >> 11 - os_patch_level = value & ((1<<11) - 1) - print('os version: %s' % format_os_version(os_version)) - print('os patch level: %s' % format_os_patch_level(os_patch_level)) +def decode_os_version_patch_level(os_version_patch_level): + """Returns a tuple of (os_version, os_patch_level).""" + os_version = os_version_patch_level >> 11 + os_patch_level = os_version_patch_level & ((1<<11) - 1) + return (format_os_version(os_version), + format_os_patch_level(os_patch_level)) -def unpack_bootimage(args): +class BootImageInfoFormatter: + """Formats the boot image info.""" + + def format_pretty_text(self): + lines = [] + lines.append(f'boot magic: {self.boot_magic}') + + if self.header_version < 3: + lines.append(f'kernel_size: {self.kernel_size}') + lines.append( + f'kernel load address: {self.kernel_load_address:#010x}') + lines.append(f'ramdisk size: {self.ramdisk_size}') + lines.append( + f'ramdisk load address: {self.ramdisk_load_address:#010x}') + lines.append(f'second bootloader size: {self.second_size}') + lines.append( + f'second bootloader load address: ' + f'{self.second_load_address:#010x}') + lines.append( + f'kernel tags load address: {self.tags_load_address:#010x}') + lines.append(f'page size: {self.page_size}') + else: + lines.append(f'kernel_size: {self.kernel_size}') + lines.append(f'ramdisk size: {self.ramdisk_size}') + + lines.append(f'os version: {self.os_version}') + lines.append(f'os patch level: {self.os_patch_level}') + lines.append(f'boot image header version: {self.header_version}') + + if self.header_version < 3: + lines.append(f'product name: {self.product_name}') + + lines.append(f'command line args: {self.cmdline}') + + if self.header_version < 3: + lines.append(f'additional command line args: {self.extra_cmdline}') + + if self.header_version in {1, 2}: + lines.append(f'recovery dtbo size: {self.recovery_dtbo_size}') + lines.append( + f'recovery dtbo offset: {self.recovery_dtbo_offset:#018x}') + lines.append(f'boot header size: {self.boot_header_size}') + + if self.header_version == 2: + lines.append(f'dtb size: {self.dtb_size}') + lines.append(f'dtb address: {self.dtb_load_address:#018x}') + + if self.header_version >= 4: + lines.append( + f'boot.img signature size: {self.boot_signature_size}') + + return '\n'.join(lines) + + def format_mkbootimg_argument(self): + args = [] + args.extend(['--header_version', str(self.header_version)]) + if self.os_version: + args.extend(['--os_version', self.os_version]) + if self.os_patch_level: + args.extend(['--os_patch_level', self.os_patch_level]) + + args.extend(['--kernel', os.path.join(self.image_dir, 'kernel')]) + args.extend(['--ramdisk', os.path.join(self.image_dir, 'ramdisk')]) + + if self.header_version <= 2: + if self.second_size > 0: + args.extend(['--second', + os.path.join(self.image_dir, 'second')]) + if self.recovery_dtbo_size > 0: + args.extend(['--recovery_dtbo', + os.path.join(self.image_dir, 'recovery_dtbo')]) + if self.dtb_size > 0: + args.extend(['--dtb', os.path.join(self.image_dir, 'dtb')]) + + args.extend(['--pagesize', f'{self.page_size:#010x}']) + + # Kernel load address is base + kernel_offset in mkbootimg.py. + # However we don't know the value of 'base' when unpacking a boot + # image in this script, so we set 'base' to zero and 'kernel_offset' + # to the kernel load address, 'ramdisk_offset' to the ramdisk load + # address, ... etc. + args.extend(['--base', f'{0:#010x}']) + args.extend(['--kernel_offset', + f'{self.kernel_load_address:#010x}']) + args.extend(['--ramdisk_offset', + f'{self.ramdisk_load_address:#010x}']) + args.extend(['--second_offset', + f'{self.second_load_address:#010x}']) + args.extend(['--tags_offset', f'{self.tags_load_address:#010x}']) + + # dtb is added in boot image v2, and is absent in v1 or v0. + if self.header_version == 2: + # dtb_offset is uint64_t. + args.extend(['--dtb_offset', f'{self.dtb_load_address:#018x}']) + + args.extend(['--board', self.product_name]) + args.extend(['--cmdline', self.cmdline + self.extra_cmdline]) + else: + args.extend(['--cmdline', self.cmdline]) + + return args + + +def unpack_boot_image(boot_img, output_dir): """extracts kernel, ramdisk, second bootloader and recovery dtbo""" - kernel_ramdisk_second_info = unpack('9I', args.boot_img.read(9 * 4)) - version = kernel_ramdisk_second_info[8] - if version < 3: - print('kernel_size: %s' % kernel_ramdisk_second_info[0]) - print('kernel load address: %#x' % kernel_ramdisk_second_info[1]) - print('ramdisk size: %s' % kernel_ramdisk_second_info[2]) - print('ramdisk load address: %#x' % kernel_ramdisk_second_info[3]) - print('second bootloader size: %s' % kernel_ramdisk_second_info[4]) - print('second bootloader load address: %#x' % kernel_ramdisk_second_info[5]) - print('kernel tags load address: %#x' % kernel_ramdisk_second_info[6]) - print('page size: %s' % kernel_ramdisk_second_info[7]) - print_os_version_patch_level(unpack('I', args.boot_img.read(1 * 4))[0]) + info = BootImageInfoFormatter() + info.boot_magic = unpack('8s', boot_img.read(8))[0].decode() + + kernel_ramdisk_second_info = unpack('9I', boot_img.read(9 * 4)) + # header_version is always at [8] regardless of the value of header_version. + info.header_version = kernel_ramdisk_second_info[8] + + if info.header_version < 3: + info.kernel_size = kernel_ramdisk_second_info[0] + info.kernel_load_address = kernel_ramdisk_second_info[1] + info.ramdisk_size = kernel_ramdisk_second_info[2] + info.ramdisk_load_address = kernel_ramdisk_second_info[3] + info.second_size = kernel_ramdisk_second_info[4] + info.second_load_address = kernel_ramdisk_second_info[5] + info.tags_load_address = kernel_ramdisk_second_info[6] + info.page_size = kernel_ramdisk_second_info[7] + os_version_patch_level = unpack('I', boot_img.read(1 * 4))[0] else: - print('kernel_size: %s' % kernel_ramdisk_second_info[0]) - print('ramdisk size: %s' % kernel_ramdisk_second_info[1]) - print_os_version_patch_level(kernel_ramdisk_second_info[2]) + info.kernel_size = kernel_ramdisk_second_info[0] + info.ramdisk_size = kernel_ramdisk_second_info[1] + os_version_patch_level = kernel_ramdisk_second_info[2] + info.second_size = 0 + info.page_size = BOOT_IMAGE_HEADER_V3_PAGESIZE - print('boot image header version: %s' % version) + info.os_version, info.os_patch_level = decode_os_version_patch_level( + os_version_patch_level) - if version < 3: - product_name = cstr(unpack('16s', args.boot_img.read(16))[0].decode()) - print('product name: %s' % product_name) - cmdline = cstr(unpack('512s', args.boot_img.read(512))[0].decode()) - print('command line args: %s' % cmdline) + if info.header_version < 3: + info.product_name = cstr(unpack('16s', + boot_img.read(16))[0].decode()) + info.cmdline = cstr(unpack('512s', boot_img.read(512))[0].decode()) + boot_img.read(32) # ignore SHA + info.extra_cmdline = cstr(unpack('1024s', + boot_img.read(1024))[0].decode()) else: - cmdline = cstr(unpack('1536s', args.boot_img.read(1536))[0].decode()) - print('command line args: %s' % cmdline) + info.cmdline = cstr(unpack('1536s', + boot_img.read(1536))[0].decode()) - if version < 3: - args.boot_img.read(32) # ignore SHA - - if version < 3: - extra_cmdline = cstr(unpack('1024s', - args.boot_img.read(1024))[0].decode()) - print('additional command line args: %s' % extra_cmdline) - - if version < 3: - kernel_size = kernel_ramdisk_second_info[0] - ramdisk_size = kernel_ramdisk_second_info[2] - second_size = kernel_ramdisk_second_info[4] - page_size = kernel_ramdisk_second_info[7] + if info.header_version in {1, 2}: + info.recovery_dtbo_size = unpack('I', boot_img.read(1 * 4))[0] + info.recovery_dtbo_offset = unpack('Q', boot_img.read(8))[0] + info.boot_header_size = unpack('I', boot_img.read(4))[0] else: - kernel_size = kernel_ramdisk_second_info[0] - ramdisk_size = kernel_ramdisk_second_info[1] - second_size = 0 - page_size = BOOT_IMAGE_HEADER_V3_PAGESIZE + info.recovery_dtbo_size = 0 - if 0 < version < 3: - recovery_dtbo_size = unpack('I', args.boot_img.read(1 * 4))[0] - print('recovery dtbo size: %s' % recovery_dtbo_size) - recovery_dtbo_offset = unpack('Q', args.boot_img.read(8))[0] - print('recovery dtbo offset: %#x' % recovery_dtbo_offset) - boot_header_size = unpack('I', args.boot_img.read(4))[0] - print('boot header size: %s' % boot_header_size) + if info.header_version == 2: + info.dtb_size = unpack('I', boot_img.read(4))[0] + info.dtb_load_address = unpack('Q', boot_img.read(8))[0] else: - recovery_dtbo_size = 0 - if 1 < version < 3: - dtb_size = unpack('I', args.boot_img.read(4))[0] - print('dtb size: %s' % dtb_size) - dtb_load_address = unpack('Q', args.boot_img.read(8))[0] - print('dtb address: %#x' % dtb_load_address) - else: - dtb_size = 0 + info.dtb_size = 0 + info.dtb_load_address = 0 + if info.header_version >= 4: + info.boot_signature_size = unpack('I', boot_img.read(4))[0] + else: + info.boot_signature_size = 0 # The first page contains the boot header num_header_pages = 1 - num_kernel_pages = get_number_of_pages(kernel_size, page_size) - kernel_offset = page_size * num_header_pages # header occupies a page - image_info_list = [(kernel_offset, kernel_size, 'kernel')] + # Convenient shorthand. + page_size = info.page_size - num_ramdisk_pages = get_number_of_pages(ramdisk_size, page_size) + num_kernel_pages = get_number_of_pages(info.kernel_size, page_size) + kernel_offset = page_size * num_header_pages # header occupies a page + image_info_list = [(kernel_offset, info.kernel_size, 'kernel')] + + num_ramdisk_pages = get_number_of_pages(info.ramdisk_size, page_size) ramdisk_offset = page_size * (num_header_pages + num_kernel_pages ) # header + kernel - image_info_list.append((ramdisk_offset, ramdisk_size, 'ramdisk')) + image_info_list.append((ramdisk_offset, info.ramdisk_size, 'ramdisk')) - if second_size > 0: + if info.second_size > 0: second_offset = page_size * ( num_header_pages + num_kernel_pages + num_ramdisk_pages ) # header + kernel + ramdisk - image_info_list.append((second_offset, second_size, 'second')) + image_info_list.append((second_offset, info.second_size, 'second')) - if recovery_dtbo_size > 0: - image_info_list.append((recovery_dtbo_offset, recovery_dtbo_size, + if info.recovery_dtbo_size > 0: + image_info_list.append((info.recovery_dtbo_offset, + info.recovery_dtbo_size, 'recovery_dtbo')) - if dtb_size > 0: - num_second_pages = get_number_of_pages(second_size, page_size) - num_recovery_dtbo_pages = get_number_of_pages(recovery_dtbo_size, page_size) + if info.dtb_size > 0: + num_second_pages = get_number_of_pages(info.second_size, page_size) + num_recovery_dtbo_pages = get_number_of_pages( + info.recovery_dtbo_size, page_size) dtb_offset = page_size * ( - num_header_pages + num_kernel_pages + num_ramdisk_pages + num_second_pages + - num_recovery_dtbo_pages - ) + num_header_pages + num_kernel_pages + num_ramdisk_pages + + num_second_pages + num_recovery_dtbo_pages) - image_info_list.append((dtb_offset, dtb_size, 'dtb')) + image_info_list.append((dtb_offset, info.dtb_size, 'dtb')) - for image_info in image_info_list: - extract_image(image_info[0], image_info[1], args.boot_img, - os.path.join(args.out, image_info[2])) + if info.boot_signature_size > 0: + # boot signature only exists in boot.img version >= v4. + # There are only kernel and ramdisk pages before the signature. + boot_signature_offset = page_size * ( + num_header_pages + num_kernel_pages + num_ramdisk_pages) + + image_info_list.append((boot_signature_offset, info.boot_signature_size, + 'boot_signature')) + + create_out_dir(output_dir) + for offset, size, name in image_info_list: + extract_image(offset, size, boot_img, os.path.join(output_dir, name)) + info.image_dir = output_dir + + return info -def unpack_vendor_bootimage(args): - kernel_ramdisk_info = unpack('5I', args.boot_img.read(5 * 4)) - print('vendor boot image header version: %s' % kernel_ramdisk_info[0]) - print('kernel load address: %#x' % kernel_ramdisk_info[2]) - print('ramdisk load address: %#x' % kernel_ramdisk_info[3]) - print('vendor ramdisk size: %s' % kernel_ramdisk_info[4]) +class VendorBootImageInfoFormatter: + """Formats the vendor_boot image info.""" - cmdline = cstr(unpack('2048s', args.boot_img.read(2048))[0].decode()) - print('vendor command line args: %s' % cmdline) + def format_pretty_text(self): + lines = [] + lines.append(f'boot magic: {self.boot_magic}') + lines.append(f'vendor boot image header version: {self.header_version}') + lines.append(f'page size: {self.page_size:#010x}') + lines.append(f'kernel load address: {self.kernel_load_address:#010x}') + lines.append(f'ramdisk load address: {self.ramdisk_load_address:#010x}') + if self.header_version > 3: + lines.append( + f'vendor ramdisk total size: {self.vendor_ramdisk_size}') + else: + lines.append(f'vendor ramdisk size: {self.vendor_ramdisk_size}') + lines.append(f'vendor command line args: {self.cmdline}') + lines.append( + f'kernel tags load address: {self.tags_load_address:#010x}') + lines.append(f'product name: {self.product_name}') + lines.append(f'vendor boot image header size: {self.header_size}') + lines.append(f'dtb size: {self.dtb_size}') + lines.append(f'dtb address: {self.dtb_load_address:#018x}') + if self.header_version > 3: + lines.append( + f'vendor ramdisk table size: {self.vendor_ramdisk_table_size}') + lines.append('vendor ramdisk table: [') + indent = lambda level: ' ' * 4 * level + for entry in self.vendor_ramdisk_table: + (output_ramdisk_name, ramdisk_size, ramdisk_offset, + ramdisk_type, ramdisk_name, board_id) = entry + lines.append(indent(1) + f'{output_ramdisk_name}: ''{') + lines.append(indent(2) + f'size: {ramdisk_size}') + lines.append(indent(2) + f'offset: {ramdisk_offset}') + lines.append(indent(2) + f'type: {ramdisk_type:#x}') + lines.append(indent(2) + f'name: {ramdisk_name}') + lines.append(indent(2) + 'board_id: [') + stride = 4 + for row_idx in range(0, len(board_id), stride): + row = board_id[row_idx:row_idx + stride] + lines.append( + indent(3) + ' '.join(f'{e:#010x},' for e in row)) + lines.append(indent(2) + ']') + lines.append(indent(1) + '}') + lines.append(']') + lines.append( + f'vendor bootconfig size: {self.vendor_bootconfig_size}') - tags_load_address = unpack('I', args.boot_img.read(1 * 4))[0] - print('kernel tags load address: %#x' % tags_load_address) + return '\n'.join(lines) - product_name = cstr(unpack('16s', args.boot_img.read(16))[0].decode()) - print('product name: %s' % product_name) + def format_mkbootimg_argument(self): + args = [] + args.extend(['--header_version', str(self.header_version)]) + args.extend(['--pagesize', f'{self.page_size:#010x}']) + args.extend(['--base', f'{0:#010x}']) + args.extend(['--kernel_offset', f'{self.kernel_load_address:#010x}']) + args.extend(['--ramdisk_offset', f'{self.ramdisk_load_address:#010x}']) + args.extend(['--tags_offset', f'{self.tags_load_address:#010x}']) + args.extend(['--dtb_offset', f'{self.dtb_load_address:#018x}']) + args.extend(['--vendor_cmdline', self.cmdline]) + args.extend(['--board', self.product_name]) - dtb_size = unpack('2I', args.boot_img.read(2 * 4))[1] - print('dtb size: %s' % dtb_size) - dtb_load_address = unpack('Q', args.boot_img.read(8))[0] - print('dtb address: %#x' % dtb_load_address) + if self.dtb_size > 0: + args.extend(['--dtb', os.path.join(self.image_dir, 'dtb')]) - ramdisk_size = kernel_ramdisk_info[4] - page_size = kernel_ramdisk_info[1] + if self.header_version > 3: + args.extend(['--vendor_bootconfig', + os.path.join(self.image_dir, 'bootconfig')]) + for entry in self.vendor_ramdisk_table: + (output_ramdisk_name, _, _, ramdisk_type, + ramdisk_name, board_id) = entry + args.extend(['--ramdisk_type', str(ramdisk_type)]) + args.extend(['--ramdisk_name', ramdisk_name]) + for idx, e in enumerate(board_id): + if e: + args.extend([f'--board_id{idx}', f'{e:#010x}']) + vendor_ramdisk_path = os.path.join( + self.image_dir, output_ramdisk_name) + args.extend(['--vendor_ramdisk_fragment', vendor_ramdisk_path]) + else: + args.extend(['--vendor_ramdisk', + os.path.join(self.image_dir, 'vendor_ramdisk')]) + + return args + + +def unpack_vendor_boot_image(boot_img, output_dir): + info = VendorBootImageInfoFormatter() + info.boot_magic = unpack('8s', boot_img.read(8))[0].decode() + info.header_version = unpack('I', boot_img.read(4))[0] + info.page_size = unpack('I', boot_img.read(4))[0] + info.kernel_load_address = unpack('I', boot_img.read(4))[0] + info.ramdisk_load_address = unpack('I', boot_img.read(4))[0] + info.vendor_ramdisk_size = unpack('I', boot_img.read(4))[0] + info.cmdline = cstr(unpack('2048s', boot_img.read(2048))[0].decode()) + info.tags_load_address = unpack('I', boot_img.read(4))[0] + info.product_name = cstr(unpack('16s', boot_img.read(16))[0].decode()) + info.header_size = unpack('I', boot_img.read(4))[0] + info.dtb_size = unpack('I', boot_img.read(4))[0] + info.dtb_load_address = unpack('Q', boot_img.read(8))[0] + + # Convenient shorthand. + page_size = info.page_size # The first pages contain the boot header - num_boot_header_pages = get_number_of_pages(VENDOR_BOOT_IMAGE_HEADER_V3_SIZE, page_size) - num_boot_ramdisk_pages = get_number_of_pages(ramdisk_size, page_size) - ramdisk_offset = page_size * num_boot_header_pages - image_info_list = [(ramdisk_offset, ramdisk_size, 'vendor_ramdisk')] + num_boot_header_pages = get_number_of_pages(info.header_size, page_size) + num_boot_ramdisk_pages = get_number_of_pages( + info.vendor_ramdisk_size, page_size) + num_boot_dtb_pages = get_number_of_pages(info.dtb_size, page_size) + + ramdisk_offset_base = page_size * num_boot_header_pages + image_info_list = [] + + if info.header_version > 3: + info.vendor_ramdisk_table_size = unpack('I', boot_img.read(4))[0] + vendor_ramdisk_table_entry_num = unpack('I', boot_img.read(4))[0] + vendor_ramdisk_table_entry_size = unpack('I', boot_img.read(4))[0] + info.vendor_bootconfig_size = unpack('I', boot_img.read(4))[0] + num_vendor_ramdisk_table_pages = get_number_of_pages( + info.vendor_ramdisk_table_size, page_size) + vendor_ramdisk_table_offset = page_size * ( + num_boot_header_pages + num_boot_ramdisk_pages + num_boot_dtb_pages) + + vendor_ramdisk_table = [] + vendor_ramdisk_symlinks = [] + for idx in range(vendor_ramdisk_table_entry_num): + entry_offset = vendor_ramdisk_table_offset + ( + vendor_ramdisk_table_entry_size * idx) + boot_img.seek(entry_offset) + ramdisk_size = unpack('I', boot_img.read(4))[0] + ramdisk_offset = unpack('I', boot_img.read(4))[0] + ramdisk_type = unpack('I', boot_img.read(4))[0] + ramdisk_name = cstr(unpack( + f'{VENDOR_RAMDISK_NAME_SIZE}s', + boot_img.read(VENDOR_RAMDISK_NAME_SIZE))[0].decode()) + board_id = unpack( + f'{VENDOR_RAMDISK_TABLE_ENTRY_BOARD_ID_SIZE}I', + boot_img.read( + 4 * VENDOR_RAMDISK_TABLE_ENTRY_BOARD_ID_SIZE)) + output_ramdisk_name = f'vendor_ramdisk{idx:02}' + + image_info_list.append((ramdisk_offset_base + ramdisk_offset, + ramdisk_size, output_ramdisk_name)) + vendor_ramdisk_symlinks.append((output_ramdisk_name, ramdisk_name)) + vendor_ramdisk_table.append( + (output_ramdisk_name, ramdisk_size, ramdisk_offset, + ramdisk_type, ramdisk_name, board_id)) + + info.vendor_ramdisk_table = vendor_ramdisk_table + + bootconfig_offset = page_size * (num_boot_header_pages + + num_boot_ramdisk_pages + num_boot_dtb_pages + + num_vendor_ramdisk_table_pages) + image_info_list.append((bootconfig_offset, info.vendor_bootconfig_size, + 'bootconfig')) + else: + image_info_list.append( + (ramdisk_offset_base, info.vendor_ramdisk_size, 'vendor_ramdisk')) dtb_offset = page_size * (num_boot_header_pages + num_boot_ramdisk_pages ) # header + vendor_ramdisk - image_info_list.append((dtb_offset, dtb_size, 'dtb')) + if info.dtb_size > 0: + image_info_list.append((dtb_offset, info.dtb_size, 'dtb')) - for image_info in image_info_list: - extract_image(image_info[0], image_info[1], args.boot_img, - os.path.join(args.out, image_info[2])) + create_out_dir(output_dir) + for offset, size, name in image_info_list: + extract_image(offset, size, boot_img, os.path.join(output_dir, name)) + info.image_dir = output_dir + + if info.header_version > 3: + vendor_ramdisk_by_name_dir = os.path.join( + output_dir, 'vendor-ramdisk-by-name') + create_out_dir(vendor_ramdisk_by_name_dir) + for src, dst in vendor_ramdisk_symlinks: + src_pathname = os.path.join('..', src) + dst_pathname = os.path.join( + vendor_ramdisk_by_name_dir, f'ramdisk_{dst}') + if os.path.lexists(dst_pathname): + os.remove(dst_pathname) + os.symlink(src_pathname, dst_pathname) + + return info -def unpack_image(args): - boot_magic = unpack('8s', args.boot_img.read(8))[0].decode() - print('boot_magic: %s' % boot_magic) - if boot_magic == "ANDROID!": - unpack_bootimage(args) - elif boot_magic == "VNDRBOOT": - unpack_vendor_bootimage(args) +def unpack_bootimg(boot_img, output_dir): + """Unpacks the |boot_img| to |output_dir|, and returns the 'info' object.""" + with open(boot_img, 'rb') as image_file: + boot_magic = unpack('8s', image_file.read(8))[0].decode() + image_file.seek(0) + if boot_magic == 'ANDROID!': + info = unpack_boot_image(image_file, output_dir) + elif boot_magic == 'VNDRBOOT': + info = unpack_vendor_boot_image(image_file, output_dir) + else: + raise ValueError(f'Not an Android boot image, magic: {boot_magic}') + + return info + + +def print_bootimg_info(info, output_format, null_separator): + """Format and print boot image info.""" + if output_format == 'mkbootimg': + mkbootimg_args = info.format_mkbootimg_argument() + if null_separator: + print('\0'.join(mkbootimg_args) + '\0', end='') + else: + print(shlex.join(mkbootimg_args)) + else: + print(info.format_pretty_text()) + + +def get_unpack_usage(): + return """Output format: + + * info + + Pretty-printed info-rich text format suitable for human inspection. + + * mkbootimg + + Output shell-escaped (quoted) argument strings that can be used to + reconstruct the boot image. For example: + + $ unpack_bootimg --boot_img vendor_boot.img --out out --format=mkbootimg | + tee mkbootimg_args + $ sh -c "mkbootimg $(cat mkbootimg_args) --vendor_boot repacked.img" + + vendor_boot.img and repacked.img would be equivalent. + + If the -0 option is specified, output unescaped null-terminated argument + strings that are suitable to be parsed by a shell script (xargs -0 format): + + $ unpack_bootimg --boot_img vendor_boot.img --out out --format=mkbootimg \\ + -0 | tee mkbootimg_args + $ declare -a MKBOOTIMG_ARGS=() + $ while IFS= read -r -d '' ARG; do + MKBOOTIMG_ARGS+=("${ARG}") + done