devtools: Improve *-check.py tool detection

This is important to make sure that we're not testing tools different
from the one we're building with.

Introduce determine_wellknown_cmd, which encapsulates how we
should handle well-known tools specification (IFS splitting, env
override, etc.).
This commit is contained in:
Carl Dong 2021-01-21 13:52:40 -05:00 committed by fanquake
parent bda62eab38
commit 9fdc8afe11
No known key found for this signature in database
GPG Key ID: 2EEB9F5CC09526C1
6 changed files with 50 additions and 24 deletions

View File

@ -58,6 +58,7 @@ DIST_SHARE = \
BIN_CHECKS=$(top_srcdir)/contrib/devtools/symbol-check.py \ BIN_CHECKS=$(top_srcdir)/contrib/devtools/symbol-check.py \
$(top_srcdir)/contrib/devtools/security-check.py \ $(top_srcdir)/contrib/devtools/security-check.py \
$(top_srcdir)/contrib/devtools/utils.py \
$(top_srcdir)/contrib/devtools/pixie.py $(top_srcdir)/contrib/devtools/pixie.py
WINDOWS_PACKAGING = $(top_srcdir)/share/pixmaps/bitcoin.ico \ WINDOWS_PACKAGING = $(top_srcdir)/share/pixmaps/bitcoin.ico \
@ -366,14 +367,14 @@ clean-local: clean-docs
test-security-check: test-security-check:
if TARGET_DARWIN if TARGET_DARWIN
$(AM_V_at) $(PYTHON) $(top_srcdir)/contrib/devtools/test-security-check.py TestSecurityChecks.test_MACHO $(AM_V_at) CC='$(CC)' $(PYTHON) $(top_srcdir)/contrib/devtools/test-security-check.py TestSecurityChecks.test_MACHO
$(AM_V_at) $(PYTHON) $(top_srcdir)/contrib/devtools/test-symbol-check.py TestSymbolChecks.test_MACHO $(AM_V_at) CC='$(CC)' $(PYTHON) $(top_srcdir)/contrib/devtools/test-symbol-check.py TestSymbolChecks.test_MACHO
endif endif
if TARGET_WINDOWS if TARGET_WINDOWS
$(AM_V_at) $(PYTHON) $(top_srcdir)/contrib/devtools/test-security-check.py TestSecurityChecks.test_PE $(AM_V_at) CC='$(CC)' $(PYTHON) $(top_srcdir)/contrib/devtools/test-security-check.py TestSecurityChecks.test_PE
$(AM_V_at) $(PYTHON) $(top_srcdir)/contrib/devtools/test-symbol-check.py TestSymbolChecks.test_PE $(AM_V_at) CC='$(CC)' $(PYTHON) $(top_srcdir)/contrib/devtools/test-symbol-check.py TestSymbolChecks.test_PE
endif endif
if TARGET_LINUX if TARGET_LINUX
$(AM_V_at) $(PYTHON) $(top_srcdir)/contrib/devtools/test-security-check.py TestSecurityChecks.test_ELF $(AM_V_at) CC='$(CC)' $(PYTHON) $(top_srcdir)/contrib/devtools/test-security-check.py TestSecurityChecks.test_ELF
$(AM_V_at) $(PYTHON) $(top_srcdir)/contrib/devtools/test-symbol-check.py TestSymbolChecks.test_ELF $(AM_V_at) CC='$(CC)' CPPFILT='$(CPPFILT)' $(PYTHON) $(top_srcdir)/contrib/devtools/test-symbol-check.py TestSymbolChecks.test_ELF
endif endif

View File

