]>
Commit | Line | Data |
---|---|---|
36a8fdfd DL |
1 | # FRR DWARF structure definition extractor |
2 | # | |
3 | # Copyright (C) 2020 David Lamparter for NetDEF, Inc. | |
4 | # | |
5 | # This program is free software; you can redistribute it and/or modify it | |
6 | # under the terms of the GNU General Public License as published by the Free | |
7 | # Software Foundation; either version 2 of the License, or (at your option) | |
8 | # any later version. | |
9 | # | |
10 | # This program is distributed in the hope that it will be useful, but WITHOUT | |
11 | # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or | |
12 | # FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for | |
13 | # more details. | |
14 | # | |
15 | # You should have received a copy of the GNU General Public License along | |
16 | # with this program; see the file COPYING; if not, write to the Free Software | |
17 | # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA | |
18 | ||
19 | import sys | |
20 | import os | |
21 | import subprocess | |
22 | import re | |
23 | import argparse | |
24 | import subprocess | |
25 | import json | |
26 | ||
27 | structs = ['xref', 'xref_logmsg', 'xref_threadsched', 'xref_install_element', 'xrefdata', 'xrefdata_logmsg', 'cmd_element'] | |
28 | ||
29 | def extract(filename='lib/.libs/libfrr.so'): | |
30 | ''' | |
31 | Convert output from "pahole" to JSON. | |
32 | ||
33 | Example pahole output: | |
34 | $ pahole -C xref lib/.libs/libfrr.so | |
35 | struct xref { | |
36 | struct xrefdata * xrefdata; /* 0 8 */ | |
37 | enum xref_type type; /* 8 4 */ | |
38 | int line; /* 12 4 */ | |
39 | const char * file; /* 16 8 */ | |
40 | const char * func; /* 24 8 */ | |
41 | ||
42 | /* size: 32, cachelines: 1, members: 5 */ | |
43 | /* last cacheline: 32 bytes */ | |
44 | }; | |
45 | ''' | |
46 | pahole = subprocess.check_output(['pahole', '-C', ','.join(structs), filename]).decode('UTF-8') | |
47 | ||
48 | struct_re = re.compile(r'^struct ([^ ]+) \{([^\}]+)};', flags=re.M | re.S) | |
49 | field_re = re.compile(r'^\s*(?P<type>[^;\(]+)\s+(?P<name>[^;\[\]]+)(?:\[(?P<array>\d+)\])?;\s*\/\*(?P<comment>.*)\*\/\s*$') | |
50 | comment_re = re.compile(r'^\s*\/\*.*\*\/\s*$') | |
51 | ||
52 | pastructs = struct_re.findall(pahole) | |
53 | out = {} | |
54 | ||
a971f071 DL |
55 | for sname, data in pastructs: |
56 | this = out.setdefault(sname, {}) | |
36a8fdfd DL |
57 | fields = this.setdefault('fields', []) |
58 | ||
59 | lines = data.strip().splitlines() | |
60 | ||
a971f071 DL |
61 | next_offs = 0 |
62 | ||
36a8fdfd DL |
63 | for line in lines: |
64 | if line.strip() == '': | |
65 | continue | |
66 | m = comment_re.match(line) | |
67 | if m is not None: | |
68 | continue | |
69 | ||
70 | m = field_re.match(line) | |
71 | if m is not None: | |
72 | offs, size = m.group('comment').strip().split() | |
73 | offs = int(offs) | |
74 | size = int(size) | |
75 | typ_ = m.group('type').strip() | |
76 | name = m.group('name') | |
77 | ||
78 | if name.startswith('(*'): | |
79 | # function pointer | |
80 | typ_ = typ_ + ' *' | |
81 | name = name[2:].split(')')[0] | |
82 | ||
83 | data = { | |
84 | 'name': name, | |
85 | 'type': typ_, | |
a971f071 DL |
86 | # 'offset': offs, |
87 | # 'size': size, | |
36a8fdfd DL |
88 | } |
89 | if m.group('array'): | |
90 | data['array'] = int(m.group('array')) | |
91 | ||
92 | fields.append(data) | |
a971f071 DL |
93 | if offs != next_offs: |
94 | raise ValueError('%d padding bytes before struct %s.%s' % (offs - next_offs, sname, name)) | |
95 | next_offs = offs + size | |
36a8fdfd DL |
96 | continue |
97 | ||
98 | raise ValueError('cannot process line: %s' % line) | |
99 | ||
100 | return out | |
101 | ||
102 | class FieldApplicator(object): | |
103 | ''' | |
104 | Fill ELFDissectStruct fields list from pahole/JSON | |
105 | ||
106 | Uses the JSON file created by the above code to fill in the struct fields | |
107 | in subclasses of ELFDissectStruct. | |
108 | ''' | |
109 | ||
110 | # only what we really need. add more as needed. | |
111 | packtypes = { | |
112 | 'int': 'i', | |
113 | 'uint8_t': 'B', | |
114 | 'uint16_t': 'H', | |
115 | 'uint32_t': 'I', | |
116 | 'char': 's', | |
117 | } | |
118 | ||
119 | def __init__(self, data): | |
120 | self.data = data | |
121 | self.classes = [] | |
122 | self.clsmap = {} | |
123 | ||
124 | def add(self, cls): | |
125 | self.classes.append(cls) | |
126 | self.clsmap[cls.struct] = cls | |
127 | ||
128 | def resolve(self, cls): | |
129 | out = [] | |
a971f071 | 130 | #offset = 0 |
36a8fdfd DL |
131 | |
132 | fieldrename = getattr(cls, 'fieldrename', {}) | |
133 | def mkname(n): | |
134 | return (fieldrename.get(n, n),) | |
135 | ||
136 | for field in self.data[cls.struct]['fields']: | |
137 | typs = field['type'].split() | |
138 | typs = [i for i in typs if i not in ['const']] | |
139 | ||
a971f071 DL |
140 | # this will break reuse of xrefstructs.json across 32bit & 64bit |
141 | # platforms | |
142 | ||
143 | #if field['offset'] != offset: | |
144 | # assert offset < field['offset'] | |
145 | # out.append(('_pad', '%ds' % (field['offset'] - offset,))) | |
36a8fdfd DL |
146 | |
147 | # pretty hacky C types handling, but covers what we need | |
148 | ||
149 | ptrlevel = 0 | |
150 | while typs[-1] == '*': | |
151 | typs.pop(-1) | |
152 | ptrlevel += 1 | |
153 | ||
154 | if ptrlevel > 0: | |
155 | packtype = ('P', None) | |
156 | if ptrlevel == 1: | |
157 | if typs[0] == 'char': | |
158 | packtype = ('P', str) | |
159 | elif typs[0] == 'struct' and typs[1] in self.clsmap: | |
160 | packtype = ('P', self.clsmap[typs[1]]) | |
161 | elif typs[0] == 'enum': | |
162 | packtype = ('I',) | |
163 | elif typs[0] in self.packtypes: | |
164 | packtype = (self.packtypes[typs[0]],) | |
165 | elif typs[0] == 'struct': | |
166 | if typs[1] in self.clsmap: | |
167 | packtype = (self.clsmap[typs[1]],) | |
168 | else: | |
a971f071 | 169 | raise ValueError('embedded struct %s not in extracted data' % (typs[1],)) |
36a8fdfd DL |
170 | else: |
171 | raise ValueError('cannot decode field %s in struct %s (%s)' % ( | |
172 | cls.struct, field['name'], field['type'])) | |
173 | ||
174 | if 'array' in field and typs[0] == 'char': | |
175 | packtype = ('%ds' % field['array'],) | |
176 | out.append(mkname(field['name']) + packtype) | |
177 | elif 'array' in field: | |
178 | for i in range(0, field['array']): | |
179 | out.append(mkname('%s_%d' % (field['name'], i)) + packtype) | |
180 | else: | |
181 | out.append(mkname(field['name']) + packtype) | |
182 | ||
a971f071 | 183 | #offset = field['offset'] + field['size'] |
36a8fdfd DL |
184 | |
185 | cls.fields = out | |
186 | ||
187 | def __call__(self): | |
188 | for cls in self.classes: | |
189 | self.resolve(cls) | |
190 | ||
191 | def main(): | |
192 | argp = argparse.ArgumentParser(description = 'FRR DWARF structure extractor') | |
193 | argp.add_argument('-o', dest='output', type=str, help='write JSON output', default='python/xrefstructs.json') | |
194 | argp.add_argument('-i', dest='input', type=str, help='ELF file to read', default='lib/.libs/libfrr.so') | |
195 | args = argp.parse_args() | |
196 | ||
197 | out = extract(args.input) | |
198 | with open(args.output + '.tmp', 'w') as fd: | |
199 | json.dump(out, fd, indent=2, sort_keys=True) | |
200 | os.rename(args.output + '.tmp', args.output) | |
201 | ||
202 | if __name__ == '__main__': | |
203 | main() |