]> git.proxmox.com Git - mirror_frr.git/blob - python/tiabwarfo.py
doc: Add `show ipv6 rpf X:X::X:X` command to docs
[mirror_frr.git] / python / tiabwarfo.py
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 json
25
26 structs = [
27 "xref",
28 "xref_logmsg",
29 "xref_threadsched",
30 "xref_install_element",
31 "xrefdata",
32 "xrefdata_logmsg",
33 "cmd_element",
34 ]
35
36
37 def extract(filename="lib/.libs/libfrr.so"):
38 """
39 Convert output from "pahole" to JSON.
40
41 Example pahole output:
42 $ pahole -C xref lib/.libs/libfrr.so
43 struct xref {
44 struct xrefdata * xrefdata; /* 0 8 */
45 enum xref_type type; /* 8 4 */
46 int line; /* 12 4 */
47 const char * file; /* 16 8 */
48 const char * func; /* 24 8 */
49
50 /* size: 32, cachelines: 1, members: 5 */
51 /* last cacheline: 32 bytes */
52 };
53 """
54 pahole = subprocess.check_output(
55 ["pahole", "-C", ",".join(structs), filename]
56 ).decode("UTF-8")
57
58 struct_re = re.compile(r"^struct ([^ ]+) \{([^\}]+)};", flags=re.M | re.S)
59 field_re = re.compile(
60 r"^\s*(?P<type>[^;\(]+)\s+(?P<name>[^;\[\]]+)(?:\[(?P<array>\d+)\])?;\s*\/\*(?P<comment>.*)\*\/\s*$"
61 )
62 comment_re = re.compile(r"^\s*\/\*.*\*\/\s*$")
63
64 pastructs = struct_re.findall(pahole)
65 out = {}
66
67 for sname, data in pastructs:
68 this = out.setdefault(sname, {})
69 fields = this.setdefault("fields", [])
70
71 lines = data.strip().splitlines()
72
73 next_offs = 0
74
75 for line in lines:
76 if line.strip() == "":
77 continue
78 m = comment_re.match(line)
79 if m is not None:
80 continue
81
82 m = field_re.match(line)
83 if m is not None:
84 offs, size = m.group("comment").strip().split()
85 offs = int(offs)
86 size = int(size)
87 typ_ = m.group("type").strip()
88 name = m.group("name")
89
90 if name.startswith("(*"):
91 # function pointer
92 typ_ = typ_ + " *"
93 name = name[2:].split(")")[0]
94
95 data = {
96 "name": name,
97 "type": typ_,
98 # 'offset': offs,
99 # 'size': size,
100 }
101 if m.group("array"):
102 data["array"] = int(m.group("array"))
103
104 fields.append(data)
105 if offs != next_offs:
106 raise ValueError(
107 "%d padding bytes before struct %s.%s"
108 % (offs - next_offs, sname, name)
109 )
110 next_offs = offs + size
111 continue
112
113 raise ValueError("cannot process line: %s" % line)
114
115 return out
116
117
118 class FieldApplicator(object):
119 """
120 Fill ELFDissectStruct fields list from pahole/JSON
121
122 Uses the JSON file created by the above code to fill in the struct fields
123 in subclasses of ELFDissectStruct.
124 """
125
126 # only what we really need. add more as needed.
127 packtypes = {
128 "int": "i",
129 "uint8_t": "B",
130 "uint16_t": "H",
131 "uint32_t": "I",
132 "char": "s",
133 }
134
135 def __init__(self, data):
136 self.data = data
137 self.classes = []
138 self.clsmap = {}
139
140 def add(self, cls):
141 self.classes.append(cls)
142 self.clsmap[cls.struct] = cls
143
144 def resolve(self, cls):
145 out = []
146 # offset = 0
147
148 fieldrename = getattr(cls, "fieldrename", {})
149
150 def mkname(n):
151 return (fieldrename.get(n, n),)
152
153 for field in self.data[cls.struct]["fields"]:
154 typs = field["type"].split()
155 typs = [i for i in typs if i not in ["const"]]
156
157 # this will break reuse of xrefstructs.json across 32bit & 64bit
158 # platforms
159
160 # if field['offset'] != offset:
161 # assert offset < field['offset']
162 # out.append(('_pad', '%ds' % (field['offset'] - offset,)))
163
164 # pretty hacky C types handling, but covers what we need
165
166 ptrlevel = 0
167 while typs[-1] == "*":
168 typs.pop(-1)
169 ptrlevel += 1
170
171 if ptrlevel > 0:
172 packtype = ("P", None)
173 if ptrlevel == 1:
174 if typs[0] == "char":
175 packtype = ("P", str)
176 elif typs[0] == "struct" and typs[1] in self.clsmap:
177 packtype = ("P", self.clsmap[typs[1]])
178 elif typs[0] == "enum":
179 packtype = ("I",)
180 elif typs[0] in self.packtypes:
181 packtype = (self.packtypes[typs[0]],)
182 elif typs[0] == "struct":
183 if typs[1] in self.clsmap:
184 packtype = (self.clsmap[typs[1]],)
185 else:
186 raise ValueError(
187 "embedded struct %s not in extracted data" % (typs[1],)
188 )
189 else:
190 raise ValueError(
191 "cannot decode field %s in struct %s (%s)"
192 % (cls.struct, field["name"], field["type"])
193 )
194
195 if "array" in field and typs[0] == "char":
196 packtype = ("%ds" % field["array"],)
197 out.append(mkname(field["name"]) + packtype)
198 elif "array" in field:
199 for i in range(0, field["array"]):
200 out.append(mkname("%s_%d" % (field["name"], i)) + packtype)
201 else:
202 out.append(mkname(field["name"]) + packtype)
203
204 # offset = field['offset'] + field['size']
205
206 cls.fields = out
207
208 def __call__(self):
209 for cls in self.classes:
210 self.resolve(cls)
211
212
213 def main():
214 argp = argparse.ArgumentParser(description="FRR DWARF structure extractor")
215 argp.add_argument(
216 "-o",
217 dest="output",
218 type=str,
219 help="write JSON output",
220 default="python/xrefstructs.json",
221 )
222 argp.add_argument(
223 "-i",
224 dest="input",
225 type=str,
226 help="ELF file to read",
227 default="lib/.libs/libfrr.so",
228 )
229 args = argp.parse_args()
230
231 out = extract(args.input)
232 with open(args.output + ".tmp", "w") as fd:
233 json.dump(out, fd, indent=2, sort_keys=True)
234 os.rename(args.output + ".tmp", args.output)
235
236
237 if __name__ == "__main__":
238 main()