Add livedumper and integrate it with the crash-manager 52/203752/23
authorMateusz Moscicki <m.moscicki2@partner.samsung.com>
Tue, 16 Apr 2019 10:05:26 +0000 (12:05 +0200)
committerMateusz Moscicki <m.moscicki2@partner.samsung.com>
Thu, 23 May 2019 12:19:51 +0000 (14:19 +0200)
Livedumper is an application that can save coredump of a running process.

crash-manager got three new switches:

   -l - run livedumper instead of minicoredumper and don't send notify
   -k - kill process after saving the report
   -r - print report path to stdout

Change-Id: If4fe21b250ae5e939d7b63ea5c4bf75bec6123a4

20 files changed:
CMakeLists.txt
include/defs.h.in
packaging/crash-worker.spec
packaging/crash-worker_system-tests.spec
src/crash-manager/crash-manager.c
src/livedumper/CMakeLists.txt [new file with mode: 0644]
src/livedumper/clog.cpp [new file with mode: 0644]
src/livedumper/core.hpp [new file with mode: 0644]
src/livedumper/dlog.cpp [new file with mode: 0644]
src/livedumper/helpers.hpp [new file with mode: 0644]
src/livedumper/livedumper.hpp [new file with mode: 0644]
src/livedumper/log.hpp [new file with mode: 0644]
src/livedumper/main.cpp [new file with mode: 0644]
src/livedumper/maps.hpp [new file with mode: 0644]
src/livedumper/note.hpp [new file with mode: 0644]
src/livedumper/program.hpp [new file with mode: 0644]
src/shared/util.c
src/shared/util.h
tests/system/CMakeLists.txt
tests/system/livedumper/livedumper.sh.template [new file with mode: 0644]

index 49a9356..0053f4c 100644 (file)
@@ -22,5 +22,9 @@ IF("${LOG_DUMP}" STREQUAL "ON")
        ADD_SUBDIRECTORY(src/log_dump)
 ENDIF()
 
+IF("${LIVEDUMPER}" STREQUAL "ON")
+       ADD_SUBDIRECTORY(src/livedumper)
+ENDIF()
+
 ADD_SUBDIRECTORY(tests)
 
index 587172d..04dbf06 100644 (file)
@@ -11,5 +11,6 @@
 #define MINICOREDUMPER_BIN_PATH                "@MINICOREDUMPER_BIN_PATH@"
 #define MINICOREDUMPER_CONFIG_PATH     "@MINICOREDUMPER_CONFIG_PATH@"
 #define DEBUGMODE_PATH                 "@DEBUGMODE_PATH@"
+#define LIVEDUMPER_BIN_PATH            "@LIVEDUMPER_BIN_PATH@"
 
 #endif /* __DEFS_H__ */
index d6e7e7a..aae948e 100644 (file)
@@ -3,10 +3,12 @@
 
 %define _with_tests on
 %define _with_logdump on
+%define _with_livedumper on
 %bcond_with doc
 %bcond_with sys_assert
 %bcond_with tests
 %bcond_with logdump
+%bcond_with livedumper
 
 # NOTE: To disable coredump set DumpCore=0 in configuration file
 
@@ -36,6 +38,10 @@ BuildRequires:  libdw-devel libdw
 BuildRequires:  doxygen
 %endif
 
+%if %{with livedumper}
+BuildRequires:  boost-devel
+%endif
+
 Requires(post): coreutils
 Requires(post): tar
 Requires(post): gzip
@@ -65,6 +71,13 @@ Summary:     Package with binaries and data for crash-worker tests
 This package contains installable tests in Bash.
 %endif
 
+%if %{with livedumper}
+%package livedumper
+Summary:       Livedumper allows to dump core of live process
+
+%description livedumper
+%endif
+
 %prep
 %setup -q
 
@@ -112,11 +125,14 @@ export CFLAGS+=" -Werror"
           -DDEBUGMODE_PATH=%{debugmode_path} \
           -DMINICOREDUMPER_BIN_PATH=%{_sbindir}/minicoredumper \
           -DMINICOREDUMPER_CONFIG_PATH=%{_sysconfdir}/minicoredumper/minicoredumper.cfg.json \
+          -DLIVEDUMPER_BIN_PATH=%{_bindir}/livedumper \
           -DCRASH_STACK_PATH=%{_libexecdir}/crash-stack \
           -DCRASH_TESTS_PATH=%{_libdir}/crash-worker-tests \
           -DSYS_ASSERT=%{on_off sys_assert} \
           -DLOG_DUMP=%{on_off logdump} \
+          -DLIVEDUMPER=%{on_off livedumper} \
           -DUPGRADE_SCRIPT_PATH=%{upgrade_script_path} \
+          -DLOGGER=dlog
 
 make %{?jobs:-j%jobs}
 %if %{with doc}
@@ -225,3 +241,9 @@ sed -i "/${pattern}/D" %{_sysconfdir}/ld.so.preload
 %{_libdir}/crash-worker-tests/crash_common.sh
 
 %endif
+
+%if %{with livedumper}
+%files livedumper
+%manifest %{name}.manifest
+%{_bindir}/livedumper
+%endif
index a9cf4d4..38d2ed6 100644 (file)
@@ -3,6 +3,8 @@
 # btee.c) that will be copied to the destination directory, but we don't want
 # them in the rpm package so this flag is to avoid RPM build error:
 %define _unpackaged_files_terminate_build 0
+%define _with_livedumper on
+%bcond_with livedumper
 
 Name:          crash-worker_system-tests
 Summary:       Package with binaries and scripts for crash-worker system tests
@@ -74,6 +76,9 @@ cd tests/system
 %{_libdir}/crash-worker_system-tests/utils/kenny
 %{_libdir}/crash-worker_system-tests/utils/minicore-utils.sh
 %{_libdir}/crash-worker_system-tests/run.sh
+%if %{with livedumper}
+%{_libdir}/crash-worker_system-tests/livedumper/livedumper.sh
+%endif
 %defattr(-,root,root)
 
 # %post
