From 594b68feba6882992c406a91e2b32b5c5414ef5b Mon Sep 17 00:00:00 2001 From: Tao Huang Date: Wed, 3 Jun 2020 10:54:15 +0800 Subject: [PATCH] rk: scripts: update mkbootimg/repack-bootimg/unpack_bootimg AOSP 9f28439ba714 ("fix size of v3 boot header") Revert 7261bb083a97 ("Check DTB image size for boot image header version 2 and above") which failed to repack image without dtb. Change-Id: I591c0c548229e16482352c94651740de3e0e8b76 Signed-off-by: Tao Huang --- scripts/mkbootimg | 150 +++++++++++++++++++++++++++++--------- scripts/repack-bootimg | 15 +--- scripts/unpack_bootimg | 162 ++++++++++++++++++++++++++++++++--------- 3 files changed, 246 insertions(+), 81 deletions(-) diff --git a/scripts/mkbootimg b/scripts/mkbootimg index c3e09aba3499..0c6093e19416 100755 --- a/scripts/mkbootimg +++ b/scripts/mkbootimg @@ -14,13 +14,15 @@ # limitations under the License. from __future__ import print_function -from sys import argv, exit, stderr + from argparse import ArgumentParser, FileType, Action -from os import fstat -from struct import pack from hashlib import sha1 -import sys +from os import fstat import re +from struct import pack + + +BOOT_IMAGE_HEADER_V3_PAGESIZE = 4096 def filesize(f): if f is None: @@ -61,18 +63,61 @@ def get_recovery_dtbo_offset(args): return dtbo_offset +def write_header_v3(args): + BOOT_IMAGE_HEADER_V3_SIZE = 1580 + BOOT_MAGIC = 'ANDROID!'.encode() + + 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 + + args.output.write(pack('I', args.header_version)) # version of bootimage header + args.output.write(pack('1536s', args.cmdline.encode())) + 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() + + 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) + def write_header(args): BOOT_IMAGE_HEADER_V1_SIZE = 1648 BOOT_IMAGE_HEADER_V2_SIZE = 1660 BOOT_MAGIC = 'ANDROID!'.encode() - if (args.header_version > 2): + 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) 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', + 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 @@ -115,6 +160,10 @@ 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.") + 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) @@ -131,8 +180,8 @@ class ValidateStrLenAction(Action): def __call__(self, parser, namespace, values, option_string=None): if len(values) > self.maxlen: - raise ValueError('String argument too long: max {0:d}, got {1:d}'. - format(self.maxlen, len(values))) + raise ValueError( + 'String argument too long: max {0:d}, got {1:d}'.format(self.maxlen, len(values))) setattr(namespace, self.dest, values) @@ -146,6 +195,7 @@ def write_padded_file(f_out, f_in, 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: @@ -162,33 +212,40 @@ def parse_os_version(x): 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) + 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 y >= 0 and y < 128 - assert m > 0 and m <= 12 + assert 0 <= y < 128 + assert 0 < m <= 12 return (y << 4) | m return 0 + def parse_cmdline(): parser = ArgumentParser() - parser.add_argument('--kernel', help='path to the kernel', type=FileType('rb'), - required=True) + 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_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') + 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) + 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('--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) @@ -201,34 +258,59 @@ def parse_cmdline(): 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) + 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'), - required=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')) + return parser.parse_args() -def write_data(args): - write_padded_file(args.output, args.kernel, args.pagesize) - write_padded_file(args.output, args.ramdisk, args.pagesize) - write_padded_file(args.output, args.second, args.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) + + +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 > 0: - write_padded_file(args.output, args.recovery_dtbo, args.pagesize) - if args.header_version > 1: - write_padded_file(args.output, args.dtb, args.pagesize) def main(): args = parse_cmdline() - img_id = write_header(args) - write_data(args) - if args.id: - if isinstance(img_id, str): + 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: + 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') + 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. - img_id = [ord(x) for x in img_id] - print('0x' + ''.join('{:02x}'.format(c) for c in img_id)) + 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)) + if __name__ == '__main__': main() diff --git a/scripts/repack-bootimg b/scripts/repack-bootimg index a4f0298c449b..bc66d2aa07a4 100755 --- a/scripts/repack-bootimg +++ b/scripts/repack-bootimg @@ -121,19 +121,8 @@ $srctree/scripts/unpack_bootimg --boot_img $boot_img --out $out > $log cmdline=$(grep -a "^command line args: " $log | tr '\0' '\n'| sed "s/^command line args: //") extra_cmdline=$(grep -a "^additional command line args: " $log | tr '\0' '\n'| sed "s/^additional command line args: //") version=$(grep -a "^boot image header version: " $log | sed "s/^boot image header version: //") - -os_version_patch_level=$(grep -a "^os version and patch level: " $log | sed "s/^os version and patch level: //") - -v=$(($os_version_patch_level >> 11)) -a=$(($v >> 14)) -b=$((($v >> 7) & 0x7f)) -c=$(($v & 0x7f)) -os_version=$(printf '%d.%d.%d' $a $b $c) - -v=$(($os_version_patch_level & 0x7ff)) -y=$((($v >> 4) + 2000)) -m=$((($v & 15))) -os_patch_level=$(printf '%d-%02d-01' $y $m) +os_version=$(grep -a "^os version: " $log | sed "s/^os version: //") +os_patch_level=$(grep -a "^os patch level: " $log | sed "s/^os patch level: //") dtb_size=$(grep -a "^dtb size: " $log | sed "s/^dtb size: //") dtb_size=${dtb_size:-0} diff --git a/scripts/unpack_bootimg b/scripts/unpack_bootimg index 789bf5e6d906..83c2bbe33d7d 100755 --- a/scripts/unpack_bootimg +++ b/scripts/unpack_bootimg @@ -23,6 +23,8 @@ from argparse import ArgumentParser, FileType from struct import unpack import os +BOOT_IMAGE_HEADER_V3_PAGESIZE = 4096 +VENDOR_BOOT_IMAGE_HEADER_V3_SIZE = 2112 def create_out_dir(dir_path): """creates a directory 'dir_path' if it does not exist""" @@ -39,41 +41,85 @@ def extract_image(offset, size, bootimage, extracted_image_name): 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 cstr(s): + """Remove first NULL character and any character beyond.""" + return s.split('\0', 1)[0] + + +def format_os_version(os_version): + a = os_version >> 14 + b = os_version >> 7 & ((1<<7) - 1) + c = os_version & ((1<<7) - 1) + return '{}.{}.{}'.format(a, b, c) + + +def format_os_patch_level(os_patch_level): + y = os_patch_level >> 4 + y += 2000 + m = os_patch_level & ((1<<4) - 1) + return '{:04d}-{:02d}'.format(y, m) + + +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 unpack_bootimage(args): """extracts kernel, ramdisk, second bootloader and recovery dtbo""" - boot_magic = unpack('8s', args.boot_img.read(8)) - print('boot_magic: %s' % boot_magic) - kernel_ramdisk_second_info = unpack('10I', args.boot_img.read(10 * 4)) - 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('boot image header version: %s' % kernel_ramdisk_second_info[8]) - print('os version and patch level: %s' % kernel_ramdisk_second_info[9]) - - product_name = unpack('16s', args.boot_img.read(16)) - print('product name: %s' % product_name) - cmdline = unpack('512s', args.boot_img.read(512)) - print('command line args: %s' % cmdline) - - args.boot_img.read(32) # ignore SHA - - extra_cmdline = unpack('1024s', args.boot_img.read(1024)) - print('additional command line args: %s' % extra_cmdline) - - 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] + kernel_ramdisk_second_info = unpack('9I', args.boot_img.read(9 * 4)) version = kernel_ramdisk_second_info[8] - if version > 0: + 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]) + 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]) + + print('boot image header version: %s' % version) + + 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) + else: + cmdline = cstr(unpack('1536s', args.boot_img.read(1536))[0].decode()) + print('command line args: %s' % cmdline) + + 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] + 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 + + 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] @@ -82,7 +128,7 @@ def unpack_bootimage(args): print('boot header size: %s' % boot_header_size) else: recovery_dtbo_size = 0 - if version > 1: + 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] @@ -105,8 +151,8 @@ def unpack_bootimage(args): if second_size > 0: second_offset = page_size * ( - num_header_pages + num_kernel_pages + num_ramdisk_pages - ) # header + kernel + ramdisk + num_header_pages + num_kernel_pages + num_ramdisk_pages + ) # header + kernel + ramdisk image_info_list.append((second_offset, second_size, 'second')) if recovery_dtbo_size > 0: @@ -127,6 +173,54 @@ def unpack_bootimage(args): os.path.join(args.out, image_info[2])) +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]) + + cmdline = cstr(unpack('2048s', args.boot_img.read(2048))[0].decode()) + print('vendor command line args: %s' % cmdline) + + tags_load_address = unpack('I', args.boot_img.read(1 * 4))[0] + print('kernel tags load address: %#x' % tags_load_address) + + product_name = cstr(unpack('16s', args.boot_img.read(16))[0].decode()) + print('product name: %s' % 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) + + ramdisk_size = kernel_ramdisk_info[4] + page_size = kernel_ramdisk_info[1] + + # 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')] + + dtb_offset = page_size * (num_boot_header_pages + num_boot_ramdisk_pages + ) # header + vendor_ramdisk + image_info_list.append((dtb_offset, 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])) + + +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 parse_cmdline(): """parse command line arguments""" parser = ArgumentParser( @@ -145,7 +239,7 @@ def main(): """parse arguments and unpack boot image""" args = parse_cmdline() create_out_dir(args.out) - unpack_bootimage(args) + unpack_image(args) if __name__ == '__main__':