]>
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 | |
36a8fdfd DL |
24 | import json |
25 | ||
00f0c399 DL |
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 | """ | |
36a8fdfd DL |
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 | }; | |
00f0c399 DL |
53 | """ |
54 | pahole = subprocess.check_output( | |
55 | ["pahole", "-C", ",".join(structs), filename] | |
56 | ).decode("UTF-8") | |
36a8fdfd | 57 | |
00f0c399 DL |
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*$") | |
36a8fdfd DL |
63 | |
64 | pastructs = struct_re.findall(pahole) | |
65 | out = {} | |
66 | ||
a971f071 DL |
67 | for sname, data in pastructs: |
68 | this = out.setdefault(sname, {}) | |
00f0c399 | 69 | fields = this.setdefault("fields", []) |
36a8fdfd DL |
70 | |
71 | lines = data.strip().splitlines() | |
72 | ||
a971f071 DL |
73 | next_offs = 0 |
74 | ||
36a8fdfd | 75 | for line in lines: |
00f0c399 | 76 | if line.strip() == "": |
36a8fdfd DL |
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: | |
00f0c399 | 84 | offs, size = m.group("comment").strip().split() |
36a8fdfd DL |
85 | offs = int(offs) |
86 | size = int(size) | |
00f0c399 DL |
87 | typ_ = m.group("type").strip() |
88 | name = m.group("name") | |
36a8fdfd | 89 | |
00f0c399 | 90 | if name.startswith("(*"): |
36a8fdfd | 91 | # function pointer |
00f0c399 DL |
92 | typ_ = typ_ + " *" |
93 | name = name[2:].split(")")[0] | |
36a8fdfd DL |
94 | |
95 | data = { | |
00f0c399 DL |
96 | "name": name, |
97 | "type": typ_, | |
98 | # 'offset': offs, | |
99 | # 'size': size, | |
36a8fdfd | 100 | } |
00f0c399 DL |
101 | if m.group("array"): |
102 | data["array"] = int(m.group("array")) | |
36a8fdfd DL |
103 | |
104 | fields.append(data) | |
a971f071 | 105 | if offs != next_offs: |
00f0c399 DL |
106 | raise ValueError( |
107 | "%d padding bytes before struct %s.%s" | |
108 | % (offs - next_offs, sname, name) | |
109 | ) | |
a971f071 | 110 | next_offs = offs + size |
36a8fdfd DL |
111 | continue |
112 | ||
00f0c399 | 113 | raise ValueError("cannot process line: %s" % line) |
36a8fdfd DL |
114 | |
115 | return out | |
116 | ||
00f0c399 | 117 | |
36a8fdfd | 118 | class FieldApplicator(object): |
00f0c399 | 119 | """ |
36a8fdfd DL |
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. | |
00f0c399 | 124 | """ |
36a8fdfd DL |
125 | |
126 | # only what we really need. add more as needed. | |
127 | packtypes = { | |
00f0c399 DL |
128 | "int": "i", |
129 | "uint8_t": "B", | |
130 | "uint16_t": "H", | |
131 | "uint32_t": "I", | |
132 | "char": "s", | |
36a8fdfd DL |
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 = [] | |
00f0c399 DL |
146 | # offset = 0 |
147 | ||
148 | fieldrename = getattr(cls, "fieldrename", {}) | |
36a8fdfd | 149 | |
36a8fdfd DL |
150 | def mkname(n): |
151 | return (fieldrename.get(n, n),) | |
152 | ||
00f0c399 DL |
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"]] | |
36a8fdfd | 156 | |
a971f071 DL |
157 | # this will break reuse of xrefstructs.json across 32bit & 64bit |
158 | # platforms | |
159 | ||
00f0c399 | 160 | # if field['offset'] != offset: |
a971f071 DL |
161 | # assert offset < field['offset'] |
162 | # out.append(('_pad', '%ds' % (field['offset'] - offset,))) | |
36a8fdfd DL |
163 | |
164 | # pretty hacky C types handling, but covers what we need | |
165 | ||
166 | ptrlevel = 0 | |
00f0c399 | 167 | while typs[-1] == "*": |
36a8fdfd DL |
168 | typs.pop(-1) |
169 | ptrlevel += 1 | |
170 | ||
171 | if ptrlevel > 0: | |
00f0c399 | 172 | packtype = ("P", None) |
36a8fdfd | 173 | if ptrlevel == 1: |
00f0c399 DL |
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",) | |
36a8fdfd DL |
180 | elif typs[0] in self.packtypes: |
181 | packtype = (self.packtypes[typs[0]],) | |
00f0c399 | 182 | elif typs[0] == "struct": |
36a8fdfd DL |
183 | if typs[1] in self.clsmap: |
184 | packtype = (self.clsmap[typs[1]],) | |
185 | else: | |
00f0c399 DL |
186 | raise ValueError( |
187 | "embedded struct %s not in extracted data" % (typs[1],) | |
188 | ) | |
36a8fdfd | 189 | else: |
00f0c399 DL |
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) | |
36a8fdfd | 201 | else: |
00f0c399 | 202 | out.append(mkname(field["name"]) + packtype) |
36a8fdfd | 203 | |
00f0c399 | 204 | # offset = field['offset'] + field['size'] |
36a8fdfd DL |
205 | |
206 | cls.fields = out | |
207 | ||
208 | def __call__(self): | |
209 | for cls in self.classes: | |
210 | self.resolve(cls) | |
211 | ||
00f0c399 | 212 | |
36a8fdfd | 213 | def main(): |
00f0c399 DL |
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 | ) | |
36a8fdfd DL |
229 | args = argp.parse_args() |
230 | ||
231 | out = extract(args.input) | |
00f0c399 | 232 | with open(args.output + ".tmp", "w") as fd: |
36a8fdfd | 233 | json.dump(out, fd, indent=2, sort_keys=True) |
00f0c399 DL |
234 | os.rename(args.output + ".tmp", args.output) |
235 | ||
36a8fdfd | 236 | |
00f0c399 | 237 | if __name__ == "__main__": |
36a8fdfd | 238 | main() |