components: Correct the Python coding style

This commit is contained in:
Roland Dobai 2018-12-04 13:06:46 +01:00
parent c69907a54b
commit e1e6c1ae0a
6 changed files with 326 additions and 300 deletions

13
.flake8
View file

@ -150,22 +150,15 @@ exclude =
components/unity/unity, components/unity/unity,
examples/build_system/cmake/import_lib/main/lib/tinyxml2 examples/build_system/cmake/import_lib/main/lib/tinyxml2
# autogenerated scripts # autogenerated scripts
examples/provisioning/custom_config/components/custom_provisioning/python/custom_config_pb2.py,
# temporary list (should be empty)
components/app_update/dump_otadata.py,
components/app_update/gen_empty_partition.py,
components/espcoredump/espcoredump.py,
components/espcoredump/test/test_espcoredump.py,
components/nvs_flash/nvs_partition_generator/nvs_partition_gen.py,
components/partition_table/gen_esp32part.py,
components/partition_table/test_gen_esp32part_host/gen_esp32part_tests.py,
components/protocomm/python/constants_pb2.py, components/protocomm/python/constants_pb2.py,
components/protocomm/python/sec0_pb2.py, components/protocomm/python/sec0_pb2.py,
components/protocomm/python/sec1_pb2.py, components/protocomm/python/sec1_pb2.py,
components/protocomm/python/session_pb2.py, components/protocomm/python/session_pb2.py,
components/ulp/esp32ulp_mapgen.py,
components/wifi_provisioning/python/wifi_config_pb2.py, components/wifi_provisioning/python/wifi_config_pb2.py,
components/wifi_provisioning/python/wifi_constants_pb2.py, components/wifi_provisioning/python/wifi_constants_pb2.py,
examples/provisioning/custom_config/components/custom_provisioning/python/custom_config_pb2.py,
# temporary list (should be empty)
components/nvs_flash/nvs_partition_generator/nvs_partition_gen.py,
tools/ci/apply_bot_filter.py, tools/ci/apply_bot_filter.py,
tools/cmake/convert_to_cmake.py, tools/cmake/convert_to_cmake.py,
tools/esp_app_trace/apptrace_proc.py, tools/esp_app_trace/apptrace_proc.py,

View file

