]>
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 lines
= text
. split ( ' \n ' )
116 yield (( self
. xref
. file , self
. xref
. line
), ' %s : %d : %s (in %s ()) %s \n ' % ( self
. xref
. file , self
. xref
. line
, lines
[ 0 ], self
. xref
. func
, '' . join ([ ' \n ' + l
for l
in lines
[ 1 :]])))
119 ( re
. compile ( r
'([\n\t]+)' ), 'error: log message contains tab or newline' ),
120 # (re.compile(r'^(\s+)'), 'warning: log message starts with whitespace'),
121 ( re
. compile ( r
'^((?:warn(?:ing)?|error):\s*)' , re
. I
), 'warning: log message starts with severity' ),
124 # the (?<![\?:] ) avoids warning for x ? inet_ntop(...) : "(bla)"
125 ( re
. compile ( r
'((?<![\?:] )inet_ntop\s*\(\s*(?:[AP]F_INET|2)\s*,)' ), 'cleanup: replace inet_ntop(AF_INET, ...) with %pI4' , lambda s
: True ),
126 ( re
. compile ( r
'((?<![\?:] )inet_ntop\s*\(\s*(?:[AP]F_INET6|10)\s*,)' ), 'cleanup: replace inet_ntop(AF_INET6, ...) with %pI6' , lambda s
: True ),
127 ( re
. compile ( r
'((?<![\?:] )inet_ntoa)' ), 'cleanup: replace inet_ntoa(...) with %pI4' , lambda s
: True ),
128 ( re
. compile ( r
'((?<![\?:] )ipaddr2str)' ), 'cleanup: replace ipaddr2str(...) with %pIA' , lambda s
: True ),
129 ( re
. compile ( r
'((?<![\?:] )prefix2str)' ), 'cleanup: replace prefix2str(...) with %pFX' , lambda s
: True ),
130 ( re
. compile ( r
'((?<![\?:] )prefix_mac2str)' ), 'cleanup: replace prefix_mac2str(...) with %pEA' , lambda s
: True ),
131 ( re
. compile ( r
'((?<![\?:] )sockunion2str)' ), 'cleanup: replace sockunion2str(...) with %pSU' , lambda s
: True ),
133 # (re.compile(r'^(\s*__(?:func|FUNCTION|PRETTY_FUNCTION)__\s*)'), 'error: debug message starts with __func__', lambda s: (s.priority & 7 == 7) ),
136 def check ( self
, wopt
):
137 def fmt_msg ( rex
, itext
):
138 if sys
. stderr
. isatty ():
139 items
= rex
. split ( itext
)
141 for i
, text
in enumerate ( items
):
143 out
. append ( ' \033 [41;37;1m %s \033 [m' % repr ( text
)[ 1 :- 1 ])
145 out
. append ( repr ( text
)[ 1 :- 1 ])
147 excerpt
= '' . join ( out
)
149 excerpt
= repr ( itext
)[ 1 :- 1 ]
153 for rex
, msg
in self
. fmt_regexes
:
154 if not rex
. search ( self
. fmtstring
):
157 excerpt
= fmt_msg ( rex
, self
. fmtstring
)
158 yield from self
._ warn
_ fmt
( ' %s : " %s "' % ( msg
, excerpt
))
161 for rex
, msg
, cond
in self
. arg_regexes
:
164 if not rex
. search ( self
. args
):
167 excerpt
= fmt_msg ( rex
, self
. args
)
168 yield from self
._ warn
_ fmt
( ' %s : \n\t " %s ", \n\t %s ' % ( msg
, repr ( self
. fmtstring
)[ 1 :- 1 ], excerpt
))
171 print ( ' %- 60s %s%s %- 25s [EC %d ] %s ' % (
172 ' %s : %d %s ()' % ( self
. xref
. file , self
. xref
. line
, self
. xref
. func
),
173 prios
[ self
. priority
& 7 ],
174 priovals
. get ( self
. priority
& 0x30 , ' ' ),
175 self
. xref
. xrefdata
. uid
, self
. ec
, self
. fmtstring
))
177 def to_dict ( self
, xrelfo
):
178 jsobj
= dict ([( i
, getattr ( self
. xref
, i
)) for i
in [ 'file' , 'line' , 'func' ]])
180 jsobj
[ 'ec' ] = self
. ec
181 jsobj
[ 'fmtstring' ] = self
. fmtstring
182 jsobj
[ 'args' ] = self
. args
183 jsobj
[ 'priority' ] = self
. priority
& 7
184 jsobj
[ 'type' ] = 'logmsg'
185 jsobj
[ 'binary' ] = self
._ elfsect
._ elfwrap
. orig_filename
187 if self
. priority
& 0x10 :
188 jsobj
. setdefault ( 'flags' , []). append ( 'errno' )
189 if self
. priority
& 0x20 :
190 jsobj
. setdefault ( 'flags' , []). append ( 'getaddrinfo' )
192 xrelfo
[ 'refs' ]. setdefault ( self
. xref
. xrefdata
. uid
, []). append ( jsobj
)
194 Xref
. containers
[ XREFT_LOGMSG
] = XrefLogmsg
196 class CmdElement ( ELFDissectStruct
, XrelfoJson
):
197 struct
= 'cmd_element'
199 cmd_attrs
= { 0 : None , 1 : 'deprecated' , 2 : 'hidden' }
201 def __init__ ( self
, * args
, ** kwargs
):
202 super () .__ init
__ (* args
, ** kwargs
)
204 def to_dict ( self
, xrelfo
):
205 jsobj
= xrelfo
[ 'cli' ]. setdefault ( self
. name
, {}). setdefault ( self
._ elfsect
._ elfwrap
. orig_filename
, {})
208 'string' : self
. string
,
210 'attr' : self
. cmd_attrs
. get ( self
. attr
, self
. attr
),
212 if jsobj
[ 'attr' ] is None :
215 jsobj
[ 'defun' ] = dict ([( i
, getattr ( self
. xref
, i
)) for i
in [ 'file' , 'line' , 'func' ]])
217 Xref
. containers
[ XREFT_DEFUN
] = CmdElement
219 class XrefInstallElement ( ELFDissectStruct
, XrelfoJson
):
220 struct
= 'xref_install_element'
222 def to_dict ( self
, xrelfo
):
223 jsobj
= xrelfo
[ 'cli' ]. setdefault ( self
. cmd_element
. name
, {}). setdefault ( self
._ elfsect
._ elfwrap
. orig_filename
, {})
224 nodes
= jsobj
. setdefault ( 'nodes' , [])
227 'node' : self
. node_type
,
228 'install' : dict ([( i
, getattr ( self
. xref
, i
)) for i
in [ 'file' , 'line' , 'func' ]]),
231 Xref
. containers
[ XREFT_INSTALL_ELEMENT
] = XrefInstallElement
233 # shove in field defs
234 fieldapply
= FieldApplicator ( xrefstructs
)
236 fieldapply
. add ( Xrefdata
)
237 fieldapply
. add ( XrefLogmsg
)
238 fieldapply
. add ( XrefThreadSched
)
239 fieldapply
. add ( CmdElement
)
240 fieldapply
. add ( XrefInstallElement
)
252 def load_file ( self
, filename
):
253 orig_filename
= filename
254 if filename
. endswith ( '.la' ) or filename
. endswith ( '.lo' ):
255 with
open ( filename
, 'r' ) as fd
:
258 if line
. startswith ( '#' ) or line
== '' or '=' not in line
:
261 var
, val
= line
. split ( '=' , 1 )
262 if var
not in [ 'library_names' , 'pic_object' ]:
264 if val
. startswith ( "'" ) or val
. startswith ( '"' ):
267 if var
== 'pic_object' :
268 filename
= os
. path
. join ( os
. path
. dirname ( filename
), val
)
271 val
= val
. strip (). split ()[ 0 ]
272 filename
= os
. path
. join ( os
. path
. dirname ( filename
), '.libs' , val
)
275 raise ValueError ( 'could not process libtool file " %s "' % orig_filename
)
278 with
open ( filename
, 'rb' ) as fd
:
281 if hdr
== b
' \x7f ELF' :
282 self
. load_elf ( filename
, orig_filename
)
286 path
, name
= os
. path
. split ( filename
)
287 filename
= os
. path
. join ( path
, '.libs' , name
)
291 with
open ( filename
, 'r' ) as fd
:
295 raise ValueError ( 'cannot determine file type for %s ' % ( filename
))
297 def load_elf ( self
, filename
, orig_filename
):
298 edf
= ELFDissectFile ( filename
)
299 edf
. orig_filename
= orig_filename
301 note
= edf
._ elffile
. find_note ( 'FRRouting' , 'XREF' )
303 endian
= '>' if edf
._ elffile
. bigendian
else '<'
304 mem
= edf
._ elffile
[ note
]
305 if edf
._ elffile
. elfclass
== 64 :
306 start
, end
= struct
. unpack ( endian
+ 'QQ' , mem
)
308 end
+= note
. start
+ 8
310 start
, end
= struct
. unpack ( endian
+ 'II' , mem
)
312 end
+= note
. start
+ 4
314 ptrs
= edf
. iter_data ( XrefPtr
, slice ( start
, end
))
317 xrefarray
= edf
. get_section ( 'xref_array' )
318 if xrefarray
is None :
319 raise ValueError ( 'file has neither xref note nor xref_array section' )
321 ptrs
= xrefarray
. iter_data ( XrefPtr
)
327 self
._ xrefs
. append ( ptr
. xref
)
329 container
= ptr
. xref
. container ()
330 if container
is None :
332 container
. to_dict ( self
)
336 def load_json ( self
, fd
):
338 for uid
, items
in data
[ 'refs' ]. items ():
339 myitems
= self
[ 'refs' ]. setdefault ( uid
, [])
345 for cmd
, items
in data
[ 'cli' ]. items ():
346 self
[ 'cli' ]. setdefault ( cmd
, {}). update ( items
)
350 def check ( self
, checks
):
351 for xref
in self
._ xrefs
:
352 yield from xref
. check ( checks
)
355 argp
= argparse
. ArgumentParser ( description
= 'FRR xref ELF extractor' )
356 argp
. add_argument ( '-o' , dest
= 'output' , type = str , help = 'write JSON output' )
357 argp
. add_argument ( '--out-by-file' , type = str , help = 'write by-file JSON output' )
358 argp
. add_argument ( '-Wlog-format' , action
= 'store_const' , const
= True )
359 argp
. add_argument ( '-Wlog-args' , action
= 'store_const' , const
= True )
360 argp
. add_argument ( '-Werror' , action
= 'store_const' , const
= True )
361 argp
. add_argument ( '--profile' , action
= 'store_const' , const
= True )
362 argp
. add_argument ( 'binaries' , metavar
= 'BINARY' , nargs
= '+' , type = str , help = 'files to read (ELF files or libtool objects)' )
363 args
= argp
. parse_args ()
367 cProfile
. runctx ( '_main(args)' , globals (), { 'args' : args
}, sort
= 'cumtime' )
375 for fn
in args
. binaries
:
380 sys
. stderr
. write ( 'while processing %s : \n ' % ( fn
))
381 traceback
. print_exc ()
383 for option
in dir ( args
):
384 if option
. startswith ( 'W' ) and option
!= 'Werror' :
385 checks
= sorted ( xrelfo
. check ( args
))
386 sys
. stderr
. write ( '' . join ([ c
[- 1 ] for c
in checks
]))
388 if args
. Werror
and len ( checks
) > 0 :
393 refs
= xrelfo
[ 'refs' ]
396 for k
, v
in refs
. items ():
397 strs
= set ([ i
[ 'fmtstring' ] for i
in v
])
399 print ( ' \033 [31;1m %s \033 [m' % k
)
404 for uid
, locs
in refs
. items ():
406 filearray
= outbyfile
. setdefault ( loc
[ 'file' ], [])
409 filearray
. append ( loc
)
411 for k
in outbyfile
. keys ():
412 outbyfile
[ k
] = sorted ( outbyfile
[ k
], key
= lambda x
: x
[ 'line' ])
418 with
open ( args
. output
+ '.tmp' , 'w' ) as fd
:
419 json
. dump ( out
, fd
, indent
= 2 , sort_keys
= True )
420 os
. rename ( args
. output
+ '.tmp' , args
. output
)
423 with
open ( args
. out_by_file
+ '.tmp' , 'w' ) as fd
:
424 json
. dump ( outbyfile
, fd
, indent
= 2 , sort_keys
= True )
425 os
. rename ( args
. out_by_file
+ '.tmp' , args
. out_by_file
)
427 if __name__
== '__main__' :