From 13a31ed8f8abbdec9bf0c80a16a845b503854813 Mon Sep 17 00:00:00 2001 From: Mateusz Moscicki Date: Tue, 16 Apr 2019 12:05:26 +0200 Subject: [PATCH] Add livedumper and integrate it with the crash-manager 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 --- CMakeLists.txt | 4 + include/defs.h.in | 1 + packaging/crash-worker.spec | 22 +++ packaging/crash-worker_system-tests.spec | 5 + src/crash-manager/crash-manager.c | 190 +++++++++++++++++++---- src/livedumper/CMakeLists.txt | 29 ++++ src/livedumper/clog.cpp | 35 +++++ src/livedumper/core.hpp | 199 ++++++++++++++++++++++++ src/livedumper/dlog.cpp | 46 ++++++ src/livedumper/helpers.hpp | 117 ++++++++++++++ src/livedumper/livedumper.hpp | 201 +++++++++++++++++++++++++ src/livedumper/log.hpp | 38 +++++ src/livedumper/main.cpp | 76 ++++++++++ src/livedumper/maps.hpp | 156 +++++++++++++++++++ src/livedumper/note.hpp | 175 +++++++++++++++++++++ src/livedumper/program.hpp | 53 +++++++ src/shared/util.c | 6 + src/shared/util.h | 2 + tests/system/CMakeLists.txt | 1 + tests/system/livedumper/livedumper.sh.template | 57 +++++++ 20 files changed, 1385 insertions(+), 28 deletions(-) create mode 100644 src/livedumper/CMakeLists.txt create mode 100644 src/livedumper/clog.cpp create mode 100644 src/livedumper/core.hpp create mode 100644 src/livedumper/dlog.cpp create mode 100644 src/livedumper/helpers.hpp create mode 100644 src/livedumper/livedumper.hpp create mode 100644 src/livedumper/log.hpp create mode 100644 src/livedumper/main.cpp create mode 100644 src/livedumper/maps.hpp create mode 100644 src/livedumper/note.hpp create mode 100644 src/livedumper/program.hpp create mode 100644 tests/system/livedumper/livedumper.sh.template diff --git a/CMakeLists.txt b/CMakeLists.txt index 49a9356..0053f4c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -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) diff --git a/include/defs.h.in b/include/defs.h.in index 587172d..04dbf06 100644 --- a/include/defs.h.in +++ b/include/defs.h.in @@ -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__ */ diff --git a/packaging/crash-worker.spec b/packaging/crash-worker.spec index d6e7e7a..aae948e 100644 --- a/packaging/crash-worker.spec +++ b/packaging/crash-worker.spec @@ -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 diff --git a/packaging/crash-worker_system-tests.spec b/packaging/crash-worker_system-tests.spec index a9cf4d4..38d2ed6 100644 --- a/packaging/crash-worker_system-tests.spec +++ b/packaging/crash-worker_system-tests.spec @@ -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 diff --git a/src/crash-manager/crash-manager.c b/src/crash-manager/crash-manager.c index 7a4c0b5..c87db05 100644 --- a/src/crash-manager/crash-manager.c +++ b/src/crash-manager/crash-manager.c @@ -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 #include #include +#include +#include #include #include @@ -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, ¶m, 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 index 0000000..27faf24 --- /dev/null +++ b/src/livedumper/CMakeLists.txt @@ -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 index 0000000..24726c8 --- /dev/null +++ b/src/livedumper/clog.cpp @@ -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 + +#include +#include + +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 index 0000000..d9abfe9 --- /dev/null +++ b/src/livedumper/core.hpp @@ -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 +#include +#include +#include + +#include +#include +#include +#include +#include + + +template +class Core { + private: + static constexpr size_t PAGESIZE = 0x1000; + ProgramTableEntryBase *m_note_header; + std::vector>> m_phdrt; + size_t current_data_address; + std::vector > m_notes; + typename T::Ehdr m_ehdr; + + public: + Core() : m_note_header(new ProgramTableEntryNote()), + current_data_address(sizeof(m_ehdr)) { + m_phdrt.push_back(std::unique_ptr>(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 record) { + ProgramTableEntryLoad *entry = new ProgramTableEntryLoad(); + entry->header.p_flags = static_castheader.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>(entry)); + } + + void SaveNotes(std::ofstream &core_file) { + for (auto const ¬e : m_notes) + note->SaveTo(core_file); + } + + void SaveELFHeader(std::ofstream &core_file) { + FillELFHeader(); + core_file.write(reinterpret_cast(&m_ehdr), sizeof(m_ehdr)); + } + + void SaveProgramHeadersTable(std::ofstream &core_file) { + for (const auto& record : m_phdrt) { + core_file.write(reinterpret_cast(&record->header), + sizeof(record->header)); + } + } + + bool IsEmpty(char *buff, size_t size) { + uint32_t *tmp_buff = reinterpret_cast(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(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)); + } + + void AddPRSTATUSNote(const user_regs_struct ®s, const pid_t pid) { + NoteCorePRStatus *note = new NoteCorePRStatus(regs, pid); + AddNote(note); + } + + void AddFILENote(const std::vector> &records) { + NoteCoreFile *note = new NoteCoreFile(); + note->ImportRecords(records); + AddNote(note); + } + + void AddAUXVNote(const std::vector &auxv_vector) { + NoteCoreAUXV *note = new NoteCoreAUXV(); + note->SetAuxv(auxv_vector); + AddNote(note); + } + + void ImportMapRecords(const std::vector> &records) { + current_data_address += (records.size() + 1)*sizeof(typename T::ProgramTableHeader); + m_note_header->header.p_offset = current_data_address; + + for (const auto ¬e : 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 index 0000000..9dec8ee --- /dev/null +++ b/src/livedumper/dlog.cpp @@ -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 +#include + +#include +#include + +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 index 0000000..60dcb0b --- /dev/null +++ b/src/livedumper/helpers.hpp @@ -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 +#include +#include + +#include +#include +#include + +#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 m_start; + const std::function m_end; + public: + Guardian(const std::function &start, const std::function &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 index 0000000..b478b40 --- /dev/null +++ b/src/livedumper/livedumper.hpp @@ -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 +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +template +class LiveDumper { + private: + pid_t m_pid; + std::vector m_tpids; + Core 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 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 auxv(std::istreambuf_iterator{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> ReadMaps() { + Maps maps(m_pid); + maps.Parse(); + return maps.Records(); + } + + void SaveRegsToPrStatusFd(const user_regs_struct ®isters) { + prstatus_t prstatus; + + memset(&prstatus, 0, sizeof(prstatus)); + memcpy(&prstatus.pr_reg, ®isters, sizeof(prstatus.pr_reg)); + prstatus.pr_pid = m_pid; + + auto buff = reinterpret_cast(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> &records) { + user_regs_struct registers; + GetRegs(®isters, 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(®isters, pid); + m_core.AddPRSTATUSNote(registers, pid); + } + + m_core.AddFILENote(records); + std::vector 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> 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 index 0000000..f051dd1 --- /dev/null +++ b/src/livedumper/log.hpp @@ -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 index 0000000..3a4430c --- /dev/null +++ b/src/livedumper/main.cpp @@ -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 +#include + +#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] " << std::endl + << std::endl + << " -f output path" << std::endl + << " -P 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 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 index 0000000..84a0555 --- /dev/null +++ b/src/livedumper/maps.hpp @@ -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 +#include +#include +#include +#include +#include +#include +#include + +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(static_cast(a) | static_cast(b)); +} + +inline Permissions operator&(Permissions a, Permissions b) { + return static_cast(static_cast(a) & static_cast(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> m_map_records; + + public: + explicit Maps(const pid_t pid) : m_pid(pid) {} + + const std::vector> &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 record = ParseLine(line); + + if (record->IsLoadable()) + m_map_records.push_back(record); + } + } + + // Parse line of /proc//maps file that has the format specifier: + // + // "%08lx-%08lx %c%c%c%c %08llx %02x:%02x %lu " + // + std::shared_ptr ParseLine(boost::string_ref line) { + static const char FIELD_DELIMITER = ' '; + std::shared_ptr record = std::make_shared(); + + 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 index 0000000..19b9927 --- /dev/null +++ b/src/livedumper/note.hpp @@ -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 +#include + +#include +#include +#include +#include +#include + +#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(name_size); + size += sizeof(desc_size); + size += desc_size + CalculatePadding(desc_size); + size += sizeof(type); + return size; + } + + template + 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(&name_size), sizeof(name_size)); + core_file.write(reinterpret_cast(&desc_size), sizeof(desc_size)); + core_file.write(reinterpret_cast(&type), sizeof(type)); + core_file.write(name.c_str(), name_size); + Pad(core_file, CalculatePadding(name_size)); + SaveDataTo(core_file); + } +}; + +class NoteCorePRStatus : public Note { + private: + prstatus_t m_prstatus; + public: + NoteCorePRStatus(const user_regs_struct ®s, 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, ®s, sizeof(m_prstatus.pr_reg)); + } + + void SaveDataTo(std::ofstream &core_file) const { + core_file.write(reinterpret_cast(&m_prstatus), sizeof(m_prstatus)); + Pad(core_file, CalculatePadding(desc_size)); + } +}; + +template +class NoteCoreFile : public Note { + private: + typename T::uint count; + typename T::uint page_size; + std::vector> files; + std::vector names; + + public: + NoteCoreFile() : Note(NT_FILE), count(0), page_size(0) { } + + void ImportRecords(const std::vector> &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(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(&cc), + sizeof(typename T::uint)); + core_file.write(reinterpret_cast(&page_size), + sizeof(typename T::uint)); + + for (const auto &file : files) + core_file.write(reinterpret_cast(file.get()), sizeof(*file)); + + for (const auto &name : names) + core_file.write(reinterpret_cast(name.c_str()), name.length() + 1); + + Pad(core_file, CalculatePadding(desc_size)); + } +}; + +class NoteCoreAUXV : public Note { + private: + std::vector m_auxv; + + public: + NoteCoreAUXV() : Note(NT_AUXV) { } + void SetAuxv(const std::vector &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 index 0000000..1c5b450 --- /dev/null +++ b/src/livedumper/program.hpp @@ -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 + +template +class ProgramTableEntryBase { + public: + typename T::ProgramTableHeader header; + explicit ProgramTableEntryBase(uint32_t p_type) { + memset(&header, 0, sizeof(header)); + header.p_type = p_type; + } +}; + +template +class ProgramTableEntryNote : public ProgramTableEntryBase { + public: + ProgramTableEntryNote() : ProgramTableEntryBase(PT_NOTE) { + this->header.p_flags = PF_R; + this->header.p_align = 1; + } + std::vector notes; +}; + +template +class ProgramTableEntryLoad : public ProgramTableEntryBase { + public: + ProgramTableEntryLoad() : ProgramTableEntryBase(PT_LOAD) { + this->header.p_flags = PF_R; + } +}; + +#endif // PROGRAM_HPP__ diff --git a/src/shared/util.c b/src/shared/util.c index 61ed8ea..f3685d4 100644 --- a/src/shared/util.c +++ b/src/shared/util.c @@ -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; +} + /** * @} */ diff --git a/src/shared/util.h b/src/shared/util.h index bbae20d..561c8bb 100644 --- a/src/shared/util.h +++ b/src/shared/util.h @@ -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 diff --git a/tests/system/CMakeLists.txt b/tests/system/CMakeLists.txt index b8015d7..07a676b 100644 --- a/tests/system/CMakeLists.txt +++ b/tests/system/CMakeLists.txt @@ -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 index 0000000..53cf1bf --- /dev/null +++ b/tests/system/livedumper/livedumper.sh.template @@ -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 -- 2.7.4