]>
git.proxmox.com Git - mirror_frr.git/blob - python/xrelfo.py
1 # FRR ELF xref extractor
3 # Copyright (C) 2020 David Lamparter for NetDEF, Inc.
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)
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
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
30 json_dump_args
["escape_forward_slashes"] = False
36 from clippy
.uidhash
import uidhash
37 from clippy
.elf
import *
38 from clippy
import frr_top_src
, CmdAttr
39 from tiabwarfo
import FieldApplicator
40 from xref2vtysh
import CommandEntry
43 with
open(os
.path
.join(frr_top_src
, "python", "xrefstructs.json"), "r") as fd
:
44 xrefstructs
= json
.load(fd
)
45 except FileNotFoundError
:
48 The "xrefstructs.json" file (created by running tiabwarfo.py with the pahole
49 tool available) could not be found. It should be included with the sources.
54 # constants, need to be kept in sync manually...
56 XREFT_THREADSCHED
= 0x100
59 XREFT_INSTALL_ELEMENT
= 0x301
63 prios
= ["0", "1", "2", "E", "W", "N", "I", "D"]
66 class XrelfoJson(object):
70 def check(self
, wopt
):
73 def to_dict(self
, refs
):
77 class Xref(ELFDissectStruct
, XrelfoJson
):
79 fieldrename
= {"type": "typ"}
82 def __init__(self
, *args
, **kwargs
):
83 super().__init
__(*args
, **kwargs
)
85 self
._container
= None
87 self
.xrefdata
.ref_from(self
, self
.typ
)
90 if self
._container
is None:
91 if self
.typ
in self
.containers
:
92 self
._container
= self
.container_of(self
.containers
[self
.typ
], "xref")
93 return self
._container
95 def check(self
, *args
, **kwargs
):
97 yield from self
._container
.check(*args
, **kwargs
)
100 class Xrefdata(ELFDissectStruct
):
103 # uid is all zeroes in the data loaded from ELF
104 fieldrename
= {"uid": "_uid"}
106 def ref_from(self
, xref
, typ
):
111 if self
.hashstr
is None:
113 return uidhash(self
.xref
.file, self
.hashstr
, self
.hashu32_0
, self
.hashu32_1
)
116 class XrefPtr(ELFDissectStruct
):
122 class XrefThreadSched(ELFDissectStruct
, XrelfoJson
):
123 struct
= "xref_threadsched"
126 Xref
.containers
[XREFT_THREADSCHED
] = XrefThreadSched
129 class XrefLogmsg(ELFDissectStruct
, XrelfoJson
):
130 struct
= "xref_logmsg"
132 def _warn_fmt(self
, text
):
133 lines
= text
.split("\n")
135 (self
.xref
.file, self
.xref
.line
),
136 "%s:%d: %s (in %s())%s\n"
142 "".join(["\n" + l
for l
in lines
[1:]]),
147 (re
.compile(r
"([\n\t]+)"), "error: log message contains tab or newline"),
148 # (re.compile(r'^(\s+)'), 'warning: log message starts with whitespace'),
150 re
.compile(r
"^((?:warn(?:ing)?|error):\s*)", re
.I
),
151 "warning: log message starts with severity",
155 # the (?<![\?:] ) avoids warning for x ? inet_ntop(...) : "(bla)"
157 re
.compile(r
"((?<![\?:] )inet_ntop\s*\(\s*(?:[AP]F_INET|2)\s*,)"),
158 "cleanup: replace inet_ntop(AF_INET, ...) with %pI4",
162 re
.compile(r
"((?<![\?:] )inet_ntop\s*\(\s*(?:[AP]F_INET6|10)\s*,)"),
163 "cleanup: replace inet_ntop(AF_INET6, ...) with %pI6",
167 # string split-up here is to not trigger "inet_ntoa forbidden"
168 re
.compile(r
"((?<![\?:] )inet_" + r
"ntoa)"),
169 "cleanup: replace inet_" + "ntoa(...) with %pI4",
173 re
.compile(r
"((?<![\?:] )ipaddr2str)"),
174 "cleanup: replace ipaddr2str(...) with %pIA",
178 re
.compile(r
"((?<![\?:] )prefix2str)"),
179 "cleanup: replace prefix2str(...) with %pFX",
183 re
.compile(r
"((?<![\?:] )prefix_mac2str)"),
184 "cleanup: replace prefix_mac2str(...) with %pEA",
188 re
.compile(r
"((?<![\?:] )sockunion2str)"),
189 "cleanup: replace sockunion2str(...) with %pSU",
192 # (re.compile(r'^(\s*__(?:func|FUNCTION|PRETTY_FUNCTION)__\s*)'), 'error: debug message starts with __func__', lambda s: (s.priority & 7 == 7) ),
195 def check(self
, wopt
):
196 def fmt_msg(rex
, itext
):
197 if sys
.stderr
.isatty():
198 items
= rex
.split(itext
)
200 for i
, text
in enumerate(items
):
202 out
.append("\033[41;37;1m%s\033[m" % repr(text
)[1:-1])
204 out
.append(repr(text
)[1:-1])
206 excerpt
= "".join(out
)
208 excerpt
= repr(itext
)[1:-1]
212 for rex
, msg
in self
.fmt_regexes
:
213 if not rex
.search(self
.fmtstring
):
216 excerpt
= fmt_msg(rex
, self
.fmtstring
)
217 yield from self
._warn
_fmt
('%s: "%s"' % (msg
, excerpt
))
220 for rex
, msg
, cond
in self
.arg_regexes
:
223 if not rex
.search(self
.args
):
226 excerpt
= fmt_msg(rex
, self
.args
)
227 yield from self
._warn
_fmt
(
228 '%s:\n\t"%s",\n\t%s' % (msg
, repr(self
.fmtstring
)[1:-1], excerpt
)
233 "%-60s %s%s %-25s [EC %d] %s"
235 "%s:%d %s()" % (self
.xref
.file, self
.xref
.line
, self
.xref
.func
),
236 prios
[self
.priority
& 7],
237 priovals
.get(self
.priority
& 0x30, " "),
238 self
.xref
.xrefdata
.uid
,
244 def to_dict(self
, xrelfo
):
245 jsobj
= dict([(i
, getattr(self
.xref
, i
)) for i
in ["file", "line", "func"]])
247 jsobj
["ec"] = self
.ec
248 jsobj
["fmtstring"] = self
.fmtstring
249 jsobj
["args"] = self
.args
250 jsobj
["priority"] = self
.priority
& 7
251 jsobj
["type"] = "logmsg"
252 jsobj
["binary"] = self
._elfsect
._elfwrap
.orig_filename
254 if self
.priority
& 0x10:
255 jsobj
.setdefault("flags", []).append("errno")
256 if self
.priority
& 0x20:
257 jsobj
.setdefault("flags", []).append("getaddrinfo")
259 xrelfo
["refs"].setdefault(self
.xref
.xrefdata
.uid
, []).append(jsobj
)
262 Xref
.containers
[XREFT_LOGMSG
] = XrefLogmsg
265 class CmdElement(ELFDissectStruct
, XrelfoJson
):
266 struct
= "cmd_element"
268 def __init__(self
, *args
, **kwargs
):
269 super().__init
__(*args
, **kwargs
)
271 def to_dict(self
, xrelfo
):
274 .setdefault(self
.name
, {})
275 .setdefault(self
._elfsect
._elfwrap
.orig_filename
, {})
280 "string": self
.string
,
285 jsobj
["attr"] = attr
= self
.attr
286 for attrname
in CmdAttr
.__members
__:
287 val
= CmdAttr
[attrname
]
289 jsobj
.setdefault("attrs", []).append(attrname
.lower())
292 jsobj
["defun"] = dict(
293 [(i
, getattr(self
.xref
, i
)) for i
in ["file", "line", "func"]]
297 Xref
.containers
[XREFT_DEFUN
] = CmdElement
300 class XrefInstallElement(ELFDissectStruct
, XrelfoJson
):
301 struct
= "xref_install_element"
303 def to_dict(self
, xrelfo
):
306 .setdefault(self
.cmd_element
.name
, {})
307 .setdefault(self
._elfsect
._elfwrap
.orig_filename
, {})
309 nodes
= jsobj
.setdefault("nodes", [])
313 "node": self
.node_type
,
315 [(i
, getattr(self
.xref
, i
)) for i
in ["file", "line", "func"]]
321 Xref
.containers
[XREFT_INSTALL_ELEMENT
] = XrefInstallElement
323 # shove in field defs
324 fieldapply
= FieldApplicator(xrefstructs
)
326 fieldapply
.add(Xrefdata
)
327 fieldapply
.add(XrefLogmsg
)
328 fieldapply
.add(XrefThreadSched
)
329 fieldapply
.add(CmdElement
)
330 fieldapply
.add(XrefInstallElement
)
344 def load_file(self
, filename
):
345 orig_filename
= filename
346 if filename
.endswith(".la") or filename
.endswith(".lo"):
347 with
open(filename
, "r") as fd
:
350 if line
.startswith("#") or line
== "" or "=" not in line
:
353 var
, val
= line
.split("=", 1)
354 if var
not in ["library_names", "pic_object"]:
356 if val
.startswith("'") or val
.startswith('"'):
359 if var
== "pic_object":
360 filename
= os
.path
.join(os
.path
.dirname(filename
), val
)
363 val
= val
.strip().split()[0]
364 filename
= os
.path
.join(os
.path
.dirname(filename
), ".libs", val
)
368 'could not process libtool file "%s"' % orig_filename
372 with
open(filename
, "rb") as fd
:
375 if hdr
== b
"\x7fELF":
376 self
.load_elf(filename
, orig_filename
)
380 path
, name
= os
.path
.split(filename
)
381 filename
= os
.path
.join(path
, ".libs", name
)
385 with
open(filename
, "r") as fd
:
389 raise ValueError("cannot determine file type for %s" % (filename
))
391 def load_elf(self
, filename
, orig_filename
):
392 edf
= ELFDissectFile(filename
)
393 edf
.orig_filename
= orig_filename
395 note
= edf
._elffile
.find_note("FRRouting", "XREF")
397 endian
= ">" if edf
._elffile
.bigendian
else "<"
398 mem
= edf
._elffile
[note
]
399 if edf
._elffile
.elfclass
== 64:
400 start
, end
= struct
.unpack(endian
+ "QQ", mem
)
402 end
+= note
.start
+ 8
404 start
, end
= struct
.unpack(endian
+ "II", mem
)
406 end
+= note
.start
+ 4
408 ptrs
= edf
.iter_data(XrefPtr
, slice(start
, end
))
411 xrefarray
= edf
.get_section("xref_array")
412 if xrefarray
is None:
413 raise ValueError("file has neither xref note nor xref_array section")
415 ptrs
= xrefarray
.iter_data(XrefPtr
)
421 self
._xrefs
.append(ptr
.xref
)
423 container
= ptr
.xref
.container()
424 if container
is None:
426 container
.to_dict(self
)
430 def load_json(self
, fd
):
432 for uid
, items
in data
["refs"].items():
433 myitems
= self
["refs"].setdefault(uid
, [])
439 for cmd
, items
in data
["cli"].items():
440 self
["cli"].setdefault(cmd
, {}).update(items
)
444 def check(self
, checks
):
445 for xref
in self
._xrefs
:
446 yield from xref
.check(checks
)
450 argp
= argparse
.ArgumentParser(description
="FRR xref ELF extractor")
451 argp
.add_argument("-o", dest
="output", type=str, help="write JSON output")
452 argp
.add_argument("--out-by-file", type=str, help="write by-file JSON output")
453 argp
.add_argument("-c", dest
="vtysh_cmds", type=str, help="write vtysh_cmd.c")
454 argp
.add_argument("-Wlog-format", action
="store_const", const
=True)
455 argp
.add_argument("-Wlog-args", action
="store_const", const
=True)
456 argp
.add_argument("-Werror", action
="store_const", const
=True)
457 argp
.add_argument("--profile", action
="store_const", const
=True)
463 help="files to read (ELF files or libtool objects)",
465 args
= argp
.parse_args()
470 cProfile
.runctx("_main(args)", globals(), {"args": args
}, sort
="cumtime")
479 for fn
in args
.binaries
:
484 sys
.stderr
.write("while processing %s:\n" % (fn
))
485 traceback
.print_exc()
487 for option
in dir(args
):
488 if option
.startswith("W") and option
!= "Werror":
489 checks
= sorted(xrelfo
.check(args
))
490 sys
.stderr
.write("".join([c
[-1] for c
in checks
]))
492 if args
.Werror
and len(checks
) > 0:
496 refs
= xrelfo
["refs"]
499 for k
, v
in refs
.items():
500 strs
= set([i
["fmtstring"] for i
in v
])
502 print("\033[31;1m%s\033[m" % k
)
507 for uid
, locs
in refs
.items():
509 filearray
= outbyfile
.setdefault(loc
["file"], [])
512 filearray
.append(loc
)
514 for k
in outbyfile
.keys():
515 outbyfile
[k
] = sorted(outbyfile
[k
], key
=lambda x
: x
["line"])
521 with
open(args
.output
+ ".tmp", "w") as fd
:
522 json
.dump(out
, fd
, indent
=2, sort_keys
=True, **json_dump_args
)
523 os
.rename(args
.output
+ ".tmp", args
.output
)
526 with
open(args
.out_by_file
+ ".tmp", "w") as fd
:
527 json
.dump(outbyfile
, fd
, indent
=2, sort_keys
=True, **json_dump_args
)
528 os
.rename(args
.out_by_file
+ ".tmp", args
.out_by_file
)
531 with
open(args
.vtysh_cmds
+ ".tmp", "w") as fd
:
532 CommandEntry
.run(out
, fd
)
533 os
.rename(args
.vtysh_cmds
+ ".tmp", args
.vtysh_cmds
)
534 if args
.Werror
and CommandEntry
.warn_counter
:
538 if __name__
== "__main__":