index 7a4c0b5..c87db05 100644 (file)
@@ -1,7 +1,7 @@
 /*
  * crash-manager
  *
- * Copyright (c) 2016 Samsung Electronics Co., Ltd.
+ * Copyright (c) 2016-2019 Samsung Electronics Co., Ltd.
  *
  * Licensed under the Apache License, Version 2.0 (the License);
  * you may not use this file except in compliance with the License.
@@ -34,6 +34,8 @@
 #include <sys/procfs.h>
 #include <sys/stat.h>
 #include <sys/types.h>
+#include <sys/wait.h>
+#include <sys/ptrace.h>
 #include <sys/vfs.h>
 #include <unistd.h>
 
@@ -61,6 +63,7 @@
 #define WAIT_FOR_OPT_TIMEOUT_SEC 60
 
 #define MINICOREDUMPER_TIMEOUT_MS  DEFAULT_COMMAND_TIMEOUT_MS
+#define LIVEDUMPER_TIMEOUT_MS      DEFAULT_COMMAND_TIMEOUT_MS
 #define CRASH_STACK_TIMEOUT_MS     DEFAULT_COMMAND_TIMEOUT_MS
 #define ZIP_TIMEOUT_MS             DEFAULT_COMMAND_TIMEOUT_MS
 
@@ -101,6 +104,9 @@ struct crash_info {
        char *log_path;
        char appid[APPID_MAX];
        char pkgid[PKGNAME_MAX];
+       bool livedump;
+       bool kill;
+       bool print_result_path;
 #ifdef SYS_ASSERT
        char *sysassert_cs_path;
        bool have_sysassert_report;
@@ -312,7 +318,12 @@ static void print_help(const char *name)
               "    -i  --tid=TID           TID of thread that triggered core dump\n"
               "    -s  --signal=SIG        number of signal causing dump\n"
               "    -t  --time=TIME         time of dump, expressed as seconds since the Epoch\n"
+              "    -l  --live              get coredump of running process\n"
+              "    -k  --kill-after-dump   kill after dump (only with --live option)\n"
+              "    -r  --print             print report path to stdout\n"
               "    -h  --help              this message\n"
+              "\n"
+              "for --live option only --pid is required\n"
               "\n", name);
 }
 
@@ -342,10 +353,13 @@ static bool parse_args(struct crash_info *cinfo, int argc, char *argv[])
                {"tid",             required_argument, NULL, 'i'},
                {"signal",          required_argument, NULL, 's'},
                {"time",            required_argument, NULL, 't'},
+               {"live",                  no_argument, NULL, 'l'},
+               {"kill-after-dump",       no_argument, NULL, 'k'},
+               {"print",                 no_argument, NULL, 'r'},
                {"help",                  no_argument, NULL, 'h'},
        };
 
-       while ((opt = getopt_long(argc, argv, "p:u:g:i:s:t:h", long_options, NULL)) != -1) {
+       while ((opt = getopt_long(argc, argv, "p:u:g:i:s:t:hlkr", long_options, NULL)) != -1) {
                switch (opt) {
                case 'p':
                        GET_NUMBER(pid)
@@ -369,18 +383,37 @@ static bool parse_args(struct crash_info *cinfo, int argc, char *argv[])
                case 't':
                        GET_NUMBER(time)
                        break;
+               case 'l':
+                       cinfo->livedump = true;
+                       break;
+               case 'k':
+                       cinfo->kill = true;
+                       break;
+               case 'r':
+                       cinfo->print_result_path = true;
+                       break;
                case 'h':
                default:
                        print_help(argv[0]);
-                       result = false;
-                       break;
+                       return false;
                }
        }
 
-       if (result && (!pid_set || !uid_set || !gid_set || !sig_set)) {
+       if (!pid_set || (!cinfo->livedump && (!gid_set || !uid_set || !sig_set))) {
                printf("Not enough parameters.\n\n");
                print_help(argv[0]);
-               result = false;
+               return false;
+       }
+
+       if (cinfo->livedump && sig_set) {
+               printf("--sig can not be used with --live option\n\n");
+               print_help(argv[0]);
+               return false;
+       }
+
+       if (!cinfo->livedump && cinfo->kill) {
+               printf("Option --kill-after-dump can be used only with --live\n");
+               return false;
        }
        return result;
 #undef QUOTE
@@ -394,17 +427,37 @@ static int set_crash_info(struct crash_info *cinfo, int argc, char *argv[])
        char date[80];
        struct tm loc_tm;
 
+       cinfo->livedump = false;
+       cinfo->kill = false;
+       cinfo->print_result_path = false;
        cinfo->tid_info = -1;
        cinfo->time_info = 0;
 
        if (!parse_args(cinfo, argc, argv))
                return -1;
 
+       if (cinfo->livedump) {
+               if (cinfo->kill)
+                       cinfo->sig_info = 9;
+               else
+                       cinfo->sig_info = 0;
+       }
+
+       if (cinfo->livedump && !file_exists(LIVEDUMPER_BIN_PATH)) {
+               fprintf(stderr, "Error: %s doesn't exist - can not perform livedump. Terminating.\n", LIVEDUMPER_BIN_PATH);
+               _E("Error: %s doesn't exist - can not perform livedump. Terminating.\n", LIVEDUMPER_BIN_PATH);
+               return -1;
+       }
+
        if (cinfo->tid_info == -1) {
-               cinfo->tid_info = find_crash_tid(cinfo->pid_info);
-               if (cinfo->tid_info < 0) {
-                       _I("TID not found");
+               if (cinfo->livedump) {
                        cinfo->tid_info = cinfo->pid_info;
+               } else {
+                       cinfo->tid_info = find_crash_tid(cinfo->pid_info);
+                       if (cinfo->tid_info < 0) {
+                               _I("TID not found");
+                               cinfo->tid_info = cinfo->pid_info;
+                       }
                }
        }
 
@@ -698,6 +751,38 @@ out:
        return is_ok;
 }
 
+static bool execute_livedumper(const struct crash_info *cinfo, int *exit_code)
+{
+       char *coredump_path = NULL;
+       char *prstatus_fd_str = NULL;
+       bool is_ok = false;
+       char pid_str[11];
+
+       if (asprintf(&coredump_path, "%s/%s.coredump", cinfo->pfx, cinfo->name) == -1 ||
+           asprintf(&prstatus_fd_str, "%d", cinfo->prstatus_fd) == -1) {
+               _E("Unable to allocate memory");
+               goto out;
+       }
+
+       SNPRINTF_OR_EXIT(pid, "%d")
+
+       /* Execute livedumper */
+       char *args[] = {
+                       LIVEDUMPER_BIN_PATH,    // livedumper filename path
+                       "-P", prstatus_fd_str,
+                       "-f", coredump_path,
+                       pid_str,                // %p - pid
+                       NULL
+                       };
+
+       spawn_param_s param = { .fn = spawn_setstdout, .u.int_val = STDERR_FILENO };
+       is_ok = spawn_wait(args, NULL, &param, LIVEDUMPER_TIMEOUT_MS, exit_code);
+out:
+       free(prstatus_fd_str);
+       free(coredump_path);
+       return is_ok;
+}
+
 static bool execute_crash_stack(const struct crash_info *cinfo, int *exit_code)
 {
        char pid_str[11], tid_str[11], sig_str[11], prstatus_fd_str[11];
@@ -739,12 +824,52 @@ out:
 #undef SNPRINTF_OR_EXIT
 #undef SNPRINTF_OR_EXIT_W
 
+static bool kill_pid(const pid_t pid, int sig, bool wait)
+{
+       if (kill(pid, sig) == -1) {
+               _E("kill sig %d error: %m\n", sig);
+               return false;
+       }
+
+       if (wait) {
+               if (waitpid(pid, NULL, 0) == -1) {
+                       _E("waitpid error: %m");
+                       return false;
+               }
+       }
+       return true;
+}
+
+static bool process_stop(const pid_t pid)
+{
+       _D("stop process %d", pid);
+       return kill_pid(pid, SIGSTOP, false);
+}
+
+static bool process_continue(const pid_t pid)
+{
+       _D("continue process %d", pid);
+       return kill_pid(pid, SIGCONT, false);
+}
+
 static bool execute_crash_modules(struct crash_info *cinfo)
 {
        int exit_code = 0;
-       if (!execute_minicoredump(cinfo, &exit_code) || exit_code != 0) {
-               _E("Failed to run minicoredumper - can not continue");
-               return false;
+       if (cinfo->livedump) {
+               _I("Starting the livedumper");
+               if (!process_stop(cinfo->pid_info))
+                       return false;
+               if (!execute_livedumper(cinfo, &exit_code) || exit_code != 0) {
+                       _E("Failed to run livedumper - can not continue");
+                       process_continue(cinfo->pid_info);
+                       return false;
+               }
+       } else {
+               _I("Starting the minicoredumper");
+               if (!execute_minicoredump(cinfo, &exit_code) || exit_code != 0) {
+                       _E("Failed to run minicoredumper - can not continue");
+                       return false;
+               }
        }
 
 #ifdef SYS_ASSERT
@@ -754,6 +879,8 @@ static bool execute_crash_modules(struct crash_info *cinfo)
                return false;
 #endif
        execute_crash_stack(cinfo, NULL);
+       if (cinfo->livedump)
+               process_continue(cinfo->pid_info);
 
        return true;
 }
@@ -1160,24 +1287,31 @@ int main(int argc, char *argv[])
                move_dump_data(cinfo.info_path, &cinfo);
        }
 
-       /* Release the core pipe as passed by kernel, allowing another
-        * coredump to be handled.
-        *
-        * Due to usage of core_pipe_limit there is limited number of
-        * crash-manager processes that kernel is going to invoke
-        * concurrently.  As the next and last step is a _synchronous_
-        * call to crash-popup we close the descriptor here.
-        *
-        * Note: for VIP processes this will likely cause the system
-        * to reboot without showing popup.
-        */
-       close(STDIN_FILENO);
+       if (cinfo.print_result_path)
+               printf("REPORT_PATH=%s\n", cinfo.result_path);
+
+       if (!cinfo.livedump) {
+               /* Release the core pipe as passed by kernel, allowing another
+                * coredump to be handled.
+                *
+                * Due to usage of core_pipe_limit there is limited number of
+                * crash-manager processes that kernel is going to invoke
+                * concurrently.  As the next and last step is a _synchronous_
+                * call to crash-popup we close the descriptor here.
+                *
+                * Note: for VIP processes this will likely cause the system
+                * to reboot without showing popup.
+                */
+               close(STDIN_FILENO);
 
-       launch_dbus_notify(&cinfo);
+               launch_dbus_notify(&cinfo);
 
-       /* launch crash-popup only if the .debugmode file exists */
-       if (debug_mode)
-               launch_crash_popup(&cinfo);
+               /* launch crash-popup only if the .debugmode file exists */
+               if (debug_mode)
+                       launch_crash_popup(&cinfo);
+       } else if (cinfo.kill) {
+               kill_pid(cinfo.pid_info, SIGKILL, false);
+       }
 
 exit:
        _I("Exiting with exit code %d", res);
diff --git a/src/livedumper/CMakeLists.txt b/src/livedumper/CMakeLists.txt
new file mode 100644 (file)
index 0000000..27faf24
--- /dev/null
@@ -0,0 +1,29 @@
+cmake_minimum_required(VERSION 2.6)
+project(livedumper CXX)
+
+set(LIVEDUMPER_BIN "livedumper")
+
+find_package( Boost 1.58 COMPONENTS system REQUIRED)
+include_directories ( ${Boost_INCLUDE_DIR} )
+set(PREFIX ${CMAKE_INSTALL_PREFIX})
+add_definitions(-std=c++11)
+
+# add_executable(${LIVEDUMPER_BIN} main.cpp)
+
+if("${LOGGER}" STREQUAL "dlog")
+       set(LOGGER_FILE dlog.cpp)
+else()
+       set(LOGGER_FILE clog.cpp)
+endif(DLOG)
+
+set(LIVEDUMPER_SRCS main.cpp ${LOGGER_FILE})
+add_executable(${LIVEDUMPER_BIN} ${LIVEDUMPER_SRCS})
+
+if("${LOGGER}" STREQUAL "dlog")
+       include(FindPkgConfig)
+       pkg_check_modules(DLOG_REQUIRED dlog)
+       set_property(TARGET ${LIVEDUMPER_BIN} APPEND_STRING PROPERTY COMPILE_FLAGS ${DLOG_CFLAGS})
+       target_link_libraries(${LIVEDUMPER_BIN} ${DLOG_LIBRARIES})
+endif()
+
+install (TARGETS ${LIVEDUMPER_BIN} DESTINATION bin)
diff --git a/src/livedumper/clog.cpp b/src/livedumper/clog.cpp
new file mode 100644 (file)
index 0000000..24726c8
--- /dev/null
@@ -0,0 +1,35 @@
+/*
+ * Copyright (c) 2019 Samsung Electronics Co., Ltd.
+ *
+ * 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 "log.hpp"
+
+#include <unistd.h>
+
+#include <cstdarg>
+#include <iostream>
+
+void Logger::log(const LogMsgType type, const char *fmt...) {
+       va_list args;
+       int fd = (type == LogMsgType::ERROR) ? STDERR_FILENO : STDOUT_FILENO;
+
+       va_start(args, fmt);
+       vdprintf(fd, fmt, args);
+       dprintf(fd, "\n");
+       va_end(args);
+}
+
+Logger logger;
diff --git a/src/livedumper/core.hpp b/src/livedumper/core.hpp
new file mode 100644 (file)
index 0000000..d9abfe9
--- /dev/null
@@ -0,0 +1,199 @@
+/*
+ * Copyright (c) 2019 Samsung Electronics Co., Ltd.
+ *
+ * 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.
+ *
+ */
+
+#ifndef CORE_HPP__
+#define CORE_HPP__
+
+#include "helpers.hpp"
+#include "log.hpp"
+#include "note.hpp"
+#include "program.hpp"
+
+#include <fcntl.h>
+#include <libelf.h>
+#include <limits.h>
+#include <sys/procfs.h>
+
+#include <cstring>
+#include <iostream>
+#include <memory>
+#include <string>
+#include <vector>
+
+
+template <typename T>
+class Core {
+ private:
+       static constexpr size_t PAGESIZE = 0x1000;
+       ProgramTableEntryBase<T> *m_note_header;
+       std::vector<std::unique_ptr<ProgramTableEntryBase<T>>> m_phdrt;
+       size_t current_data_address;
+       std::vector <std::unique_ptr<Note>> m_notes;
+       typename T::Ehdr m_ehdr;
+
+ public:
+       Core() : m_note_header(new ProgramTableEntryNote<T>()),
+                current_data_address(sizeof(m_ehdr)) {
+               m_phdrt.push_back(std::unique_ptr<ProgramTableEntryBase<T>>(m_note_header));
+       }
+
+       void FillELFHeader() {
+               memcpy(m_ehdr.e_ident, ELFMAG, sizeof(ELFMAG));
+               m_ehdr.e_ident[EI_CLASS] = T::ELFCLASS;
+               m_ehdr.e_ident[EI_DATA] = ELFDATA2LSB;
+               m_ehdr.e_ident[EI_VERSION] = EV_CURRENT;
+               m_ehdr.e_ident[EI_OSABI] = ELFOSABI_SYSV;
+               m_ehdr.e_ident[EI_ABIVERSION] = 0;
+               memset(&m_ehdr.e_ident[EI_PAD], 0, sizeof(m_ehdr.e_ident) - EI_PAD);
+               m_ehdr.e_type = ET_CORE;
+               m_ehdr.e_machine = MACHINE;
+               m_ehdr.e_version = EV_CURRENT;
+               m_ehdr.e_entry = 0;
+               m_ehdr.e_phoff = sizeof(m_ehdr);
+               m_ehdr.e_shoff = 0;
+               m_ehdr.e_flags = 0;
+               m_ehdr.e_ehsize = sizeof(m_ehdr);
+               m_ehdr.e_phentsize = sizeof(typename T::Phdr);
+               m_ehdr.e_phnum = m_phdrt.size();
+               m_ehdr.e_shentsize = 0;
+               m_ehdr.e_shnum = 0;
+               m_ehdr.e_shstrndx = 0;
+       }
+
+       void ImportMapRecord(std::shared_ptr<MapRecord> record) {
+               ProgramTableEntryLoad<T> *entry = new ProgramTableEntryLoad<T>();
+               entry->header.p_flags = static_cast<decltype(entry->header.p_flags)>(record->perms);
+               entry->header.p_offset = current_data_address;
+               current_data_address += record->end - record->start;
+               entry->header.p_vaddr = record->start;
+               entry->header.p_paddr = 0;
+               entry->header.p_filesz = record->end - record->start;
+               entry->header.p_memsz = record->end - record->start;
+               entry->header.p_align = PAGESIZE;
+               m_phdrt.push_back(std::unique_ptr<ProgramTableEntryLoad<T>>(entry));
+       }
+
+       void SaveNotes(std::ofstream &core_file) {
+               for (auto const &note : m_notes)
+                       note->SaveTo(core_file);
+       }
+
+       void SaveELFHeader(std::ofstream &core_file) {
+               FillELFHeader();
+               core_file.write(reinterpret_cast<char*>(&m_ehdr), sizeof(m_ehdr));
+       }
+
+       void SaveProgramHeadersTable(std::ofstream &core_file) {
+               for (const auto& record : m_phdrt) {
+                       core_file.write(reinterpret_cast<char*>(&record->header),
+                                                         sizeof(record->header));
+               }
+       }
+
+       bool IsEmpty(char *buff, size_t size) {
+               uint32_t *tmp_buff = reinterpret_cast<uint32_t*>(buff);
+               const size_t divider = sizeof(tmp_buff[0])/sizeof(buff[0]);
+
+               assert(size % divider == 0);
+
+               for (size_t i = 0; i < size/divider; i++) {
+                       if (tmp_buff[i] != 0)
+                               return false;
+               }
+               return true;
+       }
+
+       void CopyData(int input, std::ofstream &output, size_t size) {
+               size_t len = PAGESIZE;
+               char buff[PAGESIZE];
+
+               while (size > 0) {
+                       if (size < len)
+                               len = size;
+
+                       ssize_t cur_pos = lseek64(input, 0, SEEK_CUR);
+
+                       ssize_t bytes_read;
+                       if ((bytes_read = read(input, buff, len)) == -1) {
+                               // tell() returns 128bits value so I cast this to 64bit to be able to print it
+                               logger.log_error("read error at 0x%llx: %s\n", static_cast<long long int>(output.tellp()),
+                                               std::system_category().default_error_condition(errno).message());
+                               lseek64(input, cur_pos + len, SEEK_SET);
+                               output.seekp(len, std::ios_base::cur);
+                               size -= len;
+                       } else {
+                               if (bytes_read == PAGESIZE && IsEmpty(buff, len)) {
+                                       output.seekp(bytes_read, std::ios_base::cur);
+                               } else {
+                                       output.write(buff, bytes_read);
+                               }
+                               size -= bytes_read;
+                       }
+               }
+       }
+
+       void SaveLoadable(const std::string &mem_path, std::ofstream &core_file) {
+               int fd = open(mem_path.c_str(), O_RDONLY);
+               if (fd == -1)
+                       throw std::system_error(errno, std::system_category(), "failed to open mem file " + mem_path);
+
+               for (const auto &record : m_phdrt) {
+                       if (record->header.p_type != PT_LOAD)
+                               continue;
+                       if (lseek64(fd, record->header.p_vaddr, SEEK_SET) == -1)
+                               logger.log_error("lseek64 error: %s", std::system_category().default_error_condition(errno).message());
+                       CopyData(fd, core_file, record->header.p_filesz);
+               }
+               close(fd);
+       }
+
+       void AddNote(Note *note) {
+               m_note_header->header.p_filesz += note->GetSize();
+               m_note_header->header.p_memsz += note->GetSize();
+               m_notes.push_back(std::unique_ptr<Note>(note));
+       }
+
+       void AddPRSTATUSNote(const user_regs_struct &regs, const pid_t pid) {
+               NoteCorePRStatus *note = new NoteCorePRStatus(regs, pid);
+               AddNote(note);
+       }
+
+       void AddFILENote(const std::vector<std::shared_ptr<MapRecord>> &records) {
+               NoteCoreFile<T> *note = new NoteCoreFile<T>();
+               note->ImportRecords(records);
+               AddNote(note);
+       }
+
+       void AddAUXVNote(const std::vector<char> &auxv_vector) {
+               NoteCoreAUXV *note = new NoteCoreAUXV();
+               note->SetAuxv(auxv_vector);
+               AddNote(note);
+       }
+
+       void ImportMapRecords(const std::vector<std::shared_ptr<MapRecord>> &records) {
+               current_data_address += (records.size() + 1)*sizeof(typename T::ProgramTableHeader);
+               m_note_header->header.p_offset = current_data_address;
+
+               for (const auto &note : m_notes)
+                       current_data_address += note->GetSize();
+
+               for (const auto &record : records)
+                       ImportMapRecord(record);
+       }
+};
+
+#endif  // CORE_HPP__
diff --git a/src/livedumper/dlog.cpp b/src/livedumper/dlog.cpp
new file mode 100644 (file)
index 0000000..9dec8ee
--- /dev/null
@@ -0,0 +1,46 @@
+/*
+ * Copyright (c) 2019 Samsung Electronics Co., Ltd.
+ *
+ * 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 "log.hpp"
+
+#include <dlog.h>
+#include <unistd.h>
+
+#include <cstdarg>
+#include <iostream>
+
+void Logger::log(const LogMsgType type, const char *fmt...) {
+       va_list args;
+       log_priority prio;
+
+       switch (type) {
+       case LogMsgType::ERROR:
+               prio = DLOG_ERROR;
+               break;
+       case LogMsgType::WARNING:
+               prio = DLOG_WARN;
+               break;
+       default:
+               prio = DLOG_INFO;
+               break;
+       }
+       va_start(args, fmt);
+       vprint_log(prio, "LIVEDUMPER", fmt, args);
+       va_end(args);
+}
+
+Logger logger;
diff --git a/src/livedumper/helpers.hpp b/src/livedumper/helpers.hpp
new file mode 100644 (file)
index 0000000..60dcb0b
--- /dev/null
@@ -0,0 +1,117 @@
+/*
+ * Copyright (c) 2019 Samsung Electronics Co., Ltd.
+ *
+ * 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.
+ *
+ */
+
+#ifndef HELPERS_HPP__
+#define HELPERS_HPP__
+
+#include <libelf.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+
+#include <functional>
+#include <string>
+#include <unistd.h>
+
+#pragma pack(push, 1)
+
+struct Notefile32 {
+       uint32_t start;
+       uint32_t end;
+       uint32_t file_ofs;
+};
+
+struct Notefile64 {
+       uint64_t start;
+       uint64_t end;
+       uint64_t file_ofs;
+};
+
+struct ProgramTableHeader32 {
+       uint32_t p_type;
+       Elf32_Off p_offset;
+       Elf32_Addr p_vaddr;
+       Elf32_Addr p_paddr;
+       uint32_t p_filesz;
+       uint32_t p_memsz;
+       uint32_t p_flags;
+       uint32_t p_align;
+};
+
+struct ProgramTableHeader64 {
+       uint32_t p_type;
+       uint32_t p_flags;
+       Elf64_Off p_offset;
+       Elf64_Addr p_vaddr;
+       Elf64_Addr p_paddr;
+       uint64_t p_filesz;
+       uint64_t p_memsz;
+       uint64_t p_align;
+};
+#pragma pack(pop)
+
+struct Elf32 {
+       typedef Elf32_Ehdr Ehdr;
+       typedef Elf32_Phdr Phdr;
+       typedef Elf32_Addr Addr;
+       typedef Elf32_Off Off;
+       typedef Elf32_Section Section;
+       typedef Elf32_Versym Versym;
+       typedef Elf32_Half Half;
+       typedef Elf32_Sword Sword;
+       typedef Elf32_Word Word;
+       typedef Elf32_Sxword Sxword;
+       typedef Elf32_Xword Xword;
+       typedef ProgramTableHeader32 ProgramTableHeader;
+       typedef Notefile32 Notefile;
+       typedef uint32_t uint;
+       static const char ELFCLASS = ELFCLASS32;
+};
+
+struct Elf64 {
+       typedef Elf64_Ehdr Ehdr;
+       typedef Elf64_Phdr Phdr;
+       typedef Elf64_Addr Addr;
+       typedef Elf64_Off Off;
+       typedef Elf64_Section Section;
+       typedef Elf64_Versym Versym;
+       typedef Elf64_Half Half;
+       typedef Elf64_Sword Sword;
+       typedef Elf64_Word Word;
+       typedef Elf64_Sxword Sxword;
+       typedef Elf64_Xword Xword;
+       typedef ProgramTableHeader64 ProgramTableHeader;
+       typedef Notefile64 Notefile;
+       typedef uint64_t uint;
+       static const char ELFCLASS = ELFCLASS64;
+};
+
+class Guardian {
+ private:
+       const std::function<void()> m_start;
+       const std::function<void()> m_end;
+ public:
+       Guardian(const std::function<void()> &start, const std::function<void()> &end)
+                                                               : m_start(start), m_end(end) { m_start(); }
+       ~Guardian() { m_end(); }
+};
+
+bool fileExists(const std::string &path) {
+       struct stat buf;
+       return stat(path.c_str(), &buf) == 0;
+}
+
+#endif  // HELPERS_HPP__
diff --git a/src/livedumper/livedumper.hpp b/src/livedumper/livedumper.hpp
new file mode 100644 (file)
index 0000000..b478b40
--- /dev/null
@@ -0,0 +1,201 @@
+/*
+ * Copyright (c) 2019 Samsung Electronics Co., Ltd.
+ *
+ * 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.
+ *
+ */
+
+#ifndef LIVEDUMPER_HPP__
+#define LIVEDUMPER_HPP__
+
+#include "core.hpp"
+#include "helpers.hpp"
+#include "log.hpp"
+#include "maps.hpp"
+
+#include <dirent.h>
+#include <limits.h>
+#include <string.h>
+#include <sys/mman.h>
+#include <sys/ptrace.h>
+#include <sys/wait.h>
+
+#include <fstream>
+#include <iostream>
+#include <stdexcept>
+#include <string>
+#include <vector>
+
+template <typename T>
+class LiveDumper {
+ private:
+       pid_t m_pid;
+       std::vector<pid_t> m_tpids;
+       Core<T> m_core;
+       std::ofstream m_core_file;
+       int prstatus_fd = -1;
+
+ public:
+       explicit LiveDumper(const pid_t pid) : m_pid(pid) {}
+
+       void CollectTpids() {
+               m_tpids.clear();
+
+               std::string task_dir = "/proc/" + std::to_string(m_pid) + "/task";
+
+               DIR *dir = opendir(task_dir.c_str());
+               if (dir == nullptr)
+                       throw std::system_error(errno, std::system_category(), "failed to open directory: " + task_dir);
+
+               dirent *ent;
+               while ((ent = readdir(dir)) != nullptr) {
+                       if (ent->d_type == DT_DIR &&
+                               strncmp(ent->d_name, ".", 1) != 0)
+                                       m_tpids.push_back(std::strtol(ent->d_name, 0, 10));
+               }
+               closedir(dir);
+       }
+
+       void AttachTo(const pid_t pid) const {
+               if (ptrace(PTRACE_ATTACH, pid, nullptr, nullptr) == -1)
+                       throw std::system_error(errno, std::system_category(), "failed to attach to: " + std::to_string(pid));
+       }
+
+       void setPrstatusFd(const int prstatus_fd) {
+               this->prstatus_fd = prstatus_fd;
+       }
+
+       void Attach() {
+               AttachTo(m_pid);
+
+               waitpid(m_pid, nullptr, 0);
+
+               CollectTpids();
+
+               for (const auto &pid : m_tpids) {
+                       if (pid == m_pid) continue;
+                       AttachTo(pid);
+               }
+       }
+
+       void Detach() const {
+               for (const auto &pid : m_tpids) {
+                       if (ptrace(PTRACE_DETACH, pid, nullptr, nullptr) == -1) {
+                               logger.log_error("Detach from PID %d error: %s",
+                                                pid, std::system_category().default_error_condition(errno).message());
+                       }
+               }
+       }
+
+       void GetRegs(user_regs_struct *registers, pid_t pid) const {
+               if (ptrace(PTRACE_GETREGS, pid, nullptr, registers) == -1)
+                       throw std::system_error(errno, std::system_category(),
+                                                                       "failed to get registers for: " + std::to_string(pid));
+       }
+
+       std::vector<char> GetAuxv() {
+               auto auxv_name = "/proc/" + std::to_string(m_pid) + "/auxv";
+               std::ifstream file;
+
+               file.open(auxv_name, std::ios::binary);
+               if (!file)
+                       throw std::system_error(errno, std::system_category(),
+                                                                       "failed to open auxv file " + auxv_name);
+
+               std::vector<char> auxv(std::istreambuf_iterator<char>{file}, {});
+               return auxv;
+       }
+
+       void OpenCoredump(const std::string &path) {
+               m_core_file.open(path, std::ios::binary);
+               if (!m_core_file)
+                       throw std::system_error(errno, std::system_category(),
+                                                                       "failed to open coredump file " + path);
+       }
+
+       void CloseCoredump() {
+               m_core_file.close();
+       }
+
+       const std::vector<std::shared_ptr<MapRecord>> ReadMaps() {
+               Maps maps(m_pid);
+               maps.Parse();
+               return maps.Records();
+       }
+
+       void SaveRegsToPrStatusFd(const user_regs_struct &registers) {
+               prstatus_t prstatus;
+
+               memset(&prstatus, 0, sizeof(prstatus));
+               memcpy(&prstatus.pr_reg, &registers, sizeof(prstatus.pr_reg));
+               prstatus.pr_pid = m_pid;
+
+               auto buff = reinterpret_cast<char*>(mmap(nullptr, sizeof(prstatus),
+                                                        PROT_READ | PROT_WRITE,
+                                                        MAP_SHARED,
+                                                        prstatus_fd,
+                                                        0));
+               if (buff == MAP_FAILED)
+                       return;
+               memcpy(buff, &prstatus, sizeof(prstatus));
+       }
+
+       void AddNotes(const std::vector<std::shared_ptr<MapRecord>> &records) {
+               user_regs_struct registers;
+               GetRegs(&registers, m_pid);
+
+               if (prstatus_fd >= 0)
+                       SaveRegsToPrStatusFd(registers);
+
+               m_core.AddPRSTATUSNote(registers, m_pid);
+
+               for (const auto &pid : m_tpids) {
+                       if (pid == m_pid)
+                               continue;
+                       GetRegs(&registers, pid);
+                       m_core.AddPRSTATUSNote(registers, pid);
+               }
+
+               m_core.AddFILENote(records);
+               std::vector<char> auxv = GetAuxv();
+               m_core.AddAUXVNote(auxv);
+       }
+
+       bool DumpCore(const std::string &path) {
+               bool result = true;
+
+               try {
+                       Guardian guard_open_coredump([this, &path]() { OpenCoredump(path);},
+                                        [this]() { CloseCoredump(); });
+                       Guardian guard_attach([this]() { Attach(); },
+                                        [this]() { Detach(); });
+
+                       const std::vector<std::shared_ptr<MapRecord>> records = ReadMaps();
+
+                       AddNotes(records);
+                       m_core.ImportMapRecords(records);
+                       m_core.SaveELFHeader(m_core_file);
+                       m_core.SaveProgramHeadersTable(m_core_file);
+                       m_core.SaveNotes(m_core_file);
+                       std::string mem_path = std::string("/proc/" + std::to_string(m_pid) + "/mem");
+                       m_core.SaveLoadable(mem_path, m_core_file);
+               } catch (std::system_error &e) {
+                       logger.log_error("DumpCore: %s", e.what());
+                       result = false;
+               }
+
+               return result;
+       }
+};
+
+#endif  // LIVEDUMPER_HPP__
diff --git a/src/livedumper/log.hpp b/src/livedumper/log.hpp
new file mode 100644 (file)
index 0000000..f051dd1
--- /dev/null
@@ -0,0 +1,38 @@
+/*
+ * Copyright (c) 2019 Samsung Electronics Co., Ltd.
+ *
+ * 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.
+ *
+ */
+
+#ifndef LOG_HPP__
+#define LOG_HPP__
+
+enum class LogMsgType {
+       INFO,
+       WARNING,
+       ERROR
+};
+
+class Logger {
+ public:
+       void log(const LogMsgType type, const char *fmt...);
+};
+
+#define log_info(fmt, args...)    log(LogMsgType::INFO, fmt, args)
+#define log_warning(fmt, args...) log(LogMsgType::WARNING, fmt, args)
+#define log_error(fmt, args...)   log(LogMsgType::ERROR, fmt, args)
+
+extern Logger logger;
+
+#endif  // LOG_HPP__
diff --git a/src/livedumper/main.cpp b/src/livedumper/main.cpp
new file mode 100644 (file)
index 0000000..3a4430c
--- /dev/null
@@ -0,0 +1,76 @@
+/*
+ * Copyright (c) 2019 Samsung Electronics Co., Ltd.
+ *
+ * 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 "livedumper.hpp"
+
+#include <libelf.h>
+#include <unistd.h>
+
+#if defined(__arm__) || defined(__i386__)
+#define ELF_TYPE Elf32
+#elif defined(__x86_64__) || defined(__aarch64__)
+#define ELF_TYPE Elf64
+#endif
+
+void help(const char *app_name) {
+       std::cout << "Usage: " << std::endl
+                 << "  " << app_name << " [-f file_name] [-P fd] <pid>" << std::endl
+                 << std::endl
+                 << "  -f <file_name>   output path" << std::endl
+                 << "  -P <fd>          descriptor nr" << std::endl;
+}
+
+int main(int argc, char *argv[]) {
+       int opt;
+       int prstatus_fd = -1;
+
+       std::string output_file;
+
+       while ((opt = getopt(argc, argv, "hP:f:")) != -1) {
+               switch (opt) {
+               case 'P':
+                       prstatus_fd = atoi(optarg);
+                       break;
+               case 'h':
+                       help(argv[0]);
+                       exit(EXIT_SUCCESS);
+               case 'f':
+                       output_file = optarg;
+                       break;
+               default:
+                       help(argv[0]);
+                       exit(EXIT_FAILURE);
+               }
+       }
+
+       if (argc <= optind) {
+               std::cout << "PID not provided" << std::endl;
+               return -1;
+       }
+
+       std::string pid_str = std::string(argv[optind]);
+
+       if (output_file.empty())
+               output_file = "livecore." + pid_str;
+
+       LiveDumper<ELF_TYPE> dumper(std::stoi(pid_str));
+
+       if (prstatus_fd > 0)
+               dumper.setPrstatusFd(prstatus_fd);
+
+       return dumper.DumpCore(output_file) ? EXIT_SUCCESS : EXIT_FAILURE;
+}
diff --git a/src/livedumper/maps.hpp b/src/livedumper/maps.hpp
new file mode 100644 (file)
index 0000000..84a0555
--- /dev/null
@@ -0,0 +1,156 @@
+/*
+ * Copyright (c) 2019 Samsung Electronics Co., Ltd.
+ *
+ * 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.
+ *
+ */
+
+#ifndef MAPS_HPP__
+#define MAPS_HPP__
+
+#include <boost/algorithm/string/predicate.hpp>
+#include <boost/utility/string_ref.hpp>
+#include <fstream>
+#include <iostream>
+#include <memory>
+#include <stdexcept>
+#include <string>
+#include <vector>
+
+enum class Permissions {
+       None  = 0,
+       Exec  = 1 << 0,
+       Write = 1 << 1,
+       Read  = 1 << 2,
+       Priv  = 1 << 3
+};
+
+inline Permissions operator|(Permissions a, Permissions b) {
+       return static_cast<Permissions>(static_cast<int>(a) | static_cast<int>(b));
+}
+
+inline Permissions operator&(Permissions a, Permissions b) {
+       return static_cast<Permissions>(static_cast<int>(a) & static_cast<int>(b));
+}
+
+struct MapRecord {
+       uintptr_t start;
+       uintptr_t end;
+       uintptr_t offset;
+       Permissions perms;
+       std::string path;
+
+       bool IsLoadable() const {
+               // we need only readable records
+               bool result = (perms & Permissions::Read) == Permissions::Read;
+
+               // we don't need [vvar] becasue there are problems with reading
+               // on x86, and [vvar] doesn't contain useful data to stack resolving
+               result &= path != "[vvar]";
+
+               // we don't need executable, unless it is [vsyscall] or [vdso]
+               // https://lwn.net/Articles/615809/
+               result &= ((perms & Permissions::Exec) != Permissions::Exec) ||
+                       (path == "[vsyscall]" || path == "[vdso]");
+
+               result &= !boost::algorithm::ends_with(path, "locale-archive");
+
+               return result;
+       }
+};
+
+class Maps {
+ private:
+       const pid_t m_pid;
+       std::vector<std::shared_ptr<MapRecord>> m_map_records;
+
+ public:
+       explicit Maps(const pid_t pid) : m_pid(pid) {}
+
+       const std::vector<std::shared_ptr<MapRecord>> &Records() const {
+               return m_map_records;
+       }
+
+       void Parse() {
+               const std::string& maps_file_name = "/proc/" + std::to_string(m_pid) + "/maps";
+
+               std::ifstream maps_file(maps_file_name);
+               if (!maps_file)
+                       throw std::system_error(errno, std::system_category(), "failed to open mem file " + maps_file_name);
+
+               std::string line;
+               while (std::getline(maps_file, line)) {
+                       std::shared_ptr<MapRecord> record = ParseLine(line);
+
+                       if (record->IsLoadable())
+                               m_map_records.push_back(record);
+               }
+       }
+
+       // Parse line of /proc/<pid>/maps file that has the format specifier:
+       //
+       //     "%08lx-%08lx %c%c%c%c %08llx %02x:%02x %lu "
+       //
+       std::shared_ptr<MapRecord> ParseLine(boost::string_ref line) {
+               static const char FIELD_DELIMITER = ' ';
+               std::shared_ptr<MapRecord> record = std::make_shared<MapRecord>();
+
+               auto getNextToken = [&line](const char delim) -> boost::string_ref {
+                       auto pos = line.find_first_of(delim);
+                       auto result = line.substr(0, pos);
+                       line = line.substr(pos + 1);
+                       return result;
+               };
+
+               // START
+               record->start = std::stoull(std::string(getNextToken('-')), nullptr, 16);
+
+               // END
+               record->end = std::stoull(std::string(getNextToken(FIELD_DELIMITER)), nullptr, 16);
+
+               // PERMS
+               auto token = getNextToken(FIELD_DELIMITER);
+               record->perms = Permissions::None;
+
+               auto storePerm = [&record, &token](const char c, const Permissions perm) {
+                       if (token.find(c) != std::string::npos)
+                               record->perms = record->perms | perm;
+               };
+
+               storePerm('r', Permissions::Read);
+               storePerm('w', Permissions::Write);
+               storePerm('x', Permissions::Exec);
+               storePerm('p', Permissions::Priv);
+
+               // OFFSET
+               record->offset = std::stoull(std::string(getNextToken(FIELD_DELIMITER)), nullptr, 16);
+
+               // DEV - ignore
+               getNextToken(FIELD_DELIMITER);
+
+               // INODE - ignore
+               getNextToken(FIELD_DELIMITER);
+
+               // PATH
+               size_t delim = line.find_first_not_of(' ');
+               if (delim != std::string::npos) {
+                       line = line.substr(delim);
+                       record->path = std::string(line);
+               } else {
+                       record->path.clear();
+               }
+               return record;
+       }
+};
+
+#endif  // MAPS_HPP__
diff --git a/src/livedumper/note.hpp b/src/livedumper/note.hpp
new file mode 100644 (file)
index 0000000..19b9927
--- /dev/null
@@ -0,0 +1,175 @@
+/*
+ * Copyright (c) 2019 Samsung Electronics Co., Ltd.
+ *
+ * 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.
+ *
+ */
+
+#ifndef NOTE_HPP__
+#define NOTE_HPP__
+
+#include "helpers.hpp"
+#include "maps.hpp"
+
+#include <string.h>
+#include <sys/procfs.h>
+
+#include <fstream>
+#include <iostream>
+#include <memory>
+#include <string>
+#include <vector>
+
+#if defined(__arm__)
+typedef user_regs user_regs_struct;
+#define MACHINE EM_ARM
+#elif defined(__x86_64__)
+#define MACHINE EM_X86_64
+#elif defined(__i386__)
+#define MACHINE EM_386
+#endif
+
+class Note {
+ public:
+       // man elf(5): name and desc starting address has a 4 byte alignment.
+       static constexpr unsigned int PADDING = 4;
+       uint32_t name_size;
+       uint32_t desc_size;
+       uint32_t type;
+       std::string name;
+
+       explicit Note(uint32_t type) : name_size(0), desc_size(0), type(type) {}
+       virtual ~Note() {}
+
+       virtual void SaveDataTo(std::ofstream &core_file) const {}
+       virtual size_t GetSize() const {
+               size_t size = 0;
+               size += sizeof(name_size);
+               size += name_size + CalculatePadding<Note::PADDING>(name_size);
+               size += sizeof(desc_size);
+               size += desc_size + CalculatePadding<Note::PADDING>(desc_size);
+               size += sizeof(type);
+               return size;
+       }
+
+       template <int to>
+       static size_t CalculatePadding(size_t value) {
+               static_assert((to & (to - 1)) == 0, "must be power of 2");
+               return (to - value) & (to - 1);
+       }
+
+       void Pad(std::ofstream &core_file, size_t value) const {
+               static const char b = 0;
+               for (size_t i = 0; i < value; i++)
+                       core_file.write(&b, 1);
+       }
+
+       void SaveTo(std::ofstream &core_file) const {
+               core_file.write(reinterpret_cast<const char*>(&name_size), sizeof(name_size));
+               core_file.write(reinterpret_cast<const char*>(&desc_size), sizeof(desc_size));
+               core_file.write(reinterpret_cast<const char*>(&type), sizeof(type));
+               core_file.write(name.c_str(), name_size);
+               Pad(core_file, CalculatePadding<Note::PADDING>(name_size));
+               SaveDataTo(core_file);
+       }
+};
+
+class NoteCorePRStatus : public Note {
+ private:
+       prstatus_t m_prstatus;
+ public:
+       NoteCorePRStatus(const user_regs_struct &regs, const pid_t pid) : Note(NT_PRSTATUS) {
+               memset(&m_prstatus, 0, sizeof(m_prstatus));
+               name = "CORE";
+               name_size = name.length() + 1;
+               desc_size = sizeof(m_prstatus);
+               m_prstatus.pr_pid = pid;
+
+               memcpy(&m_prstatus.pr_reg, &regs, sizeof(m_prstatus.pr_reg));
+       }
+
+       void SaveDataTo(std::ofstream &core_file) const {
+               core_file.write(reinterpret_cast<const char*>(&m_prstatus), sizeof(m_prstatus));
+               Pad(core_file, CalculatePadding<Note::PADDING>(desc_size));
+       }
+};
+
+template <typename T>
+class NoteCoreFile : public Note {
+ private:
+       typename T::uint count;
+       typename T::uint page_size;
+       std::vector<std::unique_ptr<typename T::Notefile>> files;
+       std::vector<std::string> names;
+
+ public:
+       NoteCoreFile() : Note(NT_FILE), count(0), page_size(0) { }
+
+       void ImportRecords(const std::vector<std::shared_ptr<MapRecord>> &records) {
+               name = "CORE";
+               name_size = name.length() + 1;
+               desc_size = 2*sizeof(typename T::uint);
+
+               for (const auto &record : records) {
+                       if (!fileExists(record->path))
+                               continue;
+                       desc_size += sizeof(typename T::Notefile);
+                       auto *entry = new typename T::Notefile();
+                       entry->start = record->start;
+                       entry->end = record->end;
+                       entry->file_ofs = record->offset;
+                       files.push_back(std::unique_ptr<typename T::Notefile>(entry));
+
+                       desc_size += record->path.length() + 1;
+                       names.push_back(record->path);
+               }
+       }
+
+       void SaveDataTo(std::ofstream &core_file) const {
+               typename T::uint cc = files.size();
+               typename T::uint page_size = 1;
+
+               core_file.write(reinterpret_cast<const char*>(&cc),
+                               sizeof(typename T::uint));
+               core_file.write(reinterpret_cast<const char*>(&page_size),
+                               sizeof(typename T::uint));
+
+               for (const auto &file : files)
+                       core_file.write(reinterpret_cast<const char*>(file.get()), sizeof(*file));
+
+               for (const auto &name : names)
+                       core_file.write(reinterpret_cast<const char*>(name.c_str()), name.length() + 1);
+
+               Pad(core_file, CalculatePadding<Note::PADDING>(desc_size));
+       }
+};
+
+class NoteCoreAUXV : public Note {
+ private:
+       std::vector<char> m_auxv;
+
+ public:
+       NoteCoreAUXV() : Note(NT_AUXV) { }
+       void SetAuxv(const std::vector<char> &auxv) {
+               name = "CORE";
+
+               m_auxv = auxv;
+
+               name_size = name.length() + 1;
+               desc_size = m_auxv.size();
+       }
+       void SaveDataTo(std::ofstream &core_file) const {
+               core_file.write(m_auxv.data(), m_auxv.size());
+       }
+};
+#endif  // NOTE_HPP__
diff --git a/src/livedumper/program.hpp b/src/livedumper/program.hpp
new file mode 100644 (file)
index 0000000..1c5b450
--- /dev/null
@@ -0,0 +1,53 @@
+/*
+ * Copyright (c) 2019 Samsung Electronics Co., Ltd.
+ *
+ * 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.
+ *
+ */
+
+#ifndef PROGRAM_HPP__
+#define PROGRAM_HPP__
+
+#include "note.hpp"
+
+#include <vector>
+
+template <typename T>
+class ProgramTableEntryBase {
+ public:
+       typename T::ProgramTableHeader header;
+       explicit ProgramTableEntryBase(uint32_t p_type) {
+               memset(&header, 0, sizeof(header));
+               header.p_type = p_type;
+       }
+};
+
+template <typename T>
+class ProgramTableEntryNote : public ProgramTableEntryBase<T> {
+ public:
+       ProgramTableEntryNote() : ProgramTableEntryBase<T>(PT_NOTE) {
+               this->header.p_flags = PF_R;
+               this->header.p_align = 1;
+       }
+       std::vector<Note> notes;
+};
+
+template <typename T>
+class ProgramTableEntryLoad : public ProgramTableEntryBase<T> {
+ public:
+       ProgramTableEntryLoad() : ProgramTableEntryBase<T>(PT_LOAD) {
+               this->header.p_flags = PF_R;
+       }
+};
+
+#endif  // PROGRAM_HPP__
index 61ed8ea..f3685d4 100644 (file)
@@ -565,6 +565,12 @@ bool string_ends_with(const char *string, const char *suffix)
        return (string_len >= suffix_len) && !strcmp(string + string_len - suffix_len, suffix);
 }
 
