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 subprocess import call
     23 from tempfile import mkdtemp
     24 
     25 sys.path.append(os.path.dirname(os.path.dirname(
     26         os.path.realpath(__file__))))
     27 
     28 from common.common import FatalError
     29 from common.common import GetEnvVariableOrError
     30 from common.common import GetJackClassPath
     31 from common.common import RetCode
     32 from common.common import RunCommand
     33 
     34 
     35 #
     36 # Tester class.
     37 #
     38 
     39 
     40 class DexFuzzTester(object):
     41   """Tester that feeds JFuzz programs into DexFuzz testing."""
     42 
     43   def  __init__(self, num_tests, num_inputs, device):
     44     """Constructor for the tester.
     45 
     46     Args:
     47       num_tests: int, number of tests to run
     48       num_inputs: int, number of JFuzz programs to generate
     49       device: string, target device serial number (or None)
     50     """
     51     self._num_tests = num_tests
     52     self._num_inputs = num_inputs
     53     self._device = device
     54     self._save_dir = None
     55     self._results_dir = None
     56     self._dexfuzz_dir = None
     57     self._inputs_dir = None
     58     self._dexfuzz_env = None
     59 
     60   def __enter__(self):
     61     """On entry, enters new temp directory after saving current directory.
     62 
     63     Raises:
     64       FatalError: error when temp directory cannot be constructed
     65     """
     66     self._save_dir = os.getcwd()
     67     self._results_dir = mkdtemp(dir='/tmp/')
     68     self._dexfuzz_dir = mkdtemp(dir=self._results_dir)
     69     self._inputs_dir = mkdtemp(dir=self._dexfuzz_dir)
     70     if self._results_dir is None or self._dexfuzz_dir is None or \
     71         self._inputs_dir is None:
     72       raise FatalError('Cannot obtain temp directory')
     73     self._dexfuzz_env = os.environ.copy()
     74     self._dexfuzz_env['ANDROID_DATA'] = self._dexfuzz_dir
     75     top = GetEnvVariableOrError('ANDROID_BUILD_TOP')
     76     self._dexfuzz_env['PATH'] = (top + '/art/tools/bisection_search:' +
     77                                  self._dexfuzz_env['PATH'])
     78     android_root = GetEnvVariableOrError('ANDROID_HOST_OUT')
     79     self._dexfuzz_env['ANDROID_ROOT'] = android_root
     80     self._dexfuzz_env['LD_LIBRARY_PATH'] = android_root + '/lib'
     81     os.chdir(self._dexfuzz_dir)
     82     os.mkdir('divergent_programs')
     83     os.mkdir('bisection_outputs')
     84     return self
     85 
     86   def __exit__(self, etype, evalue, etraceback):
     87     """On exit, re-enters previously saved current directory and cleans up."""
     88     os.chdir(self._save_dir)
     89     # TODO: detect divergences or shutil.rmtree(self._results_dir)
     90 
     91   def Run(self):
     92     """Feeds JFuzz programs into DexFuzz testing."""
     93     print()
     94     print('**\n**** J/DexFuzz Testing\n**')
     95     print()
     96     print('#Tests    :', self._num_tests)
     97     print('Device    :', self._device)
     98     print('Directory :', self._results_dir)
     99     print()
    100     self.GenerateJFuzzPrograms()
    101     self.RunDexFuzz()
    102 
    103 
    104   def GenerateJFuzzPrograms(self):
    105     """Generates JFuzz programs.
    106 
    107     Raises:
    108       FatalError: error when generation fails
    109     """
    110     os.chdir(self._inputs_dir)
    111     for i in range(1, self._num_inputs + 1):
    112       jack_args = ['-cp', GetJackClassPath(), '--output-dex', '.', 'Test.java']
    113       if RunCommand(['jfuzz'], out='Test.java', err=None) != RetCode.SUCCESS:
    114         print('Unexpected error while running JFuzz')
    115         raise FatalError('Unexpected error while running JFuzz')
    116       if RunCommand(['jack'] + jack_args, out=None, err='jackerr.txt',
    117                     timeout=30) != RetCode.SUCCESS:
    118         print('Unexpected error while running Jack')
    119         raise FatalError('Unexpected error while running Jack')
    120       shutil.move('Test.java', '../Test' + str(i) + '.java')
    121       shutil.move('classes.dex', 'classes' + str(i) + '.dex')
    122     os.unlink('jackerr.txt')
    123 
    124   def RunDexFuzz(self):
    125     """Starts the DexFuzz testing."""
    126     os.chdir(self._dexfuzz_dir)
    127     dexfuzz_args = ['--inputs=' + self._inputs_dir,
    128                     '--execute',
    129                     '--execute-class=Test',
    130                     '--repeat=' + str(self._num_tests),
    131                     '--quiet',
    132                     '--dump-output',
    133                     '--dump-verify',
    134                     '--interpreter',
    135                     '--optimizing',
    136                     '--bisection-search']
    137     if self._device is not None:
    138       dexfuzz_args += ['--device=' + self._device, '--allarm']
    139     else:
    140       dexfuzz_args += ['--host']  # Assume host otherwise.
    141     cmd = ['dexfuzz'] + dexfuzz_args
    142     print('**** Running ****\n\n', cmd, '\n')
    143     call(cmd, env=self._dexfuzz_env)
    144     print('\n**** Results (report.log) ****\n')
    145     call(['tail', '-n 24', 'report.log'])
    146 
    147 
    148 def main():
    149   # Handle arguments.
    150   parser = argparse.ArgumentParser()
    151   parser.add_argument('--num_tests', default=1000,
    152                       type=int, help='number of tests to run')
    153   parser.add_argument('--num_inputs', default=10,
    154                       type=int, help='number of JFuzz program to generate')
    155   parser.add_argument('--device', help='target device serial number')
    156   args = parser.parse_args()
    157   # Run the DexFuzz tester.
    158   with DexFuzzTester(args.num_tests, args.num_inputs, args.device) as fuzzer:
    159     fuzzer.Run()
    160 
    161 if __name__ == '__main__':
    162   main()
    163