@ -5,6 +5,7 @@
from __future__ import print_function from __future__ import print_function
from __future__ import unicode_literals from __future__ import unicode_literals
from __future__ import division from __future__ import division
import sys
try: try:
from builtins import zip from builtins import zip
from builtins import str from builtins import str
@ -15,13 +16,11 @@ except ImportError:
print('Import has failed probably because of the missing "future" package. Please install all the packages for ' print('Import has failed probably because of the missing "future" package. Please install all the packages for '
'interpreter {} from the $IDF_PATH/requirements.txt file.'.format(sys.executable)) 'interpreter {} from the $IDF_PATH/requirements.txt file.'.format(sys.executable))
sys.exit(1) sys.exit(1)
import sys
import os import os
import argparse import argparse
import subprocess import subprocess
import tempfile import tempfile
import struct import struct
import array
import errno import errno
import base64 import base64
import binascii import binascii
@ -53,17 +52,18 @@ class ESPCoreDumpError(RuntimeError):
""" """
super(ESPCoreDumpError, self).__init__(message) super(ESPCoreDumpError, self).__init__(message)
class BinStruct(object): class BinStruct(object):
"""Binary structure representation """Binary structure representation
Subclasses must specify actual structure layout using 'fields' and 'format' members. Subclasses must specify actual structure layout using 'fields' and 'format' members.
For example, the following subclass represents structure with two fields: For example, the following subclass represents structure with two fields:
f1 of size 2 bytes and 4 bytes f2. Little endian. f1 of size 2 bytes and 4 bytes f2. Little endian.
class SomeStruct(BinStruct): class SomeStruct(BinStruct):
fields = ("f1", fields = ("f1",
"f2") "f2")
format = "<HL" format = "<HL"
Then subclass can be used to initialize fields of underlaying structure and convert it to binary representation: Then subclass can be used to initialize fields of underlaying structure and convert it to binary representation:
f = open('some_struct.bin', 'wb') f = open('some_struct.bin', 'wb')
s = SomeStruct() s = SomeStruct()
@ -89,7 +89,7 @@ class BinStruct(object):
def dump(self): def dump(self):
"""Returns binary representation of structure """Returns binary representation of structure
""" """
keys = self.__class__.fields keys = self.__class__.fields
return struct.pack(self.__class__.format, *(self.__dict__[k] for k in keys)) return struct.pack(self.__class__.format, *(self.__dict__[k] for k in keys))
@ -124,17 +124,17 @@ class Elf32FileHeader(BinStruct):
class Elf32ProgramHeader(BinStruct): class Elf32ProgramHeader(BinStruct):
"""ELF32 program header """ELF32 program header
""" """
fields = ("p_type", fields = ("p_type",
"p_offset", "p_offset",
"p_vaddr", "p_vaddr",
"p_paddr", "p_paddr",
"p_filesz", "p_filesz",
"p_memsz", "p_memsz",
"p_flags", "p_flags",
"p_align") "p_align")
format = "<LLLLLLLL" format = "<LLLLLLLL"
class Elf32NoteDesc(object): class Elf32NoteDesc(object):
@ -177,12 +177,12 @@ class XtensaPrStatus(BinStruct):
class ESPCoreDumpSegment(esptool.ImageSegment): class ESPCoreDumpSegment(esptool.ImageSegment):
""" Wrapper class for a program segment in core ELF file, has a segment """ Wrapper class for a program segment in core ELF file, has a segment
type and flags as well as the common properties of an ImageSegment. type and flags as well as the common properties of an ImageSegment.
""" """
# segment flags # segment flags
PF_X = 0x1 # Execute PF_X = 0x1 # Execute
PF_W = 0x2 # Write PF_W = 0x2 # Write
PF_R = 0x4 # Read PF_R = 0x4 # Read
def __init__(self, addr, data, type, flags): def __init__(self, addr, data, type, flags):
"""Constructor for program segment """Constructor for program segment
@ -217,7 +217,7 @@ class ESPCoreDumpSegment(esptool.ImageSegment):
class ESPCoreDumpSection(esptool.ELFSection): class ESPCoreDumpSection(esptool.ELFSection):
""" Wrapper class for a section in core ELF file, has a section """ Wrapper class for a section in core ELF file, has a section
flags as well as the common properties of an esptool.ELFSection. flags as well as the common properties of an esptool.ELFSection.
""" """
# section flags # section flags
SHF_WRITE = 0x1 SHF_WRITE = 0x1
@ -255,14 +255,14 @@ class ESPCoreDumpSection(esptool.ELFSection):
class ESPCoreDumpElfFile(esptool.ELFFile): class ESPCoreDumpElfFile(esptool.ELFFile):
""" Wrapper class for core dump ELF file """ Wrapper class for core dump ELF file
""" """
# ELF file type # ELF file type
ET_NONE = 0x0 # No file type ET_NONE = 0x0 # No file type
ET_REL = 0x1 # Relocatable file ET_REL = 0x1 # Relocatable file
ET_EXEC = 0x2 # Executable file ET_EXEC = 0x2 # Executable file
ET_DYN = 0x3 # Shared object file ET_DYN = 0x3 # Shared object file
ET_CORE = 0x4 # Core file ET_CORE = 0x4 # Core file
# ELF file version # ELF file version
EV_NONE = 0x0 EV_NONE = 0x0
EV_CURRENT = 0x1 EV_CURRENT = 0x1
@ -359,15 +359,15 @@ class ESPCoreDumpElfFile(esptool.ELFFile):
f.seek(offs) f.seek(offs)
return f.read(size) return f.read(size)
prog_sections = [ESPCoreDumpSection(lookup_string(n_offs), lma, read_data(offs, size), flags) for (n_offs, _type, flags, lma, size, offs) in prog_sections prog_sections = [ESPCoreDumpSection(lookup_string(n_offs), lma, read_data(offs, size), flags)
if lma != 0] for (n_offs, _type, flags, lma, size, offs) in prog_sections if lma != 0]
self.sections = prog_sections self.sections = prog_sections
def _read_program_segments(self, f, seg_table_offs, entsz, num): def _read_program_segments(self, f, seg_table_offs, entsz, num):
"""Reads core dump program segments from ELF file """Reads core dump program segments from ELF file
""" """
f.seek(seg_table_offs) f.seek(seg_table_offs)
seg_table = f.read(entsz*num) seg_table = f.read(entsz * num)
LEN_SEG_HEADER = 0x20 LEN_SEG_HEADER = 0x20
if len(seg_table) == 0: if len(seg_table) == 0:
raise ESPCoreDumpError("No program header table found at offset %04x in ELF file." % seg_table_offs) raise ESPCoreDumpError("No program header table found at offset %04x in ELF file." % seg_table_offs)
@ -387,8 +387,8 @@ class ESPCoreDumpElfFile(esptool.ELFFile):
f.seek(offs) f.seek(offs)
return f.read(size) return f.read(size)
self.program_segments = [ESPCoreDumpSegment(vaddr, read_data(offset, filesz), type, flags) for (type, offset, vaddr, filesz,flags) in prog_segments self.program_segments = [ESPCoreDumpSegment(vaddr, read_data(offset, filesz), type, flags)
if vaddr != 0] for (type, offset, vaddr, filesz,flags) in prog_segments if vaddr != 0]
def add_program_segment(self, addr, data, type, flags): def add_program_segment(self, addr, data, type, flags):
"""Adds new program segment """Adds new program segment
@ -400,11 +400,11 @@ class ESPCoreDumpElfFile(esptool.ELFFile):
for ps in self.program_segments: for ps in self.program_segments:
seg_len = len(ps.data) seg_len = len(ps.data)
if addr >= ps.addr and addr < (ps.addr + seg_len): if addr >= ps.addr and addr < (ps.addr + seg_len):
raise ESPCoreDumpError("Can not add overlapping region [%x..%x] to ELF file. Conflict with existing [%x..%x]." % raise ESPCoreDumpError("Can not add overlapping region [%x..%x] to ELF file. Conflict with existing [%x..%x]." %
(addr, addr + data_sz - 1, ps.addr, ps.addr + seg_len - 1)) (addr, addr + data_sz - 1, ps.addr, ps.addr + seg_len - 1))
if (addr + data_sz) > ps.addr and (addr + data_sz) <= (ps.addr + seg_len): if (addr + data_sz) > ps.addr and (addr + data_sz) <= (ps.addr + seg_len):
raise ESPCoreDumpError("Can not add overlapping region [%x..%x] to ELF file. Conflict with existing [%x..%x]." % raise ESPCoreDumpError("Can not add overlapping region [%x..%x] to ELF file. Conflict with existing [%x..%x]." %
(addr, addr + data_sz - 1, ps.addr, ps.addr + seg_len - 1)) (addr, addr + data_sz - 1, ps.addr, ps.addr + seg_len - 1))
# append # append
self.program_segments.append(ESPCoreDumpSegment(addr, data, type, flags)) self.program_segments.append(ESPCoreDumpSegment(addr, data, type, flags))
@ -434,11 +434,11 @@ class ESPCoreDumpElfFile(esptool.ELFFile):
phdr.p_type = self.program_segments[i].type phdr.p_type = self.program_segments[i].type
phdr.p_offset = cur_off phdr.p_offset = cur_off
phdr.p_vaddr = self.program_segments[i].addr phdr.p_vaddr = self.program_segments[i].addr
phdr.p_paddr = phdr.p_vaddr # TODO phdr.p_paddr = phdr.p_vaddr # TODO
phdr.p_filesz = len(self.program_segments[i].data) phdr.p_filesz = len(self.program_segments[i].data)
phdr.p_memsz = phdr.p_filesz # TODO phdr.p_memsz = phdr.p_filesz # TODO
phdr.p_flags = self.program_segments[i].flags phdr.p_flags = self.program_segments[i].flags
phdr.p_align = 0 # TODO phdr.p_align = 0 # TODO
f.write(phdr.dump()) f.write(phdr.dump())
cur_off += phdr.p_filesz cur_off += phdr.p_filesz
# write program segments # write program segments
@ -463,7 +463,7 @@ class ESPCoreDumpLoader(object):
ESP32_COREDUMP_HDR_SZ = struct.calcsize(ESP32_COREDUMP_HDR_FMT) ESP32_COREDUMP_HDR_SZ = struct.calcsize(ESP32_COREDUMP_HDR_FMT)
ESP32_COREDUMP_TSK_HDR_FMT = '<3L' ESP32_COREDUMP_TSK_HDR_FMT = '<3L'
ESP32_COREDUMP_TSK_HDR_SZ = struct.calcsize(ESP32_COREDUMP_TSK_HDR_FMT) ESP32_COREDUMP_TSK_HDR_SZ = struct.calcsize(ESP32_COREDUMP_TSK_HDR_FMT)
def __init__(self): def __init__(self):
"""Base constructor for core dump loader """Base constructor for core dump loader
""" """
@ -475,51 +475,51 @@ class ESPCoreDumpLoader(object):
# from "gdb/xtensa-tdep.h" # from "gdb/xtensa-tdep.h"
# typedef struct # typedef struct
# { # {
#0 xtensa_elf_greg_t pc; # 0 xtensa_elf_greg_t pc;
#1 xtensa_elf_greg_t ps; # 1 xtensa_elf_greg_t ps;
#2 xtensa_elf_greg_t lbeg; # 2 xtensa_elf_greg_t lbeg;
#3 xtensa_elf_greg_t lend; # 3 xtensa_elf_greg_t lend;
#4 xtensa_elf_greg_t lcount; # 4 xtensa_elf_greg_t lcount;
#5 xtensa_elf_greg_t sar; # 5 xtensa_elf_greg_t sar;
#6 xtensa_elf_greg_t windowstart; # 6 xtensa_elf_greg_t windowstart;
#7 xtensa_elf_greg_t windowbase; # 7 xtensa_elf_greg_t windowbase;
#8..63 xtensa_elf_greg_t reserved[8+48]; # 8..63 xtensa_elf_greg_t reserved[8+48];
#64 xtensa_elf_greg_t ar[64]; # 64 xtensa_elf_greg_t ar[64];
# } xtensa_elf_gregset_t; # } xtensa_elf_gregset_t;
REG_PC_IDX=0 REG_PC_IDX = 0
REG_PS_IDX=1 REG_PS_IDX = 1
REG_LB_IDX=2 REG_LB_IDX = 2
REG_LE_IDX=3 REG_LE_IDX = 3
REG_LC_IDX=4 REG_LC_IDX = 4
REG_SAR_IDX=5 REG_SAR_IDX = 5
REG_WS_IDX=6 # REG_WS_IDX = 6
REG_WB_IDX=7 # REG_WB_IDX = 7
REG_AR_START_IDX=64 REG_AR_START_IDX = 64
REG_AR_NUM=64 # REG_AR_NUM = 64
# FIXME: acc to xtensa_elf_gregset_t number of regs must be 128, # FIXME: acc to xtensa_elf_gregset_t number of regs must be 128,
# but gdb complanis when it less then 129 # but gdb complanis when it less then 129
REG_NUM=129 REG_NUM = 129
XT_SOL_EXIT=0 # XT_SOL_EXIT = 0
XT_SOL_PC=1 XT_SOL_PC = 1
XT_SOL_PS=2 XT_SOL_PS = 2
XT_SOL_NEXT=3 # XT_SOL_NEXT = 3
XT_SOL_AR_START=4 XT_SOL_AR_START = 4
XT_SOL_AR_NUM=4 XT_SOL_AR_NUM = 4
XT_SOL_FRMSZ=8 # XT_SOL_FRMSZ = 8
XT_STK_EXIT=0 XT_STK_EXIT = 0
XT_STK_PC=1 XT_STK_PC = 1
XT_STK_PS=2 XT_STK_PS = 2
XT_STK_AR_START=3 XT_STK_AR_START = 3
XT_STK_AR_NUM=16 XT_STK_AR_NUM = 16
XT_STK_SAR=19 XT_STK_SAR = 19
XT_STK_EXCCAUSE=20 # XT_STK_EXCCAUSE = 20
XT_STK_EXCVADDR=21 # XT_STK_EXCVADDR = 21
XT_STK_LBEG=22 XT_STK_LBEG = 22
XT_STK_LEND=23 XT_STK_LEND = 23
XT_STK_LCOUNT=24 XT_STK_LCOUNT = 24
XT_STK_FRMSZ=25 XT_STK_FRMSZ = 25
regs = [0] * REG_NUM regs = [0] * REG_NUM
# TODO: support for growing up stacks # TODO: support for growing up stacks
@ -541,7 +541,7 @@ class ESPCoreDumpLoader(object):
regs[REG_LB_IDX] = stack[XT_STK_LBEG] regs[REG_LB_IDX] = stack[XT_STK_LBEG]
regs[REG_LE_IDX] = stack[XT_STK_LEND] regs[REG_LE_IDX] = stack[XT_STK_LEND]
regs[REG_LC_IDX] = stack[XT_STK_LCOUNT] regs[REG_LC_IDX] = stack[XT_STK_LCOUNT]
# FIXME: crashed and some running tasks (e.g. prvIdleTask) have EXCM bit set # FIXME: crashed and some running tasks (e.g. prvIdleTask) have EXCM bit set
# and GDB can not unwind callstack properly (it implies not windowed call0) # and GDB can not unwind callstack properly (it implies not windowed call0)
if regs[REG_PS_IDX] & (1 << 5): if regs[REG_PS_IDX] & (1 << 5):
regs[REG_PS_IDX] &= ~(1 << 4) regs[REG_PS_IDX] &= ~(1 << 4)
@ -550,14 +550,14 @@ class ESPCoreDumpLoader(object):
regs[REG_PS_IDX] = stack[XT_SOL_PS] regs[REG_PS_IDX] = stack[XT_SOL_PS]
for i in range(XT_SOL_AR_NUM): for i in range(XT_SOL_AR_NUM):
regs[REG_AR_START_IDX + i] = stack[XT_SOL_AR_START + i] regs[REG_AR_START_IDX + i] = stack[XT_SOL_AR_START + i]
nxt = stack[XT_SOL_NEXT] # nxt = stack[XT_SOL_NEXT]
# TODO: remove magic hack with saved PC to get proper value # TODO: remove magic hack with saved PC to get proper value
regs[REG_PC_IDX] = ((regs[REG_PC_IDX] & 0x3FFFFFFF) | 0x40000000) regs[REG_PC_IDX] = ((regs[REG_PC_IDX] & 0x3FFFFFFF) | 0x40000000)
if regs[REG_PC_IDX] & 0x80000000: if regs[REG_PC_IDX] & 0x80000000:
regs[REG_PC_IDX] = (regs[REG_PC_IDX] & 0x3fffffff) | 0x40000000; regs[REG_PC_IDX] = (regs[REG_PC_IDX] & 0x3fffffff) | 0x40000000
if regs[REG_AR_START_IDX + 0] & 0x80000000: if regs[REG_AR_START_IDX + 0] & 0x80000000:
regs[REG_AR_START_IDX + 0] = (regs[REG_AR_START_IDX + 0] & 0x3fffffff) | 0x40000000; regs[REG_AR_START_IDX + 0] = (regs[REG_AR_START_IDX + 0] & 0x3fffffff) | 0x40000000
return regs return regs
def remove_tmp_file(self, fname): def remove_tmp_file(self, fname):
@ -587,7 +587,7 @@ class ESPCoreDumpLoader(object):
raise ESPCoreDumpLoaderError("Core dump version '%d' is not supported! Should be up to '%d'." % (coredump_ver, self.ESP32_COREDUMP_VESION)) raise ESPCoreDumpLoaderError("Core dump version '%d' is not supported! Should be up to '%d'." % (coredump_ver, self.ESP32_COREDUMP_VESION))
tcbsz_aligned = tcbsz tcbsz_aligned = tcbsz
if tcbsz_aligned % 4: if tcbsz_aligned % 4:
tcbsz_aligned = 4*(old_div(tcbsz_aligned,4) + 1) tcbsz_aligned = 4 * (old_div(tcbsz_aligned,4) + 1)
core_off += self.ESP32_COREDUMP_HDR_SZ core_off += self.ESP32_COREDUMP_HDR_SZ
core_elf = ESPCoreDumpElfFile() core_elf = ESPCoreDumpElfFile()
notes = b'' notes = b''
@ -600,17 +600,18 @@ class ESPCoreDumpLoader(object):
else: else:
stack_len = stack_top - stack_end stack_len = stack_top - stack_end
stack_base = stack_end stack_base = stack_end
stack_len_aligned = stack_len stack_len_aligned = stack_len
if stack_len_aligned % 4: if stack_len_aligned % 4:
stack_len_aligned = 4*(old_div(stack_len_aligned,4) + 1) stack_len_aligned = 4 * (old_div(stack_len_aligned,4) + 1)
core_off += self.ESP32_COREDUMP_TSK_HDR_SZ core_off += self.ESP32_COREDUMP_TSK_HDR_SZ
logging.info("Read TCB %d bytes @ 0x%x" % (tcbsz_aligned, tcb_addr)) logging.info("Read TCB %d bytes @ 0x%x" % (tcbsz_aligned, tcb_addr))
data = self.read_data(core_off, tcbsz_aligned) data = self.read_data(core_off, tcbsz_aligned)
try: try:
if tcbsz != tcbsz_aligned: if tcbsz != tcbsz_aligned:
core_elf.add_program_segment(tcb_addr, data[:tcbsz - tcbsz_aligned], ESPCoreDumpElfFile.PT_LOAD, ESPCoreDumpSegment.PF_R | ESPCoreDumpSegment.PF_W) core_elf.add_program_segment(tcb_addr, data[:tcbsz - tcbsz_aligned],
ESPCoreDumpElfFile.PT_LOAD, ESPCoreDumpSegment.PF_R | ESPCoreDumpSegment.PF_W)
else: else:
core_elf.add_program_segment(tcb_addr, data, ESPCoreDumpElfFile.PT_LOAD, ESPCoreDumpSegment.PF_R | ESPCoreDumpSegment.PF_W) core_elf.add_program_segment(tcb_addr, data, ESPCoreDumpElfFile.PT_LOAD, ESPCoreDumpSegment.PF_R | ESPCoreDumpSegment.PF_W)
except ESPCoreDumpError as e: except ESPCoreDumpError as e:
@ -632,8 +633,8 @@ class ESPCoreDumpLoader(object):
print(e) print(e)
return None return None
prstatus = XtensaPrStatus() prstatus = XtensaPrStatus()
prstatus.pr_cursig = 0 # TODO: set sig only for current/failed task prstatus.pr_cursig = 0 # TODO: set sig only for current/failed task
prstatus.pr_pid = i # TODO: use pid assigned by OS prstatus.pr_pid = i # TODO: use pid assigned by OS
note = Elf32NoteDesc("CORE", 1, prstatus.dump() + struct.pack("<%dL" % len(task_regs), *task_regs)).dump() note = Elf32NoteDesc("CORE", 1, prstatus.dump() + struct.pack("<%dL" % len(task_regs), *task_regs)).dump()
notes += note notes += note
@ -650,7 +651,7 @@ class ESPCoreDumpLoader(object):
core_elf.add_program_segment(ps.addr, ps.data, ESPCoreDumpElfFile.PT_LOAD, ps.flags) core_elf.add_program_segment(ps.addr, ps.data, ESPCoreDumpElfFile.PT_LOAD, ps.flags)
except ESPCoreDumpError as e: except ESPCoreDumpError as e:
logging.warning("Skip ROM segment %d bytes @ 0x%x. (Reason: %s)" % (len(ps.data), ps.addr, e)) logging.warning("Skip ROM segment %d bytes @ 0x%x. (Reason: %s)" % (len(ps.data), ps.addr, e))
core_elf.e_type = ESPCoreDumpElfFile.ET_CORE core_elf.e_type = ESPCoreDumpElfFile.ET_CORE
core_elf.e_machine = ESPCoreDumpElfFile.EM_XTENSA core_elf.e_machine = ESPCoreDumpElfFile.EM_XTENSA
if core_fname: if core_fname:
@ -673,7 +674,7 @@ class ESPCoreDumpLoader(object):
class ESPCoreDumpFileLoader(ESPCoreDumpLoader): class ESPCoreDumpFileLoader(ESPCoreDumpLoader):
"""Core dump file loader class """Core dump file loader class
""" """
def __init__(self, path, b64 = False): def __init__(self, path, b64=False):
"""Constructor for core dump file loader """Constructor for core dump file loader
""" """
super(ESPCoreDumpFileLoader, self).__init__() super(ESPCoreDumpFileLoader, self).__init__()
@ -725,7 +726,7 @@ class ESPCoreDumpFlashLoader(ESPCoreDumpLoader):
if e == '.pyc': if e == '.pyc':
self.path = self.path[:-1] self.path = self.path[:-1]
else: else:
self.path = tool_path self.path = tool_path
self.port = port self.port = port
self.baud = baud self.baud = baud
self.chip = chip self.chip = chip
@ -780,7 +781,7 @@ class ESPCoreDumpFlashLoader(ESPCoreDumpLoader):
data = self.read_data(0, self.dump_sz - self.ESP32_COREDUMP_FLASH_CRC_SZ) data = self.read_data(0, self.dump_sz - self.ESP32_COREDUMP_FLASH_CRC_SZ)
data_crc = binascii.crc32(data) & 0xffffffff data_crc = binascii.crc32(data) & 0xffffffff
if dump_crc != data_crc: if dump_crc != data_crc:
raise ESPCoreDumpLoaderError("Invalid core dump CRC %x, should be %x" % (data_crc, dump_crc)) raise ESPCoreDumpLoaderError("Invalid core dump CRC %x, should be %x" % (data_crc, dump_crc))
return super(ESPCoreDumpFlashLoader, self).create_corefile(core_fname) return super(ESPCoreDumpFlashLoader, self).create_corefile(core_fname)
@ -876,8 +877,9 @@ class GDBMIStreamConsoleHandler(GDBMIOutStreamHandler):
""" """
TAG = '~' TAG = '~'
def load_aux_elf(elf_path): def load_aux_elf(elf_path):
""" Loads auxilary ELF file and composes GDB command to read its symbols """ Loads auxilary ELF file and composes GDB command to read its symbols
""" """
elf = None elf = None
sym_cmd = '' sym_cmd = ''
@ -888,6 +890,7 @@ def load_aux_elf(elf_path):
sym_cmd = 'add-symbol-file %s 0x%x' % (elf_path, s.addr) sym_cmd = 'add-symbol-file %s 0x%x' % (elf_path, s.addr)
return (elf, sym_cmd) return (elf, sym_cmd)
def dbg_corefile(args): def dbg_corefile(args):
""" Command to load core dump from file or flash and run GDB debug session with it """ Command to load core dump from file or flash and run GDB debug session with it
""" """
@ -911,18 +914,18 @@ def dbg_corefile(args):
loader.cleanup() loader.cleanup()
return return
p = subprocess.Popen( p = subprocess.Popen(bufsize=0,
bufsize = 0, args=[args.gdb,
args = [args.gdb, '--nw', # ignore .gdbinit
'--nw', # ignore .gdbinit '--core=%s' % core_fname, # core file,
'--core=%s' % core_fname, # core file, '-ex', rom_sym_cmd,
'-ex', rom_sym_cmd, args.prog
args.prog], ],
stdin = None, stdout = None, stderr = None, stdin=None, stdout=None, stderr=None,
close_fds = CLOSE_FDS close_fds=CLOSE_FDS
) )
p.wait() p.wait()
if loader: if loader:
if not args.core and not args.save_core: if not args.core and not args.save_core:
loader.remove_tmp_file(core_fname) loader.remove_tmp_file(core_fname)
@ -931,13 +934,14 @@ def dbg_corefile(args):
def info_corefile(args): def info_corefile(args):
""" Command to load core dump from file or flash and print it's data in user friendly form """ Command to load core dump from file or flash and print it's data in user friendly form
""" """
global CLOSE_FDS global CLOSE_FDS
def gdbmi_console_stream_handler(ln): def gdbmi_console_stream_handler(ln):
sys.stdout.write(ln) sys.stdout.write(ln)
sys.stdout.flush() sys.stdout.flush()
def gdbmi_read2prompt(f, out_handlers=None): def gdbmi_read2prompt(f, out_handlers=None):
while True: while True:
ln = f.readline().decode('utf-8').rstrip(' \r\n') ln = f.readline().decode('utf-8').rstrip(' \r\n')
@ -953,20 +957,18 @@ def info_corefile(args):
def gdbmi_start(handlers, gdb_cmds): def gdbmi_start(handlers, gdb_cmds):
gdb_args = [args.gdb, gdb_args = [args.gdb,
'--quiet', # inhibit dumping info at start-up '--quiet', # inhibit dumping info at start-up
'--nx', # inhibit window interface '--nx', # inhibit window interface
'--nw', # ignore .gdbinit '--nw', # ignore .gdbinit
'--interpreter=mi2', # use GDB/MI v2 '--interpreter=mi2', # use GDB/MI v2
'--core=%s' % core_fname] # core file '--core=%s' % core_fname] # core file
for c in gdb_cmds: for c in gdb_cmds:
gdb_args += ['-ex', c] gdb_args += ['-ex', c]
gdb_args.append(args.prog) gdb_args.append(args.prog)
p = subprocess.Popen( p = subprocess.Popen(bufsize=0,
bufsize = 0, args=gdb_args,
args = gdb_args, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.STDOUT,
stdin = subprocess.PIPE, stdout = subprocess.PIPE, stderr = subprocess.STDOUT, close_fds=CLOSE_FDS)
close_fds = CLOSE_FDS
)
gdbmi_read2prompt(p.stdout, handlers) gdbmi_read2prompt(p.stdout, handlers)
return p return p
@ -1093,13 +1095,13 @@ def info_corefile(args):
p.wait() p.wait()
p.stdin.close() p.stdin.close()
p.stdout.close() p.stdout.close()
if loader: if loader:
if not args.core and not args.save_core: if not args.core and not args.save_core:
loader.remove_tmp_file(core_fname) loader.remove_tmp_file(core_fname)
loader.cleanup() loader.cleanup()
print('Done!') print('Done!')
def main(): def main():
parser = argparse.ArgumentParser(description='espcoredump.py v%s - ESP32 Core Dump Utility' % __version__, prog='espcoredump') parser = argparse.ArgumentParser(description='espcoredump.py v%s - ESP32 Core Dump Utility' % __version__, prog='espcoredump')
@ -1130,9 +1132,12 @@ def main():
parser_debug_coredump.add_argument('--debug', '-d', help='Log level (0..3)', type=int, default=2) parser_debug_coredump.add_argument('--debug', '-d', help='Log level (0..3)', type=int, default=2)
parser_debug_coredump.add_argument('--gdb', '-g', help='Path to gdb', default='xtensa-esp32-elf-gdb') parser_debug_coredump.add_argument('--gdb', '-g', help='Path to gdb', default='xtensa-esp32-elf-gdb')
parser_debug_coredump.add_argument('--core', '-c', help='Path to core dump file (if skipped core dump will be read from flash)', type=str) parser_debug_coredump.add_argument('--core', '-c', help='Path to core dump file (if skipped core dump will be read from flash)', type=str)
parser_debug_coredump.add_argument('--core-format', '-t', help='(elf, raw or b64). File specified with "-c" is an ELF ("elf"), raw (raw) or base64-encoded (b64) binary', type=str, default='elf') parser_debug_coredump.add_argument('--core-format', '-t', help='(elf, raw or b64). File specified with "-c" is an ELF ("elf"), '
parser_debug_coredump.add_argument('--off', '-o', help='Ofsset of coredump partition in flash (type "make partition_table" to see).', type=int, default=0x110000) 'raw (raw) or base64-encoded (b64) binary', type=str, default='elf')
parser_debug_coredump.add_argument('--save-core', '-s', help='Save core to file. Othwerwise temporary core file will be deleted. Ignored with "-c"', type=str) parser_debug_coredump.add_argument('--off', '-o', help='Ofsset of coredump partition in flash '
'(type "make partition_table" to see).', type=int, default=0x110000)
parser_debug_coredump.add_argument('--save-core', '-s', help='Save core to file. Othwerwise temporary core file will be deleted. '
'Ignored with "-c"', type=str)
parser_debug_coredump.add_argument('--rom-elf', '-r', help='Path to ROM ELF file.', type=str, default='esp32_rom.elf') parser_debug_coredump.add_argument('--rom-elf', '-r', help='Path to ROM ELF file.', type=str, default='esp32_rom.elf')
parser_debug_coredump.add_argument('prog', help='Path to program\'s ELF binary', type=str) parser_debug_coredump.add_argument('prog', help='Path to program\'s ELF binary', type=str)
@ -1142,9 +1147,12 @@ def main():
parser_info_coredump.add_argument('--debug', '-d', help='Log level (0..3)', type=int, default=0) parser_info_coredump.add_argument('--debug', '-d', help='Log level (0..3)', type=int, default=0)
parser_info_coredump.add_argument('--gdb', '-g', help='Path to gdb', default='xtensa-esp32-elf-gdb') parser_info_coredump.add_argument('--gdb', '-g', help='Path to gdb', default='xtensa-esp32-elf-gdb')
parser_info_coredump.add_argument('--core', '-c', help='Path to core dump file (if skipped core dump will be read from flash)', type=str) parser_info_coredump.add_argument('--core', '-c', help='Path to core dump file (if skipped core dump will be read from flash)', type=str)
parser_info_coredump.add_argument('--core-format', '-t', help='(elf, raw or b64). File specified with "-c" is an ELF ("elf"), raw (raw) or base64-encoded (b64) binary', type=str, default='elf') parser_info_coredump.add_argument('--core-format', '-t', help='(elf, raw or b64). File specified with "-c" is an ELF ("elf"), '
parser_info_coredump.add_argument('--off', '-o', help='Ofsset of coredump partition in flash (type "make partition_table" to see).', type=int, default=0x110000) 'raw (raw) or base64-encoded (b64) binary', type=str, default='elf')
parser_info_coredump.add_argument('--save-core', '-s', help='Save core to file. Othwerwise temporary core file will be deleted. Does not work with "-c"', type=str) parser_info_coredump.add_argument('--off', '-o', help='Offset of coredump partition in flash (type '
'"make partition_table" to see).', type=int, default=0x110000)
parser_info_coredump.add_argument('--save-core', '-s', help='Save core to file. Othwerwise temporary core file will be deleted. '
'Does not work with "-c"', type=str)
parser_info_coredump.add_argument('--rom-elf', '-r', help='Path to ROM ELF file.', type=str, default='esp32_rom.elf') parser_info_coredump.add_argument('--rom-elf', '-r', help='Path to ROM ELF file.', type=str, default='esp32_rom.elf')
parser_info_coredump.add_argument('--print-mem', '-m', help='Print memory dump', action='store_true') parser_info_coredump.add_argument('--print-mem', '-m', help='Print memory dump', action='store_true')
parser_info_coredump.add_argument('prog', help='Path to program\'s ELF binary', type=str) parser_info_coredump.add_argument('prog', help='Path to program\'s ELF binary', type=str)

View file

@ -18,10 +18,14 @@ import sys
import os import os
import unittest import unittest
idf_path = os.getenv('IDF_PATH') try:
if idf_path: import espcoredump
sys.path.insert(0, os.path.join(idf_path, 'components', 'espcoredump')) except ImportError:
import espcoredump idf_path = os.getenv('IDF_PATH')
if idf_path:
sys.path.insert(0, os.path.join(idf_path, 'components', 'espcoredump'))
import espcoredump
class TestESPCoreDumpFileLoader(unittest.TestCase): class TestESPCoreDumpFileLoader(unittest.TestCase):
def setUp(self): def setUp(self):
@ -34,15 +38,16 @@ class TestESPCoreDumpFileLoader(unittest.TestCase):
def testESPCoreDumpFileLoaderWithoutB64(self): def testESPCoreDumpFileLoaderWithoutB64(self):
t = espcoredump.ESPCoreDumpFileLoader(path='coredump.b64', b64=False) t = espcoredump.ESPCoreDumpFileLoader(path='coredump.b64', b64=False)
self.assertIsInstance(t, espcoredump.ESPCoreDumpFileLoader) # invoke for coverage of open() self.assertIsInstance(t, espcoredump.ESPCoreDumpFileLoader) # invoke for coverage of open()
t.cleanup() t.cleanup()
def test_cannot_remove_dir(self): def test_cannot_remove_dir(self):
self.dloader.remove_tmp_file(fname='.') # silent failure (but covers exception inside) self.dloader.remove_tmp_file(fname='.') # silent failure (but covers exception inside)
def test_create_corefile(self): def test_create_corefile(self):
self.assertEqual(self.dloader.create_corefile(core_fname=self.tmp_file, off=0, rom_elf=None), self.tmp_file) self.assertEqual(self.dloader.create_corefile(core_fname=self.tmp_file, off=0, rom_elf=None), self.tmp_file)
if __name__ == '__main__': if __name__ == '__main__':
# The purpose of these tests is to increase the code coverage at places which are sensitive to issues related to # The purpose of these tests is to increase the code coverage at places which are sensitive to issues related to
# Python 2&3 compatibility. # Python 2&3 compatibility.

View file

@ -32,7 +32,7 @@ import binascii
import errno import errno
MAX_PARTITION_LENGTH = 0xC00 # 3K for partition data (96 entries) leaves 1K in a 4K sector for signature MAX_PARTITION_LENGTH = 0xC00 # 3K for partition data (96 entries) leaves 1K in a 4K sector for signature
MD5_PARTITION_BEGIN = b"\xEB\xEB" + b"\xFF" * 14 # The first 2 bytes are like magic numbers for MD5 sum MD5_PARTITION_BEGIN = b"\xEB\xEB" + b"\xFF" * 14 # The first 2 bytes are like magic numbers for MD5 sum
PARTITION_TABLE_SIZE = 0x1000 # Size of partition table PARTITION_TABLE_SIZE = 0x1000 # Size of partition table
MIN_PARTITION_SUBTYPE_APP_OTA = 0x10 MIN_PARTITION_SUBTYPE_APP_OTA = 0x10
@ -44,25 +44,25 @@ APP_TYPE = 0x00
DATA_TYPE = 0x01 DATA_TYPE = 0x01
TYPES = { TYPES = {
"app" : APP_TYPE, "app": APP_TYPE,
"data" : DATA_TYPE, "data": DATA_TYPE,
} }
# Keep this map in sync with esp_partition_subtype_t enum in esp_partition.h # Keep this map in sync with esp_partition_subtype_t enum in esp_partition.h
SUBTYPES = { SUBTYPES = {
APP_TYPE : { APP_TYPE: {
"factory" : 0x00, "factory": 0x00,
"test" : 0x20, "test": 0x20,
}, },
DATA_TYPE : { DATA_TYPE: {
"ota" : 0x00, "ota": 0x00,
"phy" : 0x01, "phy": 0x01,
"nvs" : 0x02, "nvs": 0x02,
"coredump" : 0x03, "coredump": 0x03,
"nvs_keys" : 0x04, "nvs_keys": 0x04,
"esphttpd" : 0x80, "esphttpd": 0x80,
"fat" : 0x81, "fat": 0x81,
"spiffs" : 0x82, "spiffs": 0x82,
}, },
} }
@ -71,16 +71,19 @@ md5sum = True
secure = False secure = False
offset_part_table = 0 offset_part_table = 0
def status(msg): def status(msg):
""" Print status message to stderr """ """ Print status message to stderr """
if not quiet: if not quiet:
critical(msg) critical(msg)
def critical(msg): def critical(msg):
""" Print critical message to stderr """ """ Print critical message to stderr """
sys.stderr.write(msg) sys.stderr.write(msg)
sys.stderr.write('\n') sys.stderr.write('\n')
class PartitionTable(list): class PartitionTable(list):
def __init__(self): def __init__(self):
super(PartitionTable, self).__init__(self) super(PartitionTable, self).__init__(self)
@ -102,15 +105,15 @@ class PartitionTable(list):
if line.startswith("#") or len(line) == 0: if line.startswith("#") or len(line) == 0:
continue continue
try: try:
res.append(PartitionDefinition.from_csv(line, line_no+1)) res.append(PartitionDefinition.from_csv(line, line_no + 1))
except InputError as e: except InputError as e:
raise InputError("Error at line %d: %s" % (line_no+1, e)) raise InputError("Error at line %d: %s" % (line_no + 1, e))
except Exception: except Exception:
critical("Unexpected error parsing CSV line %d: %s" % (line_no+1, line)) critical("Unexpected error parsing CSV line %d: %s" % (line_no + 1, line))
raise raise
# fix up missing offsets & negative sizes # fix up missing offsets & negative sizes
last_end = offset_part_table + PARTITION_TABLE_SIZE # first offset after partition table last_end = offset_part_table + PARTITION_TABLE_SIZE # first offset after partition table
for e in res: for e in res:
if e.offset is not None and e.offset < last_end: if e.offset is not None and e.offset < last_end:
if e == res[0]: if e == res[0]:
@ -149,14 +152,14 @@ class PartitionTable(list):
ptype = TYPES[ptype] ptype = TYPES[ptype]
except KeyError: except KeyError:
try: try:
ptypes = int(ptype, 0) ptype = int(ptype, 0)
except TypeError: except TypeError:
pass pass
try: try:
subtype = SUBTYPES[int(ptype)][subtype] subtype = SUBTYPES[int(ptype)][subtype]
except KeyError: except KeyError:
try: try:
ptypes = int(ptype, 0) ptype = int(ptype, 0)
except TypeError: except TypeError:
pass pass
@ -175,11 +178,11 @@ class PartitionTable(list):
# verify each partition individually # verify each partition individually
for p in self: for p in self:
p.verify() p.verify()
# check on duplicate name # check on duplicate name
names = [ p.name for p in self ] names = [p.name for p in self]
duplicates = set( n for n in names if names.count(n) > 1 ) duplicates = set(n for n in names if names.count(n) > 1)
# print sorted duplicate partitions by name # print sorted duplicate partitions by name
if len(duplicates) != 0: if len(duplicates) != 0:
print("A list of partitions that have the same name:") print("A list of partitions that have the same name:")
@ -187,14 +190,14 @@ class PartitionTable(list):
if len(duplicates.intersection([p.name])) != 0: if len(duplicates.intersection([p.name])) != 0:
print("%s" % (p.to_csv())) print("%s" % (p.to_csv()))
raise InputError("Partition names must be unique") raise InputError("Partition names must be unique")
# check for overlaps # check for overlaps
last = None last = None
for p in sorted(self, key=lambda x:x.offset): for p in sorted(self, key=lambda x:x.offset):
if p.offset < offset_part_table + PARTITION_TABLE_SIZE: if p.offset < offset_part_table + PARTITION_TABLE_SIZE:
raise InputError("Partition offset 0x%x is below 0x%x" % (p.offset, offset_part_table + PARTITION_TABLE_SIZE)) raise InputError("Partition offset 0x%x is below 0x%x" % (p.offset, offset_part_table + PARTITION_TABLE_SIZE))
if last is not None and p.offset < last.offset + last.size: if last is not None and p.offset < last.offset + last.size:
raise InputError("Partition at 0x%x overlaps 0x%x-0x%x" % (p.offset, last.offset, last.offset+last.size-1)) raise InputError("Partition at 0x%x overlaps 0x%x-0x%x" % (p.offset, last.offset, last.offset + last.size - 1))
last = p last = p
def flash_size(self): def flash_size(self):
@ -209,17 +212,17 @@ class PartitionTable(list):
@classmethod @classmethod
def from_binary(cls, b): def from_binary(cls, b):
md5 = hashlib.md5(); md5 = hashlib.md5()
result = cls() result = cls()
for o in range(0,len(b),32): for o in range(0,len(b),32):
data = b[o:o+32] data = b[o:o + 32]
if len(data) != 32: if len(data) != 32:
raise InputError("Partition table length must be a multiple of 32 bytes") raise InputError("Partition table length must be a multiple of 32 bytes")
if data == b'\xFF'*32: if data == b'\xFF' * 32:
return result # got end marker return result # got end marker
if md5sum and data[:2] == MD5_PARTITION_BEGIN[:2]: #check only the magic number part if md5sum and data[:2] == MD5_PARTITION_BEGIN[:2]: # check only the magic number part
if data[16:] == md5.digest(): if data[16:] == md5.digest():
continue # the next iteration will check for the end marker continue # the next iteration will check for the end marker
else: else:
raise InputError("MD5 checksums don't match! (computed: 0x%s, parsed: 0x%s)" % (md5.hexdigest(), binascii.hexlify(data[16:]))) raise InputError("MD5 checksums don't match! (computed: 0x%s, parsed: 0x%s)" % (md5.hexdigest(), binascii.hexlify(data[16:])))
else: else:
@ -231,29 +234,30 @@ class PartitionTable(list):
result = b"".join(e.to_binary() for e in self) result = b"".join(e.to_binary() for e in self)
if md5sum: if md5sum:
result += MD5_PARTITION_BEGIN + hashlib.md5(result).digest() result += MD5_PARTITION_BEGIN + hashlib.md5(result).digest()
if len(result )>= MAX_PARTITION_LENGTH: if len(result) >= MAX_PARTITION_LENGTH:
raise InputError("Binary partition table length (%d) longer than max" % len(result)) raise InputError("Binary partition table length (%d) longer than max" % len(result))
result += b"\xFF" * (MAX_PARTITION_LENGTH - len(result)) # pad the sector, for signing result += b"\xFF" * (MAX_PARTITION_LENGTH - len(result)) # pad the sector, for signing
return result return result
def to_csv(self, simple_formatting=False): def to_csv(self, simple_formatting=False):
rows = [ "# Espressif ESP32 Partition Table", rows = ["# Espressif ESP32 Partition Table",
"# Name, Type, SubType, Offset, Size, Flags" ] "# Name, Type, SubType, Offset, Size, Flags"]
rows += [ x.to_csv(simple_formatting) for x in self ] rows += [x.to_csv(simple_formatting) for x in self]
return "\n".join(rows) + "\n" return "\n".join(rows) + "\n"
class PartitionDefinition(object): class PartitionDefinition(object):
MAGIC_BYTES = b"\xAA\x50" MAGIC_BYTES = b"\xAA\x50"
ALIGNMENT = { ALIGNMENT = {
APP_TYPE : 0x10000, APP_TYPE: 0x10000,
DATA_TYPE : 0x04, DATA_TYPE: 0x04,
} }
# dictionary maps flag name (as used in CSV flags list, property name) # dictionary maps flag name (as used in CSV flags list, property name)
# to bit set in flags words in binary format # to bit set in flags words in binary format
FLAGS = { FLAGS = {
"encrypted" : 0 "encrypted": 0
} }
# add subtypes for the 16 OTA slot values ("ota_XX, etc.") # add subtypes for the 16 OTA slot values ("ota_XX, etc.")
@ -272,7 +276,7 @@ class PartitionDefinition(object):
def from_csv(cls, line, line_no): def from_csv(cls, line, line_no):
""" Parse a line from the CSV """ """ Parse a line from the CSV """
line_w_defaults = line + ",,,," # lazy way to support default fields line_w_defaults = line + ",,,," # lazy way to support default fields
fields = [ f.strip() for f in line_w_defaults.split(",") ] fields = [f.strip() for f in line_w_defaults.split(",")]
res = PartitionDefinition() res = PartitionDefinition()
res.line_no = line_no res.line_no = line_no
@ -302,7 +306,7 @@ class PartitionDefinition(object):
def maybe_hex(x): def maybe_hex(x):
return "0x%x" % x if x is not None else "None" return "0x%x" % x if x is not None else "None"
return "PartitionDefinition('%s', 0x%x, 0x%x, %s, %s)" % (self.name, self.type, self.subtype or 0, return "PartitionDefinition('%s', 0x%x, 0x%x, %s, %s)" % (self.name, self.type, self.subtype or 0,
maybe_hex(self.offset), maybe_hex(self.size)) maybe_hex(self.offset), maybe_hex(self.size))
def __str__(self): def __str__(self):
return "Part '%s' %d/%d @ 0x%x size 0x%x" % (self.name, self.type, self.subtype, self.offset or -1, self.size or -1) return "Part '%s' %d/%d @ 0x%x size 0x%x" % (self.name, self.type, self.subtype, self.offset or -1, self.size or -1)
@ -329,7 +333,7 @@ class PartitionDefinition(object):
def parse_subtype(self, strval): def parse_subtype(self, strval):
if strval == "": if strval == "":
return 0 # default return 0 # default
return parse_int(strval, SUBTYPES.get(self.type, {})) return parse_int(strval, SUBTYPES.get(self.type, {}))
def parse_address(self, strval): def parse_address(self, strval):
@ -353,12 +357,14 @@ class PartitionDefinition(object):
raise ValidationError(self, "Size field is not set") raise ValidationError(self, "Size field is not set")
if self.name in TYPES and TYPES.get(self.name, "") != self.type: if self.name in TYPES and TYPES.get(self.name, "") != self.type:
critical("WARNING: Partition has name '%s' which is a partition type, but does not match this partition's type (0x%x). Mistake in partition table?" % (self.name, self.type)) critical("WARNING: Partition has name '%s' which is a partition type, but does not match this partition's "
"type (0x%x). Mistake in partition table?" % (self.name, self.type))
all_subtype_names = [] all_subtype_names = []
for names in (t.keys() for t in SUBTYPES.values()): for names in (t.keys() for t in SUBTYPES.values()):
all_subtype_names += names all_subtype_names += names
if self.name in all_subtype_names and SUBTYPES.get(self.type, {}).get(self.name, "") != self.subtype: if self.name in all_subtype_names and SUBTYPES.get(self.type, {}).get(self.name, "") != self.subtype:
critical("WARNING: Partition has name '%s' which is a partition subtype, but this partition has non-matching type 0x%x and subtype 0x%x. Mistake in partition table?" % (self.name, self.type, self.subtype)) critical("WARNING: Partition has name '%s' which is a partition subtype, but this partition has "
"non-matching type 0x%x and subtype 0x%x. Mistake in partition table?" % (self.name, self.type, self.subtype))
STRUCT_FORMAT = b"<2sBBLL16sL" STRUCT_FORMAT = b"<2sBBLL16sL"
@ -369,21 +375,21 @@ class PartitionDefinition(object):
res = cls() res = cls()
(magic, res.type, res.subtype, res.offset, (magic, res.type, res.subtype, res.offset,
res.size, res.name, flags) = struct.unpack(cls.STRUCT_FORMAT, b) res.size, res.name, flags) = struct.unpack(cls.STRUCT_FORMAT, b)
if b"\x00" in res.name: # strip null byte padding from name string if b"\x00" in res.name: # strip null byte padding from name string
res.name = res.name[:res.name.index(b"\x00")] res.name = res.name[:res.name.index(b"\x00")]
res.name = res.name.decode() res.name = res.name.decode()
if magic != cls.MAGIC_BYTES: if magic != cls.MAGIC_BYTES:
raise InputError("Invalid magic bytes (%r) for partition definition" % magic) raise InputError("Invalid magic bytes (%r) for partition definition" % magic)
for flag,bit in cls.FLAGS.items(): for flag,bit in cls.FLAGS.items():
if flags & (1<<bit): if flags & (1 << bit):
setattr(res, flag, True) setattr(res, flag, True)
flags &= ~(1<<bit) flags &= ~(1 << bit)
if flags != 0: if flags != 0:
critical("WARNING: Partition definition had unknown flag(s) 0x%08x. Newer binary format?" % flags) critical("WARNING: Partition definition had unknown flag(s) 0x%08x. Newer binary format?" % flags)
return res return res
def get_flags_list(self): def get_flags_list(self):
return [ flag for flag in self.FLAGS.keys() if getattr(self, flag) ] return [flag for flag in self.FLAGS.keys() if getattr(self, flag)]
def to_binary(self): def to_binary(self):
flags = sum((1 << self.FLAGS[flag]) for flag in self.get_flags_list()) flags = sum((1 << self.FLAGS[flag]) for flag in self.get_flags_list())
@ -397,14 +403,14 @@ class PartitionDefinition(object):
def to_csv(self, simple_formatting=False): def to_csv(self, simple_formatting=False):
def addr_format(a, include_sizes): def addr_format(a, include_sizes):
if not simple_formatting and include_sizes: if not simple_formatting and include_sizes:
for (val, suffix) in [ (0x100000, "M"), (0x400, "K") ]: for (val, suffix) in [(0x100000, "M"), (0x400, "K")]:
if a % val == 0: if a % val == 0:
return "%d%s" % (a // val, suffix) return "%d%s" % (a // val, suffix)
return "0x%x" % a return "0x%x" % a
def lookup_keyword(t, keywords): def lookup_keyword(t, keywords):
for k,v in keywords.items(): for k,v in keywords.items():
if simple_formatting == False and t == v: if simple_formatting is False and t == v:
return k return k
return "%d" % t return "%d" % t
@ -412,12 +418,12 @@ class PartitionDefinition(object):
""" colon-delimited list of flags """ """ colon-delimited list of flags """
return ":".join(self.get_flags_list()) return ":".join(self.get_flags_list())
return ",".join([ self.name, return ",".join([self.name,
lookup_keyword(self.type, TYPES), lookup_keyword(self.type, TYPES),
lookup_keyword(self.subtype, SUBTYPES.get(self.type, {})), lookup_keyword(self.subtype, SUBTYPES.get(self.type, {})),
addr_format(self.offset, False), addr_format(self.offset, False),
addr_format(self.size, True), addr_format(self.size, True),
generate_text_flags()]) generate_text_flags()])
def parse_int(v, keywords={}): def parse_int(v, keywords={}):
@ -425,7 +431,7 @@ def parse_int(v, keywords={}):
k/m/K/M suffixes and 'keyword' value lookup. k/m/K/M suffixes and 'keyword' value lookup.
""" """
try: try:
for letter, multiplier in [ ("k",1024), ("m",1024*1024) ]: for letter, multiplier in [("k", 1024), ("m", 1024 * 1024)]:
if v.lower().endswith(letter): if v.lower().endswith(letter):
return parse_int(v[:-1], keywords) * multiplier return parse_int(v[:-1], keywords) * multiplier
return int(v, 0) return int(v, 0)
@ -437,6 +443,7 @@ def parse_int(v, keywords={}):
except KeyError: except KeyError:
raise InputError("Value '%s' is not valid. Known keywords: %s" % (v, ", ".join(keywords))) raise InputError("Value '%s' is not valid. Known keywords: %s" % (v, ", ".join(keywords)))
def main(): def main():
global quiet global quiet
global md5sum global md5sum
@ -445,10 +452,11 @@ def main():
parser = argparse.ArgumentParser(description='ESP32 partition table utility') parser = argparse.ArgumentParser(description='ESP32 partition table utility')
parser.add_argument('--flash-size', help='Optional flash size limit, checks partition table fits in flash', parser.add_argument('--flash-size', help='Optional flash size limit, checks partition table fits in flash',
nargs='?', choices=[ '1MB', '2MB', '4MB', '8MB', '16MB' ]) nargs='?', choices=['1MB', '2MB', '4MB', '8MB', '16MB'])
parser.add_argument('--disable-md5sum', help='Disable md5 checksum for the partition table', default=False, action='store_true') parser.add_argument('--disable-md5sum', help='Disable md5 checksum for the partition table', default=False, action='store_true')
parser.add_argument('--no-verify', help="Don't verify partition table fields", action='store_true') parser.add_argument('--no-verify', help="Don't verify partition table fields", action='store_true')
parser.add_argument('--verify', '-v', help="Verify partition table fields (deprecated, this behaviour is enabled by default and this flag does nothing.", action='store_true') parser.add_argument('--verify', '-v', help="Verify partition table fields (deprecated, this behaviour is "
"enabled by default and this flag does nothing.", action='store_true')
parser.add_argument('--quiet', '-q', help="Don't print non-critical status messages to stderr", action='store_true') parser.add_argument('--quiet', '-q', help="Don't print non-critical status messages to stderr", action='store_true')
parser.add_argument('--offset', '-o', help='Set offset partition table', default='0x8000') parser.add_argument('--offset', '-o', help='Set offset partition table', default='0x8000')
parser.add_argument('--secure', help="Require app partitions to be suitable for secure boot", action='store_true') parser.add_argument('--secure', help="Require app partitions to be suitable for secure boot", action='store_true')
@ -481,7 +489,8 @@ def main():
size = size_mb * 1024 * 1024 # flash memory uses honest megabytes! size = size_mb * 1024 * 1024 # flash memory uses honest megabytes!
table_size = table.flash_size() table_size = table.flash_size()
if size < table_size: if size < table_size:
raise InputError("Partitions defined in '%s' occupy %.1fMB of flash (%d bytes) which does not fit in configured flash size %dMB. Change the flash size in menuconfig under the 'Serial Flasher Config' menu." % raise InputError("Partitions defined in '%s' occupy %.1fMB of flash (%d bytes) which does not fit in configured "
"flash size %dMB. Change the flash size in menuconfig under the 'Serial Flasher Config' menu." %
(args.input.name, table_size / 1024.0 / 1024.0, table_size, size_mb)) (args.input.name, table_size / 1024.0 / 1024.0, table_size, size_mb))
# Make sure that the output directory is created # Make sure that the output directory is created
@ -490,7 +499,7 @@ def main():
if not os.path.exists(output_dir): if not os.path.exists(output_dir):
try: try:
os.makedirs(output_dir) os.makedirs(output_dir)
except OSError as exc: except OSError as exc:
if exc.errno != errno.EEXIST: if exc.errno != errno.EEXIST:
raise raise

View file

@ -8,8 +8,13 @@ import subprocess
import tempfile import tempfile
import os import os
import io import io
sys.path.append("..") import re
from gen_esp32part import *
try:
import gen_esp32part
except ImportError:
sys.path.append("..")
import gen_esp32part
SIMPLE_CSV = """ SIMPLE_CSV = """
# Name,Type,SubType,Offset,Size,Flags # Name,Type,SubType,Offset,Size,Flags
@ -22,21 +27,21 @@ LONGER_BINARY_TABLE = b""
LONGER_BINARY_TABLE += b"\xAA\x50\x00\x00" + \ LONGER_BINARY_TABLE += b"\xAA\x50\x00\x00" + \
b"\x00\x00\x01\x00" + \ b"\x00\x00\x01\x00" + \
b"\x00\x00\x10\x00" + \ b"\x00\x00\x10\x00" + \
b"factory\0" + (b"\0"*8) + \ b"factory\0" + (b"\0" * 8) + \
b"\x00\x00\x00\x00" b"\x00\x00\x00\x00"
# type 0x01, subtype 0x20, # type 0x01, subtype 0x20,
# offset 0x110000, size 128KB # offset 0x110000, size 128KB
LONGER_BINARY_TABLE += b"\xAA\x50\x01\x20" + \ LONGER_BINARY_TABLE += b"\xAA\x50\x01\x20" + \
b"\x00\x00\x11\x00" + \ b"\x00\x00\x11\x00" + \
b"\x00\x02\x00\x00" + \ b"\x00\x02\x00\x00" + \
b"data" + (b"\0"*12) + \ b"data" + (b"\0" * 12) + \
b"\x00\x00\x00\x00" b"\x00\x00\x00\x00"
# type 0x10, subtype 0x00, # type 0x10, subtype 0x00,
# offset 0x150000, size 1MB # offset 0x150000, size 1MB
LONGER_BINARY_TABLE += b"\xAA\x50\x10\x00" + \ LONGER_BINARY_TABLE += b"\xAA\x50\x10\x00" + \
b"\x00\x00\x15\x00" + \ b"\x00\x00\x15\x00" + \
b"\x00\x10\x00\x00" + \ b"\x00\x10\x00\x00" + \
b"second" + (b"\0"*10) + \ b"second" + (b"\0" * 10) + \
b"\x00\x00\x00\x00" b"\x00\x00\x00\x00"
# MD5 checksum # MD5 checksum
LONGER_BINARY_TABLE += b"\xEB\xEB" + b"\xFF" * 14 LONGER_BINARY_TABLE += b"\xEB\xEB" + b"\xFF" * 14
@ -49,10 +54,11 @@ def _strip_trailing_ffs(binary_table):
""" """
Strip all FFs down to the last 32 bytes (terminating entry) Strip all FFs down to the last 32 bytes (terminating entry)
""" """
while binary_table.endswith(b"\xFF"*64): while binary_table.endswith(b"\xFF" * 64):
binary_table = binary_table[0:len(binary_table)-32] binary_table = binary_table[0:len(binary_table) - 32]
return binary_table return binary_table
class Py23TestCase(unittest.TestCase): class Py23TestCase(unittest.TestCase):
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
@ -64,10 +70,11 @@ class Py23TestCase(unittest.TestCase):
# This fix is used in order to avoid using the alias from the six library # This fix is used in order to avoid using the alias from the six library
self.assertRaisesRegex = self.assertRaisesRegexp self.assertRaisesRegex = self.assertRaisesRegexp
class CSVParserTests(Py23TestCase): class CSVParserTests(Py23TestCase):
def test_simple_partition(self): def test_simple_partition(self):
table = PartitionTable.from_csv(SIMPLE_CSV) table = gen_esp32part.PartitionTable.from_csv(SIMPLE_CSV)
self.assertEqual(len(table), 1) self.assertEqual(len(table), 1)
self.assertEqual(table[0].name, "factory") self.assertEqual(table[0].name, "factory")
self.assertEqual(table[0].type, 0) self.assertEqual(table[0].type, 0)
@ -75,15 +82,13 @@ class CSVParserTests(Py23TestCase):
self.assertEqual(table[0].offset, 65536) self.assertEqual(table[0].offset, 65536)
self.assertEqual(table[0].size, 1048576) self.assertEqual(table[0].size, 1048576)
def test_require_type(self): def test_require_type(self):
csv = """ csv = """
# Name,Type, SubType,Offset,Size # Name,Type, SubType,Offset,Size
ihavenotype, ihavenotype,
""" """
with self.assertRaisesRegex(InputError, "type"): with self.assertRaisesRegex(gen_esp32part.InputError, "type"):
PartitionTable.from_csv(csv) gen_esp32part.PartitionTable.from_csv(csv)
def test_type_subtype_names(self): def test_type_subtype_names(self):
csv_magicnumbers = """ csv_magicnumbers = """
@ -106,9 +111,9 @@ myota_status, data, ota,, 0x100000
""" """
# make two equivalent partition tables, one using # make two equivalent partition tables, one using
# magic numbers and one using shortcuts. Ensure they match # magic numbers and one using shortcuts. Ensure they match
magic = PartitionTable.from_csv(csv_magicnumbers) magic = gen_esp32part.PartitionTable.from_csv(csv_magicnumbers)
magic.verify() magic.verify()
nomagic = PartitionTable.from_csv(csv_nomagicnumbers) nomagic = gen_esp32part.PartitionTable.from_csv(csv_nomagicnumbers)
nomagic.verify() nomagic.verify()
self.assertEqual(nomagic["myapp"].type, 0) self.assertEqual(nomagic["myapp"].type, 0)
@ -121,17 +126,17 @@ myota_status, data, ota,, 0x100000
self.assertEqual(nomagic["mytest"], magic["mytest"]) self.assertEqual(nomagic["mytest"], magic["mytest"])
self.assertEqual(nomagic["myota_status"], magic["myota_status"]) self.assertEqual(nomagic["myota_status"], magic["myota_status"])
#self.assertEqual(nomagic.to_binary(), magic.to_binary()) # self.assertEqual(nomagic.to_binary(), magic.to_binary())
def test_unit_suffixes(self): def test_unit_suffixes(self):
csv = """ csv = """
# Name, Type, Subtype, Offset, Size # Name, Type, Subtype, Offset, Size
one_megabyte, app, factory, 64k, 1M one_megabyte, app, factory, 64k, 1M
""" """
t = PartitionTable.from_csv(csv) t = gen_esp32part.PartitionTable.from_csv(csv)
t.verify() t.verify()
self.assertEqual(t[0].offset, 64*1024) self.assertEqual(t[0].offset, 64 * 1024)
self.assertEqual(t[0].size, 1*1024*1024) self.assertEqual(t[0].size, 1 * 1024 * 1024)
def test_default_offsets(self): def test_default_offsets(self):
csv = """ csv = """
@ -141,17 +146,17 @@ second, data, 0x15,, 1M
minidata, data, 0x40,, 32K minidata, data, 0x40,, 32K
otherapp, app, factory,, 1M otherapp, app, factory,, 1M
""" """
t = PartitionTable.from_csv(csv) t = gen_esp32part.PartitionTable.from_csv(csv)
# 'first' # 'first'
self.assertEqual(t[0].offset, 0x010000) # 64KB boundary as it's an app image self.assertEqual(t[0].offset, 0x010000) # 64KB boundary as it's an app image
self.assertEqual(t[0].size, 0x100000) # Size specified in CSV self.assertEqual(t[0].size, 0x100000) # Size specified in CSV
# 'second' # 'second'
self.assertEqual(t[1].offset, 0x110000) # prev offset+size self.assertEqual(t[1].offset, 0x110000) # prev offset+size
self.assertEqual(t[1].size, 0x100000) # Size specified in CSV self.assertEqual(t[1].size, 0x100000) # Size specified in CSV
# 'minidata' # 'minidata'
self.assertEqual(t[2].offset, 0x210000) self.assertEqual(t[2].offset, 0x210000)
# 'otherapp' # 'otherapp'
self.assertEqual(t[3].offset, 0x220000) # 64KB boundary as it's an app image self.assertEqual(t[3].offset, 0x220000) # 64KB boundary as it's an app image
def test_negative_size_to_offset(self): def test_negative_size_to_offset(self):
csv = """ csv = """
@ -159,21 +164,21 @@ otherapp, app, factory,, 1M
first, app, factory, 0x10000, -2M first, app, factory, 0x10000, -2M
second, data, 0x15, , 1M second, data, 0x15, , 1M
""" """
t = PartitionTable.from_csv(csv) t = gen_esp32part.PartitionTable.from_csv(csv)
t.verify() t.verify()
# 'first' # 'first'
self.assertEqual(t[0].offset, 0x10000) # in CSV self.assertEqual(t[0].offset, 0x10000) # in CSV
self.assertEqual(t[0].size, 0x200000 - t[0].offset) # Up to 2M self.assertEqual(t[0].size, 0x200000 - t[0].offset) # Up to 2M
# 'second' # 'second'
self.assertEqual(t[1].offset, 0x200000) # prev offset+size self.assertEqual(t[1].offset, 0x200000) # prev offset+size
def test_overlapping_offsets_fail(self): def test_overlapping_offsets_fail(self):
csv = """ csv = """
first, app, factory, 0x100000, 2M first, app, factory, 0x100000, 2M
second, app, ota_0, 0x200000, 1M second, app, ota_0, 0x200000, 1M
""" """
with self.assertRaisesRegex(InputError, "overlap"): with self.assertRaisesRegex(gen_esp32part.InputError, "overlap"):
t = PartitionTable.from_csv(csv) t = gen_esp32part.PartitionTable.from_csv(csv)
t.verify() t.verify()
def test_unique_name_fail(self): def test_unique_name_fail(self):
@ -181,23 +186,24 @@ second, app, ota_0, 0x200000, 1M
first, app, factory, 0x100000, 1M first, app, factory, 0x100000, 1M
first, app, ota_0, 0x200000, 1M first, app, ota_0, 0x200000, 1M
""" """
with self.assertRaisesRegex(InputError, "Partition names must be unique"): with self.assertRaisesRegex(gen_esp32part.InputError, "Partition names must be unique"):
t = PartitionTable.from_csv(csv) t = gen_esp32part.PartitionTable.from_csv(csv)
t.verify() t.verify()
class BinaryOutputTests(Py23TestCase): class BinaryOutputTests(Py23TestCase):
def test_binary_entry(self): def test_binary_entry(self):
csv = """ csv = """
first, 0x30, 0xEE, 0x100400, 0x300000 first, 0x30, 0xEE, 0x100400, 0x300000
""" """
t = PartitionTable.from_csv(csv) t = gen_esp32part.PartitionTable.from_csv(csv)
tb = _strip_trailing_ffs(t.to_binary()) tb = _strip_trailing_ffs(t.to_binary())
self.assertEqual(len(tb), 64+32) self.assertEqual(len(tb), 64 + 32)
self.assertEqual(b'\xAA\x50', tb[0:2]) # magic self.assertEqual(b'\xAA\x50', tb[0:2]) # magic
self.assertEqual(b'\x30\xee', tb[2:4]) # type, subtype self.assertEqual(b'\x30\xee', tb[2:4]) # type, subtype
eo, es = struct.unpack("<LL", tb[4:12]) eo, es = struct.unpack("<LL", tb[4:12])
self.assertEqual(eo, 0x100400) # offset self.assertEqual(eo, 0x100400) # offset
self.assertEqual(es, 0x300000) # size self.assertEqual(es, 0x300000) # size
self.assertEqual(b"\xEB\xEB" + b"\xFF" * 14, tb[32:48]) self.assertEqual(b"\xEB\xEB" + b"\xFF" * 14, tb[32:48])
self.assertEqual(b'\x43\x03\x3f\x33\x40\x87\x57\x51\x69\x83\x9b\x40\x61\xb1\x27\x26', tb[48:64]) self.assertEqual(b'\x43\x03\x3f\x33\x40\x87\x57\x51\x69\x83\x9b\x40\x61\xb1\x27\x26', tb[48:64])
@ -206,22 +212,21 @@ first, 0x30, 0xEE, 0x100400, 0x300000
first, 0x30, 0xEE, 0x100400, 0x300000 first, 0x30, 0xEE, 0x100400, 0x300000
second,0x31, 0xEF, , 0x100000 second,0x31, 0xEF, , 0x100000
""" """
t = PartitionTable.from_csv(csv) t = gen_esp32part.PartitionTable.from_csv(csv)
tb = _strip_trailing_ffs(t.to_binary()) tb = _strip_trailing_ffs(t.to_binary())
self.assertEqual(len(tb), 96+32) self.assertEqual(len(tb), 96 + 32)
self.assertEqual(b'\xAA\x50', tb[0:2]) self.assertEqual(b'\xAA\x50', tb[0:2])
self.assertEqual(b'\xAA\x50', tb[32:34]) self.assertEqual(b'\xAA\x50', tb[32:34])
def test_encrypted_flag(self): def test_encrypted_flag(self):
csv = """ csv = """
# Name, Type, Subtype, Offset, Size, Flags # Name, Type, Subtype, Offset, Size, Flags
first, app, factory,, 1M, encrypted first, app, factory,, 1M, encrypted
""" """
t = PartitionTable.from_csv(csv) t = gen_esp32part.PartitionTable.from_csv(csv)
self.assertTrue(t[0].encrypted) self.assertTrue(t[0].encrypted)
tb = _strip_trailing_ffs(t.to_binary()) tb = _strip_trailing_ffs(t.to_binary())
tr = PartitionTable.from_binary(tb) tr = gen_esp32part.PartitionTable.from_binary(tb)
self.assertTrue(tr[0].encrypted) self.assertTrue(tr[0].encrypted)
@ -237,11 +242,11 @@ class BinaryParserTests(Py23TestCase):
b"\xFF" * 32 b"\xFF" * 32
# verify that parsing 32 bytes as a table # verify that parsing 32 bytes as a table
# or as a single Definition are the same thing # or as a single Definition are the same thing
t = PartitionTable.from_binary(entry) t = gen_esp32part.PartitionTable.from_binary(entry)
self.assertEqual(len(t), 1) self.assertEqual(len(t), 1)
t[0].verify() t[0].verify()
e = PartitionDefinition.from_binary(entry[:32]) e = gen_esp32part.PartitionDefinition.from_binary(entry[:32])
self.assertEqual(t[0], e) self.assertEqual(t[0], e)
e.verify() e.verify()
@ -252,14 +257,14 @@ class BinaryParserTests(Py23TestCase):
self.assertEqual(e.name, "0123456789abc") self.assertEqual(e.name, "0123456789abc")
def test_multiple_entries(self): def test_multiple_entries(self):
t = PartitionTable.from_binary(LONGER_BINARY_TABLE) t = gen_esp32part.PartitionTable.from_binary(LONGER_BINARY_TABLE)
t.verify() t.verify()
self.assertEqual(3, len(t)) self.assertEqual(3, len(t))
self.assertEqual(t[0].type, APP_TYPE) self.assertEqual(t[0].type, gen_esp32part.APP_TYPE)
self.assertEqual(t[0].name, "factory") self.assertEqual(t[0].name, "factory")
self.assertEqual(t[1].type, DATA_TYPE) self.assertEqual(t[1].type, gen_esp32part.DATA_TYPE)
self.assertEqual(t[1].name, "data") self.assertEqual(t[1].name, "data")
self.assertEqual(t[2].type, 0x10) self.assertEqual(t[2].type, 0x10)
@ -274,16 +279,16 @@ class BinaryParserTests(Py23TestCase):
b"\x00\x00\x20\x00" + \ b"\x00\x00\x20\x00" + \
b"0123456789abc\0\0\0" + \ b"0123456789abc\0\0\0" + \
b"\x00\x00\x00\x00" b"\x00\x00\x00\x00"
with self.assertRaisesRegex(InputError, "Invalid magic bytes"): with self.assertRaisesRegex(gen_esp32part.InputError, "Invalid magic bytes"):
PartitionTable.from_binary(bad_magic) gen_esp32part.PartitionTable.from_binary(bad_magic)
def test_bad_length(self): def test_bad_length(self):
bad_length = b"OHAI" + \ bad_length = b"OHAI" + \
b"\x00\x00\x10\x00" + \ b"\x00\x00\x10\x00" + \
b"\x00\x00\x20\x00" + \ b"\x00\x00\x20\x00" + \
b"0123456789" b"0123456789"
with self.assertRaisesRegex(InputError, "32 bytes"): with self.assertRaisesRegex(gen_esp32part.InputError, "32 bytes"):
PartitionTable.from_binary(bad_length) gen_esp32part.PartitionTable.from_binary(bad_length)
class CSVOutputTests(Py23TestCase): class CSVOutputTests(Py23TestCase):
@ -292,7 +297,7 @@ class CSVOutputTests(Py23TestCase):
return list(csv.reader(source_str.split("\n"))) return list(csv.reader(source_str.split("\n")))
def test_output_simple_formatting(self): def test_output_simple_formatting(self):
table = PartitionTable.from_csv(SIMPLE_CSV) table = gen_esp32part.PartitionTable.from_csv(SIMPLE_CSV)
as_csv = table.to_csv(True) as_csv = table.to_csv(True)
c = self._readcsv(as_csv) c = self._readcsv(as_csv)
# first two lines should start with comments # first two lines should start with comments
@ -302,15 +307,15 @@ class CSVOutputTests(Py23TestCase):
self.assertEqual(row[0], "factory") self.assertEqual(row[0], "factory")
self.assertEqual(row[1], "0") self.assertEqual(row[1], "0")
self.assertEqual(row[2], "2") self.assertEqual(row[2], "2")
self.assertEqual(row[3], "0x10000") # reformatted as hex self.assertEqual(row[3], "0x10000") # reformatted as hex
self.assertEqual(row[4], "0x100000") # also hex self.assertEqual(row[4], "0x100000") # also hex
# round trip back to a PartitionTable and check is identical # round trip back to a PartitionTable and check is identical
roundtrip = PartitionTable.from_csv(as_csv) roundtrip = gen_esp32part.PartitionTable.from_csv(as_csv)
self.assertEqual(roundtrip, table) self.assertEqual(roundtrip, table)
def test_output_smart_formatting(self): def test_output_smart_formatting(self):
table = PartitionTable.from_csv(SIMPLE_CSV) table = gen_esp32part.PartitionTable.from_csv(SIMPLE_CSV)
as_csv = table.to_csv(False) as_csv = table.to_csv(False)
c = self._readcsv(as_csv) c = self._readcsv(as_csv)
# first two lines should start with comments # first two lines should start with comments
@ -324,9 +329,10 @@ class CSVOutputTests(Py23TestCase):
self.assertEqual(row[4], "1M") self.assertEqual(row[4], "1M")
# round trip back to a PartitionTable and check is identical # round trip back to a PartitionTable and check is identical
roundtrip = PartitionTable.from_csv(as_csv) roundtrip = gen_esp32part.PartitionTable.from_csv(as_csv)
self.assertEqual(roundtrip, table) self.assertEqual(roundtrip, table)
class CommandLineTests(Py23TestCase): class CommandLineTests(Py23TestCase):
def test_basic_cmdline(self): def test_basic_cmdline(self):
@ -340,11 +346,11 @@ class CommandLineTests(Py23TestCase):
# run gen_esp32part.py to convert binary file to CSV # run gen_esp32part.py to convert binary file to CSV
output = subprocess.check_output([sys.executable, "../gen_esp32part.py", output = subprocess.check_output([sys.executable, "../gen_esp32part.py",
binpath, csvpath], stderr=subprocess.STDOUT) binpath, csvpath], stderr=subprocess.STDOUT)
# reopen the CSV and check the generated binary is identical # reopen the CSV and check the generated binary is identical
self.assertNotIn(b"WARNING", output) self.assertNotIn(b"WARNING", output)
with open(csvpath, 'r') as f: with open(csvpath, 'r') as f:
from_csv = PartitionTable.from_csv(f.read()) from_csv = gen_esp32part.PartitionTable.from_csv(f.read())
self.assertEqual(_strip_trailing_ffs(from_csv.to_binary()), LONGER_BINARY_TABLE) self.assertEqual(_strip_trailing_ffs(from_csv.to_binary()), LONGER_BINARY_TABLE)
# run gen_esp32part.py to conver the CSV to binary again # run gen_esp32part.py to conver the CSV to binary again
@ -372,30 +378,29 @@ class VerificationTests(Py23TestCase):
# Name,Type, SubType,Offset,Size # Name,Type, SubType,Offset,Size
app,app, factory, 32K, 1M app,app, factory, 32K, 1M
""" """
with self.assertRaisesRegex(ValidationError, with self.assertRaisesRegex(gen_esp32part.ValidationError, r"Offset.+not aligned"):
r"Offset.+not aligned"): t = gen_esp32part.PartitionTable.from_csv(csv)
t = PartitionTable.from_csv(csv)
t.verify() t.verify()
def test_warnings(self): def test_warnings(self):
try: try:
sys.stderr = io.StringIO() # capture stderr sys.stderr = io.StringIO() # capture stderr
csv_1 = "app, 1, 2, 32K, 1M\n" csv_1 = "app, 1, 2, 32K, 1M\n"
PartitionTable.from_csv(csv_1).verify() gen_esp32part.PartitionTable.from_csv(csv_1).verify()
self.assertIn("WARNING", sys.stderr.getvalue()) self.assertIn("WARNING", sys.stderr.getvalue())
self.assertIn("partition type", sys.stderr.getvalue()) self.assertIn("partition type", sys.stderr.getvalue())
sys.stderr = io.StringIO() sys.stderr = io.StringIO()
csv_2 = "ota_0, app, ota_1, , 1M\n" csv_2 = "ota_0, app, ota_1, , 1M\n"
PartitionTable.from_csv(csv_2).verify() gen_esp32part.PartitionTable.from_csv(csv_2).verify()
self.assertIn("WARNING", sys.stderr.getvalue()) self.assertIn("WARNING", sys.stderr.getvalue())
self.assertIn("partition subtype", sys.stderr.getvalue()) self.assertIn("partition subtype", sys.stderr.getvalue())
finally: finally:
sys.stderr = sys.__stderr__ sys.stderr = sys.__stderr__
class PartToolTests(Py23TestCase): class PartToolTests(Py23TestCase):
def _run_parttool(self, csvcontents, args, info): def _run_parttool(self, csvcontents, args, info):
@ -403,8 +408,9 @@ class PartToolTests(Py23TestCase):
with open(csvpath, "w") as f: with open(csvpath, "w") as f:
f.write(csvcontents) f.write(csvcontents)
try: try:
output = subprocess.check_output([sys.executable, "../parttool.py"] + args.split(" ") output = subprocess.check_output([sys.executable, "../parttool.py"] + args.split(" ")
+ ["--partition-table-file", csvpath , "get_partition_info", "--info", info], stderr=subprocess.STDOUT) + ["--partition-table-file", csvpath, "get_partition_info", "--info", info],
stderr=subprocess.STDOUT)
self.assertNotIn(b"WARNING", output) self.assertNotIn(b"WARNING", output)
m = re.search(b"0x[0-9a-fA-F]+", output) m = re.search(b"0x[0-9a-fA-F]+", output)
return m.group(0) if m else "" return m.group(0) if m else ""
@ -418,7 +424,9 @@ otadata, data, ota, 0xd000, 0x2000
phy_init, data, phy, 0xf000, 0x1000 phy_init, data, phy, 0xf000, 0x1000
factory, app, factory, 0x10000, 1M factory, app, factory, 0x10000, 1M
""" """
rpt = lambda args, info: self._run_parttool(csv, args, info)
def rpt(args, info):
return self._run_parttool(csv, args, info)
self.assertEqual( self.assertEqual(
rpt("--partition-type=data --partition-subtype=nvs -q", "offset"), b"0x9000") rpt("--partition-type=data --partition-subtype=nvs -q", "offset"), b"0x9000")
@ -437,7 +445,9 @@ phy_init, data, phy, 0xf000, 0x1000
ota_0, app, ota_0, 0x30000, 1M ota_0, app, ota_0, 0x30000, 1M
ota_1, app, ota_1, , 1M ota_1, app, ota_1, , 1M
""" """
rpt = lambda args, info: self._run_parttool(csv, args, info)
def rpt(args, info):
return self._run_parttool(csv, args, info)
self.assertEqual( self.assertEqual(
rpt("--partition-type=app --partition-subtype=ota_1 -q", "offset"), b"0x130000") rpt("--partition-type=app --partition-subtype=ota_1 -q", "offset"), b"0x130000")
@ -448,5 +458,6 @@ ota_1, app, ota_1, , 1M
self._run_parttool(csv_mod, "--partition-boot-default -q", "offset"), self._run_parttool(csv_mod, "--partition-boot-default -q", "offset"),
b"0x130000") # now default is ota_1 b"0x130000") # now default is ota_1
if __name__ =="__main__":
if __name__ == "__main__":
unittest.main() unittest.main()

View file

@ -7,27 +7,28 @@
from optparse import OptionParser from optparse import OptionParser
BASE_ADDR = 0x50000000; BASE_ADDR = 0x50000000
def gen_ld_h_from_sym(f_sym, f_ld, f_h): def gen_ld_h_from_sym(f_sym, f_ld, f_h):
f_ld.write("/* Variable definitions for ESP32ULP linker\n"); f_ld.write("/* Variable definitions for ESP32ULP linker\n")
f_ld.write(" * This file is generated automatically by esp32ulp_mapgen.py utility.\n"); f_ld.write(" * This file is generated automatically by esp32ulp_mapgen.py utility.\n")
f_ld.write(" */\n\n"); f_ld.write(" */\n\n")
f_h.write("// Variable definitions for ESP32ULP\n"); f_h.write("// Variable definitions for ESP32ULP\n")
f_h.write("// This file is generated automatically by esp32ulp_mapgen.py utility\n\n"); f_h.write("// This file is generated automatically by esp32ulp_mapgen.py utility\n\n")
f_h.write("#pragma once\n\n"); f_h.write("#pragma once\n\n")
for line in f_sym: for line in f_sym:
name, _, addr_str = line.split() name, _, addr_str = line.split()
addr = int(addr_str, 16) + BASE_ADDR; addr = int(addr_str, 16) + BASE_ADDR
f_h.write("extern uint32_t ulp_{0};\n".format(name)); f_h.write("extern uint32_t ulp_{0};\n".format(name))
f_ld.write("PROVIDE ( ulp_{0} = 0x{1:08x} );\n".format(name, addr)) f_ld.write("PROVIDE ( ulp_{0} = 0x{1:08x} );\n".format(name, addr))
def main(): def main():
description = ( "This application generates .h and .ld files for symbols defined in input file. " description = ("This application generates .h and .ld files for symbols defined in input file. "
"The input symbols file can be generated using nm utility like this: " "The input symbols file can be generated using nm utility like this: "
"esp32-ulp-nm -g -f posix <elf_file> > <symbols_file>" ); "esp32-ulp-nm -g -f posix <elf_file> > <symbols_file>")
parser = OptionParser(description=description) parser = OptionParser(description=description)
parser.add_option("-s", "--symfile", dest="symfile", parser.add_option("-s", "--symfile", dest="symfile",
@ -44,11 +45,10 @@ def main():
parser.print_help() parser.print_help()
return 1 return 1
with open(options.outputfile + ".h", 'w') as f_h, \ with open(options.outputfile + ".h", 'w') as f_h, open(options.outputfile + ".ld", 'w') as f_ld, open(options.symfile) as f_sym:
open(options.outputfile + ".ld", 'w') as f_ld, \
open(options.symfile) as f_sym: \
gen_ld_h_from_sym(f_sym, f_ld, f_h) gen_ld_h_from_sym(f_sym, f_ld, f_h)
return 0 return 0
if __name__ == "__main__": if __name__ == "__main__":
exit(main()); exit(main())