blob: 48ea6a293de47680e07e23301898c90a9f1cea34 [file] [log] [blame]
#!python
from collections import namedtuple
import re
import argparse
import subprocess
import struct
from Crypto.Signature import pkcs1_15
from Crypto.PublicKey import RSA
from Crypto.Hash import SHA256
from Crypto.Util import number
import binascii
import os
import StringIO
def auto_int(x):
return int(x, 0)
parser = argparse.ArgumentParser(description='DK6 Image Header Generator')
parser.add_argument('in_file', help="Binary to be post-processed: generating header and optionally appending certificate and/or signature.")
parser.add_argument('out_file', nargs='?')
parser.add_argument('-g', '--signature_path', help="Sets directory from which certificate and private key are to be retrieved")
parser.add_argument('-k', '--key', action='store_true', help="2048 bits RSA private key in PEM format used to sign the full image. If -c option is used the full image includes the certificate + the signature of the certificate. The key shall be located in the same directory as the image_tool script. See priv_key.pem example.")
parser.add_argument('-p', '--password',help="This is the pass phrase from which the encryption key is derived. This parameter is only required if key provided through the -k option is a PEM encrypted key.")
parser.add_argument('-c', '--certificate', action='store_true', help="When option is selected, the certificate cert.bin is appended to the image.")
parser.add_argument('-i', '--image_identifier', type=int, help="This parameter is to set the archive identifier. 0: SSBL or legacy JN518x/QN9090 applications, loaded at 0x00000000. 1: application (ZB) loaded at address 0x00004000 by default. 2: application (BLE) image loaded at address 0x00053000 by default")
parser.add_argument('-a', '--appcore_image', action='store_true',help="This parameter is only relevant if dual application (app1 image) shall reside in flash. Do not use in conjunction with -i option.")
parser.add_argument('-t', '--target_addr', type=int, help="Target address of image. Used in conjunction with -i option to override the default set by image identifier, or with -a option to specify address of the appcore image (app1 image).")
parser.add_argument('-s', '--stated_size', type=int, help="This is the stated size of the image in bytes. Default is 0x48000.")
parser.add_argument('-v', '--version', type=auto_int, default=0, help="Image version. Default is 0.")
parser.add_argument('-b', '--verbose', type=int, default=0, help="verbosity level. Default is 0.")
parser.add_argument('-cl', '--compatibility_list', help="Compatibility list")
parser.add_argument('-sota', '--sota_number_of_blob', type=int, help="This parameter is used to generate the image directory command to be provisioned")
parser.add_argument('-bid', '--blob_id', type=auto_int, help="This parameter is to add a blob id. Can be used only if the sota arg is given")
JN518x_ES1 = 0
args = parser.parse_args()
elf_file_name = args.in_file
bin_file_name = elf_file_name.split(".")[0]+'_temp.bin'
if args.out_file is None:
args.out_file = elf_file_name
verbose = args.verbose != 0
def get_symbol_value(file, symb_name):
val = 0
objdump = subprocess.check_output(['arm-none-eabi-objdump', '--syms', file])
symb_re = re.compile(r'^([0-9a-f]{8})[\s\S]+[\s]+([0-9a-f]{8})\s([\w\.]+)')
for ln in StringIO.StringIO(objdump):
m = symb_re.match(ln)
if m:
if m.group(3) == symb_name:
val = int(m.group(1), 16)
break
return val
def parse_sections(file):
sections = {}
Section = namedtuple('Section', ['idx', 'name', 'size', 'vma', 'lma', 'offset', 'align', 'flags'])
objdump = subprocess.check_output(['arm-none-eabi-objdump', '-h', file])
section_re = re.compile(r'(?P<idx>[0-9]+)\s'
r'(?P<name>.{13,})s*'
r'(?P<size>[0-9a-f]{8})\s*'
r'(?P<vma>[0-9a-f]{8})\s*'
r'(?P<lma>[0-9a-f]{8})\s*'
r'(?P<offset>[0-9a-f]{8})\s*'
r'(?P<align>[0-9*]*)\s*'
r'(?P<flags>[[[\w]*[, [\w]*]*)')
for match in re.finditer(section_re, objdump):
sec_dict = match.groupdict()
sec_dict['idx'] = int(sec_dict['idx'])
for attr in ['vma', 'lma', 'size', 'offset']:
sec_dict[attr] = int(sec_dict[attr], 16)
sec_dict['align'] = eval(sec_dict['align'])
sections[sec_dict['name']] = Section(**sec_dict)
return sections
def reverseString2by2(string, stringLength):
result = ""
i = stringLength-1
while i >=0:
result = result + string[i-1]
result = result + string[i]
i = i-2
return result
def print_sota_img_directory_cmd(blobNumber):
nbBlobFound = 0
sota_final_print = ""
sota_final_print += "=================> SOTA information\n"
# Generate the image directory command that will be used to provison the device
valueToPrint = ".\\DK6Programmer.exe -V5 -s <COM_PORT> -P 1000000 -w PSECT:64@0x160="
for i in range(1,9):
bootable = "00"
if i==1:
bootable = "01"
position_in_flash = get_symbol_value(elf_file_name, "m_blob_position"+str(i))
position_in_flash = '%0*X' % (2,position_in_flash)
blobTargetAddr = get_symbol_value(elf_file_name, "m_blob"+str(i)+"_start")
blobTargetAddr = '%0*X' % (8,blobTargetAddr)
blobStatedSize = get_symbol_value(elf_file_name, "m_blob"+str(i)+"_size")
if position_in_flash != "00" and blobStatedSize != 0:
nbBlobFound=nbBlobFound+1
sota_final_print += "Position in flash = "+ position_in_flash+" - targetAddr = 0x" +blobTargetAddr+ "\n"
blobTargetAddr = reverseString2by2(blobTargetAddr, len(blobTargetAddr))
blobNbPage = blobStatedSize/512
blobNbPage = '%0*X' % (4,blobNbPage)
blobNbPage = reverseString2by2(blobNbPage, len(blobNbPage))
if blobStatedSize != 0:
valueToPrint = valueToPrint + blobTargetAddr + blobNbPage + bootable + position_in_flash
else:
valueToPrint = valueToPrint + "0000000000000000"
sota_final_print += "=====> Image directory command"
sota_final_print += valueToPrint
sota_final_print += "=================> (end) SOTA information"
if nbBlobFound == blobNumber:
print sota_final_print
#
# JN518x ES1 version
######################
if JN518x_ES1 == 1: # deprecated
BOOT_BLOCK_MARKER = 0xBB0110BB
header_struct = struct.Struct('<7LLLLL')
boot_block_struct = struct.Struct('<6LQ')
sections = parse_sections(args.in_file)
last_section = None
for name, section in sections.iteritems():
if 'LOAD' in section.flags:
if last_section is None or section.lma > last_section.lma:
if section.size > 0:
last_section = section
if args.appcore_image is True:
image_size = last_section.lma + last_section.size - args.target_appcore_addr
else:
image_size = last_section.lma + last_section.size
dump_section = subprocess.check_output(['arm-none-eabi-objcopy', '--dump-section', '%s=data.bin' % last_section.name, args.in_file])
if args.appcore_image is True:
boot_block = boot_block_struct.pack(BOOT_BLOCK_MARKER, 1, args.target_appcore_addr, image_size + boot_block_struct.size, 0, 0, 0)
else:
boot_block = boot_block_struct.pack(BOOT_BLOCK_MARKER, 0, 0, image_size + boot_block_struct.size, 0, 0, 0)
with open('data.bin', 'ab') as out_file:
out_file.write(boot_block)
update_section = subprocess.check_output(['arm-none-eabi-objcopy', '--update-section', '%s=data.bin' % last_section.name, args.in_file, args.out_file])
first_section = None
for name, section in sections.iteritems():
if 'LOAD' in section.flags:
if first_section is None or section.lma < first_section.lma:
first_section = section
with open(args.out_file, 'r+b') as elf_file:
elf_file.seek(first_section.offset)
vectors = elf_file.read(header_struct.size)
fields = list(header_struct.unpack(vectors))
vectsum = 0
for x in range(7):
vectsum += fields[x]
fields[7] = (~vectsum & 0xFFFFFFFF) + 1
if args.appcore_image is True:
fields[9] = 0x02794498
else:
fields[9] = 0x98447902
#fields[9] = 0x98447902
fields[10] = image_size
print "Writing checksum {:08x} to file {:s}".format(vectsum, args.out_file)
elf_file.seek(first_section.offset)
elf_file.write(header_struct.pack(*fields))
#
# JN518x ES2 version
######################
else:
is_signature = False
error = 0
if args.signature_path is not None:
sign_dir_path = os.path.join(os.path.dirname(__file__), args.signature_path)
priv_key_file_path = os.path.join(sign_dir_path, 'priv_key.pem')
cert_file_path = os.path.join(sign_dir_path, 'cert.bin')
else:
sign_dir_path = os.path.join(os.path.dirname(__file__), '')
priv_key_file_path = os.path.join(sign_dir_path, 'testkey_es2.pem')
cert_file_path = os.path.join(sign_dir_path, 'certif_es2')
if args.key is True:
key_file_path = priv_key_file_path
if verbose:
print "key path is " + key_file_path
if (os.path.isfile(key_file_path)):
key_file=open(key_file_path, 'r')
key = RSA.importKey(key_file.read(), args.password)
print "Private RSA key processing..."
is_signature = True
compatibility_struct = struct.Struct('<2L')
compatibility_len_struct = struct.Struct('<L')
if args.compatibility_list is not None:
print "Compatibility list:"
if verbose:
print " {}".format(args.compatibility_list)
compatibility_list = [map(auto_int, compatibility_item.split(",")) for compatibility_item in args.compatibility_list.split(";")]
print " Length: {}".format(len(compatibility_list))
for i in range(len(compatibility_list)):
item = compatibility_list[i]
for j in range(len(item)):
if j ==1:
blobIdCompatibilityList = " Blob ID 0x=" + '%0*X' % (8,item[j-1])
print "Blob ID =0x"+'%0*X' % (4,item[j-1]) +" - version =0x"+'%0*X' % (8,item[j])
compatibility_len = len(compatibility_list) * compatibility_struct.size + compatibility_len_struct.size
if verbose:
print " {}".format(compatibility_len)
else:
compatibility_list = []
compatibility_len = 0
print "No compatibility list"
# make sure that the compatibility list is added and equals to nb_blob - 1
if args.sota_number_of_blob is not None and (compatibility_len/compatibility_struct.size) != args.sota_number_of_blob-1:
print "!!! Error the compatibility list length must be = to the number of blobs -1 : "+str(compatibility_len/compatibility_struct.size)+"(len) != "+ str(args.sota_number_of_blob-1)
error = 1
#make sure that the blob ID is given
if args.sota_number_of_blob is not None and args.blob_id is None:
print "!!! Error the blob ID is missing"
error = 1
bin_output = subprocess.check_output(['arm-none-eabi-objcopy', '-O', 'binary', elf_file_name, bin_file_name])
with open(bin_file_name, 'rb') as in_file:
input_file = in_file.read()
BOOT_BLOCK_MARKER = 0xBB0110BB
IMAGE_HEADER_MARKER = 0x98447902
IMAGE_HEADER_APP_CORE = 0x02794498
IMAGE_HEADER_ESCORE = IMAGE_HEADER_MARKER
SSBL_OR_LEGACY_ADDRESS = 0x00000000
SSBL_STATED_SIZE = 0x2000
ZB_TARGET_ADDRESS = SSBL_STATED_SIZE * 2
ZB_STATED_SIZE = 0x4f000
BLE_TARGET_ADDRESS = ZB_TARGET_ADDRESS + ZB_STATED_SIZE
BLE_STATED_SIZE = 0x40000
header_struct = struct.Struct('<7LLLLL')
boot_block_struct = struct.Struct('<8L')
boot_block_marker = BOOT_BLOCK_MARKER
if args.image_identifier is not None:
image_iden = args.image_identifier
else:
image_iden = 0
if verbose:
print "Image Identifier is {:d}".format(image_iden)
#Default value initialization
image_addr = 0
stated_size = 0
#Default value for SSBL, ZigbeeFull and BleFull image id
if image_iden == 0:
image_addr = SSBL_OR_LEGACY_ADDRESS
stated_size = SSBL_STATED_SIZE
elif image_iden == 1:
image_addr = ZB_TARGET_ADDRESS
elif image_iden == 2:
image_addr = BLE_TARGET_ADDRESS
image_addr = get_symbol_value(elf_file_name, 'm_app_start')
stated_size = get_symbol_value(elf_file_name, 'm_app_size')
# In case of SOTA get the position in flash from the linker it should be the app_id
if args.sota_number_of_blob is not None:
if args.image_identifier is None:
image_iden = get_symbol_value(elf_file_name, '__blob_position__')
print "Blob position in flash = #"+str(image_iden)
img_header_marker = IMAGE_HEADER_MARKER + image_iden
# Overwrite defaults for image address, stated size and header marker (-t, -s, -a options)
if args.target_addr is not None:
image_addr = args.target_addr
if args.stated_size is not None:
stated_size = args.stated_size
if args.appcore_image is True:
img_header_marker = IMAGE_HEADER_APP_CORE
if verbose:
print "image_iden=%d image_addr=%x" % (image_iden, image_addr)
print "stated_size=%d" % (stated_size)
print "version=0x%0*X" % (8,args.version)
print "boot_block_marker=%x" % (boot_block_marker)
sections = parse_sections(elf_file_name)
last_section = None
for name, section in sections.iteritems():
if 'LOAD' in section.flags:
if last_section is None or section.lma > last_section.lma:
if section.size > 0:
last_section = section
# IAR toolchain uses odd section names that contain spaces
# the regexp now may now return trailing spaces too, need to strip them
last_section_name = last_section.name.rstrip()
# and add quotes around the section name
last_section_name = r"%s" % (last_section_name)
boot_block_offset = last_section.lma + last_section.size + compatibility_len - image_addr
# Correction for image size not being multiple of 4 (IAR)
padding_len = ((4 - (boot_block_offset%4)) & 3)
padding_bytes = bytearray(padding_len)
boot_block_offset = boot_block_offset + padding_len
print "boot block offset =%x" % (boot_block_offset)
if verbose:
print "Last Section LMA={:08x} Size={:08x}".format(last_section.lma, last_section.size)
print "ImageAddress={:08x}".format(image_addr)
first_section = None
for name, section in sections.iteritems():
# print "Section: {:s} {:s} {:x} {:x}".format(name, section.flags, section.lma, section.size)
if 'LOAD' in section.flags:
if first_section is None or section.lma < first_section.lma:
first_section = section
header=""
with open(args.out_file, 'r+b') as elf_file:
elf_file.seek(first_section.offset)
vectors = elf_file.read(header_struct.size)
fields = list(header_struct.unpack(vectors))
vectsum = 0
for x in range(7):
vectsum += fields[x]
fields[7] = (~vectsum & 0xFFFFFFFF) + 1
fields[8] = img_header_marker
fields[9] = boot_block_offset
#Compute crc
head_struct = struct.Struct('<10L')
try:
if verbose:
for i in range(10):
print "Header[{:d}]= {:08x}".format(i, fields[i])
values = head_struct.pack(fields[0],
fields[1],
fields[2],
fields[3],
fields[4],
fields[5],
fields[6],
fields[7],
fields[8],
fields[9])
fields[10] = binascii.crc32(values) & 0xFFFFFFFF
except:
error = 1
print "Writing checksum {:08x} to file {:s}".format(vectsum, args.out_file)
print "Writing CRC32 of header {:08x} to file {:s}".format(fields[10], args.out_file)
elf_file.seek(first_section.offset)
header = header_struct.pack(*fields);
elf_file.write(header)
dump_section = subprocess.check_output(['arm-none-eabi-objcopy',
'--dump-section',
'%s=data.bin' % last_section_name,
args.out_file])
certificate = ""
certificate_offset = 0
signature = ""
compatibility = ""
compatibility_offset = 0
if args.compatibility_list is not None:
compatibility_offset = last_section.lma + last_section.size - image_addr
compatibility = compatibility_len_struct.pack(len(compatibility_list)) + ''.join([compatibility_struct.pack(*compatibility_item) for compatibility_item in compatibility_list])
print "Compatibility processing..."
if (args.certificate is True):
certificate_offset = boot_block_offset + boot_block_struct.size
certif_file_path = cert_file_path
if verbose:
print "Cert key path is " + cert_file_path
if (os.path.isfile(certif_file_path)):
certif_file=open(certif_file_path, 'rb')
certificate = certif_file.read()
print "Certificate processing..."
if len(certificate) != (40+256+256):
print "Certificate error"
error = 1
if verbose:
print "stated size is {:08x} ({})".format(stated_size,stated_size)
if args.appcore_image is True:
boot_block_id = 1
else:
boot_block_id = 0
if args.blob_id is not None:
boot_block_id = args.blob_id
boot_block = boot_block_struct.pack(boot_block_marker,
boot_block_id,
image_addr,
boot_block_offset + boot_block_struct.size + len(certificate), # padding already included in the boot_block_offset
stated_size,
certificate_offset,
compatibility_offset,
args.version)
if (is_signature == True):
# Sign the complete image
message = header + input_file[header_struct.size:] + boot_block + certificate
hash = SHA256.new(message)
out_file_path = os.path.join(os.path.dirname(__file__), 'dump_python.bin')
file_out=open(out_file_path, 'wb')
file_out.write(message)
signer = pkcs1_15.new(key)
signature = signer.sign(hash)
print "Signature processing..."
with open('data.bin', 'ab') as out_file:
out_file.write(compatibility+padding_bytes+boot_block+certificate+signature)
if verbose:
print "Updating last section " + last_section.name
update_section = subprocess.check_output(['arm-none-eabi-objcopy',
'--update-section',
'%s=data.bin' % last_section_name,
elf_file_name,
args.out_file])
if (is_signature == True):
file_out.close()
bin_output = subprocess.check_output(['arm-none-eabi-objcopy',
'-O',
'binary',
elf_file_name,
bin_file_name])
print "Binary size is {:08x} ({})".format(os.stat(bin_file_name).st_size,os.stat(bin_file_name).st_size)
if args.sota_number_of_blob is not None:
print_sota_img_directory_cmd(args.sota_number_of_blob)
if os.stat(bin_file_name).st_size > stated_size:
print "Error: Binary file size ({:08x}) must be less or equal to stated size {:08x}".format(os.stat(bin_file_name).st_size, stated_size)
error = 1
if error != 0:
os.remove(elf_file_name)
os.remove(out_file_path)
os.remove(bin_file_name)