+bool file_exists(const char *path)
+{
+       struct stat buf;
+       return stat(path, &buf) == 0;
+}
+
 /**
  * @}
  */
index bbae20d..561c8bb 100644 (file)
@@ -66,6 +66,8 @@ char* concatenate(char *const vec[]);
 
 bool string_ends_with(const char *string, const char *suffix);
 
+bool file_exists(const char *path);
+
 #ifdef __cplusplus
 }
 #endif
index b8015d7..07a676b 100644 (file)
@@ -36,6 +36,7 @@ configure_test("log_dump_short")
 configure_test("log_dump_normal")
 configure_test("log_dump_crash_root_path")
 configure_test("dump_systemstate_extras")
+configure_test("livedumper")
 
 get_property(TESTS_LIST GLOBAL PROPERTY TMP_TESTS_LIST)
 
diff --git a/tests/system/livedumper/livedumper.sh.template b/tests/system/livedumper/livedumper.sh.template
new file mode 100644 (file)
index 0000000..53cf1bf
--- /dev/null
@@ -0,0 +1,57 @@
+#!/bin/bash
+
+# Custom report path test
+
+if [ -z "${CRASH_WORKER_SYSTEM_TESTS}" ]; then
+    CRASH_WORKER_SYSTEM_TESTS="@CRASH_SYSTEM_TESTS_PATH@"
+fi
+
+. ${CRASH_WORKER_SYSTEM_TESTS}/utils/minicore-utils.sh
+
+mount -o rw,remount /
+
+clean_crash_dump
+
+{
+    ${CRASH_WORKER_SYSTEM_TESTS}/utils/kenny &
+    sleep 1
+    CRASH_MANAGER_OUTPUT=`crash-manager -p $! -t 555 -l -r | grep -e "^REPORT_PATH="`
+} 1> /dev/null 2>&1
+
+sleep 2
+
+echo "OUTPUT: "
+REPORT_PATH=`echo ${CRASH_MANAGER_OUTPUT} | cut -c 13-`
+
+wait_for_app crash-manager
+
+pushd ${CRASH_DUMP_PATH}
+
+if [ ! -f ${REPORT_PATH} ]; then
+    exit_with_code "Report path does not exist" 1
+fi
+
+if ! unzip ${REPORT_PATH} > /dev/null; then
+    popd
+    exit_with_code "FAIL: report not found in ${CRASH_DUMP_PATH}" 1
+fi
+
+COREDUMP=`find -name "kenny*coredump*"`
+if [[ ${COREDUMP} =~ .*tar ]]; then
+    untar_file ${COREDUMP}
+    COREDUMP=${COREDUMP%.tar}
+fi
+
+RESULT=`gdb ${CRASH_WORKER_SYSTEM_TESTS}/utils/kenny ${COREDUMP} --batch -ex "thread apply all bt"`
+popd
+
+check "MAGICNAME.*id=.*kenny.cpp:31"
+check "run.*id=.*kenny.cpp:56"
+
+if [[ $(pidof kenny) ]]; then
+    kill -9 `pidof kenny`
+else
+    exit_with_code "FAIL" 1
+fi
+
+exit_with_code "SUCCESS" 0