/*
- * 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