• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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
17import argparse
18import os
19import shutil
20import sys
21
22from glob import glob
23from subprocess import call
24from tempfile import mkdtemp
25
26sys.path.append(os.path.dirname(os.path.dirname(
27        os.path.realpath(__file__))))
28
29from common.common import FatalError
30from common.common import GetEnvVariableOrError
31from common.common import GetJackClassPath
32from common.common import RetCode
33from common.common import RunCommand
34
35
36#
37# Tester class.
38#
39
40
41class 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
182def 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
202if __name__ == '__main__':
203  main()
204