From: BigfootACA Date: 星期二, 17 May 2022 05:32:26 +0000 (+0800) Subject: add mkbootimg.py X-Git-Tag: v2.0rc2~11 X-Git-Url: https://git.renegade-project.org/?a=commitdiff_plain;h=e35db7c904046c09796c5174668349ab9481edd1;p=edk2-sdm845.git add mkbootimg.py --- diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 4105258..e051c55 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -54,7 +54,7 @@ jobs: - name: Install requires packages run: | sudo apt-get update - sudo apt-get -y install build-essential uuid-dev iasl nasm gcc-aarch64-linux-gnu mkbootimg python3-distutils python3-pil python3-git gettext + sudo apt-get -y install build-essential uuid-dev iasl nasm gcc-aarch64-linux-gnu python3-distutils python3-pil python3-git gettext - name: Build device ${{ matrix.device }} run: | diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index d69daf3..658b849 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -44,7 +44,7 @@ jobs: - name: Install requires packages run: | sudo apt-get update - sudo apt-get -y install build-essential uuid-dev iasl nasm gcc-aarch64-linux-gnu mkbootimg python3-distutils python3-pil python3-git gettext + sudo apt-get -y install build-essential uuid-dev iasl nasm gcc-aarch64-linux-gnu python3-distutils python3-pil python3-git gettext - name: Build device ${{ matrix.device }} run: | diff --git a/README.md b/README.md index 592d86c..e047db8 100644 --- a/README.md +++ b/README.md @@ -52,7 +52,7 @@ For Ubuntu 20.04: ```bash sudo apt update sudo apt upgrade -sudo apt install build-essential uuid-dev iasl git nasm gcc-aarch64-linux-gnu mkbootimg python3-distutils python3-pil python3-git gettext +sudo apt install build-essential uuid-dev iasl git nasm gcc-aarch64-linux-gnu python3-distutils python3-pil python3-git gettext ``` If you are using GCC 11+, modify `edk2/BaseTools/Source/C/Makefiles/header.makefile` diff --git a/README.zh.md b/README.zh.md index ed3ab51..3cce0fa 100644 --- a/README.zh.md +++ b/README.zh.md @@ -57,7 +57,7 @@ Ubuntu 20.04: ```bash sudo apt update sudo apt upgrade -sudo apt install build-essential uuid-dev iasl git nasm gcc-aarch64-linux-gnu mkbootimg python3-distutils python3-pil python3-git gettext +sudo apt install build-essential uuid-dev iasl git nasm gcc-aarch64-linux-gnu python3-distutils python3-pil python3-git gettext ``` 如果你的编译器为 GCC 11+, 请手动修改 `edk2/BaseTools/Source/C/Makefiles/header.makefile` diff --git a/build.sh b/build.sh index 3fc07a1..6e27077 100755 --- a/build.sh +++ b/build.sh @@ -87,7 +87,7 @@ function _build(){ "device_specific/${DEVICE}.dtb" \ > "workspace/uefi-${DEVICE}.img.gz-dtb" \ ||return "$?" - mkbootimg \ + python3 ./mkbootimg.py \ --kernel "workspace/uefi-${DEVICE}.img.gz-dtb" \ --ramdisk ramdisk \ --kernel_offset 0x00000000 \ diff --git a/mkbootimg.py b/mkbootimg.py new file mode 100755 index 0000000..e0b0839 --- /dev/null +++ b/mkbootimg.py @@ -0,0 +1,674 @@ +#!/usr/bin/env python3 +# +# Copyright 2015, The Android Open Source Project +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Creates the boot image.""" + +from argparse import (ArgumentParser, ArgumentTypeError, + FileType, RawDescriptionHelpFormatter) +from hashlib import sha1 +from os import fstat +from struct import pack + +import array +import collections +import os +import re +import subprocess +import tempfile + +# 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: + return 0 + try: + return fstat(f.fileno()).st_size + except OSError: + return 0 + + +def update_sha(sha, f): + if f: + sha.update(f.read()) + f.seek(0) + sha.update(pack('I', filesize(f))) + else: + sha.update(pack('I', 0)) + + +def pad_file(f, padding): + pad = (padding - (f.tell() & (padding - 1))) & (padding - 1) + f.write(pack(str(pad) + 'x')) + + +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 + + +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_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_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(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. + args.output.write(pack('I', BOOT_IMAGE_V4_SIGNATURE_SIZE)) + pad_file(args.output, BOOT_IMAGE_HEADER_V3_PAGESIZE) + + +def write_vendor_boot_header(args): + if filesize(args.dtb) == 0: + raise ValueError('DTB image must not be empty.') + + 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(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)) + + # 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: + 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) + + +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) + update_sha(sha, args.ramdisk) + update_sha(sha, args.second) + + if args.header_version > 0: + update_sha(sha, args.recovery_dtbo) + if args.header_version > 1: + update_sha(sha, args.dtb) + + img_id = pack('32s', sha.digest()) + + args.output.write(img_id) + args.output.write(pack(f'{BOOT_EXTRA_ARGS_SIZE}s', args.extra_cmdline)) + + if args.header_version > 0: + if args.recovery_dtbo: + # 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: + # 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: + args.output.write(pack('I', BOOT_IMAGE_HEADER_V1_SIZE)) + elif args.header_version == 2: + 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.') + + # 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)) + + pad_file(args.output, args.pagesize) + return img_id + + +class AsciizBytes: + """Parses a string and encodes it as an asciiz bytes object. + + >>> 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( + 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): + if f_in is None: + return + f_out.write(f_in.read()) + pad_file(f_out, padding) + + +def parse_int(x): + return int(x, 0) + + +def parse_os_version(x): + match = re.search(r'^(\d{1,3})(?:\.(\d{1,3})(?:\.(\d{1,3}))?)?', x) + if match: + a = int(match.group(1)) + b = c = 0 + if match.lastindex >= 2: + b = int(match.group(2)) + if match.lastindex == 3: + c = int(match.group(3)) + # 7 bits allocated for each field + assert a < 128 + assert b < 128 + assert c < 128 + return (a << 14) | (b << 7) | c + return 0 + + +def parse_os_patch_level(x): + match = re.search(r'^(\d{4})-(\d{2})(?:-(\d{2}))?', x) + if match: + y = int(match.group(1)) - 2000 + m = int(match.group(2)) + # 7 bits allocated for the year, 4 bits for the month + assert 0 <= y < 128 + assert 0 < m <= 12 + return (y << 4) | m + 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(): + 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', + 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', 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('--gki_signing_algorithm', + help='GKI signing algorithm to use') + parser.add_argument('--gki_signing_key', + help='path to RSA private key file') + parser.add_argument('--gki_signing_signature_args', + help='other hash arguments passed to avbtool') + parser.add_argument('--gki_signing_avbtool_path', + help='path to avbtool for boot signature generation') + 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') + + 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. + """ + args.output.flush() # Flush the buffer for signature calculation. + + # Appends zeros if the signing key is not specified. + if not args.gki_signing_key or not args.gki_signing_algorithm: + zeros = b'\x00' * BOOT_IMAGE_V4_SIGNATURE_SIZE + args.output.write(zeros) + pad_file(args.output, pagesize) + return + + avbtool = 'avbtool' # Used from otatools.zip or Android build env. + + # We need to specify the path of avbtool in build/core/Makefile. + # Because avbtool is not guaranteed to be in $PATH there. + if args.gki_signing_avbtool_path: + avbtool = args.gki_signing_avbtool_path + + # 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', 'boot', + '--partition_size', str(64 * 1024 * 1024), + '--image', args.output.name, + '--algorithm', args.gki_signing_algorithm, + '--key', args.gki_signing_key, + '--salt', 'd00df00d'] # TODO: use a hash of kernel/ramdisk as the salt. + + # Additional arguments passed to avbtool. + if args.gki_signing_signature_args: + avbtool_cmd += args.gki_signing_signature_args.split() + + # 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') + avbtool_cmd += ['--do_not_append_vbmeta_image', + '--output_vbmeta_image', boot_signature_output] + subprocess.check_call(avbtool_cmd) + with open(boot_signature_output, 'rb') as boot_signature: + if filesize(boot_signature) > BOOT_IMAGE_V4_SIGNATURE_SIZE: + raise ValueError( + f'boot sigature size is > {BOOT_IMAGE_V4_SIGNATURE_SIZE}') + write_padded_file(args.output, boot_signature, pagesize) + + +def write_data(args, pagesize): + write_padded_file(args.output, args.kernel, pagesize) + write_padded_file(args.output, args.ramdisk, pagesize) + write_padded_file(args.output, args.second, pagesize) + + if args.header_version > 0 and args.header_version < 3: + 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: + add_boot_image_signature(args, pagesize) + + +def write_vendor_boot_data(args): + 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 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.second is not None and args.header_version > 2: + 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: + print('0x' + ''.join(f'{octet:02x}' for octet in img_id)) + + +if __name__ == '__main__': + main()