]>
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
27 from clippy
.uidhash
import uidhash
28 from clippy
.elf
import *
29 from clippy
import frr_top_src
30 from tiabwarfo
import FieldApplicator
33 with
open(os
.path
.join(frr_top_src
, 'python', 'xrefstructs.json'), 'r') as fd
:
34 xrefstructs
= json
.load(fd
)
35 except FileNotFoundError
:
37 The "xrefstructs.json" file (created by running tiabwarfo.py with the pahole
38 tool available) could not be found. It should be included with the sources.
42 # constants, need to be kept in sync manually...
44 XREFT_THREADSCHED
= 0x100
47 XREFT_INSTALL_ELEMENT
= 0x301
51 prios
= ['0', '1', '2', 'E', 'W', 'N', 'I', 'D']
54 class XrelfoJson(object):
58 def check(self
, wopt
):
61 def to_dict(self
, refs
):
64 class Xref(ELFDissectStruct
, XrelfoJson
):
66 fieldrename
= {'type': 'typ'}
69 def __init__(self
, *args
, **kwargs
):
70 super().__init
__(*args
, **kwargs
)
72 self
._container
= None
74 self
.xrefdata
.ref_from(self
, self
.typ
)
77 if self
._container
is None:
78 if self
.typ
in self
.containers
:
79 self
._container
= self
.container_of(self
.containers
[self
.typ
], 'xref')
80 return self
._container
82 def check(self
, *args
, **kwargs
):
84 yield from self
._container
.check(*args
, **kwargs
)
87 class Xrefdata(ELFDissectStruct
):
90 # uid is all zeroes in the data loaded from ELF
91 fieldrename
= {'uid': '_uid'}
93 def ref_from(self
, xref
, typ
):
98 if self
.hashstr
is None:
100 return uidhash(self
.xref
.file, self
.hashstr
, self
.hashu32_0
, self
.hashu32_1
)
102 class XrefPtr(ELFDissectStruct
):
107 class XrefThreadSched(ELFDissectStruct
, XrelfoJson
):
108 struct
= 'xref_threadsched'
109 Xref
.containers
[XREFT_THREADSCHED
] = XrefThreadSched
111 class XrefLogmsg(ELFDissectStruct
, XrelfoJson
):
112 struct
= 'xref_logmsg'
114 def _warn_fmt(self
, text
):
115 yield ((self
.xref
.file, self
.xref
.line
), '%s:%d: %s (in %s())\n' % (self
.xref
.file, self
.xref
.line
, text
, self
.xref
.func
))
118 (re
.compile(r
'([\n\t]+)'), 'error: log message contains tab or newline'),
119 # (re.compile(r'^(\s+)'), 'warning: log message starts with whitespace'),
120 (re
.compile(r
'^((?:warn(?:ing)?|error):\s*)', re
.I
), 'warning: log message starts with severity'),
123 def check(self
, wopt
):
125 for rex
, msg
in self
.regexes
:
126 if not rex
.search(self
.fmtstring
):
129 if sys
.stderr
.isatty():
130 items
= rex
.split(self
.fmtstring
)
132 for i
, text
in enumerate(items
):
134 out
.append('\033[41;37;1m%s\033[m' % repr(text
)[1:-1])
136 out
.append(repr(text
)[1:-1])
138 excerpt
= ''.join(out
)
141 excerpt
= repr(self
.fmtstring
)[1:-1]
143 yield from self
._warn
_fmt
('%s: "%s"' % (msg
, excerpt
))
146 print('%-60s %s%s %-25s [EC %d] %s' % (
147 '%s:%d %s()' % (self
.xref
.file, self
.xref
.line
, self
.xref
.func
),
148 prios
[self
.priority
& 7],
149 priovals
.get(self
.priority
& 0x30, ' '),
150 self
.xref
.xrefdata
.uid
, self
.ec
, self
.fmtstring
))
152 def to_dict(self
, xrelfo
):
153 jsobj
= dict([(i
, getattr(self
.xref
, i
)) for i
in ['file', 'line', 'func']])
155 jsobj
['ec'] = self
.ec
156 jsobj
['fmtstring'] = self
.fmtstring
157 jsobj
['priority'] = self
.priority
& 7
158 jsobj
['type'] = 'logmsg'
159 jsobj
['binary'] = self
._elfsect
._elfwrap
.orig_filename
161 if self
.priority
& 0x10:
162 jsobj
.setdefault('flags', []).append('errno')
163 if self
.priority
& 0x20:
164 jsobj
.setdefault('flags', []).append('getaddrinfo')
166 xrelfo
['refs'].setdefault(self
.xref
.xrefdata
.uid
, []).append(jsobj
)
168 Xref
.containers
[XREFT_LOGMSG
] = XrefLogmsg
170 class CmdElement(ELFDissectStruct
, XrelfoJson
):
171 struct
= 'cmd_element'
173 cmd_attrs
= { 0: None, 1: 'deprecated', 2: 'hidden'}
175 def __init__(self
, *args
, **kwargs
):
176 super().__init
__(*args
, **kwargs
)
178 def to_dict(self
, xrelfo
):
179 jsobj
= xrelfo
['cli'].setdefault(self
.name
, {}).setdefault(self
._elfsect
._elfwrap
.orig_filename
, {})
182 'string': self
.string
,
184 'attr': self
.cmd_attrs
.get(self
.attr
, self
.attr
),
186 if jsobj
['attr'] is None:
189 jsobj
['defun'] = dict([(i
, getattr(self
.xref
, i
)) for i
in ['file', 'line', 'func']])
191 Xref
.containers
[XREFT_DEFUN
] = CmdElement
193 class XrefInstallElement(ELFDissectStruct
, XrelfoJson
):
194 struct
= 'xref_install_element'
196 def to_dict(self
, xrelfo
):
197 jsobj
= xrelfo
['cli'].setdefault(self
.cmd_element
.name
, {}).setdefault(self
._elfsect
._elfwrap
.orig_filename
, {})
198 nodes
= jsobj
.setdefault('nodes', [])
201 'node': self
.node_type
,
202 'install': dict([(i
, getattr(self
.xref
, i
)) for i
in ['file', 'line', 'func']]),
205 Xref
.containers
[XREFT_INSTALL_ELEMENT
] = XrefInstallElement
207 # shove in field defs
208 fieldapply
= FieldApplicator(xrefstructs
)
210 fieldapply
.add(Xrefdata
)
211 fieldapply
.add(XrefLogmsg
)
212 fieldapply
.add(XrefThreadSched
)
213 fieldapply
.add(CmdElement
)
214 fieldapply
.add(XrefInstallElement
)
226 def load_file(self
, filename
):
227 orig_filename
= filename
228 if filename
.endswith('.la') or filename
.endswith('.lo'):
229 with
open(filename
, 'r') as fd
:
232 if line
.startswith('#') or line
== '' or '=' not in line
:
235 var
, val
= line
.split('=', 1)
236 if var
not in ['library_names', 'pic_object']:
238 if val
.startswith("'") or val
.startswith('"'):
241 if var
== 'pic_object':
242 filename
= os
.path
.join(os
.path
.dirname(filename
), val
)
245 val
= val
.strip().split()[0]
246 filename
= os
.path
.join(os
.path
.dirname(filename
), '.libs', val
)
249 raise ValueError('could not process libtool file "%s"' % orig_filename
)
252 with
open(filename
, 'rb') as fd
:
255 if hdr
== b
'\x7fELF':
256 self
.load_elf(filename
, orig_filename
)
260 path
, name
= os
.path
.split(filename
)
261 filename
= os
.path
.join(path
, '.libs', name
)
265 with
open(filename
, 'r') as fd
:
269 raise ValueError('cannot determine file type for %s' % (filename
))
271 def load_elf(self
, filename
, orig_filename
):
272 edf
= ELFDissectFile(filename
)
273 edf
.orig_filename
= orig_filename
275 note
= edf
._elffile
.find_note('FRRouting', 'XREF')
277 endian
= '>' if edf
._elffile
.bigendian
else '<'
278 mem
= edf
._elffile
[note
]
279 if edf
._elffile
.elfclass
== 64:
280 start
, end
= struct
.unpack(endian
+ 'QQ', mem
)
282 end
+= note
.start
+ 8
284 start
, end
= struct
.unpack(endian
+ 'II', mem
)
286 end
+= note
.start
+ 4
288 ptrs
= edf
.iter_data(XrefPtr
, slice(start
, end
))
291 xrefarray
= edf
.get_section('xref_array')
292 if xrefarray
is None:
293 raise ValueError('file has neither xref note nor xref_array section')
295 ptrs
= xrefarray
.iter_data(XrefPtr
)
301 self
._xrefs
.append(ptr
.xref
)
303 container
= ptr
.xref
.container()
304 if container
is None:
306 container
.to_dict(self
)
310 def load_json(self
, fd
):
312 for uid
, items
in data
['refs'].items():
313 myitems
= self
['refs'].setdefault(uid
, [])
319 for cmd
, items
in data
['cli'].items():
320 self
['cli'].setdefault(cmd
, {}).update(items
)
324 def check(self
, checks
):
325 for xref
in self
._xrefs
:
326 yield from xref
.check(checks
)
329 argp
= argparse
.ArgumentParser(description
= 'FRR xref ELF extractor')
330 argp
.add_argument('-o', dest
='output', type=str, help='write JSON output')
331 argp
.add_argument('--out-by-file', type=str, help='write by-file JSON output')
332 argp
.add_argument('-Wlog-format', action
='store_const', const
=True)
333 argp
.add_argument('--profile', action
='store_const', const
=True)
334 argp
.add_argument('binaries', metavar
='BINARY', nargs
='+', type=str, help='files to read (ELF files or libtool objects)')
335 args
= argp
.parse_args()
339 cProfile
.runctx('_main(args)', globals(), {'args': args
}, sort
='cumtime')
347 for fn
in args
.binaries
:
352 sys
.stderr
.write('while processing %s:\n' % (fn
))
353 traceback
.print_exc()
355 for option
in dir(args
):
356 if option
.startswith('W'):
357 checks
= sorted(xrelfo
.check(args
))
358 sys
.stderr
.write(''.join([c
[-1] for c
in checks
]))
362 refs
= xrelfo
['refs']
365 for k
, v
in refs
.items():
366 strs
= set([i
['fmtstring'] for i
in v
])
368 print('\033[31;1m%s\033[m' % k
)
373 for uid
, locs
in refs
.items():
375 filearray
= outbyfile
.setdefault(loc
['file'], [])
378 filearray
.append(loc
)
380 for k
in outbyfile
.keys():
381 outbyfile
[k
] = sorted(outbyfile
[k
], key
=lambda x
: x
['line'])
387 with
open(args
.output
+ '.tmp', 'w') as fd
:
388 json
.dump(out
, fd
, indent
=2, sort_keys
=True)
389 os
.rename(args
.output
+ '.tmp', args
.output
)
392 with
open(args
.out_by_file
+ '.tmp', 'w') as fd
:
393 json
.dump(outbyfile
, fd
, indent
=2, sort_keys
=True)
394 os
.rename(args
.out_by_file
+ '.tmp', args
.out_by_file
)
396 if __name__
== '__main__':