]>
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
42 with
open ( os
. path
. join ( frr_top_src
, 'python' , 'xrefstructs.json' ), 'r' ) as fd
:
43 xrefstructs
= json
. load ( fd
)
44 except FileNotFoundError
:
46 The "xrefstructs.json" file (created by running tiabwarfo.py with the pahole
47 tool available) could not be found. It should be included with the sources.
51 # constants, need to be kept in sync manually...
53 XREFT_THREADSCHED
= 0x100
56 XREFT_INSTALL_ELEMENT
= 0x301
60 prios
= [ '0' , '1' , '2' , 'E' , 'W' , 'N' , 'I' , 'D' ]
63 class XrelfoJson ( object ):
67 def check ( self
, wopt
):
70 def to_dict ( self
, refs
):
73 class Xref ( ELFDissectStruct
, XrelfoJson
):
75 fieldrename
= { 'type' : 'typ' }
78 def __init__ ( self
, * args
, ** kwargs
):
79 super () .__ init
__ (* args
, ** kwargs
)
81 self
._ container
= None
83 self
. xrefdata
. ref_from ( self
, self
. typ
)
86 if self
._ container
is None :
87 if self
. typ
in self
. containers
:
88 self
._ container
= self
. container_of ( self
. containers
[ self
. typ
], 'xref' )
89 return self
._ container
91 def check ( self
, * args
, ** kwargs
):
93 yield from self
._ container
. check (* args
, ** kwargs
)
96 class Xrefdata ( ELFDissectStruct
):
99 # uid is all zeroes in the data loaded from ELF
100 fieldrename
= { 'uid' : '_uid' }
102 def ref_from ( self
, xref
, typ
):
107 if self
. hashstr
is None :
109 return uidhash ( self
. xref
. file , self
. hashstr
, self
. hashu32_0
, self
. hashu32_1
)
111 class XrefPtr ( ELFDissectStruct
):
116 class XrefThreadSched ( ELFDissectStruct
, XrelfoJson
):
117 struct
= 'xref_threadsched'
118 Xref
. containers
[ XREFT_THREADSCHED
] = XrefThreadSched
120 class XrefLogmsg ( ELFDissectStruct
, XrelfoJson
):
121 struct
= 'xref_logmsg'
123 def _warn_fmt ( self
, text
):
124 lines
= text
. split ( ' \n ' )
125 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 :]])))
128 ( re
. compile ( r
'([\n\t]+)' ), 'error: log message contains tab or newline' ),
129 # (re.compile(r'^(\s+)'), 'warning: log message starts with whitespace'),
130 ( re
. compile ( r
'^((?:warn(?:ing)?|error):\s*)' , re
. I
), 'warning: log message starts with severity' ),
133 # the (?<![\?:] ) avoids warning for x ? inet_ntop(...) : "(bla)"
134 ( re
. compile ( r
'((?<![\?:] )inet_ntop\s*\(\s*(?:[AP]F_INET|2)\s*,)' ), 'cleanup: replace inet_ntop(AF_INET, ...) with %pI4' , lambda s
: True ),
135 ( re
. compile ( r
'((?<![\?:] )inet_ntop\s*\(\s*(?:[AP]F_INET6|10)\s*,)' ), 'cleanup: replace inet_ntop(AF_INET6, ...) with %pI6' , lambda s
: True ),
136 ( re
. compile ( r
'((?<![\?:] )inet_ntoa)' ), 'cleanup: replace inet_ntoa(...) with %pI4' , lambda s
: True ),
137 ( re
. compile ( r
'((?<![\?:] )ipaddr2str)' ), 'cleanup: replace ipaddr2str(...) with %pIA' , lambda s
: True ),
138 ( re
. compile ( r
'((?<![\?:] )prefix2str)' ), 'cleanup: replace prefix2str(...) with %pFX' , lambda s
: True ),
139 ( re
. compile ( r
'((?<![\?:] )prefix_mac2str)' ), 'cleanup: replace prefix_mac2str(...) with %pEA' , lambda s
: True ),
140 ( re
. compile ( r
'((?<![\?:] )sockunion2str)' ), 'cleanup: replace sockunion2str(...) with %pSU' , lambda s
: True ),
142 # (re.compile(r'^(\s*__(?:func|FUNCTION|PRETTY_FUNCTION)__\s*)'), 'error: debug message starts with __func__', lambda s: (s.priority & 7 == 7) ),
145 def check ( self
, wopt
):
146 def fmt_msg ( rex
, itext
):
147 if sys
. stderr
. isatty ():
148 items
= rex
. split ( itext
)
150 for i
, text
in enumerate ( items
):
152 out
. append ( ' \033 [41;37;1m %s \033 [m' % repr ( text
)[ 1 :- 1 ])
154 out
. append ( repr ( text
)[ 1 :- 1 ])
156 excerpt
= '' . join ( out
)
158 excerpt
= repr ( itext
)[ 1 :- 1 ]
162 for rex
, msg
in self
. fmt_regexes
:
163 if not rex
. search ( self
. fmtstring
):
166 excerpt
= fmt_msg ( rex
, self
. fmtstring
)
167 yield from self
._ warn
_ fmt
( ' %s : " %s "' % ( msg
, excerpt
))
170 for rex
, msg
, cond
in self
. arg_regexes
:
173 if not rex
. search ( self
. args
):
176 excerpt
= fmt_msg ( rex
, self
. args
)
177 yield from self
._ warn
_ fmt
( ' %s : \n\t " %s ", \n\t %s ' % ( msg
, repr ( self
. fmtstring
)[ 1 :- 1 ], excerpt
))
180 print ( ' %- 60s %s%s %- 25s [EC %d ] %s ' % (
181 ' %s : %d %s ()' % ( self
. xref
. file , self
. xref
. line
, self
. xref
. func
),
182 prios
[ self
. priority
& 7 ],
183 priovals
. get ( self
. priority
& 0x30 , ' ' ),
184 self
. xref
. xrefdata
. uid
, self
. ec
, self
. fmtstring
))
186 def to_dict ( self
, xrelfo
):
187 jsobj
= dict ([( i
, getattr ( self
. xref
, i
)) for i
in [ 'file' , 'line' , 'func' ]])
189 jsobj
[ 'ec' ] = self
. ec
190 jsobj
[ 'fmtstring' ] = self
. fmtstring
191 jsobj
[ 'args' ] = self
. args
192 jsobj
[ 'priority' ] = self
. priority
& 7
193 jsobj
[ 'type' ] = 'logmsg'
194 jsobj
[ 'binary' ] = self
._ elfsect
._ elfwrap
. orig_filename
196 if self
. priority
& 0x10 :
197 jsobj
. setdefault ( 'flags' , []). append ( 'errno' )
198 if self
. priority
& 0x20 :
199 jsobj
. setdefault ( 'flags' , []). append ( 'getaddrinfo' )
201 xrelfo
[ 'refs' ]. setdefault ( self
. xref
. xrefdata
. uid
, []). append ( jsobj
)
203 Xref
. containers
[ XREFT_LOGMSG
] = XrefLogmsg
205 class CmdElement ( ELFDissectStruct
, XrelfoJson
):
206 struct
= 'cmd_element'
208 def __init__ ( self
, * args
, ** kwargs
):
209 super () .__ init
__ (* args
, ** kwargs
)
211 def to_dict ( self
, xrelfo
):
212 jsobj
= xrelfo
[ 'cli' ]. setdefault ( self
. name
, {}). setdefault ( self
._ elfsect
._ elfwrap
. orig_filename
, {})
215 'string' : self
. string
,
219 jsobj
[ 'attr' ] = attr
= self
. attr
220 for attrname
in CmdAttr
.__ members
__ :
221 val
= CmdAttr
[ attrname
]
223 jsobj
. setdefault ( 'attrs' , []). append ( attrname
. lower ())
226 jsobj
[ 'defun' ] = dict ([( i
, getattr ( self
. xref
, i
)) for i
in [ 'file' , 'line' , 'func' ]])
228 Xref
. containers
[ XREFT_DEFUN
] = CmdElement
230 class XrefInstallElement ( ELFDissectStruct
, XrelfoJson
):
231 struct
= 'xref_install_element'
233 def to_dict ( self
, xrelfo
):
234 jsobj
= xrelfo
[ 'cli' ]. setdefault ( self
. cmd_element
. name
, {}). setdefault ( self
._ elfsect
._ elfwrap
. orig_filename
, {})
235 nodes
= jsobj
. setdefault ( 'nodes' , [])
238 'node' : self
. node_type
,
239 'install' : dict ([( i
, getattr ( self
. xref
, i
)) for i
in [ 'file' , 'line' , 'func' ]]),
242 Xref
. containers
[ XREFT_INSTALL_ELEMENT
] = XrefInstallElement
244 # shove in field defs
245 fieldapply
= FieldApplicator ( xrefstructs
)
247 fieldapply
. add ( Xrefdata
)
248 fieldapply
. add ( XrefLogmsg
)
249 fieldapply
. add ( XrefThreadSched
)
250 fieldapply
. add ( CmdElement
)
251 fieldapply
. add ( XrefInstallElement
)
263 def load_file ( self
, filename
):
264 orig_filename
= filename
265 if filename
. endswith ( '.la' ) or filename
. endswith ( '.lo' ):
266 with
open ( filename
, 'r' ) as fd
:
269 if line
. startswith ( '#' ) or line
== '' or '=' not in line
:
272 var
, val
= line
. split ( '=' , 1 )
273 if var
not in [ 'library_names' , 'pic_object' ]:
275 if val
. startswith ( "'" ) or val
. startswith ( '"' ):
278 if var
== 'pic_object' :
279 filename
= os
. path
. join ( os
. path
. dirname ( filename
), val
)
282 val
= val
. strip (). split ()[ 0 ]
283 filename
= os
. path
. join ( os
. path
. dirname ( filename
), '.libs' , val
)
286 raise ValueError ( 'could not process libtool file " %s "' % orig_filename
)
289 with
open ( filename
, 'rb' ) as fd
:
292 if hdr
== b
' \x7f ELF' :
293 self
. load_elf ( filename
, orig_filename
)
297 path
, name
= os
. path
. split ( filename
)
298 filename
= os
. path
. join ( path
, '.libs' , name
)
302 with
open ( filename
, 'r' ) as fd
:
306 raise ValueError ( 'cannot determine file type for %s ' % ( filename
))
308 def load_elf ( self
, filename
, orig_filename
):
309 edf
= ELFDissectFile ( filename
)
310 edf
. orig_filename
= orig_filename
312 note
= edf
._ elffile
. find_note ( 'FRRouting' , 'XREF' )
314 endian
= '>' if edf
._ elffile
. bigendian
else '<'
315 mem
= edf
._ elffile
[ note
]
316 if edf
._ elffile
. elfclass
== 64 :
317 start
, end
= struct
. unpack ( endian
+ 'QQ' , mem
)
319 end
+= note
. start
+ 8
321 start
, end
= struct
. unpack ( endian
+ 'II' , mem
)
323 end
+= note
. start
+ 4
325 ptrs
= edf
. iter_data ( XrefPtr
, slice ( start
, end
))
328 xrefarray
= edf
. get_section ( 'xref_array' )
329 if xrefarray
is None :
330 raise ValueError ( 'file has neither xref note nor xref_array section' )
332 ptrs
= xrefarray
. iter_data ( XrefPtr
)
338 self
._ xrefs
. append ( ptr
. xref
)
340 container
= ptr
. xref
. container ()
341 if container
is None :
343 container
. to_dict ( self
)
347 def load_json ( self
, fd
):
349 for uid
, items
in data
[ 'refs' ]. items ():
350 myitems
= self
[ 'refs' ]. setdefault ( uid
, [])
356 for cmd
, items
in data
[ 'cli' ]. items ():
357 self
[ 'cli' ]. setdefault ( cmd
, {}). update ( items
)
361 def check ( self
, checks
):
362 for xref
in self
._ xrefs
:
363 yield from xref
. check ( checks
)
366 argp
= argparse
. ArgumentParser ( description
= 'FRR xref ELF extractor' )
367 argp
. add_argument ( '-o' , dest
= 'output' , type = str , help = 'write JSON output' )
368 argp
. add_argument ( '--out-by-file' , type = str , help = 'write by-file JSON output' )
369 argp
. add_argument ( '-Wlog-format' , action
= 'store_const' , const
= True )
370 argp
. add_argument ( '-Wlog-args' , action
= 'store_const' , const
= True )
371 argp
. add_argument ( '-Werror' , action
= 'store_const' , const
= True )
372 argp
. add_argument ( '--profile' , action
= 'store_const' , const
= True )
373 argp
. add_argument ( 'binaries' , metavar
= 'BINARY' , nargs
= '+' , type = str , help = 'files to read (ELF files or libtool objects)' )
374 args
= argp
. parse_args ()
378 cProfile
. runctx ( '_main(args)' , globals (), { 'args' : args
}, sort
= 'cumtime' )
386 for fn
in args
. binaries
:
391 sys
. stderr
. write ( 'while processing %s : \n ' % ( fn
))
392 traceback
. print_exc ()
394 for option
in dir ( args
):
395 if option
. startswith ( 'W' ) and option
!= 'Werror' :
396 checks
= sorted ( xrelfo
. check ( args
))
397 sys
. stderr
. write ( '' . join ([ c
[- 1 ] for c
in checks
]))
399 if args
. Werror
and len ( checks
) > 0 :
404 refs
= xrelfo
[ 'refs' ]
407 for k
, v
in refs
. items ():
408 strs
= set ([ i
[ 'fmtstring' ] for i
in v
])
410 print ( ' \033 [31;1m %s \033 [m' % k
)
415 for uid
, locs
in refs
. items ():
417 filearray
= outbyfile
. setdefault ( loc
[ 'file' ], [])
420 filearray
. append ( loc
)
422 for k
in outbyfile
. keys ():
423 outbyfile
[ k
] = sorted ( outbyfile
[ k
], key
= lambda x
: x
[ 'line' ])
429 with
open ( args
. output
+ '.tmp' , 'w' ) as fd
:
430 json
. dump ( out
, fd
, indent
= 2 , sort_keys
= True , ** json_dump_args
)
431 os
. rename ( args
. output
+ '.tmp' , args
. output
)
434 with
open ( args
. out_by_file
+ '.tmp' , 'w' ) as fd
:
435 json
. dump ( outbyfile
, fd
, indent
= 2 , sort_keys
= True , ** json_dump_args
)
436 os
. rename ( args
. out_by_file
+ '.tmp' , args
. out_by_file
)
438 if __name__
== '__main__' :