You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
194 lines
6.8 KiB
194 lines
6.8 KiB
4 months ago
|
#!/usr/bin/env python3
|
||
|
#
|
||
|
# Copyright (C) 2016 The Android Open Source Project
|
||
|
#
|
||
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||
|
# you may not use this file except in compliance with the License.
|
||
|
# You may obtain a copy of the License at
|
||
|
#
|
||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||
|
#
|
||
|
# Unless required by applicable law or agreed to in writing, software
|
||
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
||
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||
|
# See the License for the specific language governing permissions and
|
||
|
# limitations under the License.
|
||
|
|
||
|
import argparse
|
||
|
import os
|
||
|
import shutil
|
||
|
import sys
|
||
|
|
||
|
from glob import glob
|
||
|
from subprocess import call
|
||
|
from tempfile import mkdtemp
|
||
|
|
||
|
sys.path.append(os.path.dirname(os.path.dirname(
|
||
|
os.path.realpath(__file__))))
|
||
|
|
||
|
from common.common import FatalError
|
||
|
from common.common import GetEnvVariableOrError
|
||
|
from common.common import RetCode
|
||
|
from common.common import RunCommand
|
||
|
|
||
|
|
||
|
#
|
||
|
# Tester class.
|
||
|
#
|
||
|
|
||
|
|
||
|
class DexFuzzTester(object):
|
||
|
"""Tester that feeds JFuzz programs into DexFuzz testing."""
|
||
|
|
||
|
def __init__(self, num_tests, num_inputs, device, dexer, debug_info):
|
||
|
"""Constructor for the tester.
|
||
|
|
||
|
Args:
|
||
|
num_tests: int, number of tests to run
|
||
|
num_inputs: int, number of JFuzz programs to generate
|
||
|
device: string, target device serial number (or None)
|
||
|
dexer: string, defines dexer
|
||
|
debug_info: boolean, if True include debugging info
|
||
|
"""
|
||
|
self._num_tests = num_tests
|
||
|
self._num_inputs = num_inputs
|
||
|
self._device = device
|
||
|
self._save_dir = None
|
||
|
self._results_dir = None
|
||
|
self._dexfuzz_dir = None
|
||
|
self._inputs_dir = None
|
||
|
self._dexfuzz_env = None
|
||
|
self._dexer = dexer
|
||
|
self._debug_info = debug_info
|
||
|
|
||
|
def __enter__(self):
|
||
|
"""On entry, enters new temp directory after saving current directory.
|
||
|
|
||
|
Raises:
|
||
|
FatalError: error when temp directory cannot be constructed
|
||
|
"""
|
||
|
self._save_dir = os.getcwd()
|
||
|
self._results_dir = mkdtemp(dir='/tmp/')
|
||
|
self._dexfuzz_dir = mkdtemp(dir=self._results_dir)
|
||
|
self._inputs_dir = mkdtemp(dir=self._dexfuzz_dir)
|
||
|
if self._results_dir is None or self._dexfuzz_dir is None or \
|
||
|
self._inputs_dir is None:
|
||
|
raise FatalError('Cannot obtain temp directory')
|
||
|
self._dexfuzz_env = os.environ.copy()
|
||
|
self._dexfuzz_env['ANDROID_DATA'] = self._dexfuzz_dir
|
||
|
top = GetEnvVariableOrError('ANDROID_BUILD_TOP')
|
||
|
self._dexfuzz_env['PATH'] = (top + '/art/tools/bisection_search:' +
|
||
|
self._dexfuzz_env['PATH'])
|
||
|
android_root = GetEnvVariableOrError('ANDROID_HOST_OUT')
|
||
|
self._dexfuzz_env['ANDROID_ROOT'] = android_root
|
||
|
self._dexfuzz_env['LD_LIBRARY_PATH'] = android_root + '/lib'
|
||
|
os.chdir(self._dexfuzz_dir)
|
||
|
os.mkdir('divergent_programs')
|
||
|
os.mkdir('bisection_outputs')
|
||
|
return self
|
||
|
|
||
|
def __exit__(self, etype, evalue, etraceback):
|
||
|
"""On exit, re-enters previously saved current directory and cleans up."""
|
||
|
os.chdir(self._save_dir)
|
||
|
# TODO: detect divergences or shutil.rmtree(self._results_dir)
|
||
|
|
||
|
def Run(self):
|
||
|
"""Feeds JFuzz programs into DexFuzz testing."""
|
||
|
print()
|
||
|
print('**\n**** J/DexFuzz Testing\n**')
|
||
|
print()
|
||
|
print('#Tests :', self._num_tests)
|
||
|
print('Device :', self._device)
|
||
|
print('Directory :', self._results_dir)
|
||
|
print()
|
||
|
self.GenerateJFuzzPrograms()
|
||
|
self.RunDexFuzz()
|
||
|
|
||
|
def CompileOnHost(self):
|
||
|
"""Compiles Test.java into classes.dex using either javac/dx or d8.
|
||
|
|
||
|
Raises:
|
||
|
FatalError: error when compilation fails
|
||
|
"""
|
||
|
if self._dexer == 'dx' or self._dexer == 'd8':
|
||
|
dbg = '-g' if self._debug_info else '-g:none'
|
||
|
if RunCommand(['javac', '--release=8', dbg, 'Test.java'],
|
||
|
out=None, err='jerr.txt', timeout=30) != RetCode.SUCCESS:
|
||
|
print('Unexpected error while running javac')
|
||
|
raise FatalError('Unexpected error while running javac')
|
||
|
cfiles = glob('*.class')
|
||
|
dx = 'dx' if self._dexer == 'dx' else 'd8-compat-dx'
|
||
|
if RunCommand([dx, '--dex', '--output=classes.dex'] + cfiles,
|
||
|
out=None, err='dxerr.txt', timeout=30) != RetCode.SUCCESS:
|
||
|
print('Unexpected error while running dx')
|
||
|
raise FatalError('Unexpected error while running dx')
|
||
|
# Cleanup on success (nothing to see).
|
||
|
for cfile in cfiles:
|
||
|
os.unlink(cfile)
|
||
|
os.unlink('jerr.txt')
|
||
|
os.unlink('dxerr.txt')
|
||
|
else:
|
||
|
raise FatalError('Unknown dexer: ' + self._dexer)
|
||
|
|
||
|
def GenerateJFuzzPrograms(self):
|
||
|
"""Generates JFuzz programs.
|
||
|
|
||
|
Raises:
|
||
|
FatalError: error when generation fails
|
||
|
"""
|
||
|
os.chdir(self._inputs_dir)
|
||
|
for i in range(1, self._num_inputs + 1):
|
||
|
if RunCommand(['jfuzz'], out='Test.java', err=None) != RetCode.SUCCESS:
|
||
|
print('Unexpected error while running JFuzz')
|
||
|
raise FatalError('Unexpected error while running JFuzz')
|
||
|
self.CompileOnHost()
|
||
|
shutil.move('Test.java', '../Test' + str(i) + '.java')
|
||
|
shutil.move('classes.dex', 'classes' + str(i) + '.dex')
|
||
|
|
||
|
def RunDexFuzz(self):
|
||
|
"""Starts the DexFuzz testing."""
|
||
|
os.chdir(self._dexfuzz_dir)
|
||
|
dexfuzz_args = ['--inputs=' + self._inputs_dir,
|
||
|
'--execute',
|
||
|
'--execute-class=Test',
|
||
|
'--repeat=' + str(self._num_tests),
|
||
|
'--quiet',
|
||
|
'--dump-output',
|
||
|
'--dump-verify',
|
||
|
'--interpreter',
|
||
|
'--optimizing',
|
||
|
'--bisection-search']
|
||
|
if self._device is not None:
|
||
|
dexfuzz_args += ['--device=' + self._device, '--allarm']
|
||
|
else:
|
||
|
dexfuzz_args += ['--host'] # Assume host otherwise.
|
||
|
cmd = ['dexfuzz'] + dexfuzz_args
|
||
|
print('**** Running ****\n\n', cmd, '\n')
|
||
|
call(cmd, env=self._dexfuzz_env)
|
||
|
print('\n**** Results (report.log) ****\n')
|
||
|
call(['tail', '-n 24', 'report.log'])
|
||
|
|
||
|
|
||
|
def main():
|
||
|
# Handle arguments.
|
||
|
parser = argparse.ArgumentParser()
|
||
|
parser.add_argument('--num_tests', default=1000, type=int,
|
||
|
help='number of tests to run (default: 1000)')
|
||
|
parser.add_argument('--num_inputs', default=10, type=int,
|
||
|
help='number of JFuzz program to generate (default: 10)')
|
||
|
parser.add_argument('--device', help='target device serial number')
|
||
|
parser.add_argument('--dexer', default='dx', type=str,
|
||
|
help='defines dexer as dx or d8 (default: dx)')
|
||
|
parser.add_argument('--debug_info', default=False, action='store_true',
|
||
|
help='include debugging info')
|
||
|
args = parser.parse_args()
|
||
|
# Run the DexFuzz tester.
|
||
|
with DexFuzzTester(args.num_tests,
|
||
|
args.num_inputs,
|
||
|
args.device,
|
||
|
args.dexer, args.debug_info) as fuzzer:
|
||
|
fuzzer.Run()
|
||
|
|
||
|
if __name__ == '__main__':
|
||
|
main()
|