1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316
|
#!/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.
from __future__ import print_function
from argparse import ArgumentParser, FileType, Action
from hashlib import sha1
from os import fstat
import re
from struct import pack
BOOT_IMAGE_HEADER_V3_PAGESIZE = 4096
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(args):
BOOT_IMAGE_HEADER_V3_SIZE = 1596
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 = 2108
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 > 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',
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()))
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('1024s', args.cmdline[512:].encode()))
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
else:
args.output.write(pack('Q', 0)) # Will be set to 0 for devices without a recovery dtbo
# 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.")
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)
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)))
setattr(namespace, self.dest, values)
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_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)
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)
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'))
return parser.parse_args()
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)
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:
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.
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()
|