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