Changeset View
Changeset View
Standalone View
Standalone View
contrib/devtools/symbol-check.py
Show All 30 Lines | |||||
# Taking the minimum of these as our target. | # Taking the minimum of these as our target. | ||||
# | # | ||||
# According to GNU ABI document (http://gcc.gnu.org/onlinedocs/libstdc++/manual/abi.html) this corresponds to: | # According to GNU ABI document (http://gcc.gnu.org/onlinedocs/libstdc++/manual/abi.html) this corresponds to: | ||||
# GCC 4.8.0: GCC_4.8.0 | # GCC 4.8.0: GCC_4.8.0 | ||||
# GCC 4.8.0: GLIBCXX_3.4.18, CXXABI_1.3.7 | # GCC 4.8.0: GLIBCXX_3.4.18, CXXABI_1.3.7 | ||||
# (glibc) GLIBC_2_19 | # (glibc) GLIBC_2_19 | ||||
# | # | ||||
MAX_VERSIONS = { | MAX_VERSIONS = { | ||||
'GCC': (4, 8, 0), | 'GCC': (4, 8, 0), | ||||
'CXXABI': (1, 3, 7), | 'CXXABI': (1, 3, 7), | ||||
'GLIBCXX': (3, 4, 18), | 'GLIBCXX': (3, 4, 18), | ||||
'GLIBC': (2, 19) | 'GLIBC': (2, 19), | ||||
'LIBATOMIC': (1, 0) | |||||
} | } | ||||
# 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 = { | ||||
'_edata', '_end', '_init', '__bss_start', '_fini', '_IO_stdin_used', 'stdin', 'stdout', 'stderr', | '_edata', '_end', '__end__', '_init', '__bss_start', '__bss_start__', '_bss_end__', '__bss_end__', '_fini', '_IO_stdin_used', 'stdin', 'stdout', 'stderr', | ||||
deadalnix: I'm sure there is a way this can be formatted better. | |||||
# Figure out why we get these symbols exported on xenial. | # Figure out why we get these symbols exported on xenial. | ||||
'_ZNKSt5ctypeIcE8do_widenEc', 'in6addr_any', 'optarg', | '_ZNKSt5ctypeIcE8do_widenEc', 'in6addr_any', 'optarg', | ||||
'_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 | ||||
'libgcc_s.so.1', # GCC base support | 'libgcc_s.so.1', # GCC base support | ||||
'libc.so.6', # C library | 'libc.so.6', # C library | ||||
'libpthread.so.0', # threading | 'libpthread.so.0', # threading | ||||
'libanl.so.1', # DNS resolve | 'libanl.so.1', # DNS resolve | ||||
'libm.so.6', # math library | 'libm.so.6', # math library | ||||
'librt.so.1', # real-time (clock) | 'librt.so.1', # real-time (clock) | ||||
'libatomic.so.1', | |||||
'ld-linux-x86-64.so.2', # 64-bit dynamic linker | 'ld-linux-x86-64.so.2', # 64-bit dynamic linker | ||||
'ld-linux.so.2', # 32-bit dynamic linker | 'ld-linux.so.2', # 32-bit dynamic linker | ||||
'ld-linux-aarch64.so.1', # 64-bit ARM dynamic linker | |||||
'ld-linux-armhf.so.3', # 32-bit ARM dynamic linker | |||||
# bitcoin-qt only | # bitcoin-qt only | ||||
'libX11-xcb.so.1', # part of X11 | 'libX11-xcb.so.1', # part of X11 | ||||
'libX11.so.6', # part of X11 | 'libX11.so.6', # part of X11 | ||||
'libxcb.so.1', # part of X11 | 'libxcb.so.1', # part of X11 | ||||
'libfontconfig.so.1', # font support | 'libfontconfig.so.1', # font support | ||||
'libfreetype.so.6', # font parsing | 'libfreetype.so.6', # font parsing | ||||
'libdl.so.2' # programming interface to dynamic linker | 'libdl.so.2' # programming interface to dynamic linker | ||||
} | } | ||||
ARCH_MIN_GLIBC_VER = { | |||||
'80386': (2, 1), | |||||
'X86-64': (2, 2, 5), | |||||
'ARM': (2, 4), | |||||
'AArch64': (2, 17) | |||||
} | |||||
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. | ||||
''' | ''' | ||||
Show All 13 Lines | def close(self): | ||||
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], stdout=subprocess.PIPE, | p = subprocess.Popen([READELF_CMD, '--dyn-syms', '-W', '-h', executable], stdout=subprocess.PIPE, | ||||
stderr=subprocess.PIPE, stdin=subprocess.PIPE, universal_newlines=True) | 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 {}: {}'.format( | raise IOError('Could not read symbols for {}: {}'.format( | ||||
executable, stderr.strip())) | executable, stderr.strip())) | ||||
syms = [] | syms = [] | ||||
for line in stdout.splitlines(): | for line in stdout.splitlines(): | ||||
line = line.split() | line = line.split() | ||||
if 'Machine:' in line: | |||||
arch = line[-1] | |||||
if len(line) > 7 and re.match('[0-9]+:$', line[0]): | if len(line) > 7 and re.match('[0-9]+:$', line[0]): | ||||
(sym, _, version) = line[7].partition('@') | (sym, _, version) = line[7].partition('@') | ||||
is_import = line[6] == 'UND' | is_import = line[6] == 'UND' | ||||
if version.startswith('@'): | 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, arch)) | ||||
return syms | return syms | ||||
def check_version(max_versions, version): | def check_version(max_versions, version, arch): | ||||
if '_' in version: | if '_' in version: | ||||
(lib, _, ver) = version.rpartition('_') | (lib, _, ver) = version.rpartition('_') | ||||
else: | else: | ||||
lib = version | lib = version | ||||
ver = '0' | ver = '0' | ||||
ver = tuple([int(x) for x in ver.split('.')]) | 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] or lib == 'GLIBC' and ver <= ARCH_MIN_GLIBC_VER[arch] | ||||
def read_libraries(filename): | def read_libraries(filename): | ||||
p = subprocess.Popen([READELF_CMD, '-d', '-W', filename], stdout=subprocess.PIPE, | p = subprocess.Popen([READELF_CMD, '-d', '-W', filename], stdout=subprocess.PIPE, | ||||
stderr=subprocess.PIPE, stdin=subprocess.PIPE, universal_newlines=True) | 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') | ||||
Show All 10 Lines | def read_libraries(filename): | ||||
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, arch in read_symbols(filename, True): | ||||
if version and not check_version(MAX_VERSIONS, version): | if version and not check_version(MAX_VERSIONS, version, arch): | ||||
print('{}: symbol {} from unsupported version {}'.format( | print('{}: symbol {} from unsupported version {}'.format( | ||||
filename, cppfilt(sym), version)) | 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, arch in read_symbols(filename, False): | ||||
if sym in IGNORE_EXPORTS: | if sym in IGNORE_EXPORTS: | ||||
continue | continue | ||||
print('{}: export of symbol {} not allowed'.format( | print('{}: export of symbol {} not allowed'.format( | ||||
filename, cppfilt(sym))) | 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('{}: NEEDED library {} is not allowed'.format( | print('{}: NEEDED library {} is not allowed'.format( | ||||
filename, library_name)) | filename, library_name)) | ||||
retval = 1 | retval = 1 | ||||
sys.exit(retval) | sys.exit(retval) |
I'm sure there is a way this can be formatted better.