@ -12,12 +12,13 @@ Example usage:
''' '''
import subprocess import subprocess
import sys import sys
import os
from typing import List, Optional from typing import List, Optional
import lief import lief
import pixie import pixie
from utils import determine_wellknown_cmd
# Debian 8 (Jessie) EOL: 2020. https://wiki.debian.org/DebianReleases#Production_Releases # Debian 8 (Jessie) EOL: 2020. https://wiki.debian.org/DebianReleases#Production_Releases
# #
# - g++ version 4.9.2 (https://packages.debian.org/search?suite=jessie&arch=any&searchon=names&keywords=g%2B%2B) # - g++ version 4.9.2 (https://packages.debian.org/search?suite=jessie&arch=any&searchon=names&keywords=g%2B%2B)
@ -60,7 +61,6 @@ IGNORE_EXPORTS = {
'_edata', '_end', '__end__', '_init', '__bss_start', '__bss_start__', '_bss_end__', '__bss_end__', '_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',
'environ', '_environ', '__environ', 'environ', '_environ', '__environ',
} }
CPPFILT_CMD = os.getenv('CPPFILT', '/usr/bin/c++filt')
# Allowed NEEDED libraries # Allowed NEEDED libraries
ELF_ALLOWED_LIBRARIES = { ELF_ALLOWED_LIBRARIES = {
@ -140,7 +140,7 @@ class CPPFilt(object):
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(CPPFILT_CMD, stdin=subprocess.PIPE, stdout=subprocess.PIPE, universal_newlines=True) self.proc = subprocess.Popen(determine_wellknown_cmd('CPPFILT', 'c++filt'), stdin=subprocess.PIPE, stdout=subprocess.PIPE, universal_newlines=True)
def __call__(self, mangled): def __call__(self, mangled):
self.proc.stdin.write(mangled + '\n') self.proc.stdin.write(mangled + '\n')

View File

@ -9,6 +9,8 @@ import os
import subprocess import subprocess
import unittest import unittest
from utils import determine_wellknown_cmd
def write_testcode(filename): def write_testcode(filename):
with open(filename, 'w', encoding="utf8") as f: with open(filename, 'w', encoding="utf8") as f:
f.write(''' f.write('''
@ -25,7 +27,7 @@ def clean_files(source, executable):
os.remove(executable) os.remove(executable)
def call_security_check(cc, source, executable, options): def call_security_check(cc, source, executable, options):
subprocess.run([cc,source,'-o',executable] + options, check=True) subprocess.run([*cc,source,'-o',executable] + options, check=True)
p = subprocess.run(['./contrib/devtools/security-check.py',executable], stdout=subprocess.PIPE, universal_newlines=True) p = subprocess.run(['./contrib/devtools/security-check.py',executable], stdout=subprocess.PIPE, universal_newlines=True)
return (p.returncode, p.stdout.rstrip()) return (p.returncode, p.stdout.rstrip())
@ -33,7 +35,7 @@ class TestSecurityChecks(unittest.TestCase):
def test_ELF(self): def test_ELF(self):
source = 'test1.c' source = 'test1.c'
executable = 'test1' executable = 'test1'
cc = 'gcc' cc = determine_wellknown_cmd('CC', 'gcc')
write_testcode(source) write_testcode(source)
self.assertEqual(call_security_check(cc, source, executable, ['-Wl,-zexecstack','-fno-stack-protector','-Wl,-znorelro','-no-pie','-fno-PIE', '-Wl,-z,separate-code']), self.assertEqual(call_security_check(cc, source, executable, ['-Wl,-zexecstack','-fno-stack-protector','-Wl,-znorelro','-no-pie','-fno-PIE', '-Wl,-z,separate-code']),
@ -54,7 +56,7 @@ class TestSecurityChecks(unittest.TestCase):
def test_PE(self): def test_PE(self):
source = 'test1.c' source = 'test1.c'
executable = 'test1.exe' executable = 'test1.exe'
cc = 'x86_64-w64-mingw32-gcc' cc = determine_wellknown_cmd('CC', 'x86_64-w64-mingw32-gcc')
write_testcode(source) write_testcode(source)
self.assertEqual(call_security_check(cc, source, executable, ['-Wl,--no-nxcompat','-Wl,--no-dynamicbase','-Wl,--no-high-entropy-va','-no-pie','-fno-PIE']), self.assertEqual(call_security_check(cc, source, executable, ['-Wl,--no-nxcompat','-Wl,--no-dynamicbase','-Wl,--no-high-entropy-va','-no-pie','-fno-PIE']),
@ -73,7 +75,7 @@ class TestSecurityChecks(unittest.TestCase):
def test_MACHO(self): def test_MACHO(self):
source = 'test1.c' source = 'test1.c'
executable = 'test1' executable = 'test1'
cc = 'clang' cc = determine_wellknown_cmd('CC', 'clang')
write_testcode(source) write_testcode(source)
self.assertEqual(call_security_check(cc, source, executable, ['-Wl,-no_pie','-Wl,-flat_namespace','-Wl,-allow_stack_execute','-fno-stack-protector']), self.assertEqual(call_security_check(cc, source, executable, ['-Wl,-no_pie','-Wl,-flat_namespace','-Wl,-allow_stack_execute','-fno-stack-protector']),
@ -95,4 +97,3 @@ class TestSecurityChecks(unittest.TestCase):
if __name__ == '__main__': if __name__ == '__main__':
unittest.main() unittest.main()

View File

@ -7,10 +7,13 @@ Test script for symbol-check.py
''' '''
import os import os
import subprocess import subprocess
from typing import List
import unittest import unittest
def call_symbol_check(cc, source, executable, options): from utils import determine_wellknown_cmd
subprocess.run([cc,source,'-o',executable] + options, check=True)
def call_symbol_check(cc: List[str], source, executable, options):
subprocess.run([*cc,source,'-o',executable] + options, check=True)
p = subprocess.run(['./contrib/devtools/symbol-check.py',executable], stdout=subprocess.PIPE, universal_newlines=True) p = subprocess.run(['./contrib/devtools/symbol-check.py',executable], stdout=subprocess.PIPE, universal_newlines=True)
os.remove(source) os.remove(source)
os.remove(executable) os.remove(executable)
@ -20,7 +23,7 @@ class TestSymbolChecks(unittest.TestCase):
def test_ELF(self): def test_ELF(self):
source = 'test1.c' source = 'test1.c'
executable = 'test1' executable = 'test1'
cc = 'gcc' cc = determine_wellknown_cmd('CC', 'gcc')
# renameat2 was introduced in GLIBC 2.28, so is newer than the upper limit # renameat2 was introduced in GLIBC 2.28, so is newer than the upper limit
# of glibc for all platforms # of glibc for all platforms
@ -82,7 +85,7 @@ class TestSymbolChecks(unittest.TestCase):
def test_MACHO(self): def test_MACHO(self):
source = 'test1.c' source = 'test1.c'
executable = 'test1' executable = 'test1'
cc = 'clang' cc = determine_wellknown_cmd('CC', 'clang')
with open(source, 'w', encoding="utf8") as f: with open(source, 'w', encoding="utf8") as f:
f.write(''' f.write('''
@ -132,7 +135,7 @@ class TestSymbolChecks(unittest.TestCase):
def test_PE(self): def test_PE(self):
source = 'test1.c' source = 'test1.c'
executable = 'test1.exe' executable = 'test1.exe'
cc = 'x86_64-w64-mingw32-gcc' cc = determine_wellknown_cmd('CC', 'x86_64-w64-mingw32-gcc')
with open(source, 'w', encoding="utf8") as f: with open(source, 'w', encoding="utf8") as f:
f.write(''' f.write('''
@ -182,4 +185,3 @@ class TestSymbolChecks(unittest.TestCase):
if __name__ == '__main__': if __name__ == '__main__':
unittest.main() unittest.main()

22
contrib/devtools/utils.py Executable file
View File

@ -0,0 +1,22 @@
#!/usr/bin/env python3
# Copyright (c) 2021 The Bitcoin Core developers
# Distributed under the MIT software license, see the accompanying
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
'''
Common utility functions
'''
import shutil
import sys
import os
from typing import List
def determine_wellknown_cmd(envvar, progname) -> List[str]:
maybe_env = os.getenv(envvar)
maybe_which = shutil.which(progname)
if maybe_env:
return maybe_env.split(' ') # Well-known vars are often meant to be word-split
elif maybe_which:
return [ maybe_which ]
else:
sys.exit(f"{progname} not found")

View File

@ -814,23 +814,23 @@ clean-local:
check-symbols: $(bin_PROGRAMS) check-symbols: $(bin_PROGRAMS)
if TARGET_DARWIN if TARGET_DARWIN
@echo "Checking macOS dynamic libraries..." @echo "Checking macOS dynamic libraries..."
$(AM_V_at) OTOOL=$(OTOOL) $(PYTHON) $(top_srcdir)/contrib/devtools/symbol-check.py $(bin_PROGRAMS) $(AM_V_at) $(PYTHON) $(top_srcdir)/contrib/devtools/symbol-check.py $(bin_PROGRAMS)
endif endif
if TARGET_WINDOWS if TARGET_WINDOWS
@echo "Checking Windows dynamic libraries..." @echo "Checking Windows dynamic libraries..."
$(AM_V_at) OBJDUMP=$(OBJDUMP) $(PYTHON) $(top_srcdir)/contrib/devtools/symbol-check.py $(bin_PROGRAMS) $(AM_V_at) $(PYTHON) $(top_srcdir)/contrib/devtools/symbol-check.py $(bin_PROGRAMS)
endif endif
if TARGET_LINUX if TARGET_LINUX
@echo "Checking glibc back compat..." @echo "Checking glibc back compat..."
$(AM_V_at) CPPFILT=$(CPPFILT) $(PYTHON) $(top_srcdir)/contrib/devtools/symbol-check.py $(bin_PROGRAMS) $(AM_V_at) CPPFILT='$(CPPFILT)' $(PYTHON) $(top_srcdir)/contrib/devtools/symbol-check.py $(bin_PROGRAMS)
endif endif
check-security: $(bin_PROGRAMS) check-security: $(bin_PROGRAMS)
if HARDEN if HARDEN
@echo "Checking binary security..." @echo "Checking binary security..."
$(AM_V_at) OBJDUMP=$(OBJDUMP) OTOOL=$(OTOOL) $(PYTHON) $(top_srcdir)/contrib/devtools/security-check.py $(bin_PROGRAMS) $(AM_V_at) $(PYTHON) $(top_srcdir)/contrib/devtools/security-check.py $(bin_PROGRAMS)
endif endif
libbitcoin_ipc_mpgen_input = \ libbitcoin_ipc_mpgen_input = \