Refactor gdbbacktrace() function 51/33851/12
authorMarcin Niesluchowski <m.niesluchow@samsung.com>
Thu, 15 Jan 2015 12:48:09 +0000 (13:48 +0100)
committerMarcin Niesluchowski <m.niesluchow@samsung.com>
Wed, 21 Jan 2015 12:35:04 +0000 (13:35 +0100)
Add safer, less redundant code and make it part of DPL namespace

Change-Id: I19e4704f4600d9720e939b0d76aa143c5d82ec2f

packaging/security-tests.spec
tests/framework/include/dpl/gdbbacktrace.h
tests/framework/include/dpl/test/test_runner.h
tests/framework/src/gdbbacktrace.cpp

index 8b85a08..13ea227 100644 (file)
@@ -29,6 +29,7 @@ Requires(post): gum-utils
 Requires(postun): gum-utils
 Requires(postun): %{_bindir}/id
 Requires: perf
+Requires: gdb
 
 %description
 Security tests repository - for tests that can't be kept together with code.
index 8a3028b..ac779b0 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2014 Samsung Electronics Co., Ltd All Rights Reserved
+ * Copyright (c) 2014-2015 Samsung Electronics Co., Ltd All Rights Reserved
  *
  *    Licensed under the Apache License, Version 2.0 (the "License");
  *    you may not use this file except in compliance with the License.
@@ -17,6 +17,7 @@
 /*
  * @file        gdbbacktrace.h
  * @author      Pawel Broda (p.broda@partner.samsung.com)
+ * @author      Marcin Niesluchowski <m.niesluchow@samsung.com>
  * @version     1.0
  * @brief       API providing backtrace
  */
 
 #include <string>
 
+namespace DPL {
+
 std::string gdbbacktrace(void);
 
-#endif
+} // namespace DPL
+
+#endif // _GDBBACKTRACE_H_
index a2694de..d96a9ff 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2011-2014 Samsung Electronics Co., Ltd All Rights Reserved
+ * Copyright (c) 2011-2015 Samsung Electronics Co., Ltd All Rights Reserved
  *
  *    Licensed under the Apache License, Version 2.0 (the "License");
  *    you may not use this file except in compliance with the License.
