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 <huangtao@rock-chips.com>
Change-Id: Ibf9543fd4a10547f9d956b6ffc09834389fbb20f
This commit is contained in:
Tao Huang
2023-02-15 16:30:35 +08:00
parent 5f8124c0ae
commit 8cb1b2ad50
2 changed files with 954 additions and 262 deletions
+507 -132
View File
@@ -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__':
+447 -130
View File
@@ -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 <mkbootimg_args
$ mkbootimg "${MKBOOTIMG_ARGS[@]}" --vendor_boot repacked.img
"""
def parse_cmdline():
"""parse command line arguments"""
parser = ArgumentParser(
description='Unpacks boot.img/recovery.img, extracts the kernel,'
'ramdisk, second bootloader, recovery dtbo and dtb')
parser.add_argument(
'--boot_img',
help='path to boot image',
type=FileType('rb'),
required=True)
parser.add_argument('--out', help='path to out binaries', default='out')
formatter_class=RawDescriptionHelpFormatter,
description='Unpacks boot, recovery or vendor_boot image.',
epilog=get_unpack_usage(),
)
parser.add_argument('--boot_img', required=True,
help='path to the boot, recovery or vendor_boot image')
parser.add_argument('--out', default='out',
help='output directory of the unpacked images')
parser.add_argument('--format', choices=['info', 'mkbootimg'],
default='info',
help='text output format (default: info)')
parser.add_argument('-0', '--null', action='store_true',
help='output null-terminated argument strings')
return parser.parse_args()
def main():
"""parse arguments and unpack boot image"""
args = parse_cmdline()
create_out_dir(args.out)
unpack_image(args)
info = unpack_bootimg(args.boot_img, args.out)
print_bootimg_info(info, args.format, args.null)
if __name__ == '__main__':