xref: /rk3399_rockchip-uboot/scripts/unpack_bootimg (revision 4e0fa9f6a6afd1c4db979a3d2e9f1e67d6e1f06f)
1*4e0fa9f6SJoseph Chen#!/usr/bin/env python3
2*4e0fa9f6SJoseph Chen#
38a334094SJoseph Chen# Copyright 2018, The Android Open Source Project
48a334094SJoseph Chen#
58a334094SJoseph Chen# Licensed under the Apache License, Version 2.0 (the "License");
68a334094SJoseph Chen# you may not use this file except in compliance with the License.
78a334094SJoseph Chen# You may obtain a copy of the License at
88a334094SJoseph Chen#
98a334094SJoseph Chen#     http://www.apache.org/licenses/LICENSE-2.0
108a334094SJoseph Chen#
118a334094SJoseph Chen# Unless required by applicable law or agreed to in writing, software
128a334094SJoseph Chen# distributed under the License is distributed on an "AS IS" BASIS,
138a334094SJoseph Chen# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
148a334094SJoseph Chen# See the License for the specific language governing permissions and
158a334094SJoseph Chen# limitations under the License.
168a334094SJoseph Chen
17*4e0fa9f6SJoseph Chen"""Unpacks the boot image.
188a334094SJoseph Chen
198a334094SJoseph ChenExtracts the kernel, ramdisk, second bootloader, dtb and recovery dtbo images.
208a334094SJoseph Chen"""
218a334094SJoseph Chen
22*4e0fa9f6SJoseph Chenfrom argparse import ArgumentParser, FileType, RawDescriptionHelpFormatter
238a334094SJoseph Chenfrom struct import unpack
248a334094SJoseph Chenimport os
25*4e0fa9f6SJoseph Chenimport shlex
26*4e0fa9f6SJoseph Chen
27*4e0fa9f6SJoseph ChenBOOT_IMAGE_HEADER_V3_PAGESIZE = 4096
28*4e0fa9f6SJoseph ChenVENDOR_RAMDISK_NAME_SIZE = 32
29*4e0fa9f6SJoseph ChenVENDOR_RAMDISK_TABLE_ENTRY_BOARD_ID_SIZE = 16
308a334094SJoseph Chen
318a334094SJoseph Chen
328a334094SJoseph Chendef create_out_dir(dir_path):
338a334094SJoseph Chen    """creates a directory 'dir_path' if it does not exist"""
348a334094SJoseph Chen    if not os.path.exists(dir_path):
358a334094SJoseph Chen        os.makedirs(dir_path)
368a334094SJoseph Chen
378a334094SJoseph Chen
388a334094SJoseph Chendef extract_image(offset, size, bootimage, extracted_image_name):
398a334094SJoseph Chen    """extracts an image from the bootimage"""
408a334094SJoseph Chen    bootimage.seek(offset)
418a334094SJoseph Chen    with open(extracted_image_name, 'wb') as file_out:
428a334094SJoseph Chen        file_out.write(bootimage.read(size))
438a334094SJoseph Chen
448a334094SJoseph Chen
458a334094SJoseph Chendef get_number_of_pages(image_size, page_size):
468a334094SJoseph Chen    """calculates the number of pages required for the image"""
47*4e0fa9f6SJoseph Chen    return (image_size + page_size - 1) // page_size
488a334094SJoseph Chen
498a334094SJoseph Chen
50*4e0fa9f6SJoseph Chendef cstr(s):
51*4e0fa9f6SJoseph Chen    """Remove first NULL character and any character beyond."""
52*4e0fa9f6SJoseph Chen    return s.split('\0', 1)[0]
53*4e0fa9f6SJoseph Chen
54*4e0fa9f6SJoseph Chen
55*4e0fa9f6SJoseph Chendef format_os_version(os_version):
56*4e0fa9f6SJoseph Chen    a = os_version >> 14
57*4e0fa9f6SJoseph Chen    b = os_version >> 7 & ((1<<7) - 1)
58*4e0fa9f6SJoseph Chen    c = os_version & ((1<<7) - 1)
59*4e0fa9f6SJoseph Chen    return '{}.{}.{}'.format(a, b, c)
60*4e0fa9f6SJoseph Chen
61*4e0fa9f6SJoseph Chen
62*4e0fa9f6SJoseph Chendef format_os_patch_level(os_patch_level):
63*4e0fa9f6SJoseph Chen    y = os_patch_level >> 4
64*4e0fa9f6SJoseph Chen    y += 2000
65*4e0fa9f6SJoseph Chen    m = os_patch_level & ((1<<4) - 1)
66*4e0fa9f6SJoseph Chen    return '{:04d}-{:02d}'.format(y, m)
67*4e0fa9f6SJoseph Chen
68*4e0fa9f6SJoseph Chen
69*4e0fa9f6SJoseph Chendef decode_os_version_patch_level(os_version_patch_level):
70*4e0fa9f6SJoseph Chen    """Returns a tuple of (os_version, os_patch_level)."""
71*4e0fa9f6SJoseph Chen    os_version = os_version_patch_level >> 11
72*4e0fa9f6SJoseph Chen    os_patch_level = os_version_patch_level & ((1<<11) - 1)
73*4e0fa9f6SJoseph Chen    return (format_os_version(os_version),
74*4e0fa9f6SJoseph Chen            format_os_patch_level(os_patch_level))
75*4e0fa9f6SJoseph Chen
76*4e0fa9f6SJoseph Chen
77*4e0fa9f6SJoseph Chenclass BootImageInfoFormatter:
78*4e0fa9f6SJoseph Chen    """Formats the boot image info."""
79*4e0fa9f6SJoseph Chen
80*4e0fa9f6SJoseph Chen    def format_pretty_text(self):
81*4e0fa9f6SJoseph Chen        lines = []
82*4e0fa9f6SJoseph Chen        lines.append(f'boot magic: {self.boot_magic}')
83*4e0fa9f6SJoseph Chen
84*4e0fa9f6SJoseph Chen        if self.header_version < 3:
85*4e0fa9f6SJoseph Chen            lines.append(f'kernel_size: {self.kernel_size}')
86*4e0fa9f6SJoseph Chen            lines.append(
87*4e0fa9f6SJoseph Chen                f'kernel load address: {self.kernel_load_address:#010x}')
88*4e0fa9f6SJoseph Chen            lines.append(f'ramdisk size: {self.ramdisk_size}')
89*4e0fa9f6SJoseph Chen            lines.append(
90*4e0fa9f6SJoseph Chen                f'ramdisk load address: {self.ramdisk_load_address:#010x}')
91*4e0fa9f6SJoseph Chen            lines.append(f'second bootloader size: {self.second_size}')
92*4e0fa9f6SJoseph Chen            lines.append(
93*4e0fa9f6SJoseph Chen                f'second bootloader load address: '
94*4e0fa9f6SJoseph Chen                f'{self.second_load_address:#010x}')
95*4e0fa9f6SJoseph Chen            lines.append(
96*4e0fa9f6SJoseph Chen                f'kernel tags load address: {self.tags_load_address:#010x}')
97*4e0fa9f6SJoseph Chen            lines.append(f'page size: {self.page_size}')
98*4e0fa9f6SJoseph Chen        else:
99*4e0fa9f6SJoseph Chen            lines.append(f'kernel_size: {self.kernel_size}')
100*4e0fa9f6SJoseph Chen            lines.append(f'ramdisk size: {self.ramdisk_size}')
101*4e0fa9f6SJoseph Chen
102*4e0fa9f6SJoseph Chen        lines.append(f'os version: {self.os_version}')
103*4e0fa9f6SJoseph Chen        lines.append(f'os patch level: {self.os_patch_level}')
104*4e0fa9f6SJoseph Chen        lines.append(f'boot image header version: {self.header_version}')
105*4e0fa9f6SJoseph Chen
106*4e0fa9f6SJoseph Chen        if self.header_version < 3:
107*4e0fa9f6SJoseph Chen            lines.append(f'product name: {self.product_name}')
108*4e0fa9f6SJoseph Chen
109*4e0fa9f6SJoseph Chen        lines.append(f'command line args: {self.cmdline}')
110*4e0fa9f6SJoseph Chen
111*4e0fa9f6SJoseph Chen        if self.header_version < 3:
112*4e0fa9f6SJoseph Chen            lines.append(f'additional command line args: {self.extra_cmdline}')
113*4e0fa9f6SJoseph Chen
114*4e0fa9f6SJoseph Chen        if self.header_version in {1, 2}:
115*4e0fa9f6SJoseph Chen            lines.append(f'recovery dtbo size: {self.recovery_dtbo_size}')
116*4e0fa9f6SJoseph Chen            lines.append(
117*4e0fa9f6SJoseph Chen                f'recovery dtbo offset: {self.recovery_dtbo_offset:#018x}')
118*4e0fa9f6SJoseph Chen            lines.append(f'boot header size: {self.boot_header_size}')
119*4e0fa9f6SJoseph Chen
120*4e0fa9f6SJoseph Chen        if self.header_version == 2:
121*4e0fa9f6SJoseph Chen            lines.append(f'dtb size: {self.dtb_size}')
122*4e0fa9f6SJoseph Chen            lines.append(f'dtb address: {self.dtb_load_address:#018x}')
123*4e0fa9f6SJoseph Chen
124*4e0fa9f6SJoseph Chen        if self.header_version >= 4:
125*4e0fa9f6SJoseph Chen            lines.append(
126*4e0fa9f6SJoseph Chen                f'boot.img signature size: {self.boot_signature_size}')
127*4e0fa9f6SJoseph Chen
128*4e0fa9f6SJoseph Chen        return '\n'.join(lines)
129*4e0fa9f6SJoseph Chen
130*4e0fa9f6SJoseph Chen    def format_mkbootimg_argument(self):
131*4e0fa9f6SJoseph Chen        args = []
132*4e0fa9f6SJoseph Chen        args.extend(['--header_version', str(self.header_version)])
133*4e0fa9f6SJoseph Chen        args.extend(['--os_version', self.os_version])
134*4e0fa9f6SJoseph Chen        args.extend(['--os_patch_level', self.os_patch_level])
135*4e0fa9f6SJoseph Chen
136*4e0fa9f6SJoseph Chen        args.extend(['--kernel', os.path.join(self.image_dir, 'kernel')])
137*4e0fa9f6SJoseph Chen        args.extend(['--ramdisk', os.path.join(self.image_dir, 'ramdisk')])
138*4e0fa9f6SJoseph Chen
139*4e0fa9f6SJoseph Chen        if self.header_version <= 2:
140*4e0fa9f6SJoseph Chen            if self.second_size > 0:
141*4e0fa9f6SJoseph Chen                args.extend(['--second',
142*4e0fa9f6SJoseph Chen                             os.path.join(self.image_dir, 'second')])
143*4e0fa9f6SJoseph Chen            if self.recovery_dtbo_size > 0:
144*4e0fa9f6SJoseph Chen                args.extend(['--recovery_dtbo',
145*4e0fa9f6SJoseph Chen                             os.path.join(self.image_dir, 'recovery_dtbo')])
146*4e0fa9f6SJoseph Chen            if self.dtb_size > 0:
147*4e0fa9f6SJoseph Chen                args.extend(['--dtb', os.path.join(self.image_dir, 'dtb')])
148*4e0fa9f6SJoseph Chen
149*4e0fa9f6SJoseph Chen            args.extend(['--pagesize', f'{self.page_size:#010x}'])
150*4e0fa9f6SJoseph Chen
151*4e0fa9f6SJoseph Chen            # Kernel load address is base + kernel_offset in mkbootimg.py.
152*4e0fa9f6SJoseph Chen            # However we don't know the value of 'base' when unpacking a boot
153*4e0fa9f6SJoseph Chen            # image in this script, so we set 'base' to zero and 'kernel_offset'
154*4e0fa9f6SJoseph Chen            # to the kernel load address, 'ramdisk_offset' to the ramdisk load
155*4e0fa9f6SJoseph Chen            # address, ... etc.
156*4e0fa9f6SJoseph Chen            args.extend(['--base', f'{0:#010x}'])
157*4e0fa9f6SJoseph Chen            args.extend(['--kernel_offset',
158*4e0fa9f6SJoseph Chen                         f'{self.kernel_load_address:#010x}'])
159*4e0fa9f6SJoseph Chen            args.extend(['--ramdisk_offset',
160*4e0fa9f6SJoseph Chen                         f'{self.ramdisk_load_address:#010x}'])
161*4e0fa9f6SJoseph Chen            args.extend(['--second_offset',
162*4e0fa9f6SJoseph Chen                         f'{self.second_load_address:#010x}'])
163*4e0fa9f6SJoseph Chen            args.extend(['--tags_offset', f'{self.tags_load_address:#010x}'])
164*4e0fa9f6SJoseph Chen
165*4e0fa9f6SJoseph Chen            # dtb is added in boot image v2, and is absent in v1 or v0.
166*4e0fa9f6SJoseph Chen            if self.header_version == 2:
167*4e0fa9f6SJoseph Chen                # dtb_offset is uint64_t.
168*4e0fa9f6SJoseph Chen                args.extend(['--dtb_offset', f'{self.dtb_load_address:#018x}'])
169*4e0fa9f6SJoseph Chen
170*4e0fa9f6SJoseph Chen            args.extend(['--board', self.product_name])
171*4e0fa9f6SJoseph Chen            args.extend(['--cmdline', self.cmdline + self.extra_cmdline])
172*4e0fa9f6SJoseph Chen        else:
173*4e0fa9f6SJoseph Chen            args.extend(['--cmdline', self.cmdline])
174*4e0fa9f6SJoseph Chen
175*4e0fa9f6SJoseph Chen        return args
176*4e0fa9f6SJoseph Chen
177*4e0fa9f6SJoseph Chen
178*4e0fa9f6SJoseph Chendef unpack_boot_image(args):
1798a334094SJoseph Chen    """extracts kernel, ramdisk, second bootloader and recovery dtbo"""
180*4e0fa9f6SJoseph Chen    info = BootImageInfoFormatter()
181*4e0fa9f6SJoseph Chen    info.boot_magic = unpack('8s', args.boot_img.read(8))[0].decode()
1828a334094SJoseph Chen
183*4e0fa9f6SJoseph Chen    kernel_ramdisk_second_info = unpack('9I', args.boot_img.read(9 * 4))
184*4e0fa9f6SJoseph Chen    # header_version is always at [8] regardless of the value of header_version.
185*4e0fa9f6SJoseph Chen    info.header_version = kernel_ramdisk_second_info[8]
1868a334094SJoseph Chen
187*4e0fa9f6SJoseph Chen    if info.header_version < 3:
188*4e0fa9f6SJoseph Chen        info.kernel_size = kernel_ramdisk_second_info[0]
189*4e0fa9f6SJoseph Chen        info.kernel_load_address = kernel_ramdisk_second_info[1]
190*4e0fa9f6SJoseph Chen        info.ramdisk_size = kernel_ramdisk_second_info[2]
191*4e0fa9f6SJoseph Chen        info.ramdisk_load_address = kernel_ramdisk_second_info[3]
192*4e0fa9f6SJoseph Chen        info.second_size = kernel_ramdisk_second_info[4]
193*4e0fa9f6SJoseph Chen        info.second_load_address = kernel_ramdisk_second_info[5]
194*4e0fa9f6SJoseph Chen        info.tags_load_address = kernel_ramdisk_second_info[6]
195*4e0fa9f6SJoseph Chen        info.page_size = kernel_ramdisk_second_info[7]
196*4e0fa9f6SJoseph Chen        os_version_patch_level = unpack('I', args.boot_img.read(1 * 4))[0]
197*4e0fa9f6SJoseph Chen    else:
198*4e0fa9f6SJoseph Chen        info.kernel_size = kernel_ramdisk_second_info[0]
199*4e0fa9f6SJoseph Chen        info.ramdisk_size = kernel_ramdisk_second_info[1]
200*4e0fa9f6SJoseph Chen        os_version_patch_level = kernel_ramdisk_second_info[2]
201*4e0fa9f6SJoseph Chen        info.second_size = 0
202*4e0fa9f6SJoseph Chen        info.page_size = BOOT_IMAGE_HEADER_V3_PAGESIZE
203*4e0fa9f6SJoseph Chen
204*4e0fa9f6SJoseph Chen    info.os_version, info.os_patch_level = decode_os_version_patch_level(
205*4e0fa9f6SJoseph Chen        os_version_patch_level)
206*4e0fa9f6SJoseph Chen
207*4e0fa9f6SJoseph Chen    if info.header_version < 3:
208*4e0fa9f6SJoseph Chen        info.product_name = cstr(unpack('16s',
209*4e0fa9f6SJoseph Chen                                        args.boot_img.read(16))[0].decode())
210*4e0fa9f6SJoseph Chen        info.cmdline = cstr(unpack('512s', args.boot_img.read(512))[0].decode())
2118a334094SJoseph Chen        args.boot_img.read(32)  # ignore SHA
212*4e0fa9f6SJoseph Chen        info.extra_cmdline = cstr(unpack('1024s',
213*4e0fa9f6SJoseph Chen                                         args.boot_img.read(1024))[0].decode())
2148a334094SJoseph Chen    else:
215*4e0fa9f6SJoseph Chen        info.cmdline = cstr(unpack('1536s',
216*4e0fa9f6SJoseph Chen                                   args.boot_img.read(1536))[0].decode())
2178a334094SJoseph Chen
218*4e0fa9f6SJoseph Chen    if info.header_version in {1, 2}:
219*4e0fa9f6SJoseph Chen        info.recovery_dtbo_size = unpack('I', args.boot_img.read(1 * 4))[0]
220*4e0fa9f6SJoseph Chen        info.recovery_dtbo_offset = unpack('Q', args.boot_img.read(8))[0]
221*4e0fa9f6SJoseph Chen        info.boot_header_size = unpack('I', args.boot_img.read(4))[0]
222*4e0fa9f6SJoseph Chen    else:
223*4e0fa9f6SJoseph Chen        info.recovery_dtbo_size = 0
224*4e0fa9f6SJoseph Chen
225*4e0fa9f6SJoseph Chen    if info.header_version == 2:
226*4e0fa9f6SJoseph Chen        info.dtb_size = unpack('I', args.boot_img.read(4))[0]
227*4e0fa9f6SJoseph Chen        info.dtb_load_address = unpack('Q', args.boot_img.read(8))[0]
228*4e0fa9f6SJoseph Chen    else:
229*4e0fa9f6SJoseph Chen        info.dtb_size = 0
230*4e0fa9f6SJoseph Chen        info.dtb_load_address = 0
231*4e0fa9f6SJoseph Chen
232*4e0fa9f6SJoseph Chen    if info.header_version >= 4:
233*4e0fa9f6SJoseph Chen        info.boot_signature_size = unpack('I', args.boot_img.read(4))[0]
234*4e0fa9f6SJoseph Chen    else:
235*4e0fa9f6SJoseph Chen        info.boot_signature_size = 0
2368a334094SJoseph Chen
2378a334094SJoseph Chen    # The first page contains the boot header
2388a334094SJoseph Chen    num_header_pages = 1
2398a334094SJoseph Chen
240*4e0fa9f6SJoseph Chen    # Convenient shorthand.
241*4e0fa9f6SJoseph Chen    page_size = info.page_size
2428a334094SJoseph Chen
243*4e0fa9f6SJoseph Chen    num_kernel_pages = get_number_of_pages(info.kernel_size, page_size)
244*4e0fa9f6SJoseph Chen    kernel_offset = page_size * num_header_pages  # header occupies a page
245*4e0fa9f6SJoseph Chen    image_info_list = [(kernel_offset, info.kernel_size, 'kernel')]
246*4e0fa9f6SJoseph Chen
247*4e0fa9f6SJoseph Chen    num_ramdisk_pages = get_number_of_pages(info.ramdisk_size, page_size)
2488a334094SJoseph Chen    ramdisk_offset = page_size * (num_header_pages + num_kernel_pages
2498a334094SJoseph Chen                                 ) # header + kernel
250*4e0fa9f6SJoseph Chen    image_info_list.append((ramdisk_offset, info.ramdisk_size, 'ramdisk'))
2518a334094SJoseph Chen
252*4e0fa9f6SJoseph Chen    if info.second_size > 0:
2538a334094SJoseph Chen        second_offset = page_size * (
2548a334094SJoseph Chen            num_header_pages + num_kernel_pages + num_ramdisk_pages
2558a334094SJoseph Chen            )  # header + kernel + ramdisk
256*4e0fa9f6SJoseph Chen        image_info_list.append((second_offset, info.second_size, 'second'))
2578a334094SJoseph Chen
258*4e0fa9f6SJoseph Chen    if info.recovery_dtbo_size > 0:
259*4e0fa9f6SJoseph Chen        image_info_list.append((info.recovery_dtbo_offset,
260*4e0fa9f6SJoseph Chen                                info.recovery_dtbo_size,
2618a334094SJoseph Chen                                'recovery_dtbo'))
262*4e0fa9f6SJoseph Chen    if info.dtb_size > 0:
263*4e0fa9f6SJoseph Chen        num_second_pages = get_number_of_pages(info.second_size, page_size)
264*4e0fa9f6SJoseph Chen        num_recovery_dtbo_pages = get_number_of_pages(
265*4e0fa9f6SJoseph Chen            info.recovery_dtbo_size, page_size)
2668a334094SJoseph Chen        dtb_offset = page_size * (
267*4e0fa9f6SJoseph Chen            num_header_pages + num_kernel_pages + num_ramdisk_pages +
268*4e0fa9f6SJoseph Chen            num_second_pages + num_recovery_dtbo_pages)
2698a334094SJoseph Chen
270*4e0fa9f6SJoseph Chen        image_info_list.append((dtb_offset, info.dtb_size, 'dtb'))
2718a334094SJoseph Chen
272*4e0fa9f6SJoseph Chen    if info.boot_signature_size > 0:
273*4e0fa9f6SJoseph Chen        # boot signature only exists in boot.img version >= v4.
274*4e0fa9f6SJoseph Chen        # There are only kernel and ramdisk pages before the signature.
275*4e0fa9f6SJoseph Chen        boot_signature_offset = page_size * (
276*4e0fa9f6SJoseph Chen            num_header_pages + num_kernel_pages + num_ramdisk_pages)
277*4e0fa9f6SJoseph Chen
278*4e0fa9f6SJoseph Chen        image_info_list.append((boot_signature_offset, info.boot_signature_size,
279*4e0fa9f6SJoseph Chen                                'boot_signature'))
280*4e0fa9f6SJoseph Chen
281*4e0fa9f6SJoseph Chen    create_out_dir(args.out)
282*4e0fa9f6SJoseph Chen    for offset, size, name in image_info_list:
283*4e0fa9f6SJoseph Chen        extract_image(offset, size, args.boot_img, os.path.join(args.out, name))
284*4e0fa9f6SJoseph Chen    info.image_dir = args.out
285*4e0fa9f6SJoseph Chen
286*4e0fa9f6SJoseph Chen    return info
287*4e0fa9f6SJoseph Chen
288*4e0fa9f6SJoseph Chen
289*4e0fa9f6SJoseph Chenclass VendorBootImageInfoFormatter:
290*4e0fa9f6SJoseph Chen    """Formats the vendor_boot image info."""
291*4e0fa9f6SJoseph Chen
292*4e0fa9f6SJoseph Chen    def format_pretty_text(self):
293*4e0fa9f6SJoseph Chen        lines = []
294*4e0fa9f6SJoseph Chen        lines.append(f'boot magic: {self.boot_magic}')
295*4e0fa9f6SJoseph Chen        lines.append(f'vendor boot image header version: {self.header_version}')
296*4e0fa9f6SJoseph Chen        lines.append(f'page size: {self.page_size:#010x}')
297*4e0fa9f6SJoseph Chen        lines.append(f'kernel load address: {self.kernel_load_address:#010x}')
298*4e0fa9f6SJoseph Chen        lines.append(f'ramdisk load address: {self.ramdisk_load_address:#010x}')
299*4e0fa9f6SJoseph Chen        if self.header_version > 3:
300*4e0fa9f6SJoseph Chen            lines.append(
301*4e0fa9f6SJoseph Chen                f'vendor ramdisk total size: {self.vendor_ramdisk_size}')
302*4e0fa9f6SJoseph Chen        else:
303*4e0fa9f6SJoseph Chen            lines.append(f'vendor ramdisk size: {self.vendor_ramdisk_size}')
304*4e0fa9f6SJoseph Chen        lines.append(f'vendor command line args: {self.cmdline}')
305*4e0fa9f6SJoseph Chen        lines.append(
306*4e0fa9f6SJoseph Chen            f'kernel tags load address: {self.tags_load_address:#010x}')
307*4e0fa9f6SJoseph Chen        lines.append(f'product name: {self.product_name}')
308*4e0fa9f6SJoseph Chen        lines.append(f'vendor boot image header size: {self.header_size}')
309*4e0fa9f6SJoseph Chen        lines.append(f'dtb size: {self.dtb_size}')
310*4e0fa9f6SJoseph Chen        lines.append(f'dtb address: {self.dtb_load_address:#018x}')
311*4e0fa9f6SJoseph Chen        if self.header_version > 3:
312*4e0fa9f6SJoseph Chen            lines.append(
313*4e0fa9f6SJoseph Chen                f'vendor ramdisk table size: {self.vendor_ramdisk_table_size}')
314*4e0fa9f6SJoseph Chen            lines.append('vendor ramdisk table: [')
315*4e0fa9f6SJoseph Chen            indent = lambda level: ' ' * 4 * level
316*4e0fa9f6SJoseph Chen            for entry in self.vendor_ramdisk_table:
317*4e0fa9f6SJoseph Chen                (output_ramdisk_name, ramdisk_size, ramdisk_offset,
318*4e0fa9f6SJoseph Chen                 ramdisk_type, ramdisk_name, board_id) = entry
319*4e0fa9f6SJoseph Chen                lines.append(indent(1) + f'{output_ramdisk_name}: ''{')
320*4e0fa9f6SJoseph Chen                lines.append(indent(2) + f'size: {ramdisk_size}')
321*4e0fa9f6SJoseph Chen                lines.append(indent(2) + f'offset: {ramdisk_offset}')
322*4e0fa9f6SJoseph Chen                lines.append(indent(2) + f'type: {ramdisk_type:#x}')
323*4e0fa9f6SJoseph Chen                lines.append(indent(2) + f'name: {ramdisk_name}')
324*4e0fa9f6SJoseph Chen                lines.append(indent(2) + 'board_id: [')
325*4e0fa9f6SJoseph Chen                stride = 4
326*4e0fa9f6SJoseph Chen                for row_idx in range(0, len(board_id), stride):
327*4e0fa9f6SJoseph Chen                    row = board_id[row_idx:row_idx + stride]
328*4e0fa9f6SJoseph Chen                    lines.append(
329*4e0fa9f6SJoseph Chen                        indent(3) + ' '.join(f'{e:#010x},' for e in row))
330*4e0fa9f6SJoseph Chen                lines.append(indent(2) + ']')
331*4e0fa9f6SJoseph Chen                lines.append(indent(1) + '}')
332*4e0fa9f6SJoseph Chen            lines.append(']')
333*4e0fa9f6SJoseph Chen            lines.append(
334*4e0fa9f6SJoseph Chen                f'vendor bootconfig size: {self.vendor_bootconfig_size}')
335*4e0fa9f6SJoseph Chen
336*4e0fa9f6SJoseph Chen        return '\n'.join(lines)
337*4e0fa9f6SJoseph Chen
338*4e0fa9f6SJoseph Chen    def format_mkbootimg_argument(self):
339*4e0fa9f6SJoseph Chen        args = []
340*4e0fa9f6SJoseph Chen        args.extend(['--header_version', str(self.header_version)])
341*4e0fa9f6SJoseph Chen        args.extend(['--pagesize', f'{self.page_size:#010x}'])
342*4e0fa9f6SJoseph Chen        args.extend(['--base', f'{0:#010x}'])
343*4e0fa9f6SJoseph Chen        args.extend(['--kernel_offset', f'{self.kernel_load_address:#010x}'])
344*4e0fa9f6SJoseph Chen        args.extend(['--ramdisk_offset', f'{self.ramdisk_load_address:#010x}'])
345*4e0fa9f6SJoseph Chen        args.extend(['--tags_offset', f'{self.tags_load_address:#010x}'])
346*4e0fa9f6SJoseph Chen        args.extend(['--dtb_offset', f'{self.dtb_load_address:#018x}'])
347*4e0fa9f6SJoseph Chen        args.extend(['--vendor_cmdline', self.cmdline])
348*4e0fa9f6SJoseph Chen        args.extend(['--board', self.product_name])
349*4e0fa9f6SJoseph Chen
350*4e0fa9f6SJoseph Chen        args.extend(['--dtb', os.path.join(self.image_dir, 'dtb')])
351*4e0fa9f6SJoseph Chen
352*4e0fa9f6SJoseph Chen        if self.header_version > 3:
353*4e0fa9f6SJoseph Chen            args.extend(['--vendor_bootconfig',
354*4e0fa9f6SJoseph Chen                         os.path.join(self.image_dir, 'bootconfig')])
355*4e0fa9f6SJoseph Chen
356*4e0fa9f6SJoseph Chen            for entry in self.vendor_ramdisk_table:
357*4e0fa9f6SJoseph Chen                (output_ramdisk_name, _, _, ramdisk_type,
358*4e0fa9f6SJoseph Chen                 ramdisk_name, board_id) = entry
359*4e0fa9f6SJoseph Chen                args.extend(['--ramdisk_type', str(ramdisk_type)])
360*4e0fa9f6SJoseph Chen                args.extend(['--ramdisk_name', ramdisk_name])
361*4e0fa9f6SJoseph Chen                for idx, e in enumerate(board_id):
362*4e0fa9f6SJoseph Chen                    if e:
363*4e0fa9f6SJoseph Chen                        args.extend([f'--board_id{idx}', f'{e:#010x}'])
364*4e0fa9f6SJoseph Chen                vendor_ramdisk_path = os.path.join(
365*4e0fa9f6SJoseph Chen                    self.image_dir, output_ramdisk_name)
366*4e0fa9f6SJoseph Chen                args.extend(['--vendor_ramdisk_fragment', vendor_ramdisk_path])
367*4e0fa9f6SJoseph Chen        else:
368*4e0fa9f6SJoseph Chen            args.extend(['--vendor_ramdisk',
369*4e0fa9f6SJoseph Chen                         os.path.join(self.image_dir, 'vendor_ramdisk')])
370*4e0fa9f6SJoseph Chen
371*4e0fa9f6SJoseph Chen        return args
372*4e0fa9f6SJoseph Chen
373*4e0fa9f6SJoseph Chen
374*4e0fa9f6SJoseph Chendef unpack_vendor_boot_image(args):
375*4e0fa9f6SJoseph Chen    info = VendorBootImageInfoFormatter()
376*4e0fa9f6SJoseph Chen    info.boot_magic = unpack('8s', args.boot_img.read(8))[0].decode()
377*4e0fa9f6SJoseph Chen    info.header_version = unpack('I', args.boot_img.read(4))[0]
378*4e0fa9f6SJoseph Chen    info.page_size = unpack('I', args.boot_img.read(4))[0]
379*4e0fa9f6SJoseph Chen    info.kernel_load_address = unpack('I', args.boot_img.read(4))[0]
380*4e0fa9f6SJoseph Chen    info.ramdisk_load_address = unpack('I', args.boot_img.read(4))[0]
381*4e0fa9f6SJoseph Chen    info.vendor_ramdisk_size = unpack('I', args.boot_img.read(4))[0]
382*4e0fa9f6SJoseph Chen    info.cmdline = cstr(unpack('2048s', args.boot_img.read(2048))[0].decode())
383*4e0fa9f6SJoseph Chen    info.tags_load_address = unpack('I', args.boot_img.read(4))[0]
384*4e0fa9f6SJoseph Chen    info.product_name = cstr(unpack('16s', args.boot_img.read(16))[0].decode())
385*4e0fa9f6SJoseph Chen    info.header_size = unpack('I', args.boot_img.read(4))[0]
386*4e0fa9f6SJoseph Chen    info.dtb_size = unpack('I', args.boot_img.read(4))[0]
387*4e0fa9f6SJoseph Chen    info.dtb_load_address = unpack('Q', args.boot_img.read(8))[0]
388*4e0fa9f6SJoseph Chen
389*4e0fa9f6SJoseph Chen    # Convenient shorthand.
390*4e0fa9f6SJoseph Chen    page_size = info.page_size
391*4e0fa9f6SJoseph Chen    # The first pages contain the boot header
392*4e0fa9f6SJoseph Chen    num_boot_header_pages = get_number_of_pages(info.header_size, page_size)
393*4e0fa9f6SJoseph Chen    num_boot_ramdisk_pages = get_number_of_pages(
394*4e0fa9f6SJoseph Chen        info.vendor_ramdisk_size, page_size)
395*4e0fa9f6SJoseph Chen    num_boot_dtb_pages = get_number_of_pages(info.dtb_size, page_size)
396*4e0fa9f6SJoseph Chen
397*4e0fa9f6SJoseph Chen    ramdisk_offset_base = page_size * num_boot_header_pages
398*4e0fa9f6SJoseph Chen    image_info_list = []
399*4e0fa9f6SJoseph Chen
400*4e0fa9f6SJoseph Chen    if info.header_version > 3:
401*4e0fa9f6SJoseph Chen        info.vendor_ramdisk_table_size = unpack('I', args.boot_img.read(4))[0]
402*4e0fa9f6SJoseph Chen        vendor_ramdisk_table_entry_num = unpack('I', args.boot_img.read(4))[0]
403*4e0fa9f6SJoseph Chen        vendor_ramdisk_table_entry_size = unpack('I', args.boot_img.read(4))[0]
404*4e0fa9f6SJoseph Chen        info.vendor_bootconfig_size = unpack('I', args.boot_img.read(4))[0]
405*4e0fa9f6SJoseph Chen        num_vendor_ramdisk_table_pages = get_number_of_pages(
406*4e0fa9f6SJoseph Chen            info.vendor_ramdisk_table_size, page_size)
407*4e0fa9f6SJoseph Chen        vendor_ramdisk_table_offset = page_size * (
408*4e0fa9f6SJoseph Chen            num_boot_header_pages + num_boot_ramdisk_pages + num_boot_dtb_pages)
409*4e0fa9f6SJoseph Chen
410*4e0fa9f6SJoseph Chen        vendor_ramdisk_table = []
411*4e0fa9f6SJoseph Chen        vendor_ramdisk_symlinks = []
412*4e0fa9f6SJoseph Chen        for idx in range(vendor_ramdisk_table_entry_num):
413*4e0fa9f6SJoseph Chen            entry_offset = vendor_ramdisk_table_offset + (
414*4e0fa9f6SJoseph Chen                vendor_ramdisk_table_entry_size * idx)
415*4e0fa9f6SJoseph Chen            args.boot_img.seek(entry_offset)
416*4e0fa9f6SJoseph Chen            ramdisk_size = unpack('I', args.boot_img.read(4))[0]
417*4e0fa9f6SJoseph Chen            ramdisk_offset = unpack('I', args.boot_img.read(4))[0]
418*4e0fa9f6SJoseph Chen            ramdisk_type = unpack('I', args.boot_img.read(4))[0]
419*4e0fa9f6SJoseph Chen            ramdisk_name = cstr(unpack(
420*4e0fa9f6SJoseph Chen                f'{VENDOR_RAMDISK_NAME_SIZE}s',
421*4e0fa9f6SJoseph Chen                args.boot_img.read(VENDOR_RAMDISK_NAME_SIZE))[0].decode())
422*4e0fa9f6SJoseph Chen            board_id = unpack(
423*4e0fa9f6SJoseph Chen                f'{VENDOR_RAMDISK_TABLE_ENTRY_BOARD_ID_SIZE}I',
424*4e0fa9f6SJoseph Chen                args.boot_img.read(
425*4e0fa9f6SJoseph Chen                    4 * VENDOR_RAMDISK_TABLE_ENTRY_BOARD_ID_SIZE))
426*4e0fa9f6SJoseph Chen            output_ramdisk_name = f'vendor_ramdisk{idx:02}'
427*4e0fa9f6SJoseph Chen
428*4e0fa9f6SJoseph Chen            image_info_list.append((ramdisk_offset_base + ramdisk_offset,
429*4e0fa9f6SJoseph Chen                                    ramdisk_size, output_ramdisk_name))
430*4e0fa9f6SJoseph Chen            vendor_ramdisk_symlinks.append((output_ramdisk_name, ramdisk_name))
431*4e0fa9f6SJoseph Chen            vendor_ramdisk_table.append(
432*4e0fa9f6SJoseph Chen                (output_ramdisk_name, ramdisk_size, ramdisk_offset,
433*4e0fa9f6SJoseph Chen                 ramdisk_type, ramdisk_name, board_id))
434*4e0fa9f6SJoseph Chen
435*4e0fa9f6SJoseph Chen        info.vendor_ramdisk_table = vendor_ramdisk_table
436*4e0fa9f6SJoseph Chen
437*4e0fa9f6SJoseph Chen        bootconfig_offset = page_size * (num_boot_header_pages
438*4e0fa9f6SJoseph Chen            + num_boot_ramdisk_pages + num_boot_dtb_pages
439*4e0fa9f6SJoseph Chen            + num_vendor_ramdisk_table_pages)
440*4e0fa9f6SJoseph Chen        image_info_list.append((bootconfig_offset, info.vendor_bootconfig_size,
441*4e0fa9f6SJoseph Chen            'bootconfig'))
442*4e0fa9f6SJoseph Chen    else:
443*4e0fa9f6SJoseph Chen        image_info_list.append(
444*4e0fa9f6SJoseph Chen            (ramdisk_offset_base, info.vendor_ramdisk_size, 'vendor_ramdisk'))
445*4e0fa9f6SJoseph Chen
446*4e0fa9f6SJoseph Chen    dtb_offset = page_size * (num_boot_header_pages + num_boot_ramdisk_pages
447*4e0fa9f6SJoseph Chen                             ) # header + vendor_ramdisk
448*4e0fa9f6SJoseph Chen    image_info_list.append((dtb_offset, info.dtb_size, 'dtb'))
449*4e0fa9f6SJoseph Chen
450*4e0fa9f6SJoseph Chen    create_out_dir(args.out)
451*4e0fa9f6SJoseph Chen    for offset, size, name in image_info_list:
452*4e0fa9f6SJoseph Chen        extract_image(offset, size, args.boot_img, os.path.join(args.out, name))
453*4e0fa9f6SJoseph Chen    info.image_dir = args.out
454*4e0fa9f6SJoseph Chen
455*4e0fa9f6SJoseph Chen    if info.header_version > 3:
456*4e0fa9f6SJoseph Chen        vendor_ramdisk_by_name_dir = os.path.join(
457*4e0fa9f6SJoseph Chen            args.out, 'vendor-ramdisk-by-name')
458*4e0fa9f6SJoseph Chen        create_out_dir(vendor_ramdisk_by_name_dir)
459*4e0fa9f6SJoseph Chen        for src, dst in vendor_ramdisk_symlinks:
460*4e0fa9f6SJoseph Chen            src_pathname = os.path.join('..', src)
461*4e0fa9f6SJoseph Chen            dst_pathname = os.path.join(
462*4e0fa9f6SJoseph Chen                vendor_ramdisk_by_name_dir, f'ramdisk_{dst}')
463*4e0fa9f6SJoseph Chen            if os.path.lexists(dst_pathname):
464*4e0fa9f6SJoseph Chen                os.remove(dst_pathname)
465*4e0fa9f6SJoseph Chen            os.symlink(src_pathname, dst_pathname)
466*4e0fa9f6SJoseph Chen
467*4e0fa9f6SJoseph Chen    return info
468*4e0fa9f6SJoseph Chen
469*4e0fa9f6SJoseph Chen
470*4e0fa9f6SJoseph Chendef unpack_image(args):
471*4e0fa9f6SJoseph Chen    boot_magic = unpack('8s', args.boot_img.read(8))[0].decode()
472*4e0fa9f6SJoseph Chen    args.boot_img.seek(0)
473*4e0fa9f6SJoseph Chen    if boot_magic == 'ANDROID!':
474*4e0fa9f6SJoseph Chen        info = unpack_boot_image(args)
475*4e0fa9f6SJoseph Chen    elif boot_magic == 'VNDRBOOT':
476*4e0fa9f6SJoseph Chen        info = unpack_vendor_boot_image(args)
477*4e0fa9f6SJoseph Chen    else:
478*4e0fa9f6SJoseph Chen        raise ValueError(f'Not an Android boot image, magic: {boot_magic}')
479*4e0fa9f6SJoseph Chen
480*4e0fa9f6SJoseph Chen    if args.format == 'mkbootimg':
481*4e0fa9f6SJoseph Chen        mkbootimg_args = info.format_mkbootimg_argument()
482*4e0fa9f6SJoseph Chen        if args.null:
483*4e0fa9f6SJoseph Chen            print('\0'.join(mkbootimg_args) + '\0', end='')
484*4e0fa9f6SJoseph Chen        else:
485*4e0fa9f6SJoseph Chen            print(shlex.join(mkbootimg_args))
486*4e0fa9f6SJoseph Chen    else:
487*4e0fa9f6SJoseph Chen        print(info.format_pretty_text())
488*4e0fa9f6SJoseph Chen
489*4e0fa9f6SJoseph Chen
490*4e0fa9f6SJoseph Chendef get_unpack_usage():
491*4e0fa9f6SJoseph Chen    return """Output format:
492*4e0fa9f6SJoseph Chen
493*4e0fa9f6SJoseph Chen  * info
494*4e0fa9f6SJoseph Chen
495*4e0fa9f6SJoseph Chen    Pretty-printed info-rich text format suitable for human inspection.
496*4e0fa9f6SJoseph Chen
497*4e0fa9f6SJoseph Chen  * mkbootimg
498*4e0fa9f6SJoseph Chen
499*4e0fa9f6SJoseph Chen    Output shell-escaped (quoted) argument strings that can be used to
500*4e0fa9f6SJoseph Chen    reconstruct the boot image. For example:
501*4e0fa9f6SJoseph Chen
502*4e0fa9f6SJoseph Chen    $ unpack_bootimg --boot_img vendor_boot.img --out out --format=mkbootimg |
503*4e0fa9f6SJoseph Chen        tee mkbootimg_args
504*4e0fa9f6SJoseph Chen    $ sh -c "mkbootimg $(cat mkbootimg_args) --vendor_boot repacked.img"
505*4e0fa9f6SJoseph Chen
506*4e0fa9f6SJoseph Chen    vendor_boot.img and repacked.img would be equivalent.
507*4e0fa9f6SJoseph Chen
508*4e0fa9f6SJoseph Chen    If the -0 option is specified, output unescaped null-terminated argument
509*4e0fa9f6SJoseph Chen    strings that are suitable to be parsed by a shell script (xargs -0 format):
510*4e0fa9f6SJoseph Chen
511*4e0fa9f6SJoseph Chen    $ unpack_bootimg --boot_img vendor_boot.img --out out --format=mkbootimg \\
512*4e0fa9f6SJoseph Chen        -0 | tee mkbootimg_args
513*4e0fa9f6SJoseph Chen    $ declare -a MKBOOTIMG_ARGS=()
514*4e0fa9f6SJoseph Chen    $ while IFS= read -r -d '' ARG; do
515*4e0fa9f6SJoseph Chen        MKBOOTIMG_ARGS+=("${ARG}")
516*4e0fa9f6SJoseph Chen      done <mkbootimg_args
517*4e0fa9f6SJoseph Chen    $ mkbootimg "${MKBOOTIMG_ARGS[@]}" --vendor_boot repacked.img
518*4e0fa9f6SJoseph Chen"""
5198a334094SJoseph Chen
5208a334094SJoseph Chen
5218a334094SJoseph Chendef parse_cmdline():
5228a334094SJoseph Chen    """parse command line arguments"""
5238a334094SJoseph Chen    parser = ArgumentParser(
524*4e0fa9f6SJoseph Chen        formatter_class=RawDescriptionHelpFormatter,
525*4e0fa9f6SJoseph Chen        description='Unpacks boot, recovery or vendor_boot image.',
526*4e0fa9f6SJoseph Chen        epilog=get_unpack_usage(),
527*4e0fa9f6SJoseph Chen    )
528*4e0fa9f6SJoseph Chen    parser.add_argument('--boot_img', type=FileType('rb'), required=True,
529*4e0fa9f6SJoseph Chen                        help='path to the boot, recovery or vendor_boot image')
530*4e0fa9f6SJoseph Chen    parser.add_argument('--out', default='out',
531*4e0fa9f6SJoseph Chen                        help='output directory of the unpacked images')
532*4e0fa9f6SJoseph Chen    parser.add_argument('--format', choices=['info', 'mkbootimg'],
533*4e0fa9f6SJoseph Chen                        default='info',
534*4e0fa9f6SJoseph Chen                        help='text output format (default: info)')
535*4e0fa9f6SJoseph Chen    parser.add_argument('-0', '--null', action='store_true',
536*4e0fa9f6SJoseph Chen                        help='output null-terminated argument strings')
5378a334094SJoseph Chen    return parser.parse_args()
5388a334094SJoseph Chen
5398a334094SJoseph Chen
5408a334094SJoseph Chendef main():
5418a334094SJoseph Chen    """parse arguments and unpack boot image"""
5428a334094SJoseph Chen    args = parse_cmdline()
543*4e0fa9f6SJoseph Chen    unpack_image(args)
5448a334094SJoseph Chen
5458a334094SJoseph Chen
5468a334094SJoseph Chenif __name__ == '__main__':
5478a334094SJoseph Chen    main()
548