buildtools: support object file extraction for Windows
[dpdk.git] / buildtools / pmdinfogen.py
1 #!/usr/bin/env python3
2 # SPDX-License-Identifier: BSD-3-Clause
3 # Copyright (c) 2016 Neil Horman <nhorman@tuxdriver.com>
4 # Copyright (c) 2020 Dmitry Kozlyuk <dmitry.kozliuk@gmail.com>
5
6 import argparse
7 import ctypes
8 import json
9 import sys
10 import tempfile
11
12 try:
13     from elftools.elf.elffile import ELFFile
14     from elftools.elf.sections import SymbolTableSection
15 except ImportError:
16     pass
17
18 import coff
19
20
21 class ELFSymbol:
22     def __init__(self, image, symbol):
23         self._image = image
24         self._symbol = symbol
25
26     @property
27     def string_value(self):
28         size = self._symbol["st_size"]
29         value = self.get_value(0, size)
30         return value[:-1].decode() if value else ""
31
32     def get_value(self, offset, size):
33         section = self._symbol["st_shndx"]
34         data = self._image.get_section(section).data()
35         base = self._symbol["st_value"] + offset
36         return data[base : base + size]
37
38
39 class ELFImage:
40     def __init__(self, data):
41         self._image = ELFFile(data)
42         self._symtab = self._image.get_section_by_name(".symtab")
43         if not isinstance(self._symtab, SymbolTableSection):
44             raise Exception(".symtab section is not a symbol table")
45
46     @property
47     def is_big_endian(self):
48         return not self._image.little_endian
49
50     def find_by_name(self, name):
51         symbol = self._symtab.get_symbol_by_name(name)
52         return ELFSymbol(self._image, symbol[0]) if symbol else None
53
54     def find_by_prefix(self, prefix):
55         for i in range(self._symtab.num_symbols()):
56             symbol = self._symtab.get_symbol(i)
57             if symbol.name.startswith(prefix):
58                 yield ELFSymbol(self._image, symbol)
59
60
61 class COFFSymbol:
62     def __init__(self, image, symbol):
63         self._image = image
64         self._symbol = symbol
65
66     def get_value(self, offset, size):
67         value = self._symbol.get_value(offset)
68         return value[:size] if value else value
69
70     @property
71     def string_value(self):
72         value = self._symbol.get_value(0)
73         return coff.decode_asciiz(value) if value else ''
74
75
76 class COFFImage:
77     def __init__(self, data):
78         self._image = coff.Image(data)
79
80     @property
81     def is_big_endian(self):
82         return False
83
84     def find_by_prefix(self, prefix):
85         for symbol in self._image.symbols:
86             if symbol.name.startswith(prefix):
87                 yield COFFSymbol(self._image, symbol)
88
89     def find_by_name(self, name):
90         for symbol in self._image.symbols:
91             if symbol.name == name:
92                 return COFFSymbol(self._image, symbol)
93         return None
94
95
96 def define_rte_pci_id(is_big_endian):
97     base_type = ctypes.LittleEndianStructure
98     if is_big_endian:
99         base_type = ctypes.BigEndianStructure
100
101     class rte_pci_id(base_type):
102         _pack_ = True
103         _fields_ = [
104             ("class_id", ctypes.c_uint32),
105             ("vendor_id", ctypes.c_uint16),
106             ("device_id", ctypes.c_uint16),
107             ("subsystem_vendor_id", ctypes.c_uint16),
108             ("subsystem_device_id", ctypes.c_uint16),
109         ]
110
111     return rte_pci_id
112
113
114 class Driver:
115     OPTIONS = [
116         ("params", "_param_string_export"),
117         ("kmod", "_kmod_dep_export"),
118     ]
119
120     def __init__(self, name, options):
121         self.name = name
122         for key, value in options.items():
123             setattr(self, key, value)
124         self.pci_ids = []
125
126     @classmethod
127     def load(cls, image, symbol):
128         name = symbol.string_value
129
130         options = {}
131         for key, suffix in cls.OPTIONS:
132             option_symbol = image.find_by_name("__%s%s" % (name, suffix))
133             if option_symbol:
134                 value = option_symbol.string_value
135                 options[key] = value
136
137         driver = cls(name, options)
138
139         pci_table_name_symbol = image.find_by_name("__%s_pci_tbl_export" % name)
140         if pci_table_name_symbol:
141             driver.pci_ids = cls._load_pci_ids(image, pci_table_name_symbol)
142
143         return driver
144
145     @staticmethod
146     def _load_pci_ids(image, table_name_symbol):
147         table_name = table_name_symbol.string_value
148         table_symbol = image.find_by_name(table_name)
149         if not table_symbol:
150             raise Exception("PCI table declared but not defined: %d" % table_name)
151
152         rte_pci_id = define_rte_pci_id(image.is_big_endian)
153
154         result = []
155         while True:
156             size = ctypes.sizeof(rte_pci_id)
157             offset = size * len(result)
158             data = table_symbol.get_value(offset, size)
159             if not data:
160                 break
161             pci_id = rte_pci_id.from_buffer_copy(data)
162             if not pci_id.device_id:
163                 break
164             result.append(
165                 [
166                     pci_id.vendor_id,
167                     pci_id.device_id,
168                     pci_id.subsystem_vendor_id,
169                     pci_id.subsystem_device_id,
170                 ]
171             )
172         return result
173
174     def dump(self, file):
175         dumped = json.dumps(self.__dict__)
176         escaped = dumped.replace('"', '\\"')
177         print(
178             'const char %s_pmd_info[] __attribute__((used)) = "PMD_INFO_STRING= %s";'
179             % (self.name, escaped),
180             file=file,
181         )
182
183
184 def load_drivers(image):
185     drivers = []
186     for symbol in image.find_by_prefix("this_pmd_name"):
187         drivers.append(Driver.load(image, symbol))
188     return drivers
189
190
191 def dump_drivers(drivers, file):
192     # Keep legacy order of definitions.
193     for driver in reversed(drivers):
194         driver.dump(file)
195
196
197 def parse_args():
198     parser = argparse.ArgumentParser()
199     parser.add_argument("format", help="object file format, 'elf' or 'coff'")
200     parser.add_argument(
201         "input", nargs='+', help="input object file path or '-' for stdin"
202     )
203     parser.add_argument("output", help="output C file path or '-' for stdout")
204     return parser.parse_args()
205
206
207 def open_input(path):
208     if path == "-":
209         temp = tempfile.TemporaryFile()
210         temp.write(sys.stdin.buffer.read())
211         return temp
212     return open(path, "rb")
213
214
215 def read_input(path):
216     if path == "-":
217         return sys.stdin.buffer.read()
218     with open(path, "rb") as file:
219         return file.read()
220
221
222 def load_image(fmt, path):
223     if fmt == "elf":
224         return ELFImage(open_input(path))
225     if fmt == "coff":
226         return COFFImage(read_input(path))
227     raise Exception("unsupported object file format")
228
229
230 def open_output(path):
231     if path == "-":
232         return sys.stdout
233     return open(path, "w")
234
235
236 def write_header(output):
237     output.write(
238         "static __attribute__((unused)) const char *generator = \"%s\";\n" % sys.argv[0]
239     )
240
241
242 def main():
243     args = parse_args()
244     if args.input.count('-') > 1:
245         raise Exception("'-' input cannot be used multiple times")
246     if args.format == "elf" and "ELFFile" not in globals():
247         raise Exception("elftools module not found")
248
249     output = open_output(args.output)
250     write_header(output)
251     for path in args.input:
252         image = load_image(args.format, path)
253         drivers = load_drivers(image)
254         dump_drivers(drivers, output)
255
256
257 if __name__ == "__main__":
258     main()