aboutsummaryrefslogtreecommitdiff
path: root/binary_search_tool/bisect_driver.py
diff options
context:
space:
mode:
Diffstat (limited to 'binary_search_tool/bisect_driver.py')
-rw-r--r--binary_search_tool/bisect_driver.py120
1 files changed, 93 insertions, 27 deletions
diff --git a/binary_search_tool/bisect_driver.py b/binary_search_tool/bisect_driver.py
index 0b3fb1d4..21dd11fa 100644
--- a/binary_search_tool/bisect_driver.py
+++ b/binary_search_tool/bisect_driver.py
@@ -1,8 +1,8 @@
-# Copyright 2016 Google Inc. All Rights Reserved.
+# Copyright 2016 Googie Inc. All rights Reserved.
#
-# This script is used to help the compiler wrapper in the Android build system
-# bisect for bad object files.
-"""Utilities for bisection of Android object files.
+# This script is used to help the compiler wrapper in the ChromeOS and
+# Android build systems bisect for bad object files.
+"""Utilities for bisection of ChromeOS and Android object files.
This module contains a set of utilities to allow bisection between
two sets (good and bad) of object files. Mostly used to find compiler
@@ -24,7 +24,7 @@ import shutil
import subprocess
import sys
-VALID_MODES = ['POPULATE_GOOD', 'POPULATE_BAD', 'TRIAGE']
+VALID_MODES = ('POPULATE_GOOD', 'POPULATE_BAD', 'TRIAGE')
GOOD_CACHE = 'good'
BAD_CACHE = 'bad'
LIST_FILE = os.path.join(GOOD_CACHE, '_LIST')
@@ -35,7 +35,6 @@ WRAPPER_SAFE_MODE = os.environ.get('BISECT_WRAPPER_SAFE_MODE', None) == '1'
class Error(Exception):
"""The general compiler wrapper error class."""
- pass
@contextlib.contextmanager
@@ -58,7 +57,13 @@ def lock_file(path, mode):
mode: mode to open file with ('w', 'r', etc.)
"""
with open(path, mode) as f:
- # Share the lock if just reading, make lock exclusive if writing
+ # Apply FD_CLOEXEC argument to fd. This ensures that the file descriptor
+ # won't be leaked to any child processes.
+ current_args = fcntl.fcntl(f.fileno(), fcntl.F_GETFD)
+ fcntl.fcntl(f.fileno(), fcntl.F_SETFD, current_args | fcntl.FD_CLOEXEC)
+
+ # Reads can share the lock as no race conditions exist. If write is needed,
+ # give writing process exclusive access to the file.
if f.mode == 'r' or f.mode == 'rb':
lock_type = fcntl.LOCK_SH
else:
@@ -68,8 +73,6 @@ def lock_file(path, mode):
fcntl.lockf(f, lock_type)
yield f
f.flush()
- except:
- raise
finally:
fcntl.lockf(f, fcntl.LOCK_UN)
@@ -103,8 +106,7 @@ def which_cache(obj_file):
determine where an object file should be linked from (good or bad).
"""
bad_set_file = os.environ.get('BISECT_BAD_SET')
- ret = subprocess.call(['grep', '-x', '-q', obj_file, bad_set_file])
- if ret == 0:
+ if in_object_list(obj_file, bad_set_file):
return BAD_CACHE
else:
return GOOD_CACHE
@@ -124,18 +126,29 @@ def get_obj_path(execargs):
Returns:
Absolute object path from execution args (-o argument). If no object being
- outputted or output doesn't end in ".o" then return empty string.
+ outputted, then return empty string. -o argument is checked only if -c is
+ also present.
"""
try:
i = execargs.index('-o')
+ _ = execargs.index('-c')
except ValueError:
return ''
obj_path = execargs[i + 1]
- if not obj_path.endswith(('.o',)):
- # TODO: what suffixes do we need to contemplate
- # TODO: add this as a warning
- # TODO: need to handle -r compilations
+ # Ignore args that do not create a file.
+ if obj_path in (
+ '-',
+ '/dev/null',):
+ return ''
+ # Ignore files ending in .tmp.
+ if obj_path.endswith(('.tmp',)):
+ return ''
+ # Ignore configuration files generated by Automake/Autoconf/CMake etc.
+ if (obj_path.endswith('conftest.o') or
+ obj_path.endswith('CMakeFiles/test.o') or
+ obj_path.find('CMakeTmp') != -1 or
+ os.path.abspath(obj_path).find('CMakeTmp') != -1):
return ''
return os.path.abspath(obj_path)
@@ -151,7 +164,7 @@ def get_dep_path(execargs):
if '-MD' not in execargs and '-MMD' not in execargs:
return ''
- # If -MF given this is the path of the dependency file. Otherwise the
+ # If -MF is given this is the path of the dependency file. Otherwise the
# dependency file is the value of -o but with a .d extension
if '-MF' in execargs:
i = execargs.index('-MF')
@@ -217,7 +230,16 @@ def get_side_effects(execargs):
def cache_file(execargs, bisect_dir, cache, abs_file_path):
- """Cache compiler output file (.o/.d/.dwo)."""
+ """Cache compiler output file (.o/.d/.dwo).
+
+ Args:
+ execargs: compiler execution arguments.
+ bisect_dir: The directory where bisection caches live.
+ cache: Which cache the file will be cached to (GOOD/BAD).
+ abs_file_path: Absolute path to file being cached.
+ Returns:
+ True if caching was successful, False otherwise.
+ """
# os.path.join fails with absolute paths, use + instead
bisect_path = os.path.join(bisect_dir, cache) + abs_file_path
bisect_path_dir = os.path.dirname(bisect_path)
@@ -227,14 +249,36 @@ def cache_file(execargs, bisect_dir, cache, abs_file_path):
try:
if os.path.exists(abs_file_path):
+ if os.path.exists(bisect_path):
+ # File exists
+ population_dir = os.path.join(bisect_dir, cache)
+ with lock_file(os.path.join(population_dir, '_DUPS'),
+ 'a') as dup_object_list:
+ dup_object_list.write('%s\n' % abs_file_path)
+ raise Exception(
+ 'Trying to cache file %s multiple times.' % abs_file_path)
+
shutil.copy2(abs_file_path, bisect_path)
+ # Set cache object to be read-only so later compilations can't
+ # accidentally overwrite it.
+ os.chmod(bisect_path, 0o444)
+ return True
+ else:
+ # File not found (happens when compilation fails but error code is still 0)
+ return False
except Exception:
print('Could not cache file %s' % abs_file_path, file=sys.stderr)
raise
def restore_file(bisect_dir, cache, abs_file_path):
- """Restore file from cache (.o/.d/.dwo)."""
+ """Restore file from cache (.o/.d/.dwo).
+
+ Args:
+ bisect_dir: The directory where bisection caches live.
+ cache: Which cache the file will be restored from (GOOD/BAD).
+ abs_file_path: Absolute path to file being restored.
+ """
# os.path.join fails with absolute paths, use + instead
cached_path = os.path.join(bisect_dir, cache) + abs_file_path
if os.path.exists(cached_path):
@@ -264,21 +308,41 @@ def bisect_populate(execargs, bisect_dir, population_name):
return retval
full_obj_path = get_obj_path(execargs)
- # If not a normal compiler call then just exit
+ # This is not a normal compiler call because it doesn't have a -o argument,
+ # or the -o argument has an unusable output file.
+ # It's likely that this compiler call was actually made to invoke the linker,
+ # or as part of a configuratoin test. In this case we want to simply call the
+ # compiler and return.
if not full_obj_path:
- return
+ return retval
- cache_file(execargs, bisect_dir, population_name, full_obj_path)
+ # Return if not able to cache the object file
+ if not cache_file(execargs, bisect_dir, population_name, full_obj_path):
+ return retval
population_dir = os.path.join(bisect_dir, population_name)
with lock_file(os.path.join(population_dir, '_LIST'), 'a') as object_list:
object_list.write('%s\n' % full_obj_path)
for side_effect in get_side_effects(execargs):
- cache_file(execargs, bisect_dir, population_name, side_effect)
+ _ = cache_file(execargs, bisect_dir, population_name, side_effect)
+
+ return retval
def bisect_triage(execargs, bisect_dir):
+ """Use object object file from appropriate cache (good/bad).
+
+ Given a populated bisection directory, use the object file saved
+ into one of the caches (good/bad) according to what is specified
+ in the good/bad sets. The good/bad sets are generated by the
+ high level binary search tool. Additionally restore any possible
+ side effects of compiler.
+
+ Args:
+ execargs: compiler execution arguments.
+ bisect_dir: populated bisection directory.
+ """
full_obj_path = get_obj_path(execargs)
obj_list = os.path.join(bisect_dir, LIST_FILE)
@@ -309,7 +373,7 @@ def bisect_triage(execargs, bisect_dir):
return retval
os.remove(full_obj_path)
restore_file(bisect_dir, cache, full_obj_path)
- return
+ return retval
# Generate compiler side effects. Trick Make into thinking compiler was
# actually executed.
@@ -321,14 +385,16 @@ def bisect_triage(execargs, bisect_dir):
if not os.path.exists(full_obj_path):
restore_file(bisect_dir, cache, full_obj_path)
+ return 0
+
def bisect_driver(bisect_stage, bisect_dir, execargs):
"""Call appropriate bisection stage according to value in bisect_stage."""
if bisect_stage == 'POPULATE_GOOD':
- bisect_populate(execargs, bisect_dir, GOOD_CACHE)
+ return bisect_populate(execargs, bisect_dir, GOOD_CACHE)
elif bisect_stage == 'POPULATE_BAD':
- bisect_populate(execargs, bisect_dir, BAD_CACHE)
+ return bisect_populate(execargs, bisect_dir, BAD_CACHE)
elif bisect_stage == 'TRIAGE':
- bisect_triage(execargs, bisect_dir)
+ return bisect_triage(execargs, bisect_dir)
else:
raise ValueError('wrong value for BISECT_STAGE: %s' % bisect_stage)