| #!/usr/bin/env python3 |
| # |
| # Copyright (c) 2020 Intel Corporation |
| # |
| # SPDX-License-Identifier: Apache-2.0 |
| |
| """ |
| Dictionary-based Logging Database Generator |
| |
| This takes the built Zephyr ELF binary and produces a JSON database |
| file for dictionary-based logging. This database is used together |
| with the parser to decode binary log messages. |
| """ |
| |
| import argparse |
| import logging |
| import os |
| import struct |
| import sys |
| |
| import parser.log_database |
| from parser.log_database import LogDatabase |
| |
| import elftools |
| from elftools.elf.elffile import ELFFile |
| from elftools.elf.descriptions import describe_ei_data |
| from elftools.elf.sections import SymbolTableSection |
| |
| |
| LOGGER_FORMAT = "%(name)s: %(levelname)s: %(message)s" |
| logger = logging.getLogger(os.path.basename(sys.argv[0])) |
| |
| |
| # Sections that contains static strings |
| STATIC_STRING_SECTIONS = ['rodata', '.rodata', 'log_strings_sections'] |
| |
| |
| def parse_args(): |
| """Parse command line arguments""" |
| argparser = argparse.ArgumentParser() |
| |
| argparser.add_argument("elffile", help="Zephyr ELF binary") |
| argparser.add_argument("dbfile", help="Dictionary Logging Database file") |
| argparser.add_argument("--build", help="Build ID") |
| argparser.add_argument("--debug", action="store_true", |
| help="Print extra debugging information") |
| argparser.add_argument("-v", "--verbose", action="store_true", |
| help="Print more information") |
| |
| return argparser.parse_args() |
| |
| |
| def find_elf_sections(elf, sh_name): |
| """Find all sections in ELF file""" |
| for section in elf.iter_sections(): |
| if section.name == sh_name: |
| ret = { |
| 'name' : section.name, |
| 'size' : section['sh_size'], |
| 'start' : section['sh_addr'], |
| 'end' : section['sh_addr'] + section['sh_size'] - 1, |
| 'data' : section.data(), |
| } |
| |
| return ret |
| |
| return None |
| |
| |
| def get_kconfig_symbols(elf): |
| """Get kconfig symbols from the ELF file""" |
| for section in elf.iter_sections(): |
| if isinstance(section, SymbolTableSection): |
| return {sym.name: sym.entry.st_value |
| for sym in section.iter_symbols() |
| if sym.name.startswith("CONFIG_")} |
| |
| raise LookupError("Could not find symbol table") |
| |
| |
| def find_log_const_symbols(elf): |
| """Extract all "log_const_*" symbols from ELF file""" |
| symbol_tables = [s for s in elf.iter_sections() |
| if isinstance(s, elftools.elf.sections.SymbolTableSection)] |
| |
| ret_list = [] |
| |
| for section in symbol_tables: |
| if not isinstance(section, elftools.elf.sections.SymbolTableSection): |
| continue |
| |
| if section['sh_entsize'] == 0: |
| continue |
| |
| for symbol in section.iter_symbols(): |
| if symbol.name.startswith("log_const_"): |
| ret_list.append(symbol) |
| |
| return ret_list |
| |
| |
| def parse_log_const_symbols(database, log_const_section, log_const_symbols): |
| """Find the log instances and map source IDs to names""" |
| if database.is_tgt_little_endian(): |
| formatter = "<" |
| else: |
| formatter = ">" |
| |
| if database.is_tgt_64bit(): |
| # 64-bit pointer to string |
| formatter += "Q" |
| else: |
| # 32-bit pointer to string |
| formatter += "L" |
| |
| # log instance level |
| formatter += "B" |
| |
| datum_size = struct.calcsize(formatter) |
| |
| # Get the address of first log instance |
| first_offset = log_const_symbols[0].entry['st_value'] |
| for sym in log_const_symbols: |
| if sym.entry['st_value'] < first_offset: |
| first_offset = sym.entry['st_value'] |
| |
| first_offset -= log_const_section['start'] |
| |
| # find all log_const_* |
| for sym in log_const_symbols: |
| # Find data offset in log_const_section for this symbol |
| offset = sym.entry['st_value'] - log_const_section['start'] |
| |
| idx_s = offset |
| idx_e = offset + datum_size |
| |
| datum = log_const_section['data'][idx_s:idx_e] |
| |
| if len(datum) != datum_size: |
| # Not enough data to unpack |
| continue |
| |
| str_ptr, level = struct.unpack(formatter, datum) |
| |
| # Offset to rodata section for string |
| instance_name = database.find_string(str_ptr) |
| |
| logger.info("Found Log Instance: %s, level: %d", instance_name, level) |
| |
| # source ID is simply the element index in the log instance array |
| source_id = int((offset - first_offset) / sym.entry['st_size']) |
| |
| database.add_log_instance(source_id, instance_name, level, sym.entry['st_value']) |
| |
| |
| def extract_elf_information(elf, database): |
| """Extract information from ELF file and store in database""" |
| e_ident = elf.header['e_ident'] |
| elf_data = describe_ei_data(e_ident['EI_DATA']) |
| |
| if elf_data == elftools.elf.descriptions._DESCR_EI_DATA['ELFDATA2LSB']: |
| database.set_tgt_endianness(LogDatabase.LITTLE_ENDIAN) |
| elif elf_data == elftools.elf.descriptions._DESCR_EI_DATA['ELFDATA2MSB']: |
| database.set_tgt_endianness(LogDatabase.BIG_ENDIAN) |
| else: |
| logger.error("Cannot determine endianness from ELF file, exiting...") |
| sys.exit(1) |
| |
| |
| def process_kconfigs(elf, database): |
| """Process kconfigs to extract information""" |
| kconfigs = get_kconfig_symbols(elf) |
| |
| # 32 or 64-bit target |
| database.set_tgt_bits(64 if "CONFIG_64BIT" in kconfigs else 32) |
| |
| # Architecture |
| for name, arch in parser.log_database.ARCHS.items(): |
| if arch['kconfig'] in kconfigs: |
| database.set_arch(name) |
| break |
| |
| # Put some kconfigs into the database |
| # |
| # Use 32-bit timestamp? or 64-bit? |
| if "CONFIG_LOG_TIMESTAMP_64BIT" in kconfigs: |
| database.add_kconfig("CONFIG_LOG_TIMESTAMP_64BIT", |
| kconfigs['CONFIG_LOG_TIMESTAMP_64BIT']) |
| |
| |
| def extract_static_string_sections(elf, database): |
| """Extract sections containing static strings""" |
| string_sections = STATIC_STRING_SECTIONS |
| |
| # Some architectures may put static strings into additional sections. |
| # So need to extract them too. |
| arch_data = parser.log_database.ARCHS[database.get_arch()] |
| if "extra_string_section" in arch_data: |
| string_sections.extend(arch_data['extra_string_section']) |
| |
| for name in string_sections: |
| content = find_elf_sections(elf, name) |
| if content is None: |
| continue |
| |
| logger.info("Found section: %s, 0x%x - 0x%x", |
| name, content['start'], content['end']) |
| database.add_string_section(name, content) |
| |
| if not database.has_string_sections(): |
| logger.error("Cannot find any static string sections in ELF, exiting...") |
| sys.exit(1) |
| |
| |
| def extract_logging_subsys_information(elf, database): |
| """ |
| Extract logging subsys related information and store in database. |
| |
| For example, this extracts the list of log instances to establish |
| mapping from source ID to name. |
| """ |
| # Extract log constant section for module names |
| section_log_const = find_elf_sections(elf, "log_const_sections") |
| if section_log_const is None: |
| # ESP32 puts "log_const_*" info log_static_section instead of log_const_sections |
| section_log_const = find_elf_sections(elf, "log_static_section") |
| |
| if section_log_const is None: |
| logger.error("Cannot find section 'log_const_sections' in ELF file, exiting...") |
| sys.exit(1) |
| |
| # Find all "log_const_*" symbols and parse them |
| log_const_symbols = find_log_const_symbols(elf) |
| parse_log_const_symbols(database, section_log_const, log_const_symbols) |
| |
| |
| def main(): |
| """Main function of database generator""" |
| args = parse_args() |
| |
| # Setup logging |
| logging.basicConfig(format=LOGGER_FORMAT) |
| if args.verbose: |
| logger.setLevel(logging.INFO) |
| elif args.debug: |
| logger.setLevel(logging.DEBUG) |
| else: |
| logger.setLevel(logging.WARNING) |
| |
| elffile = open(args.elffile, "rb") |
| if not elffile: |
| logger.error("ERROR: Cannot open ELF file: %s, exiting...", args.elffile) |
| sys.exit(1) |
| |
| logger.info("ELF file %s", args.elffile) |
| logger.info("Database file %s", args.dbfile) |
| |
| elf = ELFFile(elffile) |
| |
| database = LogDatabase() |
| |
| if args.build: |
| database.set_build_id(args.build) |
| logger.info("Build ID: %s", args.build) |
| |
| extract_elf_information(elf, database) |
| |
| process_kconfigs(elf, database) |
| |
| logger.info("Target: %s, %d-bit", database.get_arch(), database.get_tgt_bits()) |
| if database.is_tgt_little_endian(): |
| logger.info("Endianness: Little") |
| else: |
| logger.info("Endianness: Big") |
| |
| # Extract sections from ELF files that contain strings |
| extract_static_string_sections(elf, database) |
| |
| # Extract information related to logging subsystem |
| extract_logging_subsys_information(elf, database) |
| |
| # Write database file |
| if not LogDatabase.write_json_database(args.dbfile, database): |
| logger.error("ERROR: Cannot open database file for write: %s, exiting...", args.dbfile) |
| sys.exit(1) |
| |
| elffile.close() |
| |
| |
| if __name__ == "__main__": |
| main() |