Changeset View
Changeset View
Standalone View
Standalone View
contrib/devtools/symbol-check.py
Show First 20 Lines • Show All 41 Lines • ▼ Show 20 Lines | MAX_VERSIONS = { | ||||
'GLIBCXX': (3, 4, 13), | 'GLIBCXX': (3, 4, 13), | ||||
'GLIBC': (2, 11) | 'GLIBC': (2, 11) | ||||
} | } | ||||
# See here for a description of _IO_stdin_used: | # See here for a description of _IO_stdin_used: | ||||
# https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=634261#109 | # https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=634261#109 | ||||
# Ignore symbols that are exported as part of every executable | # Ignore symbols that are exported as part of every executable | ||||
IGNORE_EXPORTS = { | IGNORE_EXPORTS = { | ||||
b'_edata', b'_end', b'_init', b'__bss_start', b'_fini', b'_IO_stdin_used', b'stdin', b'stdout', b'stderr', | '_edata', '_end', '_init', '__bss_start', '_fini', '_IO_stdin_used', 'stdin', 'stdout', 'stderr', | ||||
# Figure out why we get these symbols exported on xenial. | # Figure out why we get these symbols exported on xenial. | ||||
b'_ZNKSt5ctypeIcE8do_widenEc', b'in6addr_any', b'optarg', | '_ZNKSt5ctypeIcE8do_widenEc', 'in6addr_any', 'optarg', | ||||
b'_ZNSt16_Sp_counted_baseILN9__gnu_cxx12_Lock_policyE2EE10_M_destroyEv' | '_ZNSt16_Sp_counted_baseILN9__gnu_cxx12_Lock_policyE2EE10_M_destroyEv' | ||||
} | } | ||||
READELF_CMD = os.getenv('READELF', '/usr/bin/readelf') | READELF_CMD = os.getenv('READELF', '/usr/bin/readelf') | ||||
CPPFILT_CMD = os.getenv('CPPFILT', '/usr/bin/c++filt') | CPPFILT_CMD = os.getenv('CPPFILT', '/usr/bin/c++filt') | ||||
# Allowed NEEDED libraries | # Allowed NEEDED libraries | ||||
ALLOWED_LIBRARIES = { | ALLOWED_LIBRARIES = { | ||||
# bitcoind and bitcoin-qt | # bitcoind and bitcoin-qt | ||||
b'libgcc_s.so.1', # GCC base support | 'libgcc_s.so.1', # GCC base support | ||||
b'libc.so.6', # C library | 'libc.so.6', # C library | ||||
b'libpthread.so.0', # threading | 'libpthread.so.0', # threading | ||||
b'libanl.so.1', # DNS resolve | 'libanl.so.1', # DNS resolve | ||||
b'libm.so.6', # math library | 'libm.so.6', # math library | ||||
b'librt.so.1', # real-time (clock) | 'librt.so.1', # real-time (clock) | ||||
b'ld-linux-x86-64.so.2', # 64-bit dynamic linker | 'ld-linux-x86-64.so.2', # 64-bit dynamic linker | ||||
b'ld-linux.so.2', # 32-bit dynamic linker | 'ld-linux.so.2', # 32-bit dynamic linker | ||||
# bitcoin-qt only | # bitcoin-qt only | ||||
b'libX11-xcb.so.1', # part of X11 | 'libX11-xcb.so.1', # part of X11 | ||||
b'libX11.so.6', # part of X11 | 'libX11.so.6', # part of X11 | ||||
b'libxcb.so.1', # part of X11 | 'libxcb.so.1', # part of X11 | ||||
b'libfontconfig.so.1', # font support | 'libfontconfig.so.1', # font support | ||||
b'libfreetype.so.6', # font parsing | 'libfreetype.so.6', # font parsing | ||||
b'libdl.so.2' # programming interface to dynamic linker | 'libdl.so.2' # programming interface to dynamic linker | ||||
} | } | ||||
class CPPFilt(object): | class CPPFilt(object): | ||||
''' | ''' | ||||
Demangle C++ symbol names. | Demangle C++ symbol names. | ||||
Use a pipe to the 'c++filt' command. | Use a pipe to the 'c++filt' command. | ||||
''' | ''' | ||||
def __init__(self): | def __init__(self): | ||||
self.proc = subprocess.Popen( | self.proc = subprocess.Popen( | ||||
CPPFILT_CMD, stdin=subprocess.PIPE, stdout=subprocess.PIPE) | CPPFILT_CMD, stdin=subprocess.PIPE, stdout=subprocess.PIPE, universal_newlines=True) | ||||
def __call__(self, mangled): | def __call__(self, mangled): | ||||
self.proc.stdin.write(mangled + b'\n') | self.proc.stdin.write(mangled + '\n') | ||||
self.proc.stdin.flush() | self.proc.stdin.flush() | ||||
return self.proc.stdout.readline().rstrip() | return self.proc.stdout.readline().rstrip() | ||||
def close(self): | def close(self): | ||||
self.proc.stdin.close() | self.proc.stdin.close() | ||||
self.proc.stdout.close() | self.proc.stdout.close() | ||||
self.proc.wait() | self.proc.wait() | ||||
def read_symbols(executable, imports=True): | def read_symbols(executable, imports=True): | ||||
''' | ''' | ||||
Parse an ELF executable and return a list of (symbol,version) tuples | Parse an ELF executable and return a list of (symbol,version) tuples | ||||
for dynamic, imported symbols. | for dynamic, imported symbols. | ||||
''' | ''' | ||||
p = subprocess.Popen([READELF_CMD, '--dyn-syms', '-W', executable], | p = subprocess.Popen([READELF_CMD, '--dyn-syms', '-W', executable], stdout=subprocess.PIPE, | ||||
stdout=subprocess.PIPE, stderr=subprocess.PIPE, stdin=subprocess.PIPE) | stderr=subprocess.PIPE, stdin=subprocess.PIPE, universal_newlines=True) | ||||
(stdout, stderr) = p.communicate() | (stdout, stderr) = p.communicate() | ||||
if p.returncode: | if p.returncode: | ||||
raise IOError('Could not read symbols for %s: %s' % | raise IOError('Could not read symbols for %s: %s' % | ||||
(executable, stderr.strip())) | (executable, stderr.strip())) | ||||
syms = [] | syms = [] | ||||
for line in stdout.split(b'\n'): | for line in stdout.splitlines(): | ||||
line = line.split() | line = line.split() | ||||
if len(line) > 7 and re.match(b'[0-9]+:$', line[0]): | if len(line) > 7 and re.match('[0-9]+:$', line[0]): | ||||
(sym, _, version) = line[7].partition(b'@') | (sym, _, version) = line[7].partition('@') | ||||
is_import = line[6] == b'UND' | is_import = line[6] == 'UND' | ||||
if version.startswith(b'@'): | if version.startswith('@'): | ||||
version = version[1:] | version = version[1:] | ||||
if is_import == imports: | if is_import == imports: | ||||
syms.append((sym, version)) | syms.append((sym, version)) | ||||
return syms | return syms | ||||
def check_version(max_versions, version): | def check_version(max_versions, version): | ||||
if b'_' in version: | if '_' in version: | ||||
(lib, _, ver) = version.rpartition(b'_') | (lib, _, ver) = version.rpartition('_') | ||||
else: | else: | ||||
lib = version | lib = version | ||||
ver = '0' | ver = '0' | ||||
ver = tuple([int(x) for x in ver.split(b'.')]) | ver = tuple([int(x) for x in ver.split('.')]) | ||||
if not lib in max_versions: | if not lib in max_versions: | ||||
return False | return False | ||||
return ver <= max_versions[lib] | return ver <= max_versions[lib] | ||||
def read_libraries(filename): | def read_libraries(filename): | ||||
p = subprocess.Popen([READELF_CMD, '-d', '-W', filename], | p = subprocess.Popen([READELF_CMD, '-d', '-W', filename], stdout=subprocess.PIPE, | ||||
stdout=subprocess.PIPE, stderr=subprocess.PIPE, stdin=subprocess.PIPE) | stderr=subprocess.PIPE, stdin=subprocess.PIPE, universal_newlines=True) | ||||
(stdout, stderr) = p.communicate() | (stdout, stderr) = p.communicate() | ||||
if p.returncode: | if p.returncode: | ||||
raise IOError('Error opening file') | raise IOError('Error opening file') | ||||
libraries = [] | libraries = [] | ||||
for line in stdout.split(b'\n'): | for line in stdout.splitlines(): | ||||
tokens = line.split() | tokens = line.split() | ||||
if len(tokens) > 2 and tokens[1] == b'(NEEDED)': | if len(tokens) > 2 and tokens[1] == '(NEEDED)': | ||||
match = re.match( | match = re.match( | ||||
b'^Shared library: \[(.*)\]$', b' '.join(tokens[2:])) | '^Shared library: \[(.*)\]$', ' '.join(tokens[2:])) | ||||
if match: | if match: | ||||
libraries.append(match.group(1)) | libraries.append(match.group(1)) | ||||
else: | else: | ||||
raise ValueError('Unparseable (NEEDED) specification') | raise ValueError('Unparseable (NEEDED) specification') | ||||
return libraries | return libraries | ||||
if __name__ == '__main__': | if __name__ == '__main__': | ||||
cppfilt = CPPFilt() | cppfilt = CPPFilt() | ||||
retval = 0 | retval = 0 | ||||
for filename in sys.argv[1:]: | for filename in sys.argv[1:]: | ||||
# Check imported symbols | # Check imported symbols | ||||
for sym, version in read_symbols(filename, True): | for sym, version in read_symbols(filename, True): | ||||
if version and not check_version(MAX_VERSIONS, version): | if version and not check_version(MAX_VERSIONS, version): | ||||
print('%s: symbol %s from unsupported version %s' % ( | print('%s: symbol %s from unsupported version %s' % | ||||
filename, cppfilt(sym).decode('utf-8'), version.decode('utf-8'))) | (filename, cppfilt(sym), version)) | ||||
retval = 1 | retval = 1 | ||||
# Check exported symbols | # Check exported symbols | ||||
for sym, version in read_symbols(filename, False): | for sym, version in read_symbols(filename, False): | ||||
if sym in IGNORE_EXPORTS: | if sym in IGNORE_EXPORTS: | ||||
continue | continue | ||||
print('%s: export of symbol %s not allowed' % | print('%s: export of symbol %s not allowed' % | ||||
(filename, cppfilt(sym).decode('utf-8'))) | (filename, cppfilt(sym))) | ||||
retval = 1 | retval = 1 | ||||
# Check dependency libraries | # Check dependency libraries | ||||
for library_name in read_libraries(filename): | for library_name in read_libraries(filename): | ||||
if library_name not in ALLOWED_LIBRARIES: | if library_name not in ALLOWED_LIBRARIES: | ||||
print('%s: NEEDED library %s is not allowed' % | print('%s: NEEDED library %s is not allowed' % | ||||
(filename, library_name.decode('utf-8'))) | (filename, library_name)) | ||||
retval = 1 | retval = 1 | ||||
exit(retval) | exit(retval) |