Set LSAN_OPTIONS through environment 52/196352/8
authorVyacheslav Cherkashin <v.cherkashin@samsung.com>
Thu, 27 Dec 2018 10:52:29 +0000 (13:52 +0300)
committerVyacheslav Cherkashin <v.cherkashin@samsung.com>
Tue, 5 Feb 2019 11:10:04 +0000 (14:10 +0300)
 To configure the LSan library with needed options, we use a workaround:
before loading library, we change the environment (LSan library reads
these options during constructor execution), and after loading, we
restore the original environment.

Change-Id: I0de2652847e54a84c89f98ce4a9980853b8b3b84
Signed-off-by: Vyacheslav Cherkashin <v.cherkashin@samsung.com>
src/CMakeLists.txt
src/core/common.cpp [new file with mode: 0644]
src/core/common.h [new file with mode: 0644]
src/core/environ.cpp [new file with mode: 0644]
src/core/environ.h [new file with mode: 0644]
src/core/file.cpp [new file with mode: 0644]
src/core/file.h [new file with mode: 0644]
src/core/internal_libc.cpp
src/core/internal_libc.h
src/lsan/lsan_adapter.cpp

index a91f6c4..619c6ba 100644 (file)
@@ -15,6 +15,9 @@ include_directories(
 set(SRC
   core/log.cpp
   core/core.cpp
+  core/file.cpp
+  core/common.cpp
+  core/environ.cpp
   core/injector.cpp
   core/sys_calls.cpp
   core/internal_libc.cpp
diff --git a/src/core/common.cpp b/src/core/common.cpp
new file mode 100644 (file)
index 0000000..c7ef61d
--- /dev/null
@@ -0,0 +1,13 @@
+#include "common.h"
+#include "log.h"
+#include "sys_calls.h"
+
+namespace __swap {
+
+void die()
+{
+    LOGE("Process died");
+    sys_exit(-1);
+}
+
+} // namespace __swap
diff --git a/src/core/common.h b/src/core/common.h
new file mode 100644 (file)
index 0000000..bcf7136
--- /dev/null
@@ -0,0 +1,36 @@
+#ifndef COMMON_H
+#define COMMON_H
+
+namespace __swap {
+
+template <typename T, typename R>
+class AutoRelease {
+public:
+    AutoRelease(T &t, R &do_release) :
+        t_(t),
+        do_release_(do_release)
+    {
+    }
+
+    ~AutoRelease()
+    {
+        do_release_(t_);
+    }
+
+private:
+    T &t_;
+    R &do_release_;
+};
+
+template <typename T, typename R>
+AutoRelease<T, R> make_auto_release(T t, R r)
+{
+    return AutoRelease<T, R>(t, r);
+}
+
+
+void die();
+
+} // namespace __swap
+
+#endif // COMMON_H
diff --git a/src/core/environ.cpp b/src/core/environ.cpp
new file mode 100644 (file)
index 0000000..a2f7fbc
--- /dev/null
@@ -0,0 +1,133 @@
+#include "environ.h"
+#include "common.h"
+#include "internal_libc.h"
+#include "log.h"
+#include "file.h"
+
+
+namespace __swap {
+namespace environ {
+
+static const char *parse_dec_val_backward(const char *begin, const char *end, uptr &val)
+{
+    val = 0;
+    uptr multipler = 1;
+    for (const char *it = end; it != begin; --it) {
+        char ch = *(it - 1);
+        if (ch < '0' || ch > '9')
+            return it;
+
+        val += multipler * (ch - '0');
+        multipler *= 10;
+    }
+
+    return begin;
+}
+
+static const char *skip_symbol_backward(const char *it, const char ch)
+{
+    if (*(--it) != ch) {
+        LOGE("Incorrect symbol: 0x%x ch=0x%x", 0xff & *it, ch);
+        return nullptr;
+    }
+    return it;
+}
+
+static const char *parse_dec_array_backward(const char *begin, const char *end,
+                                            uptr array[], uptr array_size)
+{
+    const char *it = end;
+    for (uptr i = 0; i < array_size; ++i) {
+        const char *new_it = parse_dec_val_backward(begin, it, array[i]);
+        if (new_it == it) {
+            LOGE("Value not found");
+            return nullptr;
+        }
+        it = new_it;
+
+        if (it <= begin) {
+            LOGE("Incorrect buffer");
+            return nullptr;
+        }
+
+        // skip space
+        it = skip_symbol_backward(it, ' ');
+        if (it <= begin) {
+            LOGE("Incorrect buffer");
+            return nullptr;
+        }
+    }
+
+    return it;
+}
+
+// In "/proc/self/stat", the values of the variables are arranged in the following order:
+// "... env_start env_end error_code\n".
+// This function converts the required values (env_start, env_end) from
+// a string representation to a numeric representation.
+static bool parse_stat(const char *begin, const char *end, uptr &env_start, uptr &env_end)
+{
+    const char *it = end;
+    if (it <= begin) {
+        LOGE("Incorrect buffer");
+        return false;
+    }
+
+    // skip '\n'
+    it = skip_symbol_backward(it, '\n');
+    if (it <= begin) {
+        LOGE("Incorrect buffer");
+        return false;
+    }
+
+    const uptr array_size = 3;
+    uptr array[array_size];
+
+    it = parse_dec_array_backward(begin, it, array, array_size);
+    if (it == nullptr) {
+        LOGE("Cannot parse stat");
+        return false;
+    }
+
+    env_start = array[2];
+    env_end = array[1];
+    return true;
+}
+
+void get_context(uptr &start, uptr &end)
+{
+    // '/proc/self/stat' contains:
+    //   1  - int:      max 11 bytes)
+    //   1  - (string): max 18 bytes (2 parenthesis + TASK_COMM_LEN)
+    //   1  - char:     max 1 byte
+    //   49 - ull/ll:   max 20 bytes
+    //   51 -           max 1 byte (spaces between variables)
+    //
+    //   11 + 18 + 1 + 49 * 20 + 51 = 1061
+    // Allocate the buffer with the stock: 2 * 1024.
+    // This buffer size should be sufficient to read the entire file '/proc/self/stat'.
+    const uptr buf_size = 2 * 1024;
+    char *buf = (char *)internal::malloc(buf_size);
+    if (!buf)
+        die();
+    auto auto_free_buf = make_auto_release(buf, internal::free);
+
+    uptr read_size;
+    if (!fs::read_to_buffer("/proc/self/stat", buf, buf_size, read_size)) {
+        LOGE("Cannot read file: '/proc/self/stat'");
+        die();
+    }
+
+    if (!parse_stat(buf, buf + read_size, start, end)) {
+        LOGE("Cannot parse file: '/proc/self/stat'");
+        die();
+    }
+
+    if (start > end) {
+        LOGE("Incorrect environ info: env_start=%lx env_end=%lx", start, end);
+        die();
+    }
+}
+
+} // namespace environ
+} // namespace __swap
diff --git a/src/core/environ.h b/src/core/environ.h
new file mode 100644 (file)
index 0000000..4068a82
--- /dev/null
@@ -0,0 +1,14 @@
+#ifndef ENVIRON_H
+#define ENVIRON_H
+
+#include "internal_deps.h"
+
+namespace __swap {
+namespace environ {
+
+void get_context(uptr &start, uptr &end);
+
+} // namespace environ
+} // namespace __swap
+
+#endif // ENVIRON_H
diff --git a/src/core/file.cpp b/src/core/file.cpp
new file mode 100644 (file)
index 0000000..066a4d6
--- /dev/null
@@ -0,0 +1,45 @@
+#include "file.h"
+#include "log.h"
+#include "common.h"
+#include "sys_calls.h"
+#include <fcntl.h>          // for O_RDONLY
+
+
+namespace __swap {
+namespace fs {
+
+bool read_to_buffer(const char *path, char *buf, uptr buf_size, uptr &read_size)
+{
+    int err;
+    sptr fd = sys_open(path, O_RDONLY, 0400);
+    if (syscall_is_error(fd, &err)) {
+        LOGE("Cannot open file: %s, errno=%d", path, err);
+        return false;
+    }
+
+    auto auto_close_fd = make_auto_release(fd, [](sptr fd) {
+        int err;
+        sptr ret = sys_close(fd);
+        if (syscall_is_error(ret, &err)) {
+            LOGE("Cannot close file: fd=%ld errno=%d", fd, err);
+            die();
+        }
+    });
+
+    sptr count = sys_read(fd, buf, buf_size);
+    if (syscall_is_error(count, &err)) {
+        LOGE("Cannot read file: %s, errno=%d", path, err);
+        return false;
+    }
+
+    if ((uptr)count >= buf_size) {
+        LOGE("Cannot read all file, buffer is small, size");
+        return false;
+    }
+
+    read_size = count;
+    return true;
+}
+
+} // namespace fs
+} // namespace __swap
diff --git a/src/core/file.h b/src/core/file.h
new file mode 100644 (file)
index 0000000..93b073a
--- /dev/null
@@ -0,0 +1,14 @@
+#ifndef FILE_H
+#define FILE_H
+
+#include "internal_deps.h"
+
+namespace __swap {
+namespace fs {
+
+bool read_to_buffer(const char *path, char *buf, uptr buf_size, uptr &read_size);
+
+} // namespace fs
+} // namespace __swap
+
+#endif // FILE_H
index 1c09935..3f407d0 100644 (file)
@@ -42,6 +42,15 @@ void *memcpy(void *dest, const void *src, uptr n) noexcept
     return dest;
 }
 
+void *memset(void *s, int c, uptr n) noexcept
+{
+    char volatile *t = (char *)s;
+    for (uptr i = 0; i < n; ++i, ++t) {
+        *t = c;
+    }
+    return s;
+}
+
 int strcmp(const char *s1, const char *s2) noexcept
 {
     for (;; ++s1, ++s2) {
index 609d6ea..6160a1e 100644 (file)
@@ -14,6 +14,7 @@ void *realloc(void *ptr, uptr size);
 
 
 void *memcpy(void *dest, const void *src, uptr n) noexcept;
+void *memset(void *s, int c, uptr n) noexcept;
 int strcmp(const char *s1, const char *s2) noexcept;
 int mprotect(void *addr, uptr len, int prot) noexcept;
 uptr malloc_usable_size(void *ptr) noexcept;
index b30ac4d..517ff8b 100644 (file)
@@ -3,10 +3,22 @@
 #include <core/internal_deps.h>
 #include <core/internal_libc.h>
 #include <core/internal_libdl.h>
-
+#include <core/environ.h>
+#include <core/common.h>
 #include "lsan_adapter.h"
 
 
+static const char lsan_environ[] = "LSAN_OPTIONS"   // Required options:
+        "=detect_leaks=1"                           //  - enable memory leak detection
+        ":log_path=/run/swap/tmp/lsan.log"          //  - set log path
+        ":leak_check_at_exit=0"                     //  - disable because we ourselves called
+                                                    //    __lsan_do_leak_check()
+        ":exitcode=0"                               //  - disable program exit with an error
+                                                    //    if the LSan found an error
+        ":stack_trace_format='    #%n %p %L'"       //  - set output format for stacktrace
+        ;
+
+
 #define IS_CLEAR_MEMORY  1
 #define CALLER_PC      (uptr)__builtin_return_address(0)
 #define CURRENT_FRAME  (uptr)__builtin_frame_address(0)
@@ -126,7 +138,32 @@ static bool init(const char *lsan_path)
         return false;
     }
 
+    // get environ space
+    uptr env_start, env_end;
+    environ::get_context(env_start, env_end);
+    uptr env_size = env_end - env_start;
+    LOGI("Environ space: [%lx..%lx] size=%ld", env_start, env_end, env_size);
+
+    if (env_size < sizeof(lsan_environ)) {
+        LOGE("Original environ size is very small, env_size=%ld lsan_eniron=%ld",
+             env_size, sizeof(lsan_environ));
+        return false;
+    }
+
+    // save env
+    char *env_backup = (char *)internal::malloc(env_size);
+    internal::memcpy(env_backup, (void *)env_start, env_size);
+
+    // change env
+    internal::memcpy((void *)env_start, lsan_environ, sizeof(lsan_environ));
+    internal::memset((void *)env_start + sizeof(lsan_environ), 0, env_size - sizeof(lsan_environ));
+
     void *handle = internal::dlopen(lsan_path, RTLD_NOW);
+
+    // restore env
+    internal::memcpy((void *)env_start, env_backup, env_size);
+    internal::free(env_backup);
+
     if (!handle) {
         LOGE("Cannot load LSan library, path=%s", lsan_path);
         return false;