* No user-friendly error handling and no bounds checking yet. * No support for >65K sections case (is it needed?). * The order of definitions is reversed.
Signed-off-by: Dmitry Kozlyuk <dmitry.kozl...@gmail.com> --- buildtools/elf.py | 194 +++++++++++++++++++++++++++++++++++++++ buildtools/pmdinfogen.py | 144 +++++++++++++++++++++++++++++ 2 files changed, 338 insertions(+) create mode 100644 buildtools/elf.py create mode 100755 buildtools/pmdinfogen.py diff --git a/buildtools/elf.py b/buildtools/elf.py new file mode 100644 index 000000000..4085d547b --- /dev/null +++ b/buildtools/elf.py @@ -0,0 +1,194 @@ +import ctypes + + +class ElfIdent(ctypes.Structure): + _pack_ = True + _fields_ = [ + ("magic", ctypes.c_char * 4), + ("class_", ctypes.c_uint8), + ("data", ctypes.c_uint8), + ("version", ctypes.c_uint8), + ("os_abi", ctypes.c_uint8), + ("abi_version", ctypes.c_uint8), + ("pad", ctypes.c_uint8 * 7), + ] + + @property + def is_magic_ok(self): + return self.magic.value == b"\x7fELF" + + @property + def is_32bit(self): + return self.class_ == 1 + + @property + def is_big_endian(self): + return self.data == 2 + + def define_structures(self): + base_type = ctypes.LittleEndianStructure + if self.is_big_endian: + base = ctypes.BigEndianStructure + + size_type = ctypes.c_uint64 + if self.is_32bit: + size_type = ctypes.c_uint32 + + class FileHeader(base_type): + _pack_ = True + _fields_ = [ + ("e_ident", ElfIdent), + ("e_type", ctypes.c_uint16), + ("e_machine", ctypes.c_uint16), + ("e_version", ctypes.c_uint32), + ("e_entry", size_type), + ("e_phoff", size_type), + ("e_shoff", size_type), + ("e_flags", ctypes.c_uint32), + ("e_ehsize", ctypes.c_uint16), + ("e_phentsize", ctypes.c_uint16), + ("e_phnum", ctypes.c_uint16), + ("e_shentsize", ctypes.c_uint16), + ("e_shnum", ctypes.c_uint16), + ("e_shstrndx", ctypes.c_uint16), + ] + + class SectionHeader(base_type): + _pack_ = True + _fields_ = [ + ("sh_name", ctypes.c_uint32), + ("sh_type", ctypes.c_uint32), + ("sh_flags", size_type), + ("sh_addr", size_type), + ("sh_offset", size_type), + ("sh_size", size_type), + ("sh_link", ctypes.c_uint32), + ("sh_info", ctypes.c_uint32), + ("sh_addralign", size_type), + ("sh_entsize", size_type), + ] + + class Symbol32(base_type): + _pack_ = True + _fields_ = [ + ("st_name", ctypes.c_uint32), + ("st_value", ctypes.c_uint32), + ("st_size", ctypes.c_uint32), + ("st_info", ctypes.c_uint8), + ("st_other", ctypes.c_uint8), + ("st_shndx", ctypes.c_uint16), + ] + + class Symbol64(base_type): + _pack_ = True + _fields_ = [ + ("st_name", ctypes.c_uint32), + ("st_info", ctypes.c_uint8), + ("st_other", ctypes.c_uint8), + ("st_shndx", ctypes.c_uint16), + ("st_value", ctypes.c_uint64), + ("st_size", ctypes.c_uint64), + ] + + Symbol = Symbol32 if self.is_32bit else Symbol64 + + return FileHeader, SectionHeader, Symbol + + +class Symbol: + def __init__(self, image, elf): + self._image = image + self._elf = elf + + + @property + def address(self): + base = self._image._sections[self._elf.st_shndx].sh_offset + offset = base + self._elf.st_value + memory = ctypes.c_char.from_buffer(self._image._data, offset) + return ctypes.addressof(memory) + + +class Image: + def __init__(self, data): + SHN_UNDEF = 0x0000 + SHN_XINDEX = 0xFFFF + + ident = ElfIdent.from_buffer(data) + ElfFileHeader, ElfSectionHeader, ElfSymbol = ident.define_structures() + + header = ElfFileHeader.from_buffer(data) + + if header.e_shnum == SHN_UNDEF: + section = ElfSectionHeader.from_buffer(data, header.e_shoff) + sections_num = section.sh_size + else: + sections_num = header.e_shnum + sections_desc = ElfSectionHeader * sections_num + sections = sections_desc.from_buffer(data, header.e_shoff) + + if header.e_shstrndx == SHN_XINDEX: + strings_index = sections[0].sh_link + else: + strings_index = header.e_shstrndx + + symtab, strtab = Image._find_symbol_table(data, sections, ElfSymbol) + + self._data = data + self._header = header + self._sections = sections + self._strings = sections[strings_index] + self._symtab = symtab + self._strtab = strtab + + + @staticmethod + def _find_symbol_table(data, sections, symbol_type): + SHT_SYMTAB = 2 + SHT_SYMTAB_SHNDX = 18 + + for section in sections: + if section.sh_type == SHT_SYMTAB: + symbol_count = section.sh_size // ctypes.sizeof(symbol_type) + symtab_desc = symbol_type * symbol_count + symtab_offset = section.sh_offset + symtab = symtab_desc.from_buffer(data, symtab_offset) + + strtab = sections[section.sh_link].sh_offset + + return symtab, strtab + + # TODO: SHT_SYMTAB_SHNDX? + + raise Exception('no symbol table') + + + @property + def is_big_endian(self): + return self._header.e_ident.is_big_endian + + + def find_symbol(self, name: str, start: Symbol = None): + name_size = len(name) + name_desc = ctypes.c_char * name_size + + i = 0 + if start: + table_address = ctypes.addressof(self._symtab) + start_address = ctypes.addressof(start._elf) + i = (start_address - table_address) // ctypes.sizeof(start._elf) + 1 + + while i < len(self._symtab): + symbol = self._symtab[i] + + name_offset = self._strtab + symbol.st_name + if name_offset + name_size > len(self._data): + break + + symbol_name = name_desc.from_buffer(self._data, name_offset).value + if symbol_name == name.encode(): + return Symbol(self, symbol) + + i += 1 + + return None diff --git a/buildtools/pmdinfogen.py b/buildtools/pmdinfogen.py new file mode 100755 index 000000000..74d2e5285 --- /dev/null +++ b/buildtools/pmdinfogen.py @@ -0,0 +1,144 @@ +#!/usr/bin/env python3 + +import argparse +import ctypes +import elf +import json +import mmap +import os +import sys +import tempfile + + +def define_rte_pci_id(is_big_endian): + base_type = ctypes.LittleEndianStructure + if is_big_endian: + base_type = ctypes.BigEndianStructure + + class rte_pci_id(base_type): + _pack_ = True + _fields_ = [ + ("class_id", ctypes.c_uint32), + ("vendor_id", ctypes.c_uint16), + ("device_id", ctypes.c_uint16), + ("subsystem_vendor_id", ctypes.c_uint16), + ("subsystem_device_id", ctypes.c_uint16), + ] + + return rte_pci_id + + +class Driver: + OPTIONS = [ + ("params", "_param_string_export"), + ("kmod", "_kmod_dep_export"), + ] + + def __init__(self, name, options): + self.name = name + for key, value in options.items(): + setattr(self, key, value) + self.pci_ids = [] + + @classmethod + def load(cls, image, symbol): + name = ctypes.string_at(symbol.address).decode() + + options = {} + for key, suffix in cls.OPTIONS: + option_symbol = image.find_symbol(f"__{name}{suffix}") + if option_symbol: + value = ctypes.string_at(option_symbol.address).decode() + options[key] = value + + driver = cls(name, options) + + pci_table_name_symbol = image.find_symbol(f"__{name}_pci_tbl_export") + if not pci_table_name_symbol: + return driver + + pci_table_name = ctypes.string_at(pci_table_name_symbol.address).decode() + + pci_table_symbol = image.find_symbol(pci_table_name) + if not pci_table_symbol: + raise Exception('PCI table declared but not defined') + + rte_pci_id = define_rte_pci_id(image.is_big_endian) + + pci_id = rte_pci_id.from_address(pci_table_symbol.address) + while pci_id.device_id: + driver.pci_ids.append( + [ + pci_id.vendor_id, + pci_id.device_id, + pci_id.subsystem_vendor_id, + pci_id.subsystem_device_id, + ] + ) + pci_id = rte_pci_id.from_address( + ctypes.addressof(pci_id) + ctypes.sizeof(pci_id) + ) + + return driver + + def dump(self, file): + dumped = json.dumps(self.__dict__) + escaped = dumped.replace('"', '\\"') + print( + f"const char {self.name}_pmd_info[] __attribute__((used)) = " + f'"PMD_INFO_STRING= {escaped}";', + file=file, + ) + + +def get_symbols_by_prefix(image, name): + symbol = image.find_symbol(name) + while symbol: + yield symbol + symbol = image.find_symbol(name, symbol) + + +def load_drivers(image): + drivers = [] + for symbol in get_symbols_by_prefix(image, "this_pmd_name"): + drivers.append(Driver.load(image, symbol)) + return drivers + + +def dump_drivers(drivers, file): + for driver in drivers: + driver.dump(file) + + +def parse_args(): + parser = argparse.ArgumentParser() + parser.add_argument("input", help="input object file path") + parser.add_argument("output", help="output C file path") + return parser.parse_args() + + +def map_input(path): + if path == '-': + fd, name = tempfile.mkstemp() + else: + fd = os.open(path, os.O_RDONLY) + return mmap.mmap(fd, 0, access=mmap.ACCESS_COPY) + + +def open_output(path): + if path == '-': + return sys.stdout + return open(path, 'w') + + +def main(): + args = parse_args() + memory = map_input(args.input) + image = elf.Image(memory) + drivers = load_drivers(image) + output = open_output(args.output) + dump_drivers(drivers, output) + + +if __name__ == "__main__": + main() -- 2.25.4