Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adding merge_image functionality (ESPTOOL-79) #568

Closed
wants to merge 13 commits into from
108 changes: 108 additions & 0 deletions esptool.py
Original file line number Diff line number Diff line change
Expand Up @@ -3146,6 +3146,101 @@ def make_image(args):
image.save(args.output)


def merge_image(args):
import espsecure

# Helper classes
class Bin(object):
def __init__(self, addr, file):
self.start_addr = addr
self.file = file
self.file_name = os.path.basename(file.name)
self.size = self._get_size()
self.end_addr = self.start_addr + self.size

def _get_size(self):
self.file.seek(0, 2) # seek to end
size = self.file.tell()
self.file.seek(0)
return size

class MultiBin(object):
def __init__(self, name, output_folder='.'):
# Create output folder(s) (if needed)
try:
os.makedirs(os.path.realpath(output_folder))
except OSError:
pass # file already exists, overwrite

self.output_path = os.path.realpath(os.path.join(output_folder, name))
self.bins = []

def add_bin(self, addr, file):
self.bins.append(Bin(addr, file))

def _sort_bins(self):
self.bins = sorted(self.bins, key=lambda b: b.addr)

def _check_overlap(self):
# Check for bin overlap
for bin_index, curr_bin in enumerate(self.bins):
if bin_index > 1:
prev_bin = self.bins[bin_index - 1]

# Compare current bin start and previous bin end addresses
if prev_bin.end_addr > curr_bin.start_addr:
raise Exception("Not possible to create this bin, overlapping between %s and %s" % (
curr_bin.file_name, prev_bin.file_name))

def create_bin(self):
self._sort_bins()
self._check_overlap()

with open(self.output_path, "wb") as output_file:
for bin_index, curr_bin in enumerate(self.bins):
# Read previous bin end address
if bin_index > 1:
prev_end = self.bins[bin_index - 1].end_addr
else:
prev_end = 0

# Fill the gaps with 0xFF (if needed)
if curr_bin.addr > prev_end:
output_file.write('\xff' * (curr_bin.addr - prev_end))

# Inform user
print("Add %s from 0x%x to 0x%x (0x%x)" % (
curr_bin.file_name, curr_bin.start_addr, curr_bin.end_addr, curr_bin.size))

# Read bin data
with open(curr_bin.file, 'rb') as f:
bin_data = f.read()

# Write bin to output file
output_file.write(bin_data)

# Check that bins are provided
if len(args.addr_filename) == 0:
raise FatalError('No segments specified')

encrypt = bool(args.keyfile is not None)
unenc_name = args.output + '.bin'
enc_name = args.output + '.enc.bin'

# Combine bins into unencrypted image
mb = MultiBin(unenc_name)
for address, argfile in args.addr_filename:
mb.add_bin(address, argfile)
mb.create_bin()

# Encrypt image (if needed)
if encrypt:
with open(unenc_name, 'rb') as input_file:
with open(enc_name, 'wb') as output_file:
espsecure._flash_encryption_operation(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the underscore prefix in python is for private methods. overall, as this command will probably be run as part of a script or a custom build process then I'd prefer to make encryption a separate step in the script - otherwise when we add ESP32-S2 flash encryption support (for example) this command will also need updating to support that.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You are probably right! I am going to leave it as it is for now. But maybe you can change it to serve esptool needs better

output_file, input_file, 0, args.keyfile, args.flash_crypt_conf, False)


def elf2image(args):
e = ELFFile(args.input)
if args.chip == 'auto': # Default to ESP8266 for backwards compatibility
Expand Down Expand Up @@ -3488,6 +3583,19 @@ def add_spi_flash_subparsers(parent, is_elf2image):
parser_make_image.add_argument('--segaddr', '-a', action='append', help='Segment base address', type=arg_auto_int)
parser_make_image.add_argument('--entrypoint', '-e', help='Address of entry point', type=arg_auto_int, default=0)

parser_merge_image = subparsers.add_parser(
'merge_image',
help='Create an application image from binary files')
parser_merge_image.add_argument('addr_filename', metavar='<address> <filename>', help='Address followed by binary filename, separated by space',
action=AddrFilenamePairAction)
parser_merge_image.add_argument('--output', '-o', help='Output image file postfix (default: flash)', type=str, default='flash')
parser_merge_image.add_argument('--keyfile', '-k', help="File with flash encryption key", type=argparse.FileType('rb'), default=None)
parser_merge_image.add_argument('--flash_crypt_conf', help="Override FLASH_CRYPT_CONF efuse value (default: 0xF).",
default=0xF, type=arg_auto_int)

# Adding so we can use the same download.config for image composition
add_spi_flash_subparsers(parser_merge_image, is_elf2image=False)

parser_elf2image = subparsers.add_parser(
'elf2image',
help='Create an application image from ELF file')
Expand Down