diff --git a/tools/build/build b/tools/build/build index fb3ecf2b31..452bd88d8c 100755 --- a/tools/build/build +++ b/tools/build/build @@ -5,3 +5,4 @@ scriptDir=$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd ) ${scriptDir}/test "${@}" ${scriptDir}/make "${@}" +${scriptDir}/check_elf diff --git a/tools/build/check_elf b/tools/build/check_elf new file mode 100755 index 0000000000..b89317fd2d --- /dev/null +++ b/tools/build/check_elf @@ -0,0 +1,5 @@ +#!/usr/bin/env bash +set -e + +scriptDir=$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd ) +python3 ${scriptDir}/elf_sanity.py ${scriptDir}/../../*.elf diff --git a/tools/build/elf_sanity.py b/tools/build/elf_sanity.py new file mode 100644 index 0000000000..b46e8be2fa --- /dev/null +++ b/tools/build/elf_sanity.py @@ -0,0 +1,170 @@ +import argparse +import struct +import sys + +try: + from elftools.elf.elffile import ELFFile +except ImportError: + print('pytelftools missing, install to run this script', file=sys.stderr) + print('https://github.com/eliben/pyelftools#installing', file=sys.stderr) + sys.exit(1) + + +class Colors: + RED = '\033[91m' + BLUE = '\033[94m' + GREEN = '\033[92m' + END = '\033[0m' + + +param_type_to_str_dict = { + 0x0 | 0x0 << 2 | 0x1 << 3: 'PARAM_UINT8', + 0x0 | 0x0 << 2 | 0x0 << 3: 'PARAM_INT8', + 0x1 | 0x0 << 2 | 0x1 << 3: 'PARAM_UIN16', + 0x1 | 0x0 << 2 | 0x0 << 3: 'PARAM_INT16', + 0x2 | 0x0 << 2 | 0x1 << 3: 'PARAM_UINT32', + 0x2 | 0x0 << 2 | 0x0 << 3: 'PARAM_INT32', + 0x2 | 0x1 << 2 | 0x0 << 3: 'PARAM_FLOAT' +} + + +def param_type_to_str(t: int) -> str: + read_only = str() + if t & (1 << 6): # PARAM_RONLY set + read_only = ' | PARAM_RONLY' + + return '{:12}{}'.format(param_type_to_str_dict[t & ~(1 << 6)], read_only) + + +log_type_to_str_dict = { + 0x1: 'LOG_UINT8', + 0x2: 'LOG_INT8', + 0x3: 'LOG_UIN16', + 0x4: 'LOG_INT16', + 0x5: 'LOG_UINT32', + 0x6: 'LOG_INT32', + 0x7: 'LOG_FLOAT', + 0x8: 'LOG_FP16' +} + + +def log_type_to_str(t: int) -> str: + by_function = str() + if t & (1 << 6): # BY_FUNCTION set + by_function = ' | BY_FUNCTION' + + return '{:12}{}'.format(log_type_to_str_dict[t & ~(1 << 6)], by_function) + + +def process_file(filename, list_params: bool, list_logs: bool): + with open(filename, 'rb') as f: + parameters = check_structs(f, 'param') + if list_params: + for key in sorted(parameters.keys()): + t = parameters[key] + print('{:25}\t{}'.format(key, param_type_to_str(t))) + + logs = check_structs(f, 'log') + if list_logs: + for key in sorted(logs.keys()): + t = logs[key] + print('{:25}\t{}'.format(key, log_type_to_str(t))) + + n_logs = Colors.GREEN + str(len(logs.keys())) + Colors.END + n_params = Colors.BLUE + str(len(parameters.keys())) + Colors.END + print('{} parameters and {} log vars in elf'.format(n_params, n_logs)) + + +def get_offset_of(elf, addr): + for seg in elf.iter_segments(): + if seg.header['p_type'] != 'PT_LOAD': + continue + + # If the symbol is inside the range of a LOADed segment, calculate the + # file offset by subtracting the virtual start address and adding the + # file offset of the loaded section(s) + if addr >= seg['p_vaddr'] and addr < seg['p_vaddr'] + seg['p_filesz']: + return addr - seg['p_vaddr'] + seg['p_offset'] + + return None + + +def get_offset_of_symbol(elf, name): + section = elf.get_section_by_name('.symtab') + sym = section.get_symbol_by_name(name)[0] + if not sym: + print('symbol %s not found' % name, file=sys.stderr) + sys.exit(1) + + return get_offset_of(elf, sym['st_value']) + + +def check_structs(stream, what: str) -> dict: + elf = ELFFile(stream) + offset = get_offset_of_symbol(elf, '_{}_start'.format(what)) + stop_offset = get_offset_of_symbol(elf, '_{}_stop'.format(what)) + + name_type_dict = {} + name_maxlen = 25 + struct_len = 12 + group_bit = 0x1 << 7 + start_bit = 0x1 + + while offset < stop_offset: + elf.stream.seek(offset) + # + # Parsing log or param, first unpack the struct: + # struct [param_s|log_s] { + # uint8_t type; + # char * name; + # void * address; + # }; + # + # We want the type and the name. + # + buffer = elf.stream.read(struct_len) + t, addr = struct.unpack('@Bxxxixxxx', buffer) + # + # Next, convert address of name to offset in elf + # + addr = get_offset_of(elf, addr) + # + # And read the name from that offset + # + elf.stream.seek(addr) + name = ''.join(iter(lambda: stream.read(1).decode('ascii'), '\x00')) + # + # Check if this is start of a group + # + if t & group_bit != 0 and t & start_bit != 0: + current_group = name + elif t & group_bit == 0: + name = '%s.%s' % (current_group, name) + if name in name_type_dict: + print('%sDuplicate parameter detected!%s (%s)' % + (Colors.RED, Colors.END, name), file=sys.stderr) + sys.exit(1) + else: + name_type_dict[name] = t + + if len(name) > name_maxlen: + print('%sName too long!%s (%s > %d)' % + (Colors.RED, Colors.END, name, name_maxlen), + file=sys.stderr) + sys.exit(1) + + offset += struct_len + return name_type_dict + + +if __name__ == '__main__': + parser = argparse.ArgumentParser() + parser.add_argument('--list-params', action='store_true') + parser.add_argument('--list-logs', action='store_true') + parser.add_argument('filename', nargs=argparse.REMAINDER) + args = parser.parse_args() + + if args.filename: + process_file(args.filename[0], args.list_params, args.list_logs) + else: + sys.exit(1)