OSDN Git Service

implemented wrapper script to replace calls to calloc()
authorThomas Lively <tlively@google.com>
Thu, 14 Jul 2016 21:29:59 +0000 (14:29 -0700)
committerThomas Lively <tlively@google.com>
Thu, 14 Jul 2016 21:29:59 +0000 (14:29 -0700)
BUG=https://bugs.chromium.org/p/nativeclient/issues/detail?id=4374
R=kschimpf@google.com, stichnot@chromium.org

Review URL: https://codereview.chromium.org/2145213002 .

docs/ASAN.rst
pydir/sz-clang++.py [new file with mode: 0755]
pydir/sz-clang.py [new file with mode: 0755]
pydir/sz_clang_dummies.c [new file with mode: 0644]
pydir/sz_driver.py [new file with mode: 0644]
tests_lit/asan_tests/Input/calloc.c [new file with mode: 0644]
tests_lit/asan_tests/Input/calloc_err.c [new file with mode: 0644]
tests_lit/asan_tests/calloc.ll [new file with mode: 0644]
tests_lit/asan_tests/calloc_err.ll [new file with mode: 0644]

index d815817..c51c905 100644 (file)
@@ -10,14 +10,21 @@ used in production.
 
 In Subzero, AddressSanitizer depends on being able to find and instrument calls
 to various functions such as malloc() and free(), and as such the .pexe file
-being translated must not have had those symbols stripped. Subzero will not
-complain if it is told to translate a .pexe file with its symbols stripped, but
-it will not be able to find calls to malloc() and free(), so AddressSanitizer
-will not work correctly in the final executable.
+being translated must not have had those symbols stripped or inlined. Subzero
+will not complain if it is told to translate a .pexe file with its symbols
+stripped, but it will not be able to find calls to malloc(), calloc(), free(),
+etc., so AddressSanitizer will not work correctly in the final executable.
+
+Furthermore, pnacl-clang automatically inlines some calls to calloc(),
+even with inlining turned off, so we provide wrapper scripts,
+sz-clang.py and sz-clang++.py, that normally just pass their arguments
+through to pnacl-clang or pnacl-clang++, but add instrumentation to
+replace calls to calloc() at the source level if they are passed
+-fsanitize-address.
 
 These are the steps to compile hello.c to an instrumented object file::
 
-    pnacl-clang -o hello.nonfinal.pexe hello.c
+    sz-clang.py -fsanitize-address -o hello.nonfinal.pexe hello.c
     pnacl-finalize --no-strip-syms -o hello.pexe hello.nonfinal.pexe
     pnacl-sz -fsanitize-address -filetype=obj -o hello.o hello.pexe
 
