123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291 |
- #!/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 dictionary_parser.log_database
- from dictionary_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 dictionary_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 = dictionary_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()
|