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; +}