Home | History | Annotate | Download | only in jfuzz
      1 #!/usr/bin/env python3.4
      2 #
      3 # Copyright (C) 2016 The Android Open Source Project
      4 #
      5 # Licensed under the Apache License, Version 2.0 (the "License");
      6 # you may not use this file except in compliance with the License.
      7 # You may obtain a copy of the License at
      8 #
      9 #   http://www.apache.org/licenses/LICENSE-2.0
     10 #
     11 # Unless required by applicable law or agreed to in writing, software
     12 # distributed under the License is distributed on an "AS IS" BASIS,
     13 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     14 # See the License for the specific language governing permissions and
     15 # limitations under the License.
     16 
     17 import argparse
     18 import os
     19 import shutil
     20 import sys
     21 
     22 from glob import glob
     23 from subprocess import call
     24 from tempfile import mkdtemp
     25 
     26 sys.path.append(os.path.dirname(os.path.dirname(
     27         os.path.realpath(__file__))))
     28 
     29 from common.common import FatalError
     30 from common.common import GetEnvVariableOrError
     31 from common.common import GetJackClassPath
     32 from common.common import RetCode
     33 from common.common import RunCommand
     34 
     35 
     36 #
     37 # Tester class.
     38 #
     39 
     40 
     41 class DexFuzzTester(object):
     42   """Tester that feeds JFuzz programs into DexFuzz testing."""
     43 
     44   def  __init__(self, num_tests, num_inputs, device, dexer, debug_info):
     45     """Constructor for the tester.
     46 
     47     Args:
     48       num_tests: int, number of tests to run
     49       num_inputs: int, number of JFuzz programs to generate
     50       device: string, target device serial number (or None)
     51       dexer: string, defines dexer
     52       debug_info: boolean, if True include debugging info
     53     """
     54     self._num_tests = num_tests
     55     self._num_inputs = num_inputs
     56     self._device = device
     57     self._save_dir = None
     58     self._results_dir = None
     59     self._dexfuzz_dir = None
     60     self._inputs_dir = None
     61     self._dexfuzz_env = None
     62     self._dexer = dexer
     63     self._debug_info = debug_info
     64 
     65   def __enter__(self):
     66     """On entry, enters new temp directory after saving current directory.
     67 
     68     Raises:
     69       FatalError: error when temp directory cannot be constructed
     70     """
     71     self._save_dir = os.getcwd()
     72     self._results_dir = mkdtemp(dir='/tmp/')
     73     self._dexfuzz_dir = mkdtemp(dir=self._results_dir)
     74     self._inputs_dir = mkdtemp(dir=self._dexfuzz_dir)
     75     if self._results_dir is None or self._dexfuzz_dir is None or \
     76         self._inputs_dir is None:
     77       raise FatalError('Cannot obtain temp directory')
     78     self._dexfuzz_env = os.environ.copy()
     79     self._dexfuzz_env['ANDROID_DATA'] = self._dexfuzz_dir
     80     top = GetEnvVariableOrError('ANDROID_BUILD_TOP')
     81     self._dexfuzz_env['PATH'] = (top + '/art/tools/bisection_search:' +
     82                                  self._dexfuzz_env['PATH'])
     83     android_root = GetEnvVariableOrError('ANDROID_HOST_OUT')
     84     self._dexfuzz_env['ANDROID_ROOT'] = android_root
     85     self._dexfuzz_env['LD_LIBRARY_PATH'] = android_root + '/lib'
     86     os.chdir(self._dexfuzz_dir)
     87     os.mkdir('divergent_programs')
     88     os.mkdir('bisection_outputs')
     89     return self
     90 
     91   def __exit__(self, etype, evalue, etraceback):
     92     """On exit, re-enters previously saved current directory and cleans up."""
     93     os.chdir(self._save_dir)
     94     # TODO: detect divergences or shutil.rmtree(self._results_dir)
     95 
     96   def Run(self):
     97     """Feeds JFuzz programs into DexFuzz testing."""
     98     print()
     99     print('**\n**** J/DexFuzz Testing\n**')
    100     print()
    101     print('#Tests    :', self._num_tests)
    102     print('Device    :', self._device)
    103     print('Directory :', self._results_dir)
    104     print()
    105     self.GenerateJFuzzPrograms()
    106     self.RunDexFuzz()
    107 
    108   def CompileOnHost(self):
    109     """Compiles Test.java into classes.dex using either javac/dx,d8 or jack.
    110 
    111     Raises:
    112       FatalError: error when compilation fails
    113     """
    114     if self._dexer == 'dx' or self._dexer == 'd8':
    115       dbg = '-g' if self._debug_info else '-g:none'
    116       if RunCommand(['javac', '--release=8', dbg, 'Test.java'],
    117                     out=None, err='jerr.txt', timeout=30) != RetCode.SUCCESS:
    118         print('Unexpected error while running javac')
    119         raise FatalError('Unexpected error while running javac')
    120       cfiles = glob('*.class')
    121       dx = 'dx' if self._dexer == 'dx' else 'd8-compat-dx'
    122       if RunCommand([dx, '--dex', '--output=classes.dex'] + cfiles,
    123                     out=None, err='dxerr.txt', timeout=30) != RetCode.SUCCESS:
    124         print('Unexpected error while running dx')
    125         raise FatalError('Unexpected error while running dx')
    126       # Cleanup on success (nothing to see).
    127       for cfile in cfiles:
    128         os.unlink(cfile)
    129       os.unlink('jerr.txt')
    130       os.unlink('dxerr.txt')
    131 
    132     elif self._dexer == 'jack':
    133       jack_args = ['-cp', GetJackClassPath(), '--output-dex', '.', 'Test.java']
    134       if RunCommand(['jack'] + jack_args, out=None, err='jackerr.txt',
    135                     timeout=30) != RetCode.SUCCESS:
    136         print('Unexpected error while running Jack')
    137         raise FatalError('Unexpected error while running Jack')
    138       # Cleanup on success (nothing to see).
    139       os.unlink('jackerr.txt')
    140     else:
    141       raise FatalError('Unknown dexer: ' + self._dexer)
    142 
    143   def GenerateJFuzzPrograms(self):
    144     """Generates JFuzz programs.
    145 
    146     Raises:
    147       FatalError: error when generation fails
    148     """
    149     os.chdir(self._inputs_dir)
    150     for i in range(1, self._num_inputs + 1):
    151       if RunCommand(['jfuzz'], out='Test.java', err=None) != RetCode.SUCCESS:
    152         print('Unexpected error while running JFuzz')
    153         raise FatalError('Unexpected error while running JFuzz')
    154       self.CompileOnHost()
    155       shutil.move('Test.java', '../Test' + str(i) + '.java')
    156       shutil.move('classes.dex', 'classes' + str(i) + '.dex')
    157 
    158   def RunDexFuzz(self):
    159     """Starts the DexFuzz testing."""
    160     os.chdir(self._dexfuzz_dir)
    161     dexfuzz_args = ['--inputs=' + self._inputs_dir,
    162                     '--execute',
    163                     '--execute-class=Test',
    164                     '--repeat=' + str(self._num_tests),
    165                     '--quiet',
    166                     '--dump-output',
    167                     '--dump-verify',
    168                     '--interpreter',
    169                     '--optimizing',
    170                     '--bisection-search']
    171     if self._device is not None:
    172       dexfuzz_args += ['--device=' + self._device, '--allarm']
    173     else:
    174       dexfuzz_args += ['--host']  # Assume host otherwise.
    175     cmd = ['dexfuzz'] + dexfuzz_args
    176     print('**** Running ****\n\n', cmd, '\n')
    177     call(cmd, env=self._dexfuzz_env)
    178     print('\n**** Results (report.log) ****\n')
    179     call(['tail', '-n 24', 'report.log'])
    180 
    181 
    182 def main():
    183   # Handle arguments.
    184   parser = argparse.ArgumentParser()
    185   parser.add_argument('--num_tests', default=1000, type=int,
    186                       help='number of tests to run (default: 1000)')
    187   parser.add_argument('--num_inputs', default=10, type=int,
    188                       help='number of JFuzz program to generate (default: 10)')
    189   parser.add_argument('--device', help='target device serial number')
    190   parser.add_argument('--dexer', default='dx', type=str,
    191                       help='defines dexer as dx, d8, or jack (default: dx)')
    192   parser.add_argument('--debug_info', default=False, action='store_true',
    193                       help='include debugging info')
    194   args = parser.parse_args()
    195   # Run the DexFuzz tester.
    196   with DexFuzzTester(args.num_tests,
    197                      args.num_inputs,
    198                      args.device,
    199                      args.dexer, args.debug_info) as fuzzer:
    200     fuzzer.Run()
    201 
    202 if __name__ == '__main__':
    203   main()
    204