2 # Wireshark - Network traffic analyzer
3 # By Gerald Combs <gerald@wireshark.org>
4 # Copyright 1998 Gerald Combs
6 # SPDX-License-Identifier: GPL-2.0-or-later
14 # Look for dissector symbols that could/should be static.
15 # This will not run on Windows, unless/until we check the platform
16 # and use (I think) dumpbin.exe
18 # N.B. Will report false positives if symbols are extern'd rather than
19 # declared in a header file.
21 # Try to exit soon after Ctrl-C is pressed.
24 def signal_handler(sig
, frame
):
27 print('You pressed Ctrl+C - exiting')
29 signal
.signal(signal
.SIGINT
, signal_handler
)
31 # Allow this as a default build folder name...
32 build_folder
= os
.getcwd() + '-build'
35 # Record which symbols are referred to (by a set of files).
40 def addCalls(self
, file):
44 # Make sure that file is built.
45 last_dir
= os
.path
.split(os
.path
.dirname(file))[-1]
46 if file.find('ui/cli') != -1:
47 # A tshark target-only file
48 object_file
= os
.path
.join(build_folder
, 'CMakeFiles', ('tshark' + '.dir'), file + '.o')
49 elif file.find('ui/qt') != -1:
50 object_file
= os
.path
.join(build_folder
, os
.path
.dirname(file), 'CMakeFiles', ('qtui' + '.dir'), os
.path
.basename(file) + '.o')
52 if file.endswith('dissectors.c'):
53 object_file
= os
.path
.join(build_folder
, os
.path
.dirname(file), 'CMakeFiles', 'dissector-registration' + '.dir', os
.path
.basename(file) + '.o')
55 object_file
= os
.path
.join(build_folder
, os
.path
.dirname(file), 'CMakeFiles', last_dir
+ '.dir', os
.path
.basename(file) + '.o')
56 if not os
.path
.exists(object_file
):
57 # Not built for whatever reason..
58 #print('Warning -', object_file, 'does not exist')
61 # Run command to check symbols.
62 command
= ['nm', object_file
]
63 for f
in subprocess
.check_output(command
).splitlines():
65 # Lines might, or might not, have an address before letter and symbol.
66 p1
= re
.compile(r
'[0-9a-f]* ([a-zA-Z]) (.*)')
67 p2
= re
.compile(r
'[ ]* ([a-zA-Z]) (.*)')
74 function_name
= m
.group(2)
76 # Only interested in undefined/external references to symbols.
78 self
.referred
.add(function_name
)
82 # Record which symbols are defined in a single dissector file.
84 def __init__(self
, file):
86 self
.global_symbols
= {} # map from defined symbol -> whole output-line
87 self
.header_file_contents
= None
88 self
.from_generated_file
= isGeneratedFile(file)
90 # Make sure that file is built.
91 if self
.filename
.startswith('epan'):
92 object_file
= os
.path
.join(build_folder
, 'epan', 'dissectors', 'CMakeFiles', 'dissectors.dir', os
.path
.basename(file) + '.o')
93 elif self
.filename
.startswith('plugins'):
94 plugin_base_dir
= os
.path
.dirname(file)
95 plugin_base_name
= os
.path
.basename(plugin_base_dir
)
96 object_file
= os
.path
.join(build_folder
, plugin_base_dir
, 'CMakeFiles', plugin_base_name
+ '.dir', os
.path
.basename(file) + '.o')
98 #print("Warning - can't determine object file for ", self.filename)
100 if not os
.path
.exists(object_file
):
101 #print('Warning -', object_file, 'does not exist')
104 # Get header file contents if available
105 header_file
= file.replace('.c', '.h')
107 f
= open(header_file
, 'r')
108 self
.header_file_contents
= f
.read()
112 # Run command to see which symbols are defined
113 command
= ['nm', object_file
]
114 for f
in subprocess
.check_output(command
).splitlines():
115 # Line consists of whitespace, [address], letter, symbolName
117 p
= re
.compile(r
'[0-9a-f]* ([a-zA-Z]) (.*)')
121 function_name
= m
.group(2)
122 # Globally-defined symbols. Would be 't' or 'd' if already static..
124 self
.addDefinedSymbol(function_name
, line
)
126 def addDefinedSymbol(self
, symbol
, line
):
127 self
.global_symbols
[symbol
] = line
129 # Check if a given symbol is mentioned in headers
130 def mentionedInHeaders(self
, symbol
):
131 if self
.header_file_contents
:
132 if self
.header_file_contents
.find(symbol
) != -1:
134 # Also check some of the 'common' header files that don't match the dissector file name.
135 # TODO: could cache the contents of these files?
136 common_mismatched_headers
= [ os
.path
.join('epan', 'dissectors', 'packet-ncp-int.h'),
137 os
.path
.join('epan', 'dissectors', 'packet-mq.h'),
138 os
.path
.join('epan', 'dissectors', 'packet-ip.h'),
139 os
.path
.join('epan', 'dissectors', 'packet-gsm_a_common.h'),
140 os
.path
.join('epan', 'dissectors', 'packet-epl.h'),
141 os
.path
.join('epan', 'dissectors', 'packet-bluetooth.h'),
142 os
.path
.join('epan', 'dissectors', 'packet-dcerpc.h'),
143 os
.path
.join('epan', 'ip_opts.h'),
144 os
.path
.join('epan', 'eap.h')]
145 for hf
in common_mismatched_headers
:
149 if contents
.find(symbol
) != -1:
151 except EnvironmentError:
156 def checkIfSymbolsAreCalled(self
, called_symbols
):
158 for f
in self
.global_symbols
:
159 if f
not in called_symbols
:
160 mentioned_in_header
= self
.mentionedInHeaders(f
)
161 fun
= self
.global_symbols
[f
]
162 print(self
.filename
, '' if not self
.from_generated_file
else '(GENERATED)',
164 'is not referred to so could be static?', '(declared in header)' if mentioned_in_header
else '')
171 def isDissectorFile(filename
):
172 # Ignoring usb.c & errno.c
173 p
= re
.compile(r
'(packet|file)-.*\.c')
174 return p
.match(filename
)
176 # Test for whether the given dissector file was automatically generated.
177 def isGeneratedFile(filename
):
178 # Check file exists - e.g. may have been deleted in a recent commit.
179 if not os
.path
.exists(filename
):
182 if not filename
.endswith('.c'):
186 f_read
= open(os
.path
.join(filename
), 'r')
189 # The comment to say that its generated is near the top, so give up once
190 # get a few lines down.
191 if lines_tested
> 10:
194 if (line
.find('Generated automatically') != -1 or
195 line
.find('Autogenerated from') != -1 or
196 line
.find('is autogenerated') != -1 or
197 line
.find('automatically generated by Pidl') != -1 or
198 line
.find('Created by: The Qt Meta Object Compiler') != -1 or
199 line
.find('This file was generated') != -1 or
200 line
.find('This filter was automatically generated') != -1):
204 lines_tested
= lines_tested
+ 1
206 # OK, looks like a hand-written file!
211 def findDissectorFilesInFolder(folder
, include_generated
):
212 # Look at files in sorted order, to give some idea of how far through is.
215 for f
in sorted(os
.listdir(folder
)):
218 if isDissectorFile(f
):
219 if include_generated
or not isGeneratedFile(os
.path
.join('epan', 'dissectors', f
)):
220 filename
= os
.path
.join(folder
, f
)
221 tmp_files
.append(filename
)
224 def findFilesInFolder(folder
):
225 # Look at files in sorted order, to give some idea of how far through is.
228 for f
in sorted(os
.listdir(folder
)):
231 if f
.endswith('.c') or f
.endswith('.cpp'):
232 filename
= os
.path
.join(folder
, f
)
233 tmp_files
.append(filename
)
237 def is_dissector_file(filename
):
238 p
= re
.compile(r
'.*(packet|file)-.*\.c')
239 return p
.match(filename
)
244 #################################################################
247 # command-line args. Controls which dissector files should be checked.
248 # If no args given, will just scan epan/dissectors folder.
249 parser
= argparse
.ArgumentParser(description
='Check calls in dissectors')
250 parser
.add_argument('--build-folder', action
='store', default
='',
251 help='build folder', required
=False)
252 parser
.add_argument('--file', action
='append',
253 help='specify individual dissector file to test')
254 parser
.add_argument('--commits', action
='store',
255 help='last N commits to check')
256 parser
.add_argument('--open', action
='store_true',
257 help='check open files')
259 args
= parser
.parse_args()
263 # Get files from wherever command-line args indicate.
266 if args
.build_folder
:
267 build_folder
= args
.build_folder
270 # Add specified file(s)
272 if not os
.path
.isfile(f
) and not f
.startswith('epan'):
273 f
= os
.path
.join('epan', 'dissectors', f
)
274 if not os
.path
.isfile(f
):
275 print('Chosen file', f
, 'does not exist.')
280 # Get files affected by specified number of commits.
281 command
= ['git', 'diff', '--name-only', 'HEAD~' + args
.commits
]
282 files
= [f
.decode('utf-8')
283 for f
in subprocess
.check_output(command
).splitlines()]
284 # Will examine dissector files only
285 files
= list(filter(lambda f
: is_dissector_file(f
), files
))
288 command
= ['git', 'diff', '--name-only']
289 files
= [f
.decode('utf-8')
290 for f
in subprocess
.check_output(command
).splitlines()]
291 # Only interested in dissector files.
292 files
= list(filter(lambda f
: is_dissector_file(f
), files
))
294 command
= ['git', 'diff', '--staged', '--name-only']
295 files_staged
= [f
.decode('utf-8')
296 for f
in subprocess
.check_output(command
).splitlines()]
297 # Only interested in dissector files.
298 files_staged
= list(filter(lambda f
: is_dissector_file(f
), files_staged
))
301 for f
in files_staged
:
305 # Find all dissector files from folder.
306 files
= findDissectorFilesInFolder(os
.path
.join('epan', 'dissectors'),
307 include_generated
=True)
310 # If scanning a subset of files, list them here.
312 if args
.file or args
.commits
or args
.open:
314 print(' '.join(files
), '\n')
316 print('No files to check.\n')
318 print('All dissector modules\n')
321 if not os
.path
.isdir(build_folder
):
322 print('Build directory not valid', build_folder
, '- please set with --build-folder')
326 # Get the set of called functions and referred-to data.
327 called
= CalledSymbols()
328 for d
in findDissectorFilesInFolder(os
.path
.join('epan', 'dissectors'), include_generated
=True):
330 called
.addCalls(os
.path
.join('epan', 'dissectors', 'dissectors.c'))
331 # Also check calls from GUI code
332 for d
in findFilesInFolder('ui'):
334 for d
in findFilesInFolder(os
.path
.join('ui', 'qt')):
336 # These are from tshark..
337 for d
in findFilesInFolder(os
.path
.join('ui', 'cli')):
341 # Now check identified dissector files.
345 # Are these symbols called - or could they be deleted or static????
346 DefinedSymbols(f
).checkIfSymbolsAreCalled(called
.referred
)
349 print(issues_found
, 'issues found')