]>
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
:
47 The "xrefstructs.json" file (created by running tiabwarfo.py with the pahole
48 tool available) could not be found. It should be included with the sources.
52 # constants, need to be kept in sync manually...
54 XREFT_THREADSCHED
= 0x100
57 XREFT_INSTALL_ELEMENT
= 0x301
61 prios
= [ '0' , '1' , '2' , 'E' , 'W' , 'N' , 'I' , 'D' ]
64 class XrelfoJson ( object ):
68 def check ( self
, wopt
):
71 def to_dict ( self
, refs
):
74 class Xref ( ELFDissectStruct
, XrelfoJson
):
76 fieldrename
= { 'type' : 'typ' }
79 def __init__ ( self
, * args
, ** kwargs
):
80 super () .__ init
__ (* args
, ** kwargs
)
82 self
._ container
= None
84 self
. xrefdata
. ref_from ( self
, self
. typ
)
87 if self
._ container
is None :
88 if self
. typ
in self
. containers
:
89 self
._ container
= self
. container_of ( self
. containers
[ self
. typ
], 'xref' )
90 return self
._ container
92 def check ( self
, * args
, ** kwargs
):
94 yield from self
._ container
. check (* args
, ** kwargs
)
97 class Xrefdata ( ELFDissectStruct
):
100 # uid is all zeroes in the data loaded from ELF
101 fieldrename
= { 'uid' : '_uid' }
103 def ref_from ( self
, xref
, typ
):
108 if self
. hashstr
is None :
110 return uidhash ( self
. xref
. file , self
. hashstr
, self
. hashu32_0
, self
. hashu32_1
)
112 class XrefPtr ( ELFDissectStruct
):
117 class XrefThreadSched ( ELFDissectStruct
, XrelfoJson
):
118 struct
= 'xref_threadsched'
119 Xref
. containers
[ XREFT_THREADSCHED
] = XrefThreadSched
121 class XrefLogmsg ( ELFDissectStruct
, XrelfoJson
):
122 struct
= 'xref_logmsg'
124 def _warn_fmt ( self
, text
):
125 lines
= text
. split ( ' \n ' )
126 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 :]])))
129 ( re
. compile ( r
'([\n\t]+)' ), 'error: log message contains tab or newline' ),
130 # (re.compile(r'^(\s+)'), 'warning: log message starts with whitespace'),
131 ( re
. compile ( r
'^((?:warn(?:ing)?|error):\s*)' , re
. I
), 'warning: log message starts with severity' ),
134 # the (?<![\?:] ) avoids warning for x ? inet_ntop(...) : "(bla)"
135 ( re
. compile ( r
'((?<![\?:] )inet_ntop\s*\(\s*(?:[AP]F_INET|2)\s*,)' ), 'cleanup: replace inet_ntop(AF_INET, ...) with %pI4' , lambda s
: True ),
136 ( re
. compile ( r
'((?<![\?:] )inet_ntop\s*\(\s*(?:[AP]F_INET6|10)\s*,)' ), 'cleanup: replace inet_ntop(AF_INET6, ...) with %pI6' , lambda s
: True ),
137 ( re
. compile ( r
'((?<![\?:] )inet_ntoa)' ), 'cleanup: replace inet_ntoa(...) with %pI4' , lambda s
: True ),
138 ( re
. compile ( r
'((?<![\?:] )ipaddr2str)' ), 'cleanup: replace ipaddr2str(...) with %pIA' , lambda s
: True ),
139 ( re
. compile ( r
'((?<![\?:] )prefix2str)' ), 'cleanup: replace prefix2str(...) with %pFX' , lambda s
: True ),
140 ( re
. compile ( r
'((?<![\?:] )prefix_mac2str)' ), 'cleanup: replace prefix_mac2str(...) with %pEA' , lambda s
: True ),
141 ( re
. compile ( r
'((?<![\?:] )sockunion2str)' ), 'cleanup: replace sockunion2str(...) with %pSU' , lambda s
: True ),
143 # (re.compile(r'^(\s*__(?:func|FUNCTION|PRETTY_FUNCTION)__\s*)'), 'error: debug message starts with __func__', lambda s: (s.priority & 7 == 7) ),
146 def check ( self
, wopt
):
147 def fmt_msg ( rex
, itext
):
148 if sys
. stderr
. isatty ():
149 items
= rex
. split ( itext
)
151 for i
, text
in enumerate ( items
):
153 out
. append ( ' \033 [41;37;1m %s \033 [m' % repr ( text
)[ 1 :- 1 ])
155 out
. append ( repr ( text
)[ 1 :- 1 ])
157 excerpt
= '' . join ( out
)
159 excerpt
= repr ( itext
)[ 1 :- 1 ]
163 for rex
, msg
in self
. fmt_regexes
:
164 if not rex
. search ( self
. fmtstring
):
167 excerpt
= fmt_msg ( rex
, self
. fmtstring
)
168 yield from self
._ warn
_ fmt
( ' %s : " %s "' % ( msg
, excerpt
))
171 for rex
, msg
, cond
in self
. arg_regexes
:
174 if not rex
. search ( self
. args
):
177 excerpt
= fmt_msg ( rex
, self
. args
)
178 yield from self
._ warn
_ fmt
( ' %s : \n\t " %s ", \n\t %s ' % ( msg
, repr ( self
. fmtstring
)[ 1 :- 1 ], excerpt
))
181 print ( ' %- 60s %s%s %- 25s [EC %d ] %s ' % (
182 ' %s : %d %s ()' % ( self
. xref
. file , self
. xref
. line
, self
. xref
. func
),
183 prios
[ self
. priority
& 7 ],
184 priovals
. get ( self
. priority
& 0x30 , ' ' ),
185 self
. xref
. xrefdata
. uid
, self
. ec
, self
. fmtstring
))
187 def to_dict ( self
, xrelfo
):
188 jsobj
= dict ([( i
, getattr ( self
. xref
, i
)) for i
in [ 'file' , 'line' , 'func' ]])
190 jsobj
[ 'ec' ] = self
. ec
191 jsobj
[ 'fmtstring' ] = self
. fmtstring
192 jsobj
[ 'args' ] = self
. args
193 jsobj
[ 'priority' ] = self
. priority
& 7
194 jsobj
[ 'type' ] = 'logmsg'
195 jsobj
[ 'binary' ] = self
._ elfsect
._ elfwrap
. orig_filename
197 if self
. priority
& 0x10 :
198 jsobj
. setdefault ( 'flags' , []). append ( 'errno' )
199 if self
. priority
& 0x20 :
200 jsobj
. setdefault ( 'flags' , []). append ( 'getaddrinfo' )
202 xrelfo
[ 'refs' ]. setdefault ( self
. xref
. xrefdata
. uid
, []). append ( jsobj
)
204 Xref
. containers
[ XREFT_LOGMSG
] = XrefLogmsg
206 class CmdElement ( ELFDissectStruct
, XrelfoJson
):
207 struct
= 'cmd_element'
209 def __init__ ( self
, * args
, ** kwargs
):
210 super () .__ init
__ (* args
, ** kwargs
)
212 def to_dict ( self
, xrelfo
):
213 jsobj
= xrelfo
[ 'cli' ]. setdefault ( self
. name
, {}). setdefault ( self
._ elfsect
._ elfwrap
. orig_filename
, {})
216 'string' : self
. string
,
220 jsobj
[ 'attr' ] = attr
= self
. attr
221 for attrname
in CmdAttr
.__ members
__ :
222 val
= CmdAttr
[ attrname
]
224 jsobj
. setdefault ( 'attrs' , []). append ( attrname
. lower ())
227 jsobj
[ 'defun' ] = dict ([( i
, getattr ( self
. xref
, i
)) for i
in [ 'file' , 'line' , 'func' ]])
229 Xref
. containers
[ XREFT_DEFUN
] = CmdElement
231 class XrefInstallElement ( ELFDissectStruct
, XrelfoJson
):
232 struct
= 'xref_install_element'
234 def to_dict ( self
, xrelfo
):
235 jsobj
= xrelfo
[ 'cli' ]. setdefault ( self
. cmd_element
. name
, {}). setdefault ( self
._ elfsect
._ elfwrap
. orig_filename
, {})
236 nodes
= jsobj
. setdefault ( 'nodes' , [])
239 'node' : self
. node_type
,
240 'install' : dict ([( i
, getattr ( self
. xref
, i
)) for i
in [ 'file' , 'line' , 'func' ]]),
243 Xref
. containers
[ XREFT_INSTALL_ELEMENT
] = XrefInstallElement
245 # shove in field defs
246 fieldapply
= FieldApplicator ( xrefstructs
)
248 fieldapply
. add ( Xrefdata
)
249 fieldapply
. add ( XrefLogmsg
)
250 fieldapply
. add ( XrefThreadSched
)
251 fieldapply
. add ( CmdElement
)
252 fieldapply
. add ( XrefInstallElement
)
264 def load_file ( self
, filename
):
265 orig_filename
= filename
266 if filename
. endswith ( '.la' ) or filename
. endswith ( '.lo' ):
267 with
open ( filename
, 'r' ) as fd
:
270 if line
. startswith ( '#' ) or line
== '' or '=' not in line
:
273 var
, val
= line
. split ( '=' , 1 )
274 if var
not in [ 'library_names' , 'pic_object' ]:
276 if val
. startswith ( "'" ) or val
. startswith ( '"' ):
279 if var
== 'pic_object' :
280 filename
= os
. path
. join ( os
. path
. dirname ( filename
), val
)
283 val
= val
. strip (). split ()[ 0 ]
284 filename
= os
. path
. join ( os
. path
. dirname ( filename
), '.libs' , val
)
287 raise ValueError ( 'could not process libtool file " %s "' % orig_filename
)
290 with
open ( filename
, 'rb' ) as fd
:
293 if hdr
== b
' \x7f ELF' :
294 self
. load_elf ( filename
, orig_filename
)
298 path
, name
= os
. path
. split ( filename
)
299 filename
= os
. path
. join ( path
, '.libs' , name
)
303 with
open ( filename
, 'r' ) as fd
:
307 raise ValueError ( 'cannot determine file type for %s ' % ( filename
))
309 def load_elf ( self
, filename
, orig_filename
):
310 edf
= ELFDissectFile ( filename
)
311 edf
. orig_filename
= orig_filename
313 note
= edf
._ elffile
. find_note ( 'FRRouting' , 'XREF' )
315 endian
= '>' if edf
._ elffile
. bigendian
else '<'
316 mem
= edf
._ elffile
[ note
]
317 if edf
._ elffile
. elfclass
== 64 :
318 start
, end
= struct
. unpack ( endian
+ 'QQ' , mem
)
320 end
+= note
. start
+ 8
322 start
, end
= struct
. unpack ( endian
+ 'II' , mem
)
324 end
+= note
. start
+ 4
326 ptrs
= edf
. iter_data ( XrefPtr
, slice ( start
, end
))
329 xrefarray
= edf
. get_section ( 'xref_array' )
330 if xrefarray
is None :
331 raise ValueError ( 'file has neither xref note nor xref_array section' )
333 ptrs
= xrefarray
. iter_data ( XrefPtr
)
339 self
._ xrefs
. append ( ptr
. xref
)
341 container
= ptr
. xref
. container ()
342 if container
is None :
344 container
. to_dict ( self
)
348 def load_json ( self
, fd
):
350 for uid
, items
in data
[ 'refs' ]. items ():
351 myitems
= self
[ 'refs' ]. setdefault ( uid
, [])
357 for cmd
, items
in data
[ 'cli' ]. items ():
358 self
[ 'cli' ]. setdefault ( cmd
, {}). update ( items
)
362 def check ( self
, checks
):
363 for xref
in self
._ xrefs
:
364 yield from xref
. check ( checks
)
367 argp
= argparse
. ArgumentParser ( description
= 'FRR xref ELF extractor' )
368 argp
. add_argument ( '-o' , dest
= 'output' , type = str , help = 'write JSON output' )
369 argp
. add_argument ( '--out-by-file' , type = str , help = 'write by-file JSON output' )
370 argp
. add_argument ( '-c' , dest
= 'vtysh_cmds' , type = str , help = 'write vtysh_cmd.c' )
371 argp
. add_argument ( '-Wlog-format' , action
= 'store_const' , const
= True )
372 argp
. add_argument ( '-Wlog-args' , action
= 'store_const' , const
= True )
373 argp
. add_argument ( '-Werror' , action
= 'store_const' , const
= True )
374 argp
. add_argument ( '--profile' , action
= 'store_const' , const
= True )
375 argp
. add_argument ( 'binaries' , metavar
= 'BINARY' , nargs
= '+' , type = str , help = 'files to read (ELF files or libtool objects)' )
376 args
= argp
. parse_args ()
380 cProfile
. runctx ( '_main(args)' , globals (), { 'args' : args
}, sort
= 'cumtime' )
388 for fn
in args
. binaries
:
393 sys
. stderr
. write ( 'while processing %s : \n ' % ( fn
))
394 traceback
. print_exc ()
396 for option
in dir ( args
):
397 if option
. startswith ( 'W' ) and option
!= 'Werror' :
398 checks
= sorted ( xrelfo
. check ( args
))
399 sys
. stderr
. write ( '' . join ([ c
[- 1 ] for c
in checks
]))
401 if args
. Werror
and len ( checks
) > 0 :
406 refs
= xrelfo
[ 'refs' ]
409 for k
, v
in refs
. items ():
410 strs
= set ([ i
[ 'fmtstring' ] for i
in v
])
412 print ( ' \033 [31;1m %s \033 [m' % k
)
417 for uid
, locs
in refs
. items ():
419 filearray
= outbyfile
. setdefault ( loc
[ 'file' ], [])
422 filearray
. append ( loc
)
424 for k
in outbyfile
. keys ():
425 outbyfile
[ k
] = sorted ( outbyfile
[ k
], key
= lambda x
: x
[ 'line' ])
431 with
open ( args
. output
+ '.tmp' , 'w' ) as fd
:
432 json
. dump ( out
, fd
, indent
= 2 , sort_keys
= True , ** json_dump_args
)
433 os
. rename ( args
. output
+ '.tmp' , args
. output
)
436 with
open ( args
. out_by_file
+ '.tmp' , 'w' ) as fd
:
437 json
. dump ( outbyfile
, fd
, indent
= 2 , sort_keys
= True , ** json_dump_args
)
438 os
. rename ( args
. out_by_file
+ '.tmp' , args
. out_by_file
)
441 with
open ( args
. vtysh_cmds
+ '.tmp' , 'w' ) as fd
:
442 CommandEntry
. run ( out
, fd
)
443 os
. rename ( args
. vtysh_cmds
+ '.tmp' , args
. vtysh_cmds
)
444 if args
. Werror
and CommandEntry
. warn_counter
:
448 if __name__
== '__main__' :