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 <huangtao@rock-chips.com>
This commit is contained in:
Tao Huang 2020-06-03 10:54:15 +08:00
parent 76ada3f00a
commit 594b68feba
3 changed files with 246 additions and 81 deletions

View File

@ -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()

View File

@ -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}

View File

@ -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__':