@@ -244,7 +244,7 @@ typedef DPL::Singleton<TestRunner> TestRunnerSingleton;
         if (!(test))                                                  \
         {                                                             \
             std::ostringstream assertMsg;                             \
-            assertMsg << message << gdbbacktrace();                   \
+            assertMsg << message << DPL::gdbbacktrace();              \
             throw DPL::Test::TestRunner::TestFailed(#test,            \
                                                     __FILE__,         \
                                                     __LINE__,         \
@@ -264,7 +264,7 @@ typedef DPL::Singleton<TestRunner> TestRunnerSingleton;
             assertMsg << message;                                     \
             if (!assertMsg.str().empty())                             \
                 assertMsg << ". ";                                    \
-            assertMsg << err << gdbbacktrace();                       \
+            assertMsg << err << DPL::gdbbacktrace();                  \
             throw DPL::Test::TestRunner::TestFailed(#test,            \
                                                     __FILE__,         \
                                                     __LINE__,         \
index 7efe4e5..4d84cd0 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2014 Samsung Electronics Co., Ltd All Rights Reserved
+ * Copyright (c) 2014-2015 Samsung Electronics Co., Ltd All Rights Reserved
  *
  *    Licensed under the Apache License, Version 2.0 (the "License");
  *    you may not use this file except in compliance with the License.
 /*
  * @file        gdbbacktrace.cpp
  * @author      Pawel Broda (p.broda@partner.samsung.com)
+ * @author      Marcin Niesluchowski <m.niesluchow@samsung.com>
  * @version     1.0
  * @brief       API providing backtrace
  */
 
+#include <cerrno>
+#include <cstdio>
+#include <cstdlib>
+#include <cstring>
 #include <fcntl.h>
 #include <iomanip>
 #include <iostream>
+#include <regex>
 #include <sstream>
+#include <sys/stat.h>
+#include <sys/types.h>
 #include <sys/wait.h>
 #include <unistd.h>
-#include <sys/types.h>
+
+#include <dpl/colors.h>
 
 #include <dpl/gdbbacktrace.h>
 
+namespace DPL {
 namespace {
-const size_t MAX_BACKTRACE_LINE_LENGTH = 1024;
-const std::string COLOUR_CODE_RED("\033[31m");
-const std::string COLOUR_CODE_RESET("\033[0m");
 
-const std::string FRAME_PATTERN_WAITPID("#0  ");
-const std::string FRAME_PATTERN_BACKTRACE("#1  ");
 const std::string FRAME_PATTERN_DPL("in DPL::Test");
+const std::string FRAME_GDBBACKTRACE("in DPL::gdbbacktrace");
+const std::string DEV_NULL("/dev/null");
+const std::string TMP_FILE_PREFIX("/tmp/security-tests_gdbbacktrace-");
 
-void print_colour(const std::string& err)
+void printColor(const char *err, int errnoNumber = 0)
 {
-    std::cerr << COLOUR_CODE_RED << err << COLOUR_CODE_RESET << std::endl;
+    std::cerr << Colors::Text::RED_BEGIN << "gdbbacktrace() failed: " << err
+              << ((errnoNumber != 0) ? (std::string(". ") + strerror(errnoNumber)) : "")
+              << Colors::Text::RED_END << std::endl;
 }
 
-bool backtrace_parse_line(const std::string& line, std::ostream& result, size_t line_number)
+bool backtraceParseLine(const std::string &line, std::ostream &result, size_t lineNumber)
 {
-    if (line.empty() || line[0] != '#')
-        return false;
-
-    // backtrace info - omit waitpid(), backtrace() and DPL frames
-    if (line.find(FRAME_PATTERN_WAITPID) == 0)
-        return false;
-
-    if (line.find(FRAME_PATTERN_BACKTRACE) == 0)
+    if (line.find(FRAME_PATTERN_DPL, 0) != std::string::npos)
         return false;
 
-    if (line.find(FRAME_PATTERN_DPL) != std::string::npos)
+    std::smatch m;
+    std::regex expr("^#\\d+\\s+0x[0-9a-fA-F]+\\s+(.+)");
+    if (!std::regex_search (line, m, expr))
         return false;
 
-    // example std::string line content:
-    // "#5  0x000000000040198d in main () at ../src/backtrace.cpp:105"
-    // should result in (i.e. would be written into char line_formatted):
-    // "main () at ../src/backtrace.cpp:105"
-
-    char line_formatted[MAX_BACKTRACE_LINE_LENGTH];
-    line_formatted[0] = '\0';
-    sscanf(line.c_str(), "%*s %*s %[^\n]", line_formatted);
-    if (line_formatted[0] != '\0') {
-        result << "#" << std::left << std::setw(2) << line_number << " " << line_formatted <<
-                  std::endl;
-        return true;
-    }
-
-    return false;
+    result << "#" << std::left << std::setw(2) << lineNumber << " " << m[m.size()-1] << std::endl;
+    return true;
 }
 
-std::string backtrace_read(int fd)
+std::string backtraceRead(const std::string &filename)
 {
-    FILE *bt_fd = fdopen(fd, "r");
-    char read_buffer[MAX_BACKTRACE_LINE_LENGTH];
-    std::ostringstream result;
+    errno = 0;
+    FILE *bt_fd = fopen(filename.c_str(), "r");
+    if (bt_fd == nullptr) {
+        printColor("fopen() failed", errno);
+        return "";
+    }
 
+    std::ostringstream result;
     result << std::endl;
-    size_t line_number = 1;
-    while (fgets(read_buffer, sizeof(read_buffer) - 1, bt_fd) != nullptr) {
-        if (backtrace_parse_line(read_buffer, result, line_number))
-            ++line_number;
+
+    char * line = nullptr;
+    size_t len = 0;
+
+    while (true) {
+        if (-1 == getline(&line, &len, bt_fd)) {
+            printColor("No backtrace information", errno);
+            free(line);
+            fclose(bt_fd);
+            return "";
+        }
+        if (std::string(line).find(FRAME_GDBBACKTRACE, 0) != std::string::npos)
+            break;
     }
 
-    fclose(bt_fd);
+    size_t lineNumber = 1;
+    while (-1 != getline(&line, &len, bt_fd)) {
+        if (backtraceParseLine(line, result, lineNumber))
+            ++lineNumber;
+    }
 
-    // check if gdbbacktrace() called directly from the test
-    if (line_number == 2)
-        return "";
+    free(line);
+    fclose(bt_fd);
 
     return result.str();
 }
-} // anonymous backtrace namespace end
 
-std::string gdbbacktrace(void)
+void runChild()
 {
-    std::string pid_buf = std::to_string(getpid());
-    int pipe_fd[2];
-    pipe(pipe_fd);
-
-    int child_pid = fork();
-    if (child_pid == -1)
-    {
-        print_colour("fork needed to run gdb in batch mode failed...");
-        return "";
-    }
-
-    if (child_pid == 0) {
-        int devnull = open("/dev/null", O_WRONLY);
-        if (dup2(devnull, 2) == -1)
-            exit(2);
-        if (dup2(pipe_fd[1], 1) == -1)
-            exit(2);
-        execlp("/usr/bin/gdb", "gdb", "--batch", "-n", "-ex", "thread", "-ex", "bt", "--pid",
-               pid_buf.c_str(), nullptr);
+    try {
+        std::string filename = TMP_FILE_PREFIX + std::to_string(getpid());
+        int devnullWrite = TEMP_FAILURE_RETRY(open(DEV_NULL.c_str(), O_WRONLY));
+        if (-1 == devnullWrite)
+            exit(EXIT_FAILURE);
+        int writeFd = TEMP_FAILURE_RETRY(creat(filename.c_str(), S_IRUSR | S_IWUSR));
+        if (-1 == writeFd)
+            exit(EXIT_FAILURE);
+        int devnullRead = TEMP_FAILURE_RETRY(open(DEV_NULL.c_str(), O_RDONLY));
+        if (-1 == devnullRead)
+            exit(EXIT_FAILURE);
+        if (-1 == TEMP_FAILURE_RETRY(dup2(devnullWrite, STDERR_FILENO)))
+            exit(EXIT_FAILURE);
+        if (-1 == TEMP_FAILURE_RETRY(dup2(writeFd, STDOUT_FILENO)))
+            exit(EXIT_FAILURE);
+        if (-1 == TEMP_FAILURE_RETRY(dup2(devnullRead, STDIN_FILENO)))
+            exit(EXIT_FAILURE);
+        std::string ppid = std::to_string(getppid());
+        execl("/usr/bin/gdb", "gdb", "--batch", "-n", "-ex", "bt", "--pid", ppid.c_str(),
+              (char*)nullptr);
         // gdb failed to start...
-        exit(1);
+    } catch (...) {
     }
+    exit(EXIT_FAILURE);
+}
 
-    int status;
-    int waitpid_res = waitpid(child_pid, &status, 0);
-
-    close(pipe_fd[1]);
-
-    std::string result;
-    if (waitpid_res == -1) {
-        print_colour("Backtrace not available (waitpid failed)");
-    } else if (status == 2) {
-        print_colour("Error: file descriptor duplication failed... failed to start gdb...");
-    } else if (status != 0) {
-        print_colour("Error: no gdb or failed to start gdb...");
-    } else {
-        result.append(backtrace_read(pipe_fd[0]));
+} // namespace
+
+std::string gdbbacktrace(void)
+{
+    std::string ret;
+
+    pid_t childPid = fork();
+    switch (childPid) {
+        case -1:
+            printColor("fork() failed", errno);
+            return ret;
+        case 0:
+            runChild();
+        default:
+            break;
     }
 
-    close(pipe_fd[0]);
+    std::string filename = TMP_FILE_PREFIX + std::to_string(childPid);
 
-    return result;
+    int status;
+    pid_t pid = TEMP_FAILURE_RETRY(waitpid(childPid, &status, 0));
+    if (-1 == pid)
+        printColor("waitpid() failed", errno);
+    else if (!WIFEXITED(status) || WEXITSTATUS(status) != EXIT_SUCCESS)
+        printColor("Error: no gdb or failed to start gdb...");
+    else
+        ret = backtraceRead(filename);
+
+    if (-1 == unlink(filename.c_str()) && errno != ENOENT)
+        printColor("unlink() failed", errno);
+    return ret;
 }
+
+} // namespace DPL