BA / Mbed OS BaBoRo1
Embed: (wiki syntax)

« Back to documentation index

Show/hide line numbers __init__.py Source File

__init__.py

00001 #!/usr/bin/env python
00002 """
00003  mbed
00004  Copyright (c) 2017-2017 ARM Limited
00005 
00006  Licensed under the Apache License, Version 2.0 (the "License");
00007  you may not use this file except in compliance with the License.
00008  You may obtain a copy of the License at
00009 
00010      http://www.apache.org/licenses/LICENSE-2.0
00011 
00012  Unless required by applicable law or agreed to in writing, software
00013  distributed under the License is distributed on an "AS IS" BASIS,
00014  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
00015  See the License for the specific language governing permissions and
00016  limitations under the License.
00017 """
00018 
00019 from __future__ import print_function
00020 import os
00021 import struct
00022 import binascii
00023 import argparse
00024 import logging
00025 try:
00026     from StringIO import StringIO
00027 except ImportError:
00028     from io import StringIO
00029 import jinja2
00030 from collections import namedtuple
00031 from itertools import count
00032 
00033 from elftools.common.py3compat import bytes2str
00034 from elftools.elf.elffile import ELFFile
00035 from elftools.elf.sections import SymbolTableSection
00036 
00037 logger = logging.getLogger(__name__)
00038 logger.addHandler(logging.NullHandler())
00039 
00040 
00041 def main():
00042     parser = argparse.ArgumentParser(description="Algo Extracter")
00043     parser.add_argument("input", help="File to extract flash algo from")
00044     parser.add_argument("template", default="py_blob.tmpl",
00045                         help="Template to use")
00046     parser.add_argument("output", help="Output file")
00047     args = parser.parse_args()
00048 
00049     with open(args.input, "rb") as file_handle:
00050         data = file_handle.read()
00051     algo = PackFlashAlgo(data)
00052     algo.process_template(args.template, args.output)
00053 
00054 
00055 class PackFlashAlgo (object):
00056     """
00057     Class to wrap a flash algo
00058 
00059     This class is intended to provide easy access to the information
00060     provided by a flash algorithm, such as symbols and the flash
00061     algorithm itself.
00062     """
00063 
00064     REQUIRED_SYMBOLS = set([
00065         "Init",
00066         "UnInit",
00067         "EraseSector",
00068         "ProgramPage",
00069     ])
00070 
00071     EXTRA_SYMBOLS = set([
00072         "BlankCheck",
00073         "EraseChip",
00074         "Verify",
00075     ])
00076 
00077     def __init__ (self, data):
00078         """Construct a PackFlashAlgorithm from an ElfFileSimple"""
00079         self.elf  = ElfFileSimple(data)
00080         self.flash_info  = PackFlashInfo(self.elf )
00081 
00082         self.flash_start  = self.flash_info .start
00083         self.flash_size  = self.flash_info .size
00084         self.page_size  = self.flash_info .page_size
00085         self.sector_sizes  = self.flash_info .sector_info_list
00086 
00087         symbols = {}
00088         symbols.update(_extract_symbols(self.elf , self.REQUIRED_SYMBOLS ))
00089         symbols.update(_extract_symbols(self.elf , self.EXTRA_SYMBOLS ,
00090                                         default=0xFFFFFFFF))
00091         self.symbols  = symbols
00092 
00093         sections_to_find = (
00094             ("PrgCode", "SHT_PROGBITS"),
00095             ("PrgData", "SHT_PROGBITS"),
00096             ("PrgData", "SHT_NOBITS"),
00097         )
00098 
00099         ro_rw_zi = _find_sections(self.elf , sections_to_find)
00100         ro_rw_zi = _algo_fill_zi_if_missing(ro_rw_zi)
00101         error_msg = _algo_check_for_section_problems(ro_rw_zi)
00102         if error_msg is not None:
00103             raise Exception(error_msg)
00104 
00105         sect_ro, sect_rw, sect_zi = ro_rw_zi
00106         self.ro_start  = sect_ro["sh_addr"]
00107         self.ro_size  = sect_ro["sh_size"]
00108         self.rw_start  = sect_rw["sh_addr"]
00109         self.rw_size  = sect_rw["sh_size"]
00110         self.zi_start  = sect_zi["sh_addr"]
00111         self.zi_size  = sect_zi["sh_size"]
00112 
00113         self.algo_data  = _create_algo_bin(ro_rw_zi)
00114 
00115     def format_algo_data (self, spaces, group_size, fmt):
00116         """"
00117         Return a string representing algo_data suitable for use in a template
00118 
00119         The string is intended for use in a template.
00120 
00121         :param spaces: The number of leading spaces for each line
00122         :param group_size: number of elements per line (element type
00123             depends of format)
00124         :param fmt: - format to create - can be either "hex" or "c"
00125         """
00126         padding = " " * spaces
00127         if fmt == "hex":
00128             blob = binascii.b2a_hex(self.algo_data )
00129             line_list = []
00130             for i in range(0, len(blob), group_size):
00131                 line_list.append('"' + blob[i:i + group_size] + '"')
00132             return ("\n" + padding).join(line_list)
00133         elif fmt == "c":
00134             blob = self.algo_data [:]
00135             pad_size = 0 if len(blob) % 4 == 0 else 4 - len(blob) % 4
00136             blob = blob + "\x00" * pad_size
00137             integer_list = struct.unpack("<" + "L" * (len(blob) / 4), blob)
00138             line_list = []
00139             for pos in range(0, len(integer_list), group_size):
00140                 group = ["0x%08x" % value for value in
00141                          integer_list[pos:pos + group_size]]
00142                 line_list.append(", ".join(group))
00143             return (",\n" + padding).join(line_list)
00144         else:
00145             raise Exception("Unsupported format %s" % fmt)
00146 
00147     def process_template (self, template_path, output_path, data_dict=None):
00148         """
00149         Generate output from the supplied template
00150 
00151         All the public methods and fields of this class can be accessed from
00152         the template via "algo".
00153 
00154         :param template_path: Relative or absolute file path to the template
00155         :param output_path: Relative or absolute file path to create
00156         :param data_dict: Additional data to use when generating
00157         """
00158         if data_dict is None:
00159             data_dict = {}
00160         else:
00161             assert isinstance(data_dict, dict)
00162             data_dict = dict(data_dict)
00163         assert "algo" not in data_dict, "algo already set by user data"
00164         data_dict["algo"] = self
00165 
00166         with open(template_path) as file_handle:
00167             template_text = file_handle.read()
00168 
00169         template = jinja2.Template(template_text)
00170         target_text = template.render(data_dict)
00171 
00172         with open(output_path, "wb") as file_handle:
00173             file_handle.write(target_text)
00174 
00175 
00176 def _extract_symbols(simple_elf, symbols, default=None):
00177     """Fill 'symbols' field with required flash algo symbols"""
00178     to_ret = {}
00179     for symbol in symbols:
00180         if symbol not in simple_elf.symbols:
00181             if default is not None:
00182                 to_ret[symbol] = default
00183                 continue
00184             raise Exception("Missing symbol %s" % symbol)
00185         to_ret[symbol] = simple_elf.symbols[symbol].value
00186     return to_ret
00187 
00188 
00189 def _find_sections(elf, name_type_pairs):
00190     """Return a list of sections the same length and order of the input list"""
00191     sections = [None] * len(name_type_pairs)
00192     for section in elf.iter_sections():
00193         section_name = bytes2str(section.name)
00194         section_type = section["sh_type"]
00195         for i, name_and_type in enumerate(name_type_pairs):
00196             if name_and_type != (section_name, section_type):
00197                 continue
00198             if sections[i] is not None:
00199                 raise Exception("Elf contains duplicate section %s attr %s" %
00200                                 (section_name, section_type))
00201             sections[i] = section
00202     return sections
00203 
00204 
00205 def _algo_fill_zi_if_missing(ro_rw_zi):
00206     """Create an empty zi section if it is missing"""
00207     s_ro, s_rw, s_zi = ro_rw_zi
00208     if s_rw is None:
00209         return ro_rw_zi
00210     if s_zi is not None:
00211         return ro_rw_zi
00212     s_zi = {
00213         "sh_addr": s_rw["sh_addr"] + s_rw["sh_size"],
00214         "sh_size": 0
00215     }
00216     return s_ro, s_rw, s_zi
00217 
00218 
00219 def _algo_check_for_section_problems(ro_rw_zi):
00220     """Return a string describing any errors with the layout or None if good"""
00221     s_ro, s_rw, s_zi = ro_rw_zi
00222     if s_ro is None:
00223         return "RO section is missing"
00224     if s_rw is None:
00225         return "RW section is missing"
00226     if s_zi is None:
00227         return "ZI section is missing"
00228     if s_ro["sh_addr"] != 0:
00229         return "RO section does not start at address 0"
00230     if s_ro["sh_addr"] + s_ro["sh_size"] != s_rw["sh_addr"]:
00231         return "RW section does not follow RO section"
00232     if s_rw["sh_addr"] + s_rw["sh_size"] != s_zi["sh_addr"]:
00233         return "ZI section does not follow RW section"
00234     return None
00235 
00236 
00237 def _create_algo_bin(ro_rw_zi):
00238     """Create a binary blob of the flash algo which can execute from ram"""
00239     sect_ro, sect_rw, sect_zi = ro_rw_zi
00240     algo_size = sect_ro["sh_size"] + sect_rw["sh_size"] + sect_zi["sh_size"]
00241     algo_data = bytearray(algo_size)
00242     for section in (sect_ro, sect_rw):
00243         start = section["sh_addr"]
00244         size = section["sh_size"]
00245         data = section.data()
00246         assert len(data) == size
00247         algo_data[start:start + size] = data
00248     return algo_data
00249 
00250 
00251 class PackFlashInfo (object):
00252     """Wrapper class for the non-executable information in an FLM file"""
00253 
00254     FLASH_DEVICE_STRUCT = "<H128sHLLLLBxxxLL"
00255     FLASH_SECTORS_STRUCT = "<LL"
00256     FLASH_SECTORS_STRUCT_SIZE = struct.calcsize(FLASH_SECTORS_STRUCT)
00257     SECTOR_END = 0xFFFFFFFF
00258 
00259     def __init__(self, elf_simple):
00260         dev_info = elf_simple.symbols["FlashDevice"]
00261         info_start = dev_info.value
00262         info_size = struct.calcsize(self.FLASH_DEVICE_STRUCT )
00263         data = elf_simple.read(info_start, info_size)
00264         values = struct.unpack(self.FLASH_DEVICE_STRUCT , data)
00265 
00266         self.version  = values[0]
00267         self.name  = values[1].strip("\x00")
00268         self.type  = values[2]
00269         self.start  = values[3]
00270         self.size  = values[4]
00271         self.page_size  = values[5]
00272         self.value_empty  = values[7]
00273         self.prog_timeout_ms  = values[8]
00274         self.erase_timeout_ms  = values[9]
00275 
00276         sector_gen = self._sector_and_sz_itr (elf_simple,
00277                                              info_start + info_size)
00278         self.sector_info_list  = list(sector_gen)
00279 
00280     def __str__(self):
00281         desc = ""
00282         desc += "Flash Device:" + os.linesep
00283         desc += "  name=%s" % self.name  + os.linesep
00284         desc += "  version=0x%x" % self.version  + os.linesep
00285         desc += "  type=%i" % self.type  + os.linesep
00286         desc += "  start=0x%x" % self.start  + os.linesep
00287         desc += "  size=0x%x" % self.size  + os.linesep
00288         desc += "  page_size=0x%x" % self.page_size  + os.linesep
00289         desc += "  value_empty=0x%x" % self.value_empty  + os.linesep
00290         desc += "  prog_timeout_ms=%i" % self.prog_timeout_ms  + os.linesep
00291         desc += "  erase_timeout_ms=%i" % self.erase_timeout_ms  + os.linesep
00292         desc += "  sectors:" + os.linesep
00293         for sector_start, sector_size in self.sector_info_list :
00294             desc += ("    start=0x%x, size=0x%x" %
00295                      (sector_start, sector_size) + os.linesep)
00296         return desc
00297 
00298     def _sector_and_sz_itr(self, elf_simple, data_start):
00299         """Iterator which returns starting address and sector size"""
00300         for entry_start in count(data_start, self.FLASH_SECTORS_STRUCT_SIZE ):
00301             data = elf_simple.read(entry_start, self.FLASH_SECTORS_STRUCT_SIZE )
00302             size, start = struct.unpack(self.FLASH_SECTORS_STRUCT , data)
00303             start_and_size = start, size
00304             if start_and_size == (self.SECTOR_END , self.SECTOR_END ):
00305                 return
00306             yield start_and_size
00307 
00308 
00309 SymbolSimple = namedtuple("SymbolSimple", "name, value, size")
00310 
00311 
00312 class ElfFileSimple (ELFFile):
00313     """Wrapper for elf object which allows easy access to symbols and rom"""
00314 
00315     def __init__ (self, data):
00316         """Construct a ElfFileSimple from bytes or a bytearray"""
00317         super(ElfFileSimple, self).__init__(StringIO(data))
00318         self.symbols  = self._read_symbol_table ()
00319 
00320     def _read_symbol_table(self):
00321         """Read the symbol table into the field "symbols" for easy use"""
00322         section = self.get_section_by_name(b".symtab")
00323         if not section:
00324             raise Exception("Missing symbol table")
00325 
00326         if not isinstance(section, SymbolTableSection):
00327             raise Exception("Invalid symbol table section")
00328 
00329         symbols = {}
00330         for symbol in section.iter_symbols():
00331             name_str = bytes2str(symbol.name)
00332             if name_str in symbols:
00333                 logging.debug("Duplicate symbol %s", name_str)
00334             symbols[name_str] = SymbolSimple(name_str, symbol["st_value"],
00335                                              symbol["st_size"])
00336         return symbols
00337 
00338     def read (self, addr, size):
00339         """Read program data from the elf file
00340 
00341         :param addr: physical address (load address) to read from
00342         :param size: number of bytes to read
00343         :return: Requested data or None if address is unmapped
00344         """
00345         for segment in self.iter_segments():
00346             seg_addr = segment["p_paddr"]
00347             seg_size = min(segment["p_memsz"], segment["p_filesz"])
00348             if addr >= seg_addr + seg_size:
00349                 continue
00350             if addr + size <= seg_addr:
00351                 continue
00352             # There is at least some overlap
00353 
00354             if addr >= seg_addr and addr + size <= seg_addr + seg_size:
00355                 # Region is fully contained
00356                 data = segment.data()
00357                 start = addr - seg_addr
00358                 return data[start:start + size]
00359 
00360 
00361 if __name__ == '__main__':
00362     main()