Merge "Add static code analyzer NDK_ANALYZE=1"
diff --git a/build/core/ndk-common.sh b/build/core/ndk-common.sh
index 77cca66..e8c2389 100644
--- a/build/core/ndk-common.sh
+++ b/build/core/ndk-common.sh
@@ -204,6 +204,13 @@
     fi
 }
 
+fail_warning ()
+{
+    if [ $? != 0 ] ; then
+        dump "WARNING: $@"
+    fi
+}
+
 
 ## Utilities
 ##
diff --git a/build/tools/build-llvm.sh b/build/tools/build-llvm.sh
index 9057747..9f64a3b 100755
--- a/build/tools/build-llvm.sh
+++ b/build/tools/build-llvm.sh
@@ -222,18 +222,12 @@
     dump "Running  : llvm toolchain regression test"
     cd $LLVM_BUILD_OUT
     run make check-all
-    if [ $? != 0 ] ; then
-        dump "ERROR: Couldn't pass all llvm regression test"
-        #exit 1
-    fi
+    fail_warning "Couldn't pass all llvm regression test"  # change to fail_panic later
     if [ "$POLLY" = "yes" ]; then
         dump "Running  : polly toolchain regression test"
         cd $LLVM_BUILD_OUT
         run make polly-test -C tools/polly/test
-        if [ $? != 0 ] ; then
-            dump "ERROR: Couldn't pass all polly regression test"
-            #exit 1
-        fi
+        fail_warning "Couldn't pass all polly regression test"  # change to fail_panic later
     fi
 fi
 
diff --git a/ndk-gdb-py b/ndk-gdb-py
new file mode 100755
index 0000000..f2a4d94
--- /dev/null
+++ b/ndk-gdb-py
@@ -0,0 +1,17 @@
+#!/bin/sh
+
+PROGDIR=`dirname $0`
+PROGDIR=`cd $PROGDIR && pwd`
+
+# Check if absolute NDK path contain space
+#
+case $PROGDIR in
+    *\ *) echo "ERROR: NDK path cannot contain space"
+          exit 1
+        ;;
+esac
+
+. $PROGDIR/build/core/ndk-common.sh
+
+# -u means unbuffered standard io.
+$PROGDIR/prebuilt/$HOST_TAG/bin/python -u $PROGDIR/ndk-gdb.py "$@"
diff --git a/ndk-gdb-py.cmd b/ndk-gdb-py.cmd
new file mode 100644
index 0000000..82a99f2
--- /dev/null
+++ b/ndk-gdb-py.cmd
@@ -0,0 +1,17 @@
+@echo off

+rem This is a Windows cmd.exe script used to invoke the NDK-specific Python executable

+set NDK_ROOT=%~dp0

+set NDK_MAKE=%NDK_ROOT%/prebuilt/windows/bin/make.exe

+

+rem Check if %NDK_ROOT% contains space

+goto :L

+:FOO

+  if "%2"=="" goto:EOF

+  echo ERROR: NDK path cannot contain space!

+  exit /b 1

+

+:L

+call :FOO %NDK_ROOT%

+if ERRORLEVEL 1 exit /b 1

+

+%NDK_ROOT%\prebuilt\windows\bin\python.exe -u %NDK_ROOT%ndk-gdb.py %*

