]> git.proxmox.com Git - mirror_frr.git/blame - python/tiabwarfo.py
Merge pull request #10944 from pguibert6WIND/isis_misc_fixes
[mirror_frr.git] / python / tiabwarfo.py
CommitLineData
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
19import sys
20import os
21import subprocess
22import re
23import argparse
24import subprocess
25import json
26
27structs = ['xref', 'xref_logmsg', 'xref_threadsched', 'xref_install_element', 'xrefdata', 'xrefdata_logmsg', 'cmd_element']
28
29def 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
102class 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
191def 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
202if __name__ == '__main__':
203 main()