From dd17672594d5de0b7e6e5539093c5e05732af580 Mon Sep 17 00:00:00 2001 From: "ramkumar.s" Date: Wed, 20 Nov 2024 10:31:33 +0000 Subject: [PATCH] Introduced an API to collect the core from an unrelated process The API WriteCoreDumpOfPID() will take argument pid, and try to collect the core from the process with pid, this uses the existing logic to identify the threads under the given process and to identify the maps and the properties of each memory maps. Then read the memory content from /proc/[pid]/mem, we assume that the caller has the permission to trace and read the mem from process [pid]. A tool also introduced, which will use the API WriteCoreDumpOfPID and provide a convenient CLI for the user, which will help in creating core dump of any process that the user has permission to trace. Following is the help from the tool: Usage: ./coredumper_tool <-c corefile> <-p pid> [-s core_size] [-z] <-h> Show this message and exit <-c core_file> Collect the core dump into a file core_file <-p pid> Collect the core dump of process with pid <-s core_size> Limit the size of core_file to core_size <-z> Compress the core_file on the fly with gzip --- CMakeLists.txt | 2 + include/coredumper/coredumper.h | 14 ++ src/coredumper.c | 19 ++ src/elfcore.cc | 393 +++++++++++++++++++++++++++----- src/libcoredumper.sym | 1 + src/linuxthreads.cc | 186 +++++++++------ src/thread_lister.h | 2 + src/utils.h | 47 ++++ test/coredumper_unittest.c | 133 ++++++++++- tool/CMakeLists.txt | 12 + tool/main.c | 113 +++++++++ 11 files changed, 793 insertions(+), 129 deletions(-) create mode 100644 src/utils.h create mode 100644 tool/CMakeLists.txt create mode 100644 tool/main.c diff --git a/CMakeLists.txt b/CMakeLists.txt index 49e71a9..40ca82e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -12,6 +12,8 @@ set(COREDUMPER_SRC_DIR "${CMAKE_CURRENT_SOURCE_DIR}") add_subdirectory(src) +add_subdirectory(tool) + include(CTest) if(BUILD_TESTING) enable_testing() diff --git a/include/coredumper/coredumper.h b/include/coredumper/coredumper.h index 71d73dc..de84129 100644 --- a/include/coredumper/coredumper.h +++ b/include/coredumper/coredumper.h @@ -36,6 +36,7 @@ #define _COREDUMP_H #include +#include #ifdef __cplusplus extern "C" { @@ -105,6 +106,11 @@ struct CoreDumpParameters { */ #define COREDUMPER_FLAG_LIMITED_BY_PRIORITY 2 +/* The core file is requested for an external process, By default we try to + * collect the core from self. + */ +#define COREDUMPER_FLAG_EXTERNAL_PROCESS 4 + /* Try compressing with either bzip2, gzip, or compress. If all of those fail, * fall back on generating an uncompressed file. */ @@ -166,6 +172,14 @@ int GetCompressedCoreDump(const struct CoredumperCompressor compressors[], */ int WriteCoreDump(const char *file_name); +/* Writes the core file of a process with pid to disk. This is a convenience + * method wrapping GetCoreDump(). If a core file could not be generated + * for any reason, -1 is returned and errno is set appropriately. + * On success, zero is returned. + */ +int WriteCoreDumpOfPID(struct CoreDumpParameters *params, + const char *file_name, pid_t pid); + /* Writes a core dump to the given file with the given parameters. */ int WriteCoreDumpWith(const struct CoreDumpParameters *params, const char *file_name); diff --git a/src/coredumper.c b/src/coredumper.c index 7115930..7935065 100644 --- a/src/coredumper.c +++ b/src/coredumper.c @@ -178,6 +178,25 @@ int WriteCoreDump(const char *file_name) { return WriteCoreDumpFunction(&frame, ¶ms, file_name); } +/* + * This variant collects the core from a process with pid. + * Useful to collect core from un-related process, this assumes that + * the caller has the permission or privilege to trace 'pid' process. + */ +int WriteCoreDumpOfPID(struct CoreDumpParameters *params, + const char *file_name, pid_t pid) { + Frame f; + + /* + * Frame cannot be used to retrieve register value for an external process + * we are just using it to pass the pid below. + */ + f.tid = pid; + + SetCoreDumpParameter(params, flags, COREDUMPER_FLAG_EXTERNAL_PROCESS); + return ListAllThreadsOfPid(&f, pid, InternalGetCoreDump, params, file_name, getenv("PATH")); +} + int WriteCoreDumpWith(const struct CoreDumpParameters *params, const char *file_name) { FRAME(frame); return WriteCoreDumpFunction(&frame, params, file_name); diff --git a/src/elfcore.cc b/src/elfcore.cc index c6a436d..ab76677 100644 --- a/src/elfcore.cc +++ b/src/elfcore.cc @@ -32,6 +32,9 @@ */ #include "elfcore.h" +#ifdef DEBUG +#include +#endif #if defined DUMPER #ifdef __cplusplus extern "C" { @@ -58,6 +61,7 @@ extern "C" { #include "linux_syscall_support.h" #include "linuxthreads.h" #include "thread_lister.h" +#include "utils.h" #ifndef CLONE_UNTRACED #define CLONE_UNTRACED 0x00800000 @@ -79,6 +83,8 @@ extern "C" { #endif #endif +#define MIN(A, B) ((A < B) ? A : B) + /* Data structures found in x86-32/64, ARM, and MIPS core dumps on Linux; * similar data structures are defined in /usr/include/{linux,asm}/... but * those headers conflict with the rest of the libc headers. So we cannot @@ -290,6 +296,39 @@ class SysCalls { do { \ } while ((fn) < 0 && ERRNO == EINTR) + +static void GenProcPath(char *proc_path, pid_t ext_pid, const char *file_name) { + + /* + * "/proc/[pid]/maps" should work for both self and external process, + * since we are going to read the memory from local address space(i.e child AS) + * we are using /proc/self for self. + */ + proc_path[0] = '\0'; + if (ext_pid) { + int pid_len; + local_strcat(proc_path, "/proc/"); + pid_len = local_itoa(&proc_path[6], ext_pid) - &proc_path[6]; + local_strcat(&proc_path[pid_len + 6], "/"); + local_strcat(&proc_path[pid_len + 7], file_name); + } else { + local_strcat(proc_path, "/proc/self/"); + local_strcat(&proc_path[11], file_name); + } +} + +/* This function will append the file name to the proc path then + * open the file, and return the fd. + */ +static int OpenProcFile(pid_t ext_pid, const char *file_name) { + char proc_path[80]; + int fd = -1; + + GenProcPath(proc_path, ext_pid, file_name); + NO_INTR(fd = sys_open(proc_path, O_RDONLY, 0)); + return fd; +} + /* Replacement memcpy. GCC's __builtin_memcpy causes cores? * Yes I know the return value isn't the same as memcpy(). */ @@ -644,11 +683,13 @@ static int WriteThreadRegs(void *handle, ssize_t (*writer)(void *, const void *, * to also return the address of VDSO Elf header, if AT_SYSINFO_EHDR * is present. */ -static void CountAUXV(size_t *pnum_auxv, size_t *pvdso_ehdr) { +static void CountAUXV(size_t *pnum_auxv, size_t *pvdso_ehdr, pid_t ext_pid) { int fd; auxv_t auxv; size_t num_auxv = 0, vdso_ehdr = 0; - NO_INTR(fd = sys_open("/proc/self/auxv", O_RDONLY, 0)); + + // /proc/[pid]/auxv + fd = OpenProcFile(ext_pid, "auxv"); if (fd >= 0) { ssize_t nread; do { @@ -711,6 +752,98 @@ static Ehdr *SanitizeVDSO(Ehdr *ehdr, size_t start, size_t end) { return ehdr; } +/* This function will update the prinfo of the process based on the key + * and value passed. + */ +static void UpdatePsInfo(prpsinfo *prinfo, char *key, char *value) { + if (strncmp(key, "Uid", 3) == 0) { + /* Only Uid and Gid will have 4 fields and separated by tabs */ + char *split_ptr = strchr(value, '\t'); + *split_ptr = '\0'; + prinfo->pr_uid = local_atoi(value); + } else if (strncmp(key, "Gid", 3) == 0) { + char *split_ptr = strchr(value, '\t'); + *split_ptr = '\0'; + prinfo->pr_gid = local_atoi(value); + } else if (strncmp(key, "PPid", 4) == 0) { + prinfo->pr_ppid = local_atoi(value); + } else if (strncmp(key, "NSpgid", 6) == 0) { + prinfo->pr_pgrp = local_atoi(value); + } +} + +/* + * This function reads the /proc/[pid]/status file to find the prinfo structure + */ +static void ReadPsStatus(prpsinfo *prinfo, pid_t ext_pid) { + struct io io_status; + char *key_start, *value_start; + char *ind; + + prinfo->pr_pid = ext_pid; + prinfo->pr_sid = sys_getsid(ext_pid); + prinfo->pr_sname = 'R'; // Emulating the process as Running + + io_status.data = io_status.end = 0; + // /proc/[pid]/status + io_status.fd = OpenProcFile(ext_pid, "status"); + if (io_status.fd >= 0) { + if (GetChar(&io_status) > 0) { + ind = (char *)io_status.buf; + key_start = ind; + while (ind < (char *)io_status.end) { + if (*ind == ':') { + *ind = '\0'; + value_start = ind + 2; /* To skip the : */ + } + if (*ind == '\n') { + *ind = '\0'; + UpdatePsInfo(prinfo, key_start, value_start); + key_start = ++ind; + } + ind++; + } + } + } +} + +/* + * This function helps to find, if we can access the given + * address from a process memory particularly vdso section of + * extenal process. + */ +static int +PeekMemAddr(size_t offset, void *dest, size_t size, pid_t ext_pid) { + int mem_fd, ret = -1; + size_t nread = 0; + + // /proc/[pid]/mem + mem_fd = OpenProcFile(ext_pid, "mem"); + if (mem_fd == -1) { +#ifdef DEBUG + fprintf(stderr, "proc mem open failed\n"); +#endif + return -1; + } + + if (sys_lseek(mem_fd, offset, SEEK_SET) < 0) { +#ifdef DEBUG + fprintf(stderr, "Seek failed for offset $p err = %d\n", offset, errno); +#endif + goto done; + } + + NO_INTR(nread = sys_read(mem_fd, dest, size)); + if (nread != size) { + goto done; + } + ret = 0; +done: + sys_close(mem_fd); + + return ret; +} + /* This function is invoked from a separate process. It has access to a * copy-on-write copy of the parents address space, and all crucial * information about the parent has been computed by the caller. @@ -718,23 +851,35 @@ static Ehdr *SanitizeVDSO(Ehdr *ehdr, size_t start, size_t end) { static int CreateElfCore(void *handle, ssize_t (*writer)(void *, const void *, size_t), int (*is_done)(void *), prpsinfo *prpsinfo, core_user *user, prstatus *prstatus, int num_threads, pid_t *pids, regs *regs, fpregs *fpregs, fpxregs *fpxregs, size_t pagesize, size_t prioritize_max_length, - pid_t main_pid, const struct CoredumperNote *extra_notes, int extra_notes_count) { + pid_t main_pid, const struct CoredumperNote *extra_notes, int extra_notes_count, pid_t ext_pid) { /* Count the number of mappings in "/proc/self/maps". We are guaranteed * that this number is not going to change while this function executes. */ int rc = -1, num_mappings = 0; + int mem_fd = -1; struct io io; +/* + * The loopback is used to validate accessiblity of an address, for self process + */ int loopback[2] = {-1, -1}; size_t num_auxv; + Ehdr vdso_ehdr; union { Ehdr *ehdr; size_t address; } vdso; - if (sys_pipe(loopback) < 0) goto done; - + if (!ext_pid) { + if (sys_pipe(loopback) < 0) goto done; + } + vdso.address = 0; + vdso_ehdr.e_phnum = 0; io.data = io.end = 0; - NO_INTR(io.fd = sys_open("/proc/self/maps", O_RDONLY, 0)); + + + + // /proc/[pid]/maps + io.fd = OpenProcFile(ext_pid, "maps"); if (io.fd >= 0) { int i, ch; while ((ch = GetChar(&io)) >= 0) { @@ -747,16 +892,45 @@ static int CreateElfCore(void *handle, ssize_t (*writer)(void *, const void *, s } NO_INTR(sys_close(io.fd)); - CountAUXV(&num_auxv, &vdso.address); + CountAUXV(&num_auxv, &vdso.address, ext_pid); + /* + * We check if we can read address pointed by vdso here. + */ + if (ext_pid && vdso.address) { + rc = PeekMemAddr(vdso.address, &vdso_ehdr, sizeof(Ehdr), ext_pid); + if (rc == -1) { +#ifdef DEBUG + fprintf(stderr, "Could not peek to vdso, addr = %p err = %d\n", vdso.address, errno); +#endif + vdso.address = 0; + } + } /* Read all mappings. This requires re-opening "/proc/self/maps" */ /* scope */ { + Phdr vdso_phdr_arr[vdso_ehdr.e_phnum]; + Phdr *vdso_phdr = &vdso_phdr_arr[0]; static const int PF_MASK = 0x00000007; struct { size_t start_address, end_address, offset, write_size; int flags; } mappings[num_mappings]; io.data = io.end = 0; - NO_INTR(io.fd = sys_open("/proc/self/smaps", O_RDONLY, 0)); + + /* Read vdso phdr here for once. */ + if (vdso.address) { + if (ext_pid) { + if (PeekMemAddr((vdso.address + vdso_ehdr.e_phoff), (void *)vdso_phdr, + sizeof(Phdr) * vdso_ehdr.e_phnum, ext_pid) < 0) { + vdso.ehdr = 0; + } else { + vdso.ehdr = &vdso_ehdr; + } + } else { + vdso_phdr = (Phdr *)(vdso.address + vdso.ehdr->e_phoff); + } + } + // /proc/[pid]/smaps + io.fd = OpenProcFile(ext_pid, "smaps"); if (io.fd >= 0) { size_t note_align; size_t num_extra_phdrs = 0; @@ -905,11 +1079,13 @@ static int CreateElfCore(void *handle, ssize_t (*writer)(void *, const void *, s */ mappings[i].flags = (mappings[i].flags >> 1) & PF_MASK; - /* Skip leading zeroed pages (as found in the stack segment) */ - if ((mappings[i].flags & PF_R) && !is_device) { - zeros = LeadingZeros(loopback, (void *)mappings[i].start_address, - mappings[i].end_address - mappings[i].start_address, pagesize); - mappings[i].start_address += zeros; + if (!ext_pid) { + /* Skip leading zeroed pages (as found in the stack segment) */ + if ((mappings[i].flags & PF_R) && !is_device) { + zeros = LeadingZeros(loopback, (void *)mappings[i].start_address, + mappings[i].end_address - mappings[i].start_address, pagesize); + mappings[i].start_address += zeros; + } } /* Write segment content if the don't dump flag is not set, and one @@ -927,17 +1103,34 @@ static int CreateElfCore(void *handle, ssize_t (*writer)(void *, const void *, s if (is_anonymous || has_anonymous_pages || (mappings[i].flags & PF_W) != 0) { mappings[i].write_size = mappings[i].end_address - mappings[i].start_address; - } else if (!is_anonymous && mappings[i].offset == 0 && mappings[i].flags & PF_R + } else if (!is_anonymous && mappings[i].offset == 0 && mappings[i].flags & PF_R) { + if (!ext_pid // Avoid using memcmp here since we are very low level, do the comparison // manually. && ((char*)mappings[i].start_address)[0] == ELFMAG0 && ((char*)mappings[i].start_address)[1] == ELFMAG1 && ((char*)mappings[i].start_address)[2] == ELFMAG2 && ((char*)mappings[i].start_address)[3] == ELFMAG3) { - mappings[i].write_size = pagesize; + mappings[i].write_size = pagesize; + } else if (ext_pid) { + /* For now we are just dumping it if other parameters + * confirms it as executable/library. Otherwise we have + * to read the proc file here just to look for the ELFMAG* + */ + mappings[i].write_size = pagesize; + } } } + /* We cannot read vsyscall segment from an external process, skip it. + * Since the vsyscall segments address is fixed we are comparing against it + * Refer https://lwn.net/Articles/446528/ + */ + if (ext_pid && mappings[i].start_address == 0xffffffffff600000 && + mappings[i].write_size == 0x1000) { + mappings[i].write_size = 0; + } + /* Remove mapping, if it was not readable, or completely zero * anyway. The former is usually the case of stack guard pages, and * the latter occasionally happens for unused memory. @@ -977,7 +1170,6 @@ static int CreateElfCore(void *handle, ssize_t (*writer)(void *, const void *, s * This isn't strictly necessary, but matches what kernel code * in fs/binfmt_elf.c does on platforms that have vdso. */ - Phdr *vdso_phdr = (Phdr *)(vdso.address + vdso.ehdr->e_phoff); for (i = 0; i < vdso.ehdr->e_phnum; i++) { if (vdso_phdr[i].p_type == PT_LOAD) { /* This will be written as "normal" mapping */ @@ -1066,7 +1258,6 @@ static int CreateElfCore(void *handle, ssize_t (*writer)(void *, const void *, s */ size_t vdso_size = 0; if (vdso.address) { - Phdr *vdso_phdr = (Phdr *)(vdso.address + vdso.ehdr->e_phoff); for (i = 0; i < vdso.ehdr->e_phnum; i++) { Phdr *p = vdso_phdr + i; if (p->p_type != PT_LOAD) { @@ -1126,7 +1317,6 @@ static int CreateElfCore(void *handle, ssize_t (*writer)(void *, const void *, s } } if (vdso.ehdr) { - Phdr *vdso_phdr = (Phdr *)(vdso.address + vdso.ehdr->e_phoff); for (i = 0; i < vdso.ehdr->e_phnum; i++) { if (vdso_phdr[i].p_type != PT_LOAD) { memcpy(&phdr, vdso_phdr + i, sizeof(Phdr)); @@ -1166,7 +1356,8 @@ static int CreateElfCore(void *handle, ssize_t (*writer)(void *, const void *, s * Without this, gdb can't unwind through vdso on i686. */ int fd, i; - NO_INTR(fd = sys_open("/proc/self/auxv", O_RDONLY, 0)); + // /proc/[pid]/auxv + fd = OpenProcFile(ext_pid, "auxv"); if (fd == -1) { goto done; } @@ -1255,26 +1446,88 @@ static int CreateElfCore(void *handle, ssize_t (*writer)(void *, const void *, s } } - /* Write all memory segments */ - for (i = 0; i < num_mappings; i++) { - if (mappings[i].write_size > 0 && - writer(handle, (void *)mappings[i].start_address, mappings[i].write_size) != mappings[i].write_size) { + if (ext_pid) { + /* Write all memory segments */ + char read_buf[pagesize]; + // /proc/[pid]/mem + mem_fd = OpenProcFile(ext_pid, "mem"); + if (mem_fd == -1) { goto done; } - } - if (vdso.address) { - /* Finally write the contents of Phdrs that "belong" to vdso. */ - Phdr *vdso_phdr = (Phdr *)(vdso.address + vdso.ehdr->e_phoff); - for (i = 0; i < vdso.ehdr->e_phnum; i++) { - Phdr *p = vdso_phdr + i; - if (p->p_type == PT_LOAD) { - /* This segment has already been dumped, because it is one of - * the mappings[]. - */ - } else if (writer(handle, (void *)p->p_vaddr, p->p_filesz) != p->p_filesz) { + for (i = 0; i < num_mappings; i++) { + if (mappings[i].write_size > 0) { + size_t pending = mappings[i].write_size; + size_t inter; + size_t nread; + if (sys_lseek(mem_fd, mappings[i].start_address, SEEK_SET) < 0) { + sys_close(mem_fd); +#ifdef DEBUG + fprintf(stderr, "Cannot Seek to address %lx err=%d\n", + mappings[i].start_address, errno); +#endif + goto done; + } + while (pending > 0) { + inter = MIN(pending, pagesize); + NO_INTR(nread = sys_read(mem_fd, read_buf, inter)); + if (nread != inter) { +#ifdef DEBUG + fprintf(stderr, "Failed to read from mem file err=%d\n", errno); +#endif + sys_close(mem_fd); + goto done; + } + if (writer(handle, (void *)read_buf, nread) != nread) { + sys_close(mem_fd); + goto done; + } + pending -= nread; + } + } + } + sys_close(mem_fd); + + if (vdso.address) { + /* Finally write the contents of Phdrs that "belong" to vdso. */ + for (i = 0; i < vdso.ehdr->e_phnum; i++) { + Phdr *p = vdso_phdr + i; + if (p->p_type == PT_LOAD) { + /* This segment has already been dumped, because it is one of + * the mappings[]. + */ + } else { + char scratch[p->p_filesz]; + + if (PeekMemAddr((size_t)p->p_vaddr, (void *)scratch, p->p_filesz, + ext_pid) < 0) { + goto done; + } + if (writer(handle, (void *)scratch, p->p_filesz) != p->p_filesz) { + goto done; + } + } + } + } + } else { // self core write + for (i = 0; i < num_mappings; i++) { + if (mappings[i].write_size > 0 && + writer(handle, (void *)mappings[i].start_address, mappings[i].write_size) != mappings[i].write_size) { goto done; } } + if (vdso.address) { + /* Finally write the contents of Phdrs that "belong" to vdso. */ + for (i = 0; i < vdso.ehdr->e_phnum; i++) { + Phdr *p = vdso_phdr + i; + if (p->p_type == PT_LOAD) { + /* This segment has already been dumped, because it is one of + * the mappings[]. + */ + } else if (writer(handle, (void *)p->p_vaddr, p->p_filesz) != p->p_filesz) { + goto done; + } + } + } } rc = 0; } @@ -1603,7 +1856,10 @@ int InternalGetCoreDump(void *frame, int num_threads, pid_t *pids, fpregs thread_fpregs[threads]; fpxregs thread_fpxregs[threads]; int pair[2]; - int main_pid = ((Frame *)frame)->tid; + char proc_path[80]; + bool ext_process = false; + pid_t main_pid = ((Frame *)frame)->tid; + pid_t ext_pid = 0; const struct CoreDumpParameters *params = va_arg(ap, const struct CoreDumpParameters *); @@ -1615,12 +1871,18 @@ int InternalGetCoreDump(void *frame, int num_threads, pid_t *pids, } } + ext_process = GetCoreDumpParameter(params, flags) & COREDUMPER_FLAG_EXTERNAL_PROCESS; + /* Get thread status */ memset(puser, 0, sizeof(struct core_user)); memset(thread_regs, 0, threads * sizeof(struct regs)); memset(thread_fpregs, 0, threads * sizeof(struct fpregs)); memset(thread_fpxregs, 0, threads * sizeof(struct fpxregs)); + if (ext_process) { + ext_pid = main_pid; + } + /* Threads are already attached, read their registers now */ #ifdef THREADS for (i = 0; i < threads; i++) { @@ -1674,7 +1936,7 @@ int InternalGetCoreDump(void *frame, int num_threads, pid_t *pids, /* Set the saved integer registers, if we are looking at the thread that * called us. */ - if (main_pid == pids[i]) { + if (ext_process == false && main_pid == pids[i]) { SET_FRAME(*(Frame *)frame, thread_regs[i]); } hasSSE = 0; @@ -1682,7 +1944,7 @@ int InternalGetCoreDump(void *frame, int num_threads, pid_t *pids, memset(scratch, 0xFF, sizeof(scratch)); if (sys_ptrace(PTRACE_GETREGS, pids[i], scratch, scratch) == 0) { memcpy(thread_regs + i, scratch, sizeof(struct regs)); - if (main_pid == pids[i]) { + if (ext_process == false && main_pid == pids[i]) { SET_FRAME(*(Frame *)frame, thread_regs[i]); } memset(scratch, 0xFF, sizeof(scratch)); @@ -1731,20 +1993,30 @@ int InternalGetCoreDump(void *frame, int num_threads, pid_t *pids, /* Build the PRPSINFO data structure */ memset(&prpsinfo, 0, sizeof(struct prpsinfo)); - prpsinfo.pr_sname = 'R'; - prpsinfo.pr_nice = sys_getpriority(PRIO_PROCESS, 0); - prpsinfo.pr_uid = sys_geteuid(); - prpsinfo.pr_gid = sys_getegid(); - prpsinfo.pr_pid = main_pid; - prpsinfo.pr_ppid = sys_getppid(); - prpsinfo.pr_pgrp = sys_getpgrp(); - prpsinfo.pr_sid = sys_getsid(0); + if (ext_process) { + /* We read the ps info from /proc/[pid]/status for + * external process + */ + ReadPsStatus(&prpsinfo, ext_pid); + } else { + prpsinfo.pr_sname = 'R'; + prpsinfo.pr_nice = sys_getpriority(PRIO_PROCESS, 0); + prpsinfo.pr_uid = sys_geteuid(); + prpsinfo.pr_gid = sys_getegid(); + prpsinfo.pr_pid = main_pid; + prpsinfo.pr_ppid = sys_getppid(); + prpsinfo.pr_pgrp = sys_getpgrp(); + prpsinfo.pr_sid = sys_getsid(0); + } /* scope */ { char scratch[4096], *cmd = scratch, *ptr; ssize_t size, len; int cmd_fd; memset(&scratch, 0, sizeof(scratch)); - size = sys_readlink("/proc/self/exe", scratch, sizeof(scratch)); + + // /proc/[pid]/exe + GenProcPath(proc_path, ext_pid, "exe"); + size = sys_readlink(proc_path, scratch, sizeof(scratch)); len = 0; for (ptr = cmd; *ptr != '\000' && size-- > 0; ptr++) { if (*ptr == '/') { @@ -1754,7 +2026,8 @@ int InternalGetCoreDump(void *frame, int num_threads, pid_t *pids, len++; } memcpy(prpsinfo.pr_fname, cmd, len > sizeof(prpsinfo.pr_fname) ? sizeof(prpsinfo.pr_fname) : len); - NO_INTR(cmd_fd = sys_open("/proc/self/cmdline", O_RDONLY, 0)); + // /proc/[pid]/cmdline + cmd_fd = OpenProcFile(ext_pid, "cmdline"); if (cmd_fd >= 0) { char *ptr; ssize_t size = c_read(cmd_fd, &prpsinfo.pr_psargs, sizeof(prpsinfo.pr_psargs), &errno); @@ -1773,12 +2046,15 @@ int InternalGetCoreDump(void *frame, int num_threads, pid_t *pids, prstatus.pr_pgrp = prpsinfo.pr_pgrp; prstatus.pr_sid = prpsinfo.pr_sid; prstatus.pr_fpvalid = 1; - NO_INTR(stat_fd = sys_open("/proc/self/stat", O_RDONLY, 0)); + // /proc/[pid]/stat + stat_fd = OpenProcFile(ext_pid, "stat"); if (stat_fd >= 0) { char scratch[4096]; ssize_t size = c_read(stat_fd, scratch, sizeof(scratch) - 1, &errno); if (size >= 0) { unsigned long tms; + int prio; + int neg = 1; char *ptr = scratch; scratch[size] = '\000'; @@ -1811,8 +2087,22 @@ int InternalGetCoreDump(void *frame, int num_threads, pid_t *pids, prstatus.pr_cstime.tv_sec = tms / 1000; prstatus.pr_cstime.tv_usec = (tms % 1000) * 1000; + /* Priority, same as sys_getpriority(PRIO_PROCESS, 0); */ + if (*ptr) ptr++; + while (*ptr && *ptr != ' ') ptr++; + + /* Nice value */ + if (*ptr) ptr++; + prio = 0; + if (*ptr == '-') { + neg = -1; + ptr++; + } + while (*ptr && *ptr != ' ') prio = 10 * prio + *ptr++ - '0'; + prpsinfo.pr_nice = prio * neg; + /* Pending signals */ - for (i = 14; i && *ptr; ptr++) + for (i = 12; i && *ptr; ptr++) if (*ptr == ' ') i--; while (*ptr && *ptr != ' ') prstatus.pr_sigpend = 10 * prstatus.pr_sigpend + *ptr++ - '0'; @@ -1935,7 +2225,8 @@ int InternalGetCoreDump(void *frame, int num_threads, pid_t *pids, } CreateElfCore(&fds[1], SimpleWriter, SimpleDone, &prpsinfo, puser, &prstatus, threads, pids, thread_regs, - thread_fpregs, hasSSE ? thread_fpxregs : NULL, pagesize, 0, main_pid, notes, note_count); + thread_fpregs, hasSSE ? thread_fpxregs : NULL, pagesize, 0, main_pid, notes, note_count, + ext_pid); NO_INTR(sys_close(fds[1])); sys__exit(0); @@ -2072,7 +2363,7 @@ int InternalGetCoreDump(void *frame, int num_threads, pid_t *pids, rc = CreateElfCore(&writer_fds, writer, PipeDone, &prpsinfo, puser, &prstatus, threads, pids, thread_regs, thread_fpregs, hasSSE ? thread_fpxregs : NULL, pagesize, prioritize ? max_length : 0, - main_pid, notes, note_count); + main_pid, notes, note_count, ext_pid); if (fds[0] >= 0) { saved_errno = errno; /* Close the input side of the compression pipeline, and flush diff --git a/src/libcoredumper.sym b/src/libcoredumper.sym index 4e3ef5d..78dcea6 100644 --- a/src/libcoredumper.sym +++ b/src/libcoredumper.sym @@ -14,6 +14,7 @@ WriteCoreDumpWith WriteCoreDumpLimited WriteCoreDumpLimitedByPriority WriteCompressedCoreDump +WriteCoreDumpOfPID ClearCoreDumpParametersInternal SetCoreDumpCompressed SetCoreDumpLimited diff --git a/src/linuxthreads.cc b/src/linuxthreads.cc index 7eba5fa..99d0d19 100644 --- a/src/linuxthreads.cc +++ b/src/linuxthreads.cc @@ -48,9 +48,12 @@ extern "C" { #include #include #include - +#ifdef DEBUG +#include +#endif #include "linux_syscall_support.h" #include "thread_lister.h" +#include "utils.h" #ifndef CLONE_UNTRACED #define CLONE_UNTRACED 0x00800000 @@ -60,21 +63,6 @@ extern "C" { */ static const int sync_signals[] = {SIGABRT, SIGILL, SIGFPE, SIGSEGV, SIGBUS, SIGXCPU, SIGXFSZ}; -/* itoa() is not a standard function, and we cannot safely call printf() - * after suspending threads. So, we just implement our own copy. A - * recursive approach is the easiest here. - */ -static char *local_itoa(char *buf, int i) { - if (i < 0) { - *buf++ = '-'; - return local_itoa(buf, -i); - } else { - if (i >= 10) buf = local_itoa(buf, i / 10); - *buf++ = (i % 10) + '0'; - *buf = '\000'; - return buf; - } -} /* Wrapper around clone() that runs "fn" on the same stack as the * caller! Unlike fork(), the cloned thread shares the same address space. @@ -110,17 +98,6 @@ static int local_clone(int (*fn)(void *), void *arg, ...) { return sys_clone(fn, (char *)&arg - 4096, CLONE_VM | CLONE_FS | CLONE_FILES | CLONE_UNTRACED, arg, 0, 0, 0); } -/* Local substitute for the atoi() function, which is not necessarily safe - * to call once threads are suspended (depending on whether libc looks up - * locale information, when executing atoi()). - */ -static int local_atoi(const char *s) { - int n = 0; - int neg = *s == '-'; - if (neg) s++; - while (*s >= '0' && *s <= '9') n = 10 * n + (*s++ - '0'); - return neg ? -n : n; -} /* Re-runs fn until it doesn't cause EINTR */ @@ -226,49 +203,63 @@ struct ListerParams { char *altstack_mem; ListAllProcessThreadsCallBack callback; void *parameter; + pid_t pid; va_list ap; }; static void ListerThread(struct ListerParams *args) { int found_parent = 0; pid_t clone_pid = sys_gettid(), ppid = sys_getppid(); - char proc_self_task[80], marker_name[48], *marker_path; + char proc_task[80], marker_name[48], *marker_path; const char *proc_paths[3]; const char *const *proc_path = proc_paths; int proc = -1, marker = -1, num_threads = 0; int max_threads = 0, sig; struct kernel_stat marker_sb, proc_sb; stack_t altstack; + pid_t expid = args->pid; + marker_path = NULL; /* Create "marker" that we can use to detect threads sharing the same * address space and the same file handles. By setting the FD_CLOEXEC flag * we minimize the risk of misidentifying child processes as threads; * and since there is still a race condition, we will filter those out * later, anyway. */ - if ((marker = sys_socket(PF_LOCAL, SOCK_DGRAM, 0)) < 0 || sys_fcntl(marker, F_SETFD, FD_CLOEXEC) < 0) { - failure: - args->result = -1; - args->err = errno; - if (marker >= 0) NO_INTR(sys_close(marker)); - sig_marker = marker = -1; - if (proc >= 0) NO_INTR(sys_close(proc)); - sig_proc = proc = -1; - sys__exit(1); - } + if (expid == 0) { + if ((marker = sys_socket(PF_LOCAL, SOCK_DGRAM, 0)) < 0 || sys_fcntl(marker, F_SETFD, FD_CLOEXEC) < 0) { + goto failure; + } /* Compute search paths for finding thread directories in /proc */ - local_itoa(strrchr(strcpy(proc_self_task, "/proc/"), '\000'), ppid); - strcpy(marker_name, proc_self_task); - marker_path = marker_name + strlen(marker_name); - strcat(proc_self_task, "/task/"); - proc_paths[0] = proc_self_task; /* /proc/$$/task/ */ - proc_paths[1] = "/proc/"; /* /proc/ */ - proc_paths[2] = NULL; - + local_itoa(strrchr(strcpy(proc_task, "/proc/"), '\000'), ppid); + strcpy(marker_name, proc_task); + marker_path = marker_name + strlen(marker_name); /* Compute path for marker socket in /proc */ - local_itoa(strcpy(marker_path, "/fd/") + 4, marker); - if (sys_stat(marker_name, &marker_sb) < 0) { + local_itoa(strcpy(marker_path, "/fd/") + 4, marker); + if (sys_stat(marker_name, &marker_sb) < 0) { + goto failure; + } + strcat(proc_task, "/task/"); + proc_paths[0] = proc_task; /* /proc/$$/task/ */ + proc_paths[1] = "/proc/"; /* /proc/ */ + proc_paths[2] = NULL; + } else { + /* Markers cannot be used to differentiate between + * process and thread, if we are tracing external process. + * Hence we cannot iterate /proc/ to find thread belong our + * process. + */ + local_itoa(strrchr(strcpy(proc_task, "/proc/"), '\000'), expid); + strcat(proc_task, "/task/"); + proc_paths[0] = proc_task; /* /proc/$$/task/ */ + proc_paths[1] = NULL; + } + + if (expid && sys_stat(proc_task, &proc_sb) < 0) { +#ifdef DEBUG + fprintf(stderr, "Cannot access proc file %s\n", proc_task); +#endif goto failure; } @@ -365,15 +356,25 @@ static void ListerThread(struct ListerParams *args) { pid = local_atoi(ptr); /* Attach (and suspend) all threads */ - if (pid && pid != clone_pid) { + if (pid) { struct kernel_stat tmp_sb; char fname[entry->d_reclen + 48]; - strcat(strcat(strcpy(fname, "/proc/"), entry->d_name), marker_path); + + /* If pid belong to the one called the library no need to proceed */ + if (expid == 0 && pid == clone_pid) continue; + + strcat(strcpy(fname, "/proc/"), entry->d_name); + if (expid == 0) { + strcat(fname, marker_path); + } /* Check if the marker is identical to the one we created */ - if (sys_stat(fname, &tmp_sb) >= 0 && marker_sb.st_ino == tmp_sb.st_ino) { + if (sys_stat(fname, &tmp_sb) >= 0) { + long i, j; + /* If we are doing self core and the marker does not match, skip */ + if (expid == 0 && marker_sb.st_ino != tmp_sb.st_ino) continue; /* Found one of our threads, make sure it is no duplicate */ for (i = 0; i < num_threads; i++) { /* Linear search is slow, but should not matter much for @@ -395,6 +396,9 @@ static void ListerThread(struct ListerParams *args) { /* Attaching to thread suspends it */ pids[num_threads++] = pid; sig_num_threads = num_threads; + if (expid && pid == expid) { + found_parent = true; + } if (sys_ptrace(PTRACE_ATTACH, pid, (void *)0, (void *)0) < 0) { /* If operation failed, ignore thread. Maybe it * just died? There might also be a race @@ -416,18 +420,20 @@ static void ListerThread(struct ListerParams *args) { } } - if (sys_ptrace(PTRACE_PEEKDATA, pid, &i, &j) || i++ != j || sys_ptrace(PTRACE_PEEKDATA, pid, &i, &j) || - i != j) { - /* Address spaces are distinct, even though both - * processes show the "marker". This is probably - * a forked child process rather than a thread. - */ - sys_ptrace_detach(pid); - num_threads--; - sig_num_threads = num_threads; - } else { - found_parent |= pid == ppid; - added_entries++; + if (expid == 0) { + if (sys_ptrace(PTRACE_PEEKDATA, pid, &i, &j) || i++ != j || sys_ptrace(PTRACE_PEEKDATA, pid, &i, &j) || + i != j) { + /* Address spaces are distinct, even though both + * processes show the "marker". This is probably + * a forked child process rather than a thread. + */ + sys_ptrace_detach(pid); + num_threads--; + sig_num_threads = num_threads; + } else { + found_parent |= pid == ppid; + added_entries++; + } } } } @@ -441,8 +447,9 @@ static void ListerThread(struct ListerParams *args) { /* If we failed to find any threads, try looking somewhere else in * /proc. Maybe, threads are reported differently on this system. */ - if (num_threads > 1 || !*++proc_path) { - NO_INTR(sys_close(marker)); + if (num_threads >= 1 || !*++proc_path) { + if (marker >= 0) + NO_INTR(sys_close(marker)); sig_marker = marker = -1; /* If we never found the parent process, something is very wrong. @@ -452,7 +459,11 @@ static void ListerThread(struct ListerParams *args) { */ if (!found_parent) { ResumeAllProcessThreads(num_threads, pids); - sys__exit(3); + if (expid == 0) { + sys__exit(3); + } else { + return; + } } /* Now we are ready to call the callback, @@ -468,7 +479,11 @@ static void ListerThread(struct ListerParams *args) { args->result = -1; } - sys__exit(0); + if (expid == 0) { + sys__exit(0); + } else { + return; + } } detach_threads: /* Resume all threads prior to retrying the operation */ @@ -479,6 +494,19 @@ static void ListerThread(struct ListerParams *args) { max_threads += 100; } } + failure: + args->result = -1; + args->err = errno; + if (marker >= 0) NO_INTR(sys_close(marker)); + sig_marker = marker = -1; + if (proc >= 0) NO_INTR(sys_close(proc)); + sig_proc = proc = -1; + if (expid == 0) { + /* Exit if we are running as a cloned process */ + sys__exit(1); + } else { + return; + } } /* This function gets the list of all linux threads of the current process @@ -534,6 +562,7 @@ int ListAllProcessThreads(void *parameter, ListAllProcessThreadsCallBack callbac args.altstack_mem = altstack_mem; args.parameter = parameter; args.callback = callback; + args.pid = 0; /* Before cloning the thread lister, block all asynchronous signals, as we */ /* are not prepared to handle them. */ @@ -623,6 +652,29 @@ int ListAllProcessThreads(void *parameter, ListAllProcessThreadsCallBack callbac return args.result; } +int ListAllThreadsOfPid(void *parameter, pid_t pid, ListAllProcessThreadsCallBack callback, ...) { + struct ListerParams args; + char altstack_mem[ALT_STACKSIZE]; + + va_start(args.ap, callback); + + memset(altstack_mem, 0, sizeof(altstack_mem)); + /* Fill in argument block for dumper thread */ + args.result = -1; + args.err = 0; + args.altstack_mem = altstack_mem; + args.parameter = parameter; + args.callback = callback; + args.pid = pid; + + ListerThread(&args); + + va_end(args.ap); + + errno = args.err; + return args.result; +} + /* This function resumes the list of all linux threads that * ListAllProcessThreads pauses before giving to its callback. * The function returns non-zero if at least one thread was diff --git a/src/thread_lister.h b/src/thread_lister.h index 9e81c85..8cf1d00 100644 --- a/src/thread_lister.h +++ b/src/thread_lister.h @@ -64,6 +64,8 @@ typedef int (*ListAllProcessThreadsCallBack)(void *parameter, int num_threads, p */ int ListAllProcessThreads(void *parameter, ListAllProcessThreadsCallBack callback, ...); +int ListAllThreadsOfPid(void *parameter, pid_t pid, ListAllProcessThreadsCallBack callback, ...); + /* This function resumes the list of all linux threads that * ListAllProcessThreads pauses before giving to its callback. * The function returns non-zero if at least one thread was diff --git a/src/utils.h b/src/utils.h new file mode 100644 index 0000000..453a6da --- /dev/null +++ b/src/utils.h @@ -0,0 +1,47 @@ +#ifndef _LOCAL_UTILS +#define _LOCAL_UTILS + +/* itoa() is not a standard function, and we cannot safely call printf() + * after suspending threads. So, we just implement our own copy. A + * recursive approach is the easiest here. + */ +static char *local_itoa(char *buf, int i) { + if (i < 0) { + *buf++ = '-'; + return local_itoa(buf, -i); + } else { + if (i >= 10) buf = local_itoa(buf, i / 10); + *buf++ = (i % 10) + '0'; + *buf = '\000'; + return buf; + } +} + +/* Local substitute for the atoi() function, which is not necessarily safe + * to call once threads are suspended (depending on whether libc looks up + * locale information, when executing atoi()). + */ +static int local_atoi(const char *s) { + int n = 0; + int neg = *s == '-'; + if (neg) s++; + while (*s >= '0' && *s <= '9') n = 10 * n + (*s++ - '0'); + return neg ? -n : n; +} + +/* Local substitute for strcat */ +static void local_strcat(char *dst, const char *src) +{ + while(*dst != '\0') { + dst++; + } + + while(*src != '\0') { + *dst++ = *src++; + } + *dst = '\0'; +} + + + +#endif /* _LOCAL_UTILS */ diff --git a/test/coredumper_unittest.c b/test/coredumper_unittest.c index 282ba75..ee5bb4c 100644 --- a/test/coredumper_unittest.c +++ b/test/coredumper_unittest.c @@ -130,7 +130,7 @@ static void *Busy(void *arg) { * might differ between runs, and it seems overkill recomputing them here. */ static void CheckWithReadElf(FILE *input, FILE *output, const char *filename, const char *suffix, - const char *decompress, const char *args) { + const char *decompress, const char *args, pid_t pid) { static const char *msg[] = { " ELF", #if __BYTE_ORDER == __LITTLE_ENDIAN @@ -169,13 +169,18 @@ static void CheckWithReadElf(FILE *input, FILE *output, const char *filename, co * the test to fail. To prevent this, ignore readelf stderr (using '2>&1' * does not suffice when stdout is fully buffered). */ - int rc = fprintf(input, + int rc; + + if (pid == 0) { + pid = getpid(); + } + rc = fprintf(input, "cat /proc/%d/maps &&" "%s %s <\"%s%s\" >core.%d &&" "%s -a core.%d 2>/dev/null; " "rm -f core.%d; " "(set +x; echo DONE)\n", - getpid(), decompress, args, filename, suffix, getpid(), getReadelf(), getpid(), getpid()); + pid, decompress, args, filename, suffix, pid, getReadelf(), pid, pid); assert(rc > 0); *buffer = '\000'; @@ -669,6 +674,53 @@ static int MyCallback(void *arg) { return 0; } +int runChildMain = 1; +/* + * This are dummy function with very similar name to the library functions + * so that same GDB template match in case of WritePidCoreDumpWith. + */ +void __attribute__ ((noinline)) WriteCoreDumpDummy(void) { + while(runChildMain); +} + +void TestCoreDumpDummy(void *arg) +{ + WriteCoreDumpDummy(); +} + +void *ControlThread(void *arg) { + char input; + int *test_child_pipe = (int *)arg; + /* Send signal to parent */ + write(test_child_pipe[1], "a", 1); + + /* Wait for parent to trigger coredump and signal */ + read(test_child_pipe[2], &input, 1); + runChildMain = 0; +} +/* + * To test WritePidCoreDumpWith we fork a process, and pass the pid to + * WritePidCoreDumpWith(). Here we are creating set of thread and wait + * for parent to signal via pipe. + */ +static void TestCoreDumpExternal(void *arg) { + enum State th_state1, th_state2; + pthread_t thread; + + pthread_create(&thread, 0, Busy, (void *)&th_state1); + pthread_create(&thread, 0, Busy, (void *)&th_state2); + + while (th_state1 != RUNNING || th_state2 != RUNNING) { + usleep(100 * 1000); + } + pthread_create(&thread, 0, ControlThread, arg); + + TestCoreDumpDummy(arg); + th_state1 = DEAD; + th_state2 = DEAD; +} + + /* Do not declare this function static, so that the compiler does not get * tempted to inline it. We want to be able to see some stack traces. */ @@ -677,8 +729,8 @@ void TestCoreDump() { {0, 0, 0}, /* Will be overwritten by test */ {0, 0, 0}}; - int loop, in[2], out[2], dummy, cmp, rc; - pid_t pid; + int loop, in[2], out[2], dummy, cmp, rc, test_child_pipe[4]; + pid_t pid, test_pid; FILE *input, *output; pthread_t thread; struct stat statBuf; @@ -778,7 +830,7 @@ void TestCoreDump() { assert(!rc); assert(!stat(core_test, &statBuf)); assert(statBuf.st_size == 60000); - CheckWithReadElf(input, output, core_test, "", "cat", ""); + CheckWithReadElf(input, output, core_test, "", "cat", "", 0); CheckPrioritizationWithReadElf(input, output, core_test); assert(!unlink(core_test)); } @@ -791,7 +843,7 @@ void TestCoreDump() { assert(compressor); assert(strstr(compressor->compressor, "gzip")); assert(!strcmp(compressor->suffix, ".gz")); - CheckWithReadElf(input, output, core_test, compressor->suffix, compressor->compressor, "-d"); + CheckWithReadElf(input, output, core_test, compressor->suffix, compressor->compressor, "-d", 0); assert(!unlink(core_test_gz)); /* Check wether fallback to uncompressed core files works */ @@ -805,7 +857,7 @@ void TestCoreDump() { assert(!rc); assert(compressor->compressor); assert(!*compressor->compressor); - CheckWithReadElf(input, output, core_test, "", "cat", ""); + CheckWithReadElf(input, output, core_test, "", "cat", "", 0); assert(!unlink(core_test)); /* Check if additional notes are written correctly to the core file */ @@ -815,7 +867,7 @@ void TestCoreDump() { assert(!SetCoreDumpLimited(¬e_params, 0x10000)); rc = (loop ? MyWriteCoreDumpWith : WriteCoreDumpWith)(¬e_params, core_test); assert(!rc); - CheckWithReadElf(input, output, core_test, "", "cat", ""); + CheckWithReadElf(input, output, core_test, "", "cat", "", 0); CheckExtraNotesWithReadElf(input, output, core_test); assert(!unlink(core_test)); @@ -827,7 +879,7 @@ void TestCoreDump() { assert(!SetCoreDumpLimited(¬e_params, 0x10000)); rc = (loop ? MyWriteCoreDumpWith : WriteCoreDumpWith)(¬e_params, core_test); assert(!rc); - CheckWithReadElf(input, output, core_test, "", "cat", ""); + CheckWithReadElf(input, output, core_test, "", "cat", "", 0); assert(count == 1); assert(!unlink(core_test)); count = -1; @@ -840,12 +892,71 @@ void TestCoreDump() { puts("Checking uncompressed core files"); rc = (loop ? MyWriteCoreDump : WriteCoreDump)(core_test); assert(!rc); - CheckWithReadElf(input, output, core_test, "", "cat", ""); + CheckWithReadElf(input, output, core_test, "", "cat", "", 0); CheckWithGDB(input, output, core_test, &dummy, cmp); unlink(core_test); } + /* Test for WritePidCoreDumpWith, we create a child process + * and try to create coredump of the process from the parent + * using WritePidCoreDumpWith. And use the readelf and GDB + * to validate the collected core. + */ + + /* Two different pipe for child to signal parent and + * parent to signal child + */ + rc = pipe(test_child_pipe); + assert(!rc); + rc = pipe(&test_child_pipe[2]); + assert(!rc); + + test_pid = fork(); + + if (test_pid == 0) { + + TestCoreDumpExternal(test_child_pipe); + + /* Signal parent, to let it know child it running after the + * coredump collection + */ + write(test_child_pipe[1], "c", 1); + puts("Exiting the test child"); + close(test_child_pipe[0]); + close(test_child_pipe[1]); + close(test_child_pipe[2]); + close(test_child_pipe[3]); + exit(0); + } else if (test_pid > 0) { + struct CoreDumpParameters params; + char child_sig; + + /* Testing external process core */ + /* Wait for child to setup threads */ + read(test_child_pipe[0], &child_sig, 1); + + puts("Checking core of external process"); + ClearCoreDumpParameters(¶ms); + rc = WriteCoreDumpOfPID(¶ms, core_test, test_pid); + assert(!rc); + CheckWithReadElf(input, output, core_test, "", "cat", "", test_pid); + CheckWithGDB(input, output, core_test, &dummy, cmp); + unlink(core_test); + + /* Signal child to exit */ + write(test_child_pipe[3], "b", 1); + + /* This is to ensure child is not blocked */ + read(test_child_pipe[0], &child_sig, 1); + assert(child_sig == 'c'); + + close(test_child_pipe[0]); + close(test_child_pipe[1]); + close(test_child_pipe[2]); + close(test_child_pipe[3]); + } + /* Stop our threads */ puts("Stopping threads"); state1 = DEAD; diff --git a/tool/CMakeLists.txt b/tool/CMakeLists.txt new file mode 100644 index 0000000..3744646 --- /dev/null +++ b/tool/CMakeLists.txt @@ -0,0 +1,12 @@ +add_executable( + coredumper_tool + main.c +) + +target_link_libraries(coredumper_tool coredumper) + +# Needs access to internal headers +target_include_directories( + coredumper_tool PRIVATE + $ +) diff --git a/tool/main.c b/tool/main.c new file mode 100644 index 0000000..6973fa6 --- /dev/null +++ b/tool/main.c @@ -0,0 +1,113 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include "coredumper/coredumper.h" + +static void +Usage(const char *arg0) +{ + fprintf(stderr, "Usage: %s <-c corefile> <-p pid> [-s core_size] [-z]\n", arg0); + fprintf(stderr, "<-h> Show this message and exit\n"); + fprintf(stderr, "<-c core_file> Collect the core dump into a file core_file\n"); + fprintf(stderr, "<-p pid> Collect the core dump of process with pid\n"); + fprintf(stderr, "<-s core_size> Limit the size of core_file to core_size\n"); + fprintf(stderr, "<-z> Compress the core_file on the fly with gzip\n"); +} + +static int +ValidateNumericArg(const char *pid_str) +{ + while ((*pid_str) != '\0') { + if (!isdigit(*(pid_str++))) { + return -1; + } + } + return 0; +} + +int +main(int num, char *argv[]) +{ + int ret = -1, c = 0; + char *pid_str = NULL; + char *corefile = NULL; + size_t core_limit = 0; + long pid = 0; + bool z_flag = false; + struct CoreDumpParameters params; + struct CoredumperCompressor *selected_compressor; + + ClearCoreDumpParameters(¶ms); + while ((c = getopt(num, argv, "c:hp:s:z")) != -1) { + switch (c) { + case 'h': + Usage(argv[0]); + return 0; + case 'c': + corefile = optarg; + break; + case 'p': + pid_str = optarg; + break; + case 's': + if (ValidateNumericArg(optarg) < 0) { + fprintf(stderr, "[%s] is not a valid size\n", optarg); + Usage(argv[0]); + return -1; + } + core_limit = strtol(optarg, NULL, 10); + /* Prioritize the smaller memory segment over larger segments */ + SetCoreDumpLimitedByPriority(¶ms, core_limit); + break; + case 'z': + SetCoreDumpCompressed(¶ms, COREDUMPER_GZIP_COMPRESSED, + &selected_compressor); + SetCoreDumpLimited(¶ms, SIZE_MAX); + z_flag = true; + break; + case ':': + Usage(argv[0]); + return -1; + case '?': + Usage(argv[0]); + return -1; + } + } + + if (optind < num) { + fprintf(stderr, "Argument <%s> unexpected\n", argv[optind]); + Usage(argv[0]); + return -1; + } + + if (pid_str == NULL || corefile == NULL) { + fprintf(stderr, "Option pid and core file is required\n", pid_str); + Usage(argv[0]); + return -1; + } + + if (ValidateNumericArg(pid_str) < 0) { + fprintf(stderr, "[%s] is not a valid pid\n", pid_str); + Usage(argv[0]); + return -1; + } + + if (z_flag && core_limit > 0) { + fprintf(stderr, "Conflicting option -z and -s\n"); + Usage(argv[0]); + return -1; + } + + pid = strtol(pid_str, NULL, 10); + + ret = WriteCoreDumpOfPID(¶ms, corefile, pid); + if (ret < 0) { + fprintf(stderr, "Failed to collect core, reason: %s\n", strerror(errno)); + } + return ret; +}