diff --git a/ndk-gdb.py b/ndk-gdb.py
new file mode 100755
index 0000000..9cd3794
--- /dev/null
+++ b/ndk-gdb.py
@@ -0,0 +1,692 @@
+#!/bin/env python
+
+r'''
+ Copyright (C) 2010 The Android Open Source Project
+ Copyright (C) 2012 Ray Donnelly <mingw.android@gmail.com>
+
+ 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.
+
+
+ This wrapper script is used to launch a native debugging session
+ on a given NDK application. The application must be debuggable, i.e.
+ its android:debuggable attribute must be set to 'true' in the
+ <application> element of its manifest.
+
+ See docs/NDK-GDB.TXT for usage description. Essentially, you just
+ need to launch ndk-gdb-py from your application project directory
+ after doing ndk-build && ant debug && \
+  adb install && <start-application-on-device>
+'''
+
+import sys, os, argparse, subprocess, types
+import xml.etree.cElementTree as ElementTree
+import shutil
+from threading import Thread
+try:
+    from Queue import Queue, Empty
+except ImportError:
+    from queue import Queue, Empty  # python 3.x
+
+def find_program(program, extra_paths = []):
+    ''' extra_paths are searched before PATH '''
+    PATHS = extra_paths+os.environ['PATH'].split(os.pathsep)
+    exts = ['']
+    if sys.platform.startswith('win'):
+        exts += ['.exe', '.bat', '.cmd']
+    for path in PATHS:
+        if os.path.isdir(path):
+            for ext in exts:
+                full = path + os.sep + program + ext
+                if os.path.isfile(full):
+                    return True, full
+    return False, None
+
+# Return the prebuilt bin path for the host os.
+def ndk_bin_path(ndk):
+    if sys.platform.startswith('linux'):
+        return ndk+os.sep+'prebuilt/linux-x86/bin'
+    elif sys.platform.startswith('darwin'):
+        return ndk+os.sep+'prebuilt/darwin-x86/bin'
+    elif sys.platform.startswith('win'):
+        return ndk+os.sep+'prebuilt/windows/bin'
+    return ndk+os.sep+'prebuilt/UNKNOWN/bin'
+
+VERBOSE = False
+PROJECT = None
+PYTHON_CMD = None
+ADB_CMD = None
+GNUMAKE_CMD = None
+
+OPTION_FORCE = None
+OPTION_EXEC = None
+OPTION_START = None
+OPTION_LAUNCH = None
+OPTION_LAUNCH_LIST = None
+OPTION_TUI = None
+
+
+DEBUG_PORT = 5039
+
+# Name of the manifest file
+MANIFEST = 'AndroidManifest.xml'
+
+# Delay in seconds between launching the activity and attaching gdbserver on it.
+# This is needed because there is no way to know when the activity has really
+# started, and sometimes this takes a few seconds.
+#
+DELAY = 2.0
+NDK = os.path.abspath(os.path.dirname(sys.argv[0]))
+DEVICE_SERIAL = ''
+ADB_FLAGS = ''
+
+def log(string):
+    global VERBOSE
+    if VERBOSE:
+        print(string)
+
+def error(string, errcode=1):
+    print('ERROR: %s' % (string))
+    exit(errcode)
+
+def handle_args():
+    global VERBOSE, DEBUG_PORT, DELAY, DEVICE_SERIAL
+    global PYTHON_CMD, GNUMAKE_CMD, ADB_CMD, ADB_FLAGS
+    global PROJECT, NDK
+    global OPTION_START, OPTION_LAUNCH, OPTION_LAUNCH_LIST
+    global OPTION_FORCE, OPTION_EXEC, OPTION_TUI
+
+    parser = argparse.ArgumentParser(description='''
+    Setup a gdb debugging session for your Android NDK application.
+    Read ''' + NDK + '''/docs/NDK-GDB.html for complete usage instructions.''')
+
+    parser.add_argument( '--verbose',
+                         help='Enable verbose mode', action='store_true', dest='verbose')
+
+    parser.add_argument( '--force',
+                         help='Kill existing debug session if it exists',
+                         action='store_true')
+
+    parser.add_argument( '--start',
+                         help='Launch application instead of attaching to existing one',
+                         action='store_true')
+
+    parser.add_argument( '--launch',
+                         help='Same as --start, but specify activity name (see below)',
+                         dest='launch_name', nargs=1)
+
+    parser.add_argument( '--launch-list',
+                         help='List all launchable activity names from manifest',
+                         action='store_true')
+
+    parser.add_argument( '--delay',
+                         help='Delay in seconds between activity start and gdbserver attach',
+                         type=float, default=DELAY,
+                         dest='delay')
+
+    parser.add_argument( '-p', '--project',
+                         help='Specify application project path',
+                         dest='project')
+
+    parser.add_argument( '--port',
+                         help='Use tcp:localhost:<DEBUG_PORT> to communicate with gdbserver',
+                         type=int, default=DEBUG_PORT,
+                         dest='debug_port')
+
+    parser.add_argument( '-x', '--exec',
+                         help='Execute gdb initialization commands in <EXEC_FILE> after connection',
+                         dest='exec_file')
+
+    parser.add_argument( '--adb',
+                         help='Use specific adb command',
+                         dest='adb_cmd')
+
+    parser.add_argument( '--awk',
+                         help='Use specific awk command (unused flag retained for compatability)')
+
+    parser.add_argument( '-e',
+                         help='Connect to single emulator instance....(either this,)',
+                         action='store_true', dest='emulator')
+
+    parser.add_argument( '-d',
+                         help='Connect to single target device........(this,)',
+                         action='store_true', dest='device')
+
+    parser.add_argument( '-s',
+                         help='Connect to specific emulator or device.(or this)',
+                         default=DEVICE_SERIAL,
+                         dest='device_serial')
+
+    parser.add_argument( '-t','--tui',
+                         help='Use tui mode',
+                         action='store_true', dest='tui')
+
+    args = parser.parse_args()
+
+    VERBOSE = args.verbose
+
+    ndk_bin = ndk_bin_path(NDK)
+    (found_python,  PYTHON_CMD)  = find_program('python', [ndk_bin])
+    (found_adb,     ADB_CMD)     = find_program('adb',    [ndk_bin])
+    (found_gnumake, GNUMAKE_CMD) = find_program('make',   [ndk_bin])
+
+    if not found_gnumake:
+        error('Failed to find GNU make')
+
+    log('Android NDK installation path: %s' % (NDK))
+
+    if args.device:
+        ADB_FLAGS = '-d'
+    if args.emulator:
+        if ADB_FLAGS != '':
+            parser.print_help()
+            exit(1)
+        ADB_FLAGS = '-e'
+    if args.device_serial != '':
+        DEVICE_SERIAL = args.device_serial
+        if ADB_FLAGS != '':
+            parser.print_help()
+            exit(1)
+        ADB_FLAGS = '-s'
+    if args.adb_cmd != None:
+        log('Using specific adb command: %s' % (args.adb_cmd))
+        ADB_CMD = args.adb_cmd
+    if ADB_CMD is None:
+        error('''The 'adb' tool is not in your path.
+       You can change your PATH variable, or use
+       --adb=<executable> to point to a valid one.''')
+    if not os.path.isfile(ADB_CMD):
+        error('Could not run ADB with: %s' % (ADB_CMD))
+
+    if args.project != None:
+        PROJECT = args.project
+
+    if args.start != None:
+        OPTION_START = args.start
+
+    if args.launch_name != None:
+        OPTION_LAUNCH = args.launch_name
+
+    if args.launch_list != None:
+        OPTION_LAUNCH_LIST = args.launch_list
+
+    if args.force != None:
+        OPTION_FORCE = args.force
+
+    if args.exec_file != None:
+        OPTION_EXEC = args.exec_file
+
+    if args.tui != False:
+        OPTION_TUI = True
+
+    if args.delay != None:
+        DELAY = args.delay
+
+def get_build_var(var):
+    global GNUMAKE_CMD, NDK, PROJECT
+    text = subprocess.check_output([GNUMAKE_CMD,
+                                  '--no-print-dir',
+                                  '-f',
+                                  NDK+'/build/core/build-local.mk',
+                                  '-C',
+                                  PROJECT,
+                                  'DUMP_'+var]
+                                  )
+    # replace('\r', '') due to Windows crlf (\r\n)
+    #  ...universal_newlines=True causes bytes to be returned
+    #     rather than a str
+    return text.decode('ascii').replace('\r', '').splitlines()[0]
+
+def get_build_var_for_abi(var, abi):
+    global GNUMAKE_CMD, NDK, PROJECT
+    text = subprocess.check_output([GNUMAKE_CMD,
+                                   '--no-print-dir',
+                                   '-f',
+                                   NDK+'/build/core/build-local.mk',
+                                   '-C',
+                                   PROJECT,
+                                   'DUMP_'+var,
+                                   'APP_ABI='+abi],
+                                   )
+    return text.decode('ascii').replace('\r', '').splitlines()[0]
+
+# Silent if gdb is running in tui mode to keep things tidy.
+def output_gdbserver(text):
+    if not OPTION_TUI or OPTION_TUI != 'running':
+        print(text)
+
+def background_spawn(args, redirect_stderr, output_fn):
+
+    def async_stdout(out, queue, output_fn):
+        for line in iter(out.readline, b''):
+            output_fn(line.replace('\r', '').replace('\n', ''))
+        out.close()
+
+    def async_stderr(out, queue, output_fn):
+        for line in iter(out.readline, b''):
+            output_fn(line.replace('\r', '').replace('\n', ''))
+        out.close()
+
+    if redirect_stderr:
+        used_stderr = subprocess.PIPE
+    else:
+        used_stderr = subprocess.STDOUT
+    p = subprocess.Popen(args, stdout=subprocess.PIPE, stderr=used_stderr,
+                         bufsize=1, close_fds='posix' in sys.builtin_module_names)
+    qo = Queue()
+    to = Thread(target=async_stdout, args=(p.stdout, qo, output_fn))
+    to.daemon = True
+    to.start()
+    if redirect_stderr:
+        te = Thread(target=async_stderr, args=(p.stderr, qo, output_fn))
+        te.daemon = True
+        te.start()
+
+def adb_cmd(redirect_stderr, args, log_command=False, adb_trace=False, background=False):
+    global ADB_CMD, ADB_FLAGS, DEVICE_SERIAL
+    fullargs = [ADB_CMD]
+    if ADB_FLAGS != '':
+        fullargs += [ADB_FLAGS]
+    if DEVICE_SERIAL != '':
+        fullargs += [DEVICE_SERIAL]
+    if isinstance(args, str):
+        fullargs.append(args)
+    else:
+        fullargs += [arg for arg in args]
+    new_env = os.environ.copy()
+    retval = 0
+    if adb_trace:
+        new_env["ADB_TRACE"] = "1"
+    if background:
+        if log_command:
+            log('## COMMAND: adb_cmd %s [BACKGROUND]' % (' '.join(args)))
+        background_spawn(fullargs, redirect_stderr, output_gdbserver)
+        return 0, ''
+    else:
+        if log_command:
+            log('## COMMAND: adb_cmd %s' % (' '.join(args)))
+        try:
+            if redirect_stderr:
+                text = subprocess.check_output(fullargs,
+                                               stderr=subprocess.STDOUT,
+                                               env=new_env
+                                               )
+            else:
+                text = subprocess.check_output(fullargs,
+                                               env=new_env
+                                               )
+        except subprocess.CalledProcessError as e:
+            retval = e.returncode
+            text = e.output
+        # rstrip() because of final newline.
+        return retval, text.decode('ascii').replace('\r', '').rstrip()
+
+def _adb_var_shell(args, redirect_stderr=False, log_command=True):
+    if log_command:
+        log('## COMMAND: adb_cmd shell %s' % (' '.join(args)))
+    arg_str = str(' '.join(args)+' ; echo $?')
+    adb_ret,output = adb_cmd(redirect_stderr=redirect_stderr,
+                           args=['shell', arg_str], log_command=False)
+    output = output.splitlines()
+    retcode = int(output.pop())
+    return retcode,'\n'.join(output)
+
+def adb_var_shell(args, log_command=False):
+    return _adb_var_shell(args, redirect_stderr=False, log_command=log_command)
+
+def adb_var_shell2(args, log_command=False):
+    return _adb_var_shell(args, redirect_stderr=True, log_command=log_command)
+
+# Return the PID of a given package or program, or 0 if it doesn't run
+# $1: Package name ("com.example.hellojni") or program name ("/lib/gdbserver")
+# Out: PID number, or 0 if not running
+#
+def get_pid_of(package_name):
+    '''
+    Some custom ROMs use busybox instead of toolbox for ps.
+    Without -w, busybox truncates the output, and very long
+    package names like com.exampleisverylongtoolongbyfar.plasma
+    exceed the limit.
+    '''
+    ps_command = 'ps'
+    retcode,output = adb_cmd(False, ['shell', 'readlink $(which ps)'])
+    if output:
+        output = output.replace('\r', '').splitlines()[0]
+    if output == 'busybox':
+        ps_command = 'ps -w'
+    retcode,output = adb_cmd(False,['shell', ps_command])
+    output = output.replace('\r', '').splitlines()
+    columns = output.pop(0).split()
+    try:
+        PID_column = columns.index('PID')
+    except:
+        PID_column = 1
+    while output:
+        columns = output.pop().split()
+        if columns.pop() == package_name:
+            return 0,int(columns[PID_column])
+    return 1,0
+
+def extract_package_name(xmlfile):
+    '''
+    The name itself is the value of the 'package' attribute in the
+    'manifest' element.
+    '''
+    tree = ElementTree.ElementTree(file=xmlfile)
+    root = tree.getroot()
+    if 'package' in root.attrib:
+        return root.attrib['package']
+    return None
+
+def extract_debuggable(xmlfile):
+    '''
+    simply extract the 'android:debuggable' attribute value from
+    the first <manifest><application> element we find.
+    '''
+    tree = ElementTree.ElementTree(file=xmlfile)
+    root = tree.getroot()
+    for application in root.iter('application'):
+        for k in application.attrib.keys():
+            if str(k).endswith('debuggable'):
+                return application.attrib[k] == 'true'
+    return False
+
+def extract_launchable(xmlfile):
+    '''
+    A given application can have several activities, and each activity
+    can have several intent filters. We want to only list, in the final
+    output, the activities which have a intent-filter that contains the
+    following elements:
+
+      <action android:name="android.intent.action.MAIN" />
+      <category android:name="android.intent.category.LAUNCHER" />
+    '''
+    tree = ElementTree.ElementTree(file=xmlfile)
+    root = tree.getroot()
+    launchable_activities = []
+    for application in root.iter('application'):
+        for activity in application.iter('activity'):
+            for intent_filter in activity.iter('intent-filter'):
+                found_action_MAIN = False
+                found_category_LAUNCHER = False
+                for child in intent_filter:
+                    if child.tag == 'action':
+                        if True in [str(child.attrib[k]).endswith('MAIN') for k in child.attrib.keys()]:
+                            found_action_MAIN = True
+                    if child.tag == 'category':
+                        if True in [str(child.attrib[k]).endswith('LAUNCHER') for k in child.attrib.keys()]:
+                            found_category_LAUNCHER = True
+                if found_action_MAIN and found_category_LAUNCHER:
+                    names = [str(activity.attrib[k]) for k in activity.attrib.keys() if str(k).endswith('name')]
+                    for name in names:
+                        if name[0] != '.':
+                            name = '.'+name
+                        launchable_activities.append(name)
+    return launchable_activities
+
+def main():
+    global ADB_CMD, NDK, PROJECT
+    global OPTION_START, OPTION_LAUNCH, OPTION_LAUNCH_LIST
+    global OPTION_FORCE, OPTION_EXEC, OPTION_TUI
+
+    if NDK.find(' ')!=-1:
+        error('NDK path cannot contain space')
+    handle_args()
+    if OPTION_EXEC:
+        if not os.path.isfile(OPTION_EXEC):
+            error('Invalid initialization file: %s' % (OPTION_EXEC))
+    ADB_VERSION = subprocess.check_output([ADB_CMD, 'version'],
+                                        ).decode('ascii').replace('\r', '').splitlines()[0]
+    log('ADB version found: %s' % (ADB_VERSION))
+    if DEVICE_SERIAL == '':
+        log('Using ADB flags: %s' % (ADB_FLAGS))
+    else:
+        log('Using ADB flags: %s "%s"' % (ADB_FLAGS,DEVICE_SERIAL))
+
+    if PROJECT != None:
+        log('Using specified project path: %s' % (PROJECT))
+        if not os.path.isdir(PROJECT):
+            error('Your --project option does not point to a directory!')
+        if not os.path.isfile(PROJECT+os.sep+MANIFEST):
+            error('''Your --project does not point to an Android project path!
+       It is missing a %s file.''' % (MANIFEST))
+    else:
+        # Assume we are in the project directory
+        if os.path.isfile(MANIFEST):
+            PROJECT = '.'
+        else:
+            PROJECT = ''
+            CURDIR = os.getcwd()
+
+            while CURDIR != os.path.dirname(CURDIR):
+                if os.path.isfile(CURDIR+os.sep+MANIFEST):
+                    PROJECT=CURDIR
+                    break
+                CURDIR = os.path.dirname(CURDIR)
+
+            if not os.path.isdir(PROJECT):
+                error('Launch this script from an application project directory, or use --project=<path>.')
+        log('Using auto-detected project path: %s' % (PROJECT))
+
+    PACKAGE_NAME = extract_package_name(PROJECT+os.sep+MANIFEST)
+    if PACKAGE_NAME is None:
+        PACKAGE_NAME = '<none>'
+    log('Found package name: %s' % (PACKAGE_NAME))
+    if PACKAGE_NAME is '<none>':
+        error('''Could not extract package name from %s.
+       Please check that the file is well-formed!''' % (PROJECT+os.sep+MANIFEST))
+    if OPTION_LAUNCH_LIST:
+        log('Extracting list of launchable activities from manifest:')
+        print(' '.join(extract_launchable(PROJECT+os.sep+MANIFEST)))
+        exit(0)
+    APP_ABIS = get_build_var('APP_ABI').split(' ')
+    if 'all' in APP_ABIS:
+        ALL_ABIS = get_build_var('NDK_ALL_ABIS').split(' ')
+        APP_ABIS = APP_ABIS[:APP_ABIS.index('all')]+ALL_ABIS+APP_ABIS[APP_ABIS.index('all')+1:]
+    log('ABIs targetted by application: %s' % (' '.join(APP_ABIS)))
+
+    retcode,ADB_TEST = adb_cmd(True,['shell', 'ls'])
+    if retcode != 0:
+        print(ADB_TEST)
+        error('''Could not connect to device or emulator!
+       Please check that an emulator is running or a device is connected
+       through USB to this machine. You can use -e, -d and -s <serial>
+       in case of multiple ones.''')
+
+    retcode,API_LEVEL = adb_var_shell(['getprop', 'ro.build.version.sdk'])
+    if retcode != 0 or API_LEVEL == '':
+        error('''Could not find target device's supported API level!
+ndk-gdb will only work if your device is running Android 2.2 or higher.''')
+    API_LEVEL = int(API_LEVEL)
+    log('Device API Level: %d' % (API_LEVEL))
+    if API_LEVEL < 8:
+        error('''ndk-gdb requires a target device running Android 2.2 (API level 8) or higher.
+The target device is running API level %d!''' % (API_LEVEL))
+    COMPAT_ABI = []
+    _,CPU_ABI1 = adb_var_shell(['getprop', 'ro.product.cpu.abi'])
+    _,CPU_ABI2 = adb_var_shell(['getprop', 'ro.product.cpu.abi2'])
+    # Both CPU_ABI1 and CPU_ABI2 may contain multiple comma-delimited abis.
+    # Concatanate CPU_ABI1 and CPU_ABI2.
+    CPU_ABIS = CPU_ABI1.split(',')+CPU_ABI2.split(',')
+    log('Device CPU ABIs: %s' % (' '.join(CPU_ABIS)))
+    COMPAT_ABI = [ABI for ABI in CPU_ABIS if ABI in APP_ABIS]
+
+    if not len(COMPAT_ABI):
+        error('''The device does not support the application's targetted CPU ABIs!
+       Device supports:  %s
+       Package supports: %s''' % (' '.join(CPU_ABIS),' '.join(APP_ABIS)))
+    COMPAT_ABI = COMPAT_ABI[0]
+    log('Compatible device ABI: %s' % (COMPAT_ABI))
+    GDBSETUP_INIT = get_build_var_for_abi('NDK_APP_GDBSETUP', COMPAT_ABI)
+    log('Using gdb setup init: %s' % (GDBSETUP_INIT))
+
+    TOOLCHAIN_PREFIX = get_build_var_for_abi('TOOLCHAIN_PREFIX', COMPAT_ABI)
+    log('Using toolchain prefix: %s' % (TOOLCHAIN_PREFIX))
+
+    APP_OUT = get_build_var_for_abi('TARGET_OUT', COMPAT_ABI)
+    log('Using app out directory: %s' % (APP_OUT))
+    DEBUGGABLE = extract_debuggable(PROJECT+os.sep+MANIFEST)
+    log('Found debuggable flag: %s' % ('true' if DEBUGGABLE==True else 'false'))
+    # If gdbserver exists, then we built with 'ndk-build NDK_DEBUG=1' and it's
+    # ok to not have android:debuggable set to true in the original manifest.
+    # However, if this is not the case, then complain!!
+    #
+    gdbserver_path = os.path.join(PROJECT,'libs',COMPAT_ABI,'gdbserver')
+    if not DEBUGGABLE:
+        if os.path.isfile(gdbserver_path):
+            log('Found gdbserver under libs/%s, assuming app was built with NDK_DEBUG=1' % (COMPAT_ABI))
+        else:
+            error('''Package %s is not debuggable ! You can fix that in two ways:
+
+  - Rebuilt with the NDK_DEBUG=1 option when calling 'ndk-build'.
+
+  - Modify your manifest to set android:debuggable attribute to "true",
+    then rebuild normally.
+
+After one of these, re-install to the device!''' % (PACKAGE_NAME))
+    elif not os.path.isfile(gdbserver_path):
+        error('''Could not find gdbserver binary under %s/libs/%s
+       This usually means you modified your AndroidManifest.xml to set
+       the android:debuggable flag to 'true' but did not rebuild the
+       native binaries. Please call 'ndk-build' to do so,
+       *then* re-install to the device!''' % (PROJECT,COMPAT_ABI))
+
+    # Let's check that 'gdbserver' is properly installed on the device too. If this
+    # is not the case, the user didn't install the proper package after rebuilding.
+    #
+    retcode,DEVICE_GDBSERVER = adb_var_shell2(['ls', '/data/data/%s/lib/gdbserver' % (PACKAGE_NAME)])
+    if retcode:
+        error('''Non-debuggable application installed on the target device.
+       Please re-install the debuggable version!''')
+    log('Found device gdbserver: %s' % (DEVICE_GDBSERVER))
+
+    # Find the <dataDir> of the package on the device
+    retcode,DATA_DIR = adb_var_shell2(['run-as', PACKAGE_NAME, '/system/bin/sh', '-c', 'pwd'])
+    if retcode or DATA_DIR == '':
+        error('''Could not extract package's data directory. Are you sure that
+       your installed application is debuggable?''')
+    log("Found data directory: '%s'" % (DATA_DIR))
+
+    # Launch the activity if needed
+    if OPTION_START:
+        if not OPTION_LAUNCH:
+            OPTION_LAUNCH = extract_launchable(PROJECT+os.sep+MANIFEST)
+            if not len(OPTION_LAUNCH):
+                error('''Could not extract name of launchable activity from manifest!
+           Try to use --launch=<name> directly instead as a work-around.''')
+            log('Found first launchable activity: %s' % (OPTION_LAUNCH[0]))
+        if not len(OPTION_LAUNCH):
+            error('''It seems that your Application does not have any launchable activity!
+       Please fix your manifest file and rebuild/re-install your application.''')
+
+    if len(OPTION_LAUNCH):
+        log('Launching activity: %s/%s' % (PACKAGE_NAME,OPTION_LAUNCH[0]))
+        retcode,LAUNCH_OUTPUT=adb_cmd(True,
+                                      ['shell', 'am', 'start', '-n', '%s/%s' % (PACKAGE_NAME,OPTION_LAUNCH[0])],
+                                      log_command=True)
+        if retcode:
+            error('''Could not launch specified activity: %s
+       Use --launch-list to dump a list of valid values.''' % (OPTION_LAUNCH[0]))
+
+        # Sleep a bit, it sometimes take one second to start properly
+        # Note that we use the 'sleep' command on the device here.
+        #
+        adb_cmd(True, ['shell', 'sleep', '%f' % (DELAY)], log_command=True)
+
+    # Find the PID of the application being run
+    retcode,PID = get_pid_of(PACKAGE_NAME)
+    log('Found running PID: %d' % (PID))
+    if retcode or PID == 0:
+        if len(OPTION_LAUNCH):
+            error('''Could not extract PID of application on device/emulator.
+       Weird, this probably means one of these:
+
+         - The installed package does not match your current manifest.
+         - The application process was terminated.
+
+       Try using the --verbose option and look at its output for details.''')
+        else:
+            error('''Could not extract PID of application on device/emulator.
+       Are you sure the application is already started?
+       Consider using --start or --launch=<name> if not.''')
+
+    # Check that there is no other instance of gdbserver running
+    retcode,GDBSERVER_PID = get_pid_of('lib/gdbserver')
+    if not retcode and not GDBSERVER_PID == 0:
+        if not OPTION_FORCE:
+            error('Another debug session running, Use --force to kill it.')
+        log('Killing existing debugging session')
+        adb_cmd(False, ['shell', 'kill -9 %s' % (GDBSERVER_PID)])
+
+    # Launch gdbserver now
+    DEBUG_SOCKET = 'debug-socket'
+    adb_cmd(False,
+            ['shell', 'run-as', PACKAGE_NAME, 'lib/gdbserver', '+%s' % (DEBUG_SOCKET), '--attach', str(PID)],
+            log_command=True, adb_trace=True, background=True)
+    log('Launched gdbserver succesfully')
+
+# Make sure gdbserver was launched - debug check.
+#    adb_var_shell(['sleep', '0.1'], log_command=False)
+#    retcode,GDBSERVER_PID = get_pid_of('lib/gdbserver')
+#    if retcode or GDBSERVER_PID == 0:
+#        error('Could not launch gdbserver on the device?')
+#    log('Launched gdbserver succesfully (PID=%s)' % (GDBSERVER_PID))
+
+    # Setup network redirection
+    log('Setup network redirection')
+    retcode,_ = adb_cmd(False,
+                        ['forward', 'tcp:%d' % (DEBUG_PORT), 'localfilesystem:%s/%s' % (DATA_DIR,DEBUG_SOCKET)],
+                        log_command=True)
+    if retcode:
+        error('''Could not setup network redirection to gdbserver?
+       Maybe using --port=<port> to use a different TCP port might help?''')
+
+    # Get the app_server binary from the device
+    APP_PROCESS = '%s/app_process' % (APP_OUT)
+    adb_cmd(False, ['pull', '/system/bin/app_process', APP_PROCESS], log_command=True)
+    log('Pulled app_process from device/emulator.')
+
+    adb_cmd(False, ['pull', '/system/bin/linker', '%s/linker' % (APP_OUT)], log_command=True)
+    log('Pulled linker from device/emulator.')
+
+    adb_cmd(False, ['pull', '/system/lib/libc.so', '%s/libc.so' % (APP_OUT)], log_command=True)
+    log('Pulled libc.so from device/emulator.')
+
+    # Now launch the appropriate gdb client with the right init commands
+    #
+    GDBCLIENT = '%sgdb' % (TOOLCHAIN_PREFIX)
+    GDBSETUP = '%s/gdb.setup' % (APP_OUT)
+    shutil.copyfile(GDBSETUP_INIT, GDBSETUP)
+    with open(GDBSETUP, "a") as gdbsetup:
+        #uncomment the following to debug the remote connection only
+        #echo "set debug remote 1" >> $GDBSETUP
+        gdbsetup.write('file '+APP_PROCESS+'\n')
+        gdbsetup.write('target remote :%d\n' % (DEBUG_PORT))
+        if OPTION_EXEC:
+            with open(OPTION_EXEC, 'r') as execfile:
+                for line in execfile.readline():
+                    gdbsetup.write(line)
+    gdbsetup.close()
+
+    gdbargs = [GDBCLIENT, '-x', '%s' % (GDBSETUP)]
+    if OPTION_TUI:
+        gdbhelp = subprocess.check_output([GDBCLIENT, '--help']).decode('ascii')
+        try:
+            gdbhelp.index('--tui')
+            gdbargs.append('--tui')
+            OPTION_TUI = 'running'
+        except:
+            print('Warning: Disabled tui mode as %s does not support it' % (os.path.basename(GDBCLIENT)))
+    subprocess.call(gdbargs)
+
+if __name__ == '__main__':
+    main()
diff --git a/tests/device/fenv/BROKEN_BUILD b/tests/device/fenv/BROKEN_BUILD
new file mode 100644
index 0000000..f0f76fb
--- /dev/null
+++ b/tests/device/fenv/BROKEN_BUILD
@@ -0,0 +1 @@
+4.4.3
\ No newline at end of file
diff --git a/tests/device/fenv/jni/Android.mk b/tests/device/fenv/jni/Android.mk
new file mode 100644
index 0000000..c6df9ed
--- /dev/null
+++ b/tests/device/fenv/jni/Android.mk
@@ -0,0 +1,7 @@
+LOCAL_PATH := $(call my-dir)
+
+include $(CLEAR_VARS)
+LOCAL_MODULE := test_fenv_c
+LOCAL_SRC_FILES := test_fenv.c
+LOCAL_LDFLAGS += -lm
+include $(BUILD_EXECUTABLE)
diff --git a/tests/device/fenv/jni/Application.mk b/tests/device/fenv/jni/Application.mk
new file mode 100644
index 0000000..4a17da3
--- /dev/null
+++ b/tests/device/fenv/jni/Application.mk
@@ -0,0 +1 @@
+APP_ABI := armeabi-v7a x86 mips
diff --git a/tests/device/fenv/jni/test_fenv.c b/tests/device/fenv/jni/test_fenv.c
new file mode 100644
index 0000000..4a27af3
--- /dev/null
+++ b/tests/device/fenv/jni/test_fenv.c
@@ -0,0 +1,137 @@
+/*
+ * Copyright (C) 2012 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.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <assert.h>
+#include <math.h>
+#include <fenv.h>
+
+#define ASSERT_TRUE(condition) \
+  (condition)? (void)0 : fail(__FILE__, __LINE__, __func__, #condition)
+
+#define ASSERT_EQ(x, y) \
+  ((x)==(y))? (void)0 : fail(__FILE__, __LINE__, __func__, #x "==" #y)
+
+#define ASSERT_FLOAT_EQ(x, y) \
+  float_eq(x, y)? (void)0 : fail(__FILE__, __LINE__, __func__, "float_eq(" #x "," #y ")")
+
+#define TEST(f, g) void g()
+
+int total_fail = 0;
+void fail(const char* file, int line, const char* func, const char* expr)
+{
+    printf("ERROR %s:%d %s: %s\n", file, line, func, expr);
+    total_fail++;
+}
+
+/* See AOSP external/gtest/include/gtest/internal/gtest-internal.h */
+const int kMaxUlps = 4;
+
+int float_eq(float x, float y) {
+    int32_t ix0, iy0, ix, iy;
+
+    if (isnanf(x) || isnanf(y))
+        return 0;
+
+    ix = ix0 = *(int32_t *)&x;
+    iy = iy0 = *(int32_t *)&y;
+    if (ix < 0) {
+        ix = -ix;
+        if (!(iy0 < 0))
+            return 0;
+    }
+    if (iy < 0) {
+        iy = -iy;
+        if (!(ix0 < 0))
+            return 0;
+    }
+    return abs(ix - iy) <= kMaxUlps;
+}
+
+/* See AOSP bionic/tests/fenv_test.cpp */
+
+static void TestRounding(float expectation1, float expectation2) {
+  // volatile to prevent compiler optimizations.
+  volatile float f = 1.968750f;
+  volatile float m = 0x1.0p23f;
+  volatile float x = f + m;
+  ASSERT_FLOAT_EQ(expectation1, x);
+  x -= m;
+  ASSERT_EQ(expectation2, x);
+}
+
+static void DivideByZero() {
+  // volatile to prevent compiler optimizations.
+  volatile float zero = 0.0f;
+  volatile float result __attribute__((unused)) = 123.0f / zero;
+}
+
+TEST(fenv, fesetround_fegetround_FE_TONEAREST) {
+  fesetround(FE_TONEAREST);
+  ASSERT_EQ(FE_TONEAREST, fegetround());
+  TestRounding(8388610.0f, 2.0f);
+}
+
+TEST(fenv, fesetround_fegetround_FE_TOWARDZERO) {
+  fesetround(FE_TOWARDZERO);
+  ASSERT_EQ(FE_TOWARDZERO, fegetround());
+  TestRounding(8388609.0f, 1.0f);
+}
+
+TEST(fenv, fesetround_fegetround_FE_UPWARD) {
+  fesetround(FE_UPWARD);
+  ASSERT_EQ(FE_UPWARD, fegetround());
+  TestRounding(8388610.0f, 2.0f);
+}
+
+TEST(fenv, fesetround_fegetround_FE_DOWNWARD) {
+  fesetround(FE_DOWNWARD);
+  ASSERT_EQ(FE_DOWNWARD, fegetround());
+  TestRounding(8388609.0f, 1.0f);
+}
+
+TEST(fenv, feclearexcept_fetestexcept) {
+  // Clearing clears.
+  feclearexcept(FE_ALL_EXCEPT);
+  ASSERT_EQ(0, fetestexcept(FE_ALL_EXCEPT));
+
+  // Dividing by zero sets FE_DIVBYZERO.
+  DivideByZero();
+  int raised = fetestexcept(FE_DIVBYZERO | FE_OVERFLOW);
+  ASSERT_TRUE((raised & FE_OVERFLOW) == 0);
+  ASSERT_TRUE((raised & FE_DIVBYZERO) != 0);
+
+  // Clearing an unset bit is a no-op.
+  feclearexcept(FE_OVERFLOW);
+  ASSERT_TRUE((raised & FE_OVERFLOW) == 0);
+  ASSERT_TRUE((raised & FE_DIVBYZERO) != 0);
+
+  // Clearing a set bit works.
+  feclearexcept(FE_DIVBYZERO);
+  ASSERT_EQ(0, fetestexcept(FE_ALL_EXCEPT));
+}
+
+int main()
+{
+    fesetround_fegetround_FE_TONEAREST();
+    fesetround_fegetround_FE_TOWARDZERO();
+    fesetround_fegetround_FE_UPWARD();
+    fesetround_fegetround_FE_DOWNWARD();
+    feclearexcept_fetestexcept();
+
+    return total_fail == 0 ? 0 : 1;
+}