1#!/usr/bin/env python3 2# 3# Copyright (c) 2020 Intel Corporation 4# 5# SPDX-License-Identifier: Apache-2.0 6 7""" 8Dictionary-based Logging Database Generator 9 10This takes the built Zephyr ELF binary and produces a JSON database 11file for dictionary-based logging. This database is used together 12with the parser to decode binary log messages. 13""" 14 15import argparse 16import logging 17import os 18import struct 19import sys 20 21import dictionary_parser.log_database 22from dictionary_parser.log_database import LogDatabase 23 24import elftools 25from elftools.elf.elffile import ELFFile 26from elftools.elf.descriptions import describe_ei_data 27from elftools.elf.sections import SymbolTableSection 28 29 30LOGGER_FORMAT = "%(name)s: %(levelname)s: %(message)s" 31logger = logging.getLogger(os.path.basename(sys.argv[0])) 32 33 34# Sections that contains static strings 35STATIC_STRING_SECTIONS = ['rodata', '.rodata', 'log_strings_sections'] 36 37 38def parse_args(): 39 """Parse command line arguments""" 40 argparser = argparse.ArgumentParser() 41 42 argparser.add_argument("elffile", help="Zephyr ELF binary") 43 argparser.add_argument("dbfile", help="Dictionary Logging Database file") 44 argparser.add_argument("--build", help="Build ID") 45 argparser.add_argument("--debug", action="store_true", 46 help="Print extra debugging information") 47 argparser.add_argument("-v", "--verbose", action="store_true", 48 help="Print more information") 49 50 return argparser.parse_args() 51 52 53def find_elf_sections(elf, sh_name): 54 """Find all sections in ELF file""" 55 for section in elf.iter_sections(): 56 if section.name == sh_name: 57 ret = { 58 'name' : section.name, 59 'size' : section['sh_size'], 60 'start' : section['sh_addr'], 61 'end' : section['sh_addr'] + section['sh_size'] - 1, 62 'data' : section.data(), 63 } 64 65 return ret 66 67 return None 68 69 70def get_kconfig_symbols(elf): 71 """Get kconfig symbols from the ELF file""" 72 for section in elf.iter_sections(): 73 if isinstance(section, SymbolTableSection): 74 return {sym.name: sym.entry.st_value 75 for sym in section.iter_symbols() 76 if sym.name.startswith("CONFIG_")} 77 78 raise LookupError("Could not find symbol table") 79 80 81def find_log_const_symbols(elf): 82 """Extract all "log_const_*" symbols from ELF file""" 83 symbol_tables = [s for s in elf.iter_sections() 84 if isinstance(s, elftools.elf.sections.SymbolTableSection)] 85 86 ret_list = [] 87 88 for section in symbol_tables: 89 if not isinstance(section, elftools.elf.sections.SymbolTableSection): 90 continue 91 92 if section['sh_entsize'] == 0: 93 continue 94 95 for symbol in section.iter_symbols(): 96 if symbol.name.startswith("log_const_"): 97 ret_list.append(symbol) 98 99 return ret_list 100 101 102def parse_log_const_symbols(database, log_const_section, log_const_symbols): 103 """Find the log instances and map source IDs to names""" 104 if database.is_tgt_little_endian(): 105 formatter = "<" 106 else: 107 formatter = ">" 108 109 if database.is_tgt_64bit(): 110 # 64-bit pointer to string 111 formatter += "Q" 112 else: 113 # 32-bit pointer to string 114 formatter += "L" 115 116 # log instance level 117 formatter += "B" 118 119 datum_size = struct.calcsize(formatter) 120 121 # Get the address of first log instance 122 first_offset = log_const_symbols[0].entry['st_value'] 123 for sym in log_const_symbols: 124 if sym.entry['st_value'] < first_offset: 125 first_offset = sym.entry['st_value'] 126 127 first_offset -= log_const_section['start'] 128 129 # find all log_const_* 130 for sym in log_const_symbols: 131 # Find data offset in log_const_section for this symbol 132 offset = sym.entry['st_value'] - log_const_section['start'] 133 134 idx_s = offset 135 idx_e = offset + datum_size 136 137 datum = log_const_section['data'][idx_s:idx_e] 138 139 if len(datum) != datum_size: 140 # Not enough data to unpack 141 continue 142 143 str_ptr, level = struct.unpack(formatter, datum) 144 145 # Offset to rodata section for string 146 instance_name = database.find_string(str_ptr) 147 148 logger.info("Found Log Instance: %s, level: %d", instance_name, level) 149 150 # source ID is simply the element index in the log instance array 151 source_id = int((offset - first_offset) / sym.entry['st_size']) 152 153 database.add_log_instance(source_id, instance_name, level, sym.entry['st_value']) 154 155 156def extract_elf_information(elf, database): 157 """Extract information from ELF file and store in database""" 158 e_ident = elf.header['e_ident'] 159 elf_data = describe_ei_data(e_ident['EI_DATA']) 160 161 if elf_data == elftools.elf.descriptions._DESCR_EI_DATA['ELFDATA2LSB']: 162 database.set_tgt_endianness(LogDatabase.LITTLE_ENDIAN) 163 elif elf_data == elftools.elf.descriptions._DESCR_EI_DATA['ELFDATA2MSB']: 164 database.set_tgt_endianness(LogDatabase.BIG_ENDIAN) 165 else: 166 logger.error("Cannot determine endianness from ELF file, exiting...") 167 sys.exit(1) 168 169 170def process_kconfigs(elf, database): 171 """Process kconfigs to extract information""" 172 kconfigs = get_kconfig_symbols(elf) 173 174 # 32 or 64-bit target 175 database.set_tgt_bits(64 if "CONFIG_64BIT" in kconfigs else 32) 176 177 # Architecture 178 for name, arch in dictionary_parser.log_database.ARCHS.items(): 179 if arch['kconfig'] in kconfigs: 180 database.set_arch(name) 181 break 182 183 # Put some kconfigs into the database 184 # 185 # Use 32-bit timestamp? or 64-bit? 186 if "CONFIG_LOG_TIMESTAMP_64BIT" in kconfigs: 187 database.add_kconfig("CONFIG_LOG_TIMESTAMP_64BIT", 188 kconfigs['CONFIG_LOG_TIMESTAMP_64BIT']) 189 190 191def extract_static_string_sections(elf, database): 192 """Extract sections containing static strings""" 193 string_sections = STATIC_STRING_SECTIONS 194 195 # Some architectures may put static strings into additional sections. 196 # So need to extract them too. 197 arch_data = dictionary_parser.log_database.ARCHS[database.get_arch()] 198 if "extra_string_section" in arch_data: 199 string_sections.extend(arch_data['extra_string_section']) 200 201 for name in string_sections: 202 content = find_elf_sections(elf, name) 203 if content is None: 204 continue 205 206 logger.info("Found section: %s, 0x%x - 0x%x", 207 name, content['start'], content['end']) 208 database.add_string_section(name, content) 209 210 if not database.has_string_sections(): 211 logger.error("Cannot find any static string sections in ELF, exiting...") 212 sys.exit(1) 213 214 215def extract_logging_subsys_information(elf, database): 216 """ 217 Extract logging subsys related information and store in database. 218 219 For example, this extracts the list of log instances to establish 220 mapping from source ID to name. 221 """ 222 # Extract log constant section for module names 223 section_log_const = find_elf_sections(elf, "log_const_sections") 224 if section_log_const is None: 225 # ESP32 puts "log_const_*" info log_static_section instead of log_const_sections 226 section_log_const = find_elf_sections(elf, "log_static_section") 227 228 if section_log_const is None: 229 logger.error("Cannot find section 'log_const_sections' in ELF file, exiting...") 230 sys.exit(1) 231 232 # Find all "log_const_*" symbols and parse them 233 log_const_symbols = find_log_const_symbols(elf) 234 parse_log_const_symbols(database, section_log_const, log_const_symbols) 235 236 237def main(): 238 """Main function of database generator""" 239 args = parse_args() 240 241 # Setup logging 242 logging.basicConfig(format=LOGGER_FORMAT) 243 if args.verbose: 244 logger.setLevel(logging.INFO) 245 elif args.debug: 246 logger.setLevel(logging.DEBUG) 247 else: 248 logger.setLevel(logging.WARNING) 249 250 elffile = open(args.elffile, "rb") 251 if not elffile: 252 logger.error("ERROR: Cannot open ELF file: %s, exiting...", args.elffile) 253 sys.exit(1) 254 255 logger.info("ELF file %s", args.elffile) 256 logger.info("Database file %s", args.dbfile) 257 258 elf = ELFFile(elffile) 259 260 database = LogDatabase() 261 262 if args.build: 263 database.set_build_id(args.build) 264 logger.info("Build ID: %s", args.build) 265 266 extract_elf_information(elf, database) 267 268 process_kconfigs(elf, database) 269 270 logger.info("Target: %s, %d-bit", database.get_arch(), database.get_tgt_bits()) 271 if database.is_tgt_little_endian(): 272 logger.info("Endianness: Little") 273 else: 274 logger.info("Endianness: Big") 275 276 # Extract sections from ELF files that contain strings 277 extract_static_string_sections(elf, database) 278 279 # Extract information related to logging subsystem 280 extract_logging_subsys_information(elf, database) 281 282 # Write database file 283 if not LogDatabase.write_json_database(args.dbfile, database): 284 logger.error("ERROR: Cannot open database file for write: %s, exiting...", args.dbfile) 285 sys.exit(1) 286 287 elffile.close() 288 289 290if __name__ == "__main__": 291 main() 292