diff --git a/pydir/sz-clang++.py b/pydir/sz-clang++.py
new file mode 100755 (executable)
index 0000000..121dd5a
--- /dev/null
@@ -0,0 +1,6 @@
+#!/usr/bin/env python2
+
+import sz_driver
+
+if __name__ == '__main__':
+  sz_driver.run(is_cpp=True)
diff --git a/pydir/sz-clang.py b/pydir/sz-clang.py
new file mode 100755 (executable)
index 0000000..7b93ea0
--- /dev/null
@@ -0,0 +1,6 @@
+#!/usr/bin/env python2
+
+import sz_driver
+
+if __name__ == '__main__':
+  sz_driver.run(is_cpp=False)
diff --git a/pydir/sz_clang_dummies.c b/pydir/sz_clang_dummies.c
new file mode 100644 (file)
index 0000000..d0abc94
--- /dev/null
@@ -0,0 +1,13 @@
+#include <stdlib.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+void *__asan_dummy_calloc(size_t nmemb, size_t size) {
+  return calloc(nmemb, size);
+}
+
+#ifdef __cplusplus
+} // extern "C"
+#endif
diff --git a/pydir/sz_driver.py b/pydir/sz_driver.py
new file mode 100644 (file)
index 0000000..f27d678
--- /dev/null
@@ -0,0 +1,88 @@
+import os
+import shutil
+import subprocess
+import sys
+import tempfile
+
+from utils import FindBaseNaCl, shellcmd
+
+def subsToMacros(subs, src):
+    macros = ['#include <stddef.h>',
+              '#ifdef __cplusplus',
+              'extern "C" {',
+              '#endif']
+    for func in subs:
+        args = [('{atype} a{num}').format(atype=atype, num=i) for
+                i, atype in enumerate(subs[func]['sig'][1:])]
+        macros.append((
+            '{ftype} {name}({args});'
+            ).format(ftype=subs[func]['sig'][0],
+                     name=subs[func]['sub'],
+                     args=', '.join(args)))
+        macros.append((
+            '#define {func}(args...) ({sub}(args))'
+            ).format(func=func, sub=subs[func]['sub']))
+    macros += ['#ifdef __cplusplus',
+               '} // extern "C"',
+               '#endif',
+               '#line 1 "{src}"'.format(src=src)]
+    return '\n'.join(macros) + '\n'
+
+def run(is_cpp):
+    """Passes its arguments directly to pnacl-clang.
+
+    If -fsanitize-address is specified, extra information is passed to
+    pnacl-clang to ensure that later instrumentation in pnacl-sz can be
+    performed. For example, clang automatically inlines many memory allocation
+    functions, so this script will redefine them at compile time to make sure
+    they can be correctly instrumented by pnacl-sz.
+    """
+    pnacl_root = FindBaseNaCl()
+    dummy_subs = {'calloc': {'sig': ['void *', 'size_t', 'size_t'],
+                             'sub': '__asan_dummy_calloc'},
+                  '_calloc': {'sig': ['void *', 'size_t', 'size_t'],
+                              'sub': '__asan_dummy_calloc'}}
+    subs_src = (
+        '{root}/toolchain_build/src/subzero/pydir/sz_clang_dummies.c'
+        ).format(root=pnacl_root)
+    clang = (
+        '{root}/toolchain/linux_x86/pnacl_newlib_raw/bin/pnacl-clang{pp}'
+        ).format(root=pnacl_root, pp='++' if is_cpp else '')
+    args = sys.argv
+    args[0] = clang
+    tmp_dir = ''
+    if '-fsanitize-address' in args:
+        args.remove('-fsanitize-address')
+        include_dirs = set()
+        tmp_dir = tempfile.mkdtemp()
+        for i, arg in enumerate(args[1:], 1):
+            if not os.path.isfile(arg):
+                continue
+            src = os.path.basename(arg)
+            ext = os.path.splitext(arg)[1]
+            if ext in ['.c', '.cc', '.cpp']:
+                include_dirs |= {os.path.dirname(arg)}
+                dest_name = os.path.join(tmp_dir, src)
+                with open(dest_name, 'w') as dest:
+                    dest.write(subsToMacros(dummy_subs, arg))
+                    with open(arg) as src:
+                        for line in src:
+                            dest.write(line)
+                args[i] = dest_name
+        # If linking (not single file compilation) then add dummy definitions
+        if not ('-o' in args and
+                ('-c' in args or '-S' in args or '-E' in args)):
+            args.append(subs_src)
+        for d in include_dirs:
+            args.append('-iquote {d}'.format(d=d))
+        if '-fno-inline' not in args:
+            args.append('-fno-inline')
+    err_code = 0
+    try:
+        shellcmd(args, echo=True)
+    except subprocess.CalledProcessError as e:
+        print e.output
+        err_code = e.returncode
+    if tmp_dir != '':
+        shutil.rmtree(tmp_dir)
+    exit(err_code)
diff --git a/tests_lit/asan_tests/Input/calloc.c b/tests_lit/asan_tests/Input/calloc.c
new file mode 100644 (file)
index 0000000..16dd6d7
--- /dev/null
@@ -0,0 +1,10 @@
+#include <stdlib.h>
+#include <string.h>
+#include <stdio.h>
+
+int main(void) {
+  void *buf = calloc(14, sizeof(int));
+  strcpy(buf, "Hello, world!");
+  printf("%s\n", buf);
+  free(buf);
+}
diff --git a/tests_lit/asan_tests/Input/calloc_err.c b/tests_lit/asan_tests/Input/calloc_err.c
new file mode 100644 (file)
index 0000000..3dbf902
--- /dev/null
@@ -0,0 +1 @@
+int main(void) { not_defined(); }
diff --git a/tests_lit/asan_tests/calloc.ll b/tests_lit/asan_tests/calloc.ll
new file mode 100644 (file)
index 0000000..a6d7c4c
--- /dev/null
@@ -0,0 +1,14 @@
+; Test that sz-clang.py and sz-clang++.py successfully replace calls to calloc
+
+; RUN: %S/../../pydir/sz-clang.py -fsanitize-address %S/Input/calloc.c -E \
+; RUN:     | FileCheck %s
+
+; RUN: %S/../../pydir/sz-clang++.py -fsanitize-address %S/Input/calloc.c -E \
+; RUN:     | FileCheck %s
+
+; CHECK-LABEL: int main(void) {
+; CHECK-NEXT:    void *buf = (__asan_dummy_calloc(14, sizeof(int)));
+; CHECK-NEXT:    strcpy(buf, "Hello, world!");
+; CHECK-NEXT:    printf("%s\n", buf);
+; CHECK-NEXT:    free(buf);
+; CHECK-NEXT:  }
diff --git a/tests_lit/asan_tests/calloc_err.ll b/tests_lit/asan_tests/calloc_err.ll
new file mode 100644 (file)
index 0000000..f492dbb
--- /dev/null
@@ -0,0 +1,11 @@
+; Test that errors in source files are transparently reported by
+; sz-clang.py and sz-clang++.py
+
+; RUN: not %S/../../pydir/sz-clang.py -fsanitize-address %S/Input/calloc_err.c \
+; RUN:     2>&1 | FileCheck %s
+
+; RUN: not %S/../../pydir/sz-clang\+\+.py -fsanitize-address \
+; RUN:     %S/Input/calloc_err.c 2>&1 | FileCheck %s
+
+; CHECK-LABEL: Input/calloc_err.c:1:18: warning:
+; CHECK-SAME: implicit declaration of function 'not_defined' is invalid in C99