diff options
Diffstat (limited to 'lib/libproc')
-rw-r--r-- | lib/libproc/Makefile | 41 | ||||
-rw-r--r-- | lib/libproc/Makefile.depend | 20 | ||||
-rw-r--r-- | lib/libproc/Makefile.depend.options | 10 | ||||
-rw-r--r-- | lib/libproc/Makefile.inc | 1 | ||||
-rw-r--r-- | lib/libproc/_libproc.h | 89 | ||||
-rw-r--r-- | lib/libproc/libproc.h | 168 | ||||
-rw-r--r-- | lib/libproc/proc_bkpt.c | 262 | ||||
-rw-r--r-- | lib/libproc/proc_create.c | 265 | ||||
-rw-r--r-- | lib/libproc/proc_regs.c | 144 | ||||
-rw-r--r-- | lib/libproc/proc_rtld.c | 135 | ||||
-rw-r--r-- | lib/libproc/proc_sym.c | 714 | ||||
-rw-r--r-- | lib/libproc/proc_util.c | 242 | ||||
-rw-r--r-- | lib/libproc/tests/Makefile | 14 | ||||
-rw-r--r-- | lib/libproc/tests/Makefile.depend | 26 | ||||
-rw-r--r-- | lib/libproc/tests/proc_test.c | 479 | ||||
-rw-r--r-- | lib/libproc/tests/target_prog.c | 80 |
16 files changed, 2690 insertions, 0 deletions
diff --git a/lib/libproc/Makefile b/lib/libproc/Makefile new file mode 100644 index 000000000000..785007188255 --- /dev/null +++ b/lib/libproc/Makefile @@ -0,0 +1,41 @@ +.include <src.opts.mk> + +LIB= proc + +SRCS= proc_bkpt.c \ + proc_create.c \ + proc_regs.c \ + proc_sym.c \ + proc_rtld.c \ + proc_util.c + +INCS= libproc.h + +CFLAGS+= -I${.CURDIR} + +LIBADD+= cxxrt elf procstat rtld_db util z + +.if ${MK_CDDL} != "no" +LIBADD+= ctf +IGNORE_PRAGMA= YES +CFLAGS+= -DIN_BASE +CFLAGS+= -I${SRCTOP}/sys/contrib/openzfs/include +CFLAGS+= -I${SRCTOP}/sys/contrib/openzfs/lib/libspl/include/ +CFLAGS+= -I${SRCTOP}/sys/contrib/openzfs/lib/libspl/include/os/freebsd +CFLAGS+= -include ${SRCTOP}/sys/contrib/openzfs/include/os/freebsd/spl/sys/ccompile.h +CFLAGS+= -DHAVE_ISSETUGID -DHAVE_BOOLEAN -DHAVE_STRLCAT -DHAVE_STRLCPY +CFLAGS+= -I${SRCTOP}/cddl/contrib/opensolaris/lib/libctf/common \ + -I${SRCTOP}/sys/cddl/contrib/opensolaris/uts/common \ + -I${SRCTOP}/sys/cddl/compat/opensolaris +.else +CFLAGS+= -DNO_CTF +.endif + +SHLIB_MAJOR= 5 + +MAN= + +HAS_TESTS= +SUBDIR.${MK_TESTS}+= tests + +.include <bsd.lib.mk> diff --git a/lib/libproc/Makefile.depend b/lib/libproc/Makefile.depend new file mode 100644 index 000000000000..c3a6f8cb5190 --- /dev/null +++ b/lib/libproc/Makefile.depend @@ -0,0 +1,20 @@ +# Autogenerated - do NOT edit! + +DIRDEPS = \ + include \ + include/xlocale \ + lib/${CSU_DIR} \ + lib/libc \ + lib/libcompiler_rt \ + lib/libelf \ + lib/libprocstat \ + lib/librtld_db \ + lib/libutil \ + lib/libz \ + + +.include <dirdeps.mk> + +.if ${DEP_RELDIR} == ${_DEP_RELDIR} +# local dependencies - needed for -jN in clean tree +.endif diff --git a/lib/libproc/Makefile.depend.options b/lib/libproc/Makefile.depend.options new file mode 100644 index 000000000000..4c4d0162404c --- /dev/null +++ b/lib/libproc/Makefile.depend.options @@ -0,0 +1,10 @@ +# This file is not autogenerated - take care! + +DIRDEPS_OPTIONS= CDDL LIBCPLUSPLUS + +DIRDEPS.CDDL.yes= cddl/lib/libctf + +DIRDEPS.LIBCPLUSPLUS.yes= lib/libcxxrt +DIRDEPS.LIBCPLUSPLUS.no= gnu/lib/libsupc++ + +.include <dirdeps-options.mk> diff --git a/lib/libproc/Makefile.inc b/lib/libproc/Makefile.inc new file mode 100644 index 000000000000..01b5f23410c8 --- /dev/null +++ b/lib/libproc/Makefile.inc @@ -0,0 +1 @@ +.include "../Makefile.inc" diff --git a/lib/libproc/_libproc.h b/lib/libproc/_libproc.h new file mode 100644 index 000000000000..10244d874b08 --- /dev/null +++ b/lib/libproc/_libproc.h @@ -0,0 +1,89 @@ +/*- + * SPDX-License-Identifier: BSD-2-Clause + * + * Copyright (c) 2008 John Birrell (jb@freebsd.org) + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#ifndef __LIBPROC_H_ +#define __LIBPROC_H_ + +#include <sys/types.h> +#include <sys/ptrace.h> + +#include <libelf.h> +#include <rtld_db.h> + +#include "libproc.h" + +struct procstat; + +struct symtab { + Elf_Data *data; + u_int nsyms; + u_int *index; + u_long stridx; +}; + +struct file_info { + Elf *elf; + int fd; + u_int refs; + GElf_Ehdr ehdr; + + /* Symbol tables, sorted by value. */ + struct symtab dynsymtab; + struct symtab symtab; +}; + +struct map_info { + prmap_t map; + struct file_info *file; +}; + +struct proc_handle { + struct proc_handle_public public; /* Public fields. */ + int flags; /* Process flags. */ + int status; /* Process status (PS_*). */ + int wstat; /* Process wait status. */ + int model; /* Process data model. */ + rd_agent_t *rdap; /* librtld_db agent */ + struct map_info *mappings; /* File mappings for proc. */ + size_t maparrsz; /* Map array size. */ + size_t nmappings; /* Number of mappings. */ + size_t exec_map; /* Executable text mapping index. */ + lwpstatus_t lwps; /* Process status. */ + struct procstat *procstat; /* libprocstat handle. */ + char execpath[PATH_MAX]; /* Path to program executable. */ +}; + +#ifdef DEBUG +#define DPRINTF(...) warn(__VA_ARGS__) +#define DPRINTFX(...) warnx(__VA_ARGS__) +#else +#define DPRINTF(...) do { } while (0) +#define DPRINTFX(...) do { } while (0) +#endif + +#endif /* __LIBPROC_H_ */ diff --git a/lib/libproc/libproc.h b/lib/libproc/libproc.h new file mode 100644 index 000000000000..a77bdbb35b59 --- /dev/null +++ b/lib/libproc/libproc.h @@ -0,0 +1,168 @@ +/*- + * SPDX-License-Identifier: BSD-2-Clause + * + * Copyright (c) 2010 The FreeBSD Foundation + * Copyright (c) 2008 John Birrell (jb@freebsd.org) + * All rights reserved. + * + * Portions of this software were developed by Rui Paulo under sponsorship + * from the FreeBSD Foundation. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#ifndef _LIBPROC_H_ +#define _LIBPROC_H_ + +#include <gelf.h> +#include <rtld_db.h> +#include <limits.h> + +struct ctf_file; +struct proc_handle; + +typedef void (*proc_child_func)(void *); + +/* Values returned by proc_state(). */ +#define PS_IDLE 1 +#define PS_STOP 2 +#define PS_RUN 3 +#define PS_UNDEAD 4 +#define PS_DEAD 5 +#define PS_LOST 6 + +/* Flags for proc_attach(). */ +#define PATTACH_FORCE 0x01 +#define PATTACH_RDONLY 0x02 +#define PATTACH_NOSTOP 0x04 + +/* Reason values for proc_detach(). */ +#define PRELEASE_HANG 1 +#define PRELEASE_KILL 2 + +typedef struct prmap { + uintptr_t pr_vaddr; /* Virtual address. */ + size_t pr_size; /* Mapping size in bytes */ + size_t pr_offset; /* Mapping offset in object */ + char pr_mapname[PATH_MAX]; /* Mapping filename */ + uint8_t pr_mflags; /* Protection flags */ +#define MA_READ 0x01 +#define MA_WRITE 0x02 +#define MA_EXEC 0x04 +#define MA_COW 0x08 +#define MA_NEEDS_COPY 0x10 +#define MA_NOCOREDUMP 0x20 +} prmap_t; + +typedef struct prsyminfo { + u_int prs_lmid; /* Map id. */ + u_int prs_id; /* Symbol id. */ +} prsyminfo_t; + +typedef int proc_map_f(void *, const prmap_t *, const char *); +typedef int proc_sym_f(void *, const GElf_Sym *, const char *); + +/* Values for ELF sections */ +#define PR_SYMTAB 1 +#define PR_DYNSYM 2 + +/* Values for the 'mask' parameter in the iteration functions */ +#define BIND_LOCAL 0x0001 +#define BIND_GLOBAL 0x0002 +#define BIND_WEAK 0x0004 +#define BIND_ANY (BIND_LOCAL|BIND_GLOBAL|BIND_WEAK) +#define TYPE_NOTYPE 0x0100 +#define TYPE_OBJECT 0x0200 +#define TYPE_FUNC 0x0400 +#define TYPE_SECTION 0x0800 +#define TYPE_FILE 0x1000 +#define TYPE_ANY (TYPE_NOTYPE|TYPE_OBJECT|TYPE_FUNC|TYPE_SECTION|\ + TYPE_FILE) + +typedef enum { + REG_PC, + REG_SP, + REG_RVAL1, + REG_RVAL2 +} proc_reg_t; + +typedef struct lwpstatus { + int pr_why; +#define PR_REQUESTED 1 /* not implemented */ +#define PR_FAULTED 2 +#define PR_SYSENTRY 3 +#define PR_SYSEXIT 4 +#define PR_SIGNALLED 5 + int pr_what; +#define FLTBPT -1 +} lwpstatus_t; + +#define PR_MODEL_ILP32 1 +#define PR_MODEL_LP64 2 + +struct proc_handle_public { + pid_t pid; +}; + +#define proc_getpid(phdl) (((struct proc_handle_public *)(phdl))->pid) + +/* Function prototype definitions. */ +__BEGIN_DECLS + +prmap_t *proc_addr2map(struct proc_handle *, uintptr_t); +prmap_t *proc_name2map(struct proc_handle *, const char *); +char *proc_objname(struct proc_handle *, uintptr_t, char *, size_t); +int proc_iter_objs(struct proc_handle *, proc_map_f *, void *); +int proc_iter_symbyaddr(struct proc_handle *, const char *, int, + int, proc_sym_f *, void *); +int proc_addr2sym(struct proc_handle *, uintptr_t, char *, size_t, GElf_Sym *); +int proc_attach(pid_t pid, int flags, struct proc_handle **pphdl); +int proc_continue(struct proc_handle *); +int proc_clearflags(struct proc_handle *, int); +int proc_create(const char *, char * const *, char * const *, + proc_child_func *, void *, struct proc_handle **); +int proc_detach(struct proc_handle *, int); +int proc_getflags(struct proc_handle *); +int proc_name2sym(struct proc_handle *, const char *, const char *, + GElf_Sym *, prsyminfo_t *); +struct ctf_file *proc_name2ctf(struct proc_handle *, const char *); +int proc_setflags(struct proc_handle *, int); +int proc_state(struct proc_handle *); +int proc_getmodel(struct proc_handle *); +int proc_wstatus(struct proc_handle *); +int proc_getwstat(struct proc_handle *); +char * proc_signame(int, char *, size_t); +int proc_read(struct proc_handle *, void *, size_t, size_t); +const lwpstatus_t *proc_getlwpstatus(struct proc_handle *); +void proc_free(struct proc_handle *); +rd_agent_t *proc_rdagent(struct proc_handle *); +void proc_updatesyms(struct proc_handle *); +int proc_bkptset(struct proc_handle *, uintptr_t, unsigned long *); +int proc_bkptdel(struct proc_handle *, uintptr_t, unsigned long); +void proc_bkptregadj(unsigned long *); +int proc_bkptexec(struct proc_handle *, unsigned long); +int proc_regget(struct proc_handle *, proc_reg_t, unsigned long *); +int proc_regset(struct proc_handle *, proc_reg_t, unsigned long); + +__END_DECLS + +#endif /* _LIBPROC_H_ */ diff --git a/lib/libproc/proc_bkpt.c b/lib/libproc/proc_bkpt.c new file mode 100644 index 000000000000..8649da178bb9 --- /dev/null +++ b/lib/libproc/proc_bkpt.c @@ -0,0 +1,262 @@ +/*- + * SPDX-License-Identifier: BSD-2-Clause + * + * Copyright (c) 2010 The FreeBSD Foundation + * + * This software was developed by Rui Paulo under sponsorship from the + * FreeBSD Foundation. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include <sys/types.h> +#include <sys/ptrace.h> +#include <sys/wait.h> + +#include <assert.h> +#include <err.h> +#include <errno.h> +#include <signal.h> +#include <stdio.h> + +#include "_libproc.h" + +#if defined(__aarch64__) +#define AARCH64_BRK 0xd4200000 +#define AARCH64_BRK_IMM16_SHIFT 5 +#define AARCH64_BRK_IMM16_VAL (0xd << AARCH64_BRK_IMM16_SHIFT) +#define BREAKPOINT_INSTR (AARCH64_BRK | AARCH64_BRK_IMM16_VAL) +#define BREAKPOINT_INSTR_SZ 4 +#elif defined(__amd64__) || defined(__i386__) +#define BREAKPOINT_INSTR 0xcc /* int 0x3 */ +#define BREAKPOINT_INSTR_SZ 1 +#define BREAKPOINT_ADJUST_SZ BREAKPOINT_INSTR_SZ +#elif defined(__arm__) +#define BREAKPOINT_INSTR 0xe7ffffff /* bkpt */ +#define BREAKPOINT_INSTR_SZ 4 +#elif defined(__powerpc__) +#define BREAKPOINT_INSTR 0x7fe00008 /* trap */ +#define BREAKPOINT_INSTR_SZ 4 +#elif defined(__riscv) +#define BREAKPOINT_INSTR 0x00100073 /* sbreak */ +#define BREAKPOINT_INSTR_SZ 4 +#else +#error "Add support for your architecture" +#endif + +/* + * Use 4-bytes holder for breakpoint instruction on all the platforms. + * Works for x86 as well until it is endian-little platform. + * (We are coping one byte only on x86 from this 4-bytes piece of + * memory). + */ +typedef uint32_t instr_t; + +static int +proc_stop(struct proc_handle *phdl) +{ + int status; + + if (kill(proc_getpid(phdl), SIGSTOP) == -1) { + DPRINTF("kill %d", proc_getpid(phdl)); + return (-1); + } else if (waitpid(proc_getpid(phdl), &status, WSTOPPED) == -1) { + DPRINTF("waitpid %d", proc_getpid(phdl)); + return (-1); + } else if (!WIFSTOPPED(status)) { + DPRINTFX("waitpid: unexpected status 0x%x", status); + return (-1); + } + + return (0); +} + +int +proc_bkptset(struct proc_handle *phdl, uintptr_t address, + unsigned long *saved) +{ + struct ptrace_io_desc piod; + int ret = 0, stopped; + instr_t instr; + + *saved = 0; + if (phdl->status == PS_DEAD || phdl->status == PS_UNDEAD || + phdl->status == PS_IDLE) { + errno = ENOENT; + return (-1); + } + + DPRINTFX("adding breakpoint at 0x%lx", (unsigned long)address); + + stopped = 0; + if (phdl->status != PS_STOP) { + if (proc_stop(phdl) != 0) + return (-1); + stopped = 1; + } + + /* + * Read the original instruction. + */ + instr = 0; + piod.piod_op = PIOD_READ_I; + piod.piod_offs = (void *)address; + piod.piod_addr = &instr; + piod.piod_len = BREAKPOINT_INSTR_SZ; + if (ptrace(PT_IO, proc_getpid(phdl), (caddr_t)&piod, 0) < 0) { + DPRINTF("ERROR: couldn't read instruction at address 0x%jx", + (uintmax_t)address); + ret = -1; + goto done; + } + *saved = instr; + /* + * Write a breakpoint instruction to that address. + */ + instr = BREAKPOINT_INSTR; + piod.piod_op = PIOD_WRITE_I; + piod.piod_offs = (void *)address; + piod.piod_addr = &instr; + piod.piod_len = BREAKPOINT_INSTR_SZ; + if (ptrace(PT_IO, proc_getpid(phdl), (caddr_t)&piod, 0) < 0) { + DPRINTF("ERROR: couldn't write instruction at address 0x%jx", + (uintmax_t)address); + ret = -1; + goto done; + } + +done: + if (stopped) + /* Restart the process if we had to stop it. */ + proc_continue(phdl); + + return (ret); +} + +int +proc_bkptdel(struct proc_handle *phdl, uintptr_t address, + unsigned long saved) +{ + struct ptrace_io_desc piod; + int ret = 0, stopped; + instr_t instr; + + if (phdl->status == PS_DEAD || phdl->status == PS_UNDEAD || + phdl->status == PS_IDLE) { + errno = ENOENT; + return (-1); + } + + DPRINTFX("removing breakpoint at 0x%lx", (unsigned long)address); + + stopped = 0; + if (phdl->status != PS_STOP) { + if (proc_stop(phdl) != 0) + return (-1); + stopped = 1; + } + + /* + * Overwrite the breakpoint instruction that we setup previously. + */ + instr = saved; + piod.piod_op = PIOD_WRITE_I; + piod.piod_offs = (void *)address; + piod.piod_addr = &instr; + piod.piod_len = BREAKPOINT_INSTR_SZ; + if (ptrace(PT_IO, proc_getpid(phdl), (caddr_t)&piod, 0) < 0) { + DPRINTF("ERROR: couldn't write instruction at address 0x%jx", + (uintmax_t)address); + ret = -1; + } + + if (stopped) + /* Restart the process if we had to stop it. */ + proc_continue(phdl); + + return (ret); +} + +/* + * Decrement pc so that we delete the breakpoint at the correct + * address, i.e. at the BREAKPOINT_INSTR address. + * + * This is only needed on some architectures where the pc value + * when reading registers points at the instruction after the + * breakpoint, e.g. x86. + */ +void +proc_bkptregadj(unsigned long *pc) +{ + + (void)pc; +#ifdef BREAKPOINT_ADJUST_SZ + *pc = *pc - BREAKPOINT_ADJUST_SZ; +#endif +} + +/* + * Step over the breakpoint. + */ +int +proc_bkptexec(struct proc_handle *phdl, unsigned long saved) +{ + unsigned long pc; + unsigned long samesaved; + int status; + + if (proc_regget(phdl, REG_PC, &pc) < 0) { + DPRINTFX("ERROR: couldn't get PC register"); + return (-1); + } + proc_bkptregadj(&pc); + if (proc_bkptdel(phdl, pc, saved) < 0) { + DPRINTFX("ERROR: couldn't delete breakpoint"); + return (-1); + } + /* + * Go back in time and step over the new instruction just + * set up by proc_bkptdel(). + */ + proc_regset(phdl, REG_PC, pc); + if (ptrace(PT_STEP, proc_getpid(phdl), (caddr_t)1, 0) < 0) { + DPRINTFX("ERROR: ptrace step failed"); + return (-1); + } + proc_wstatus(phdl); + status = proc_getwstat(phdl); + if (!WIFSTOPPED(status)) { + DPRINTFX("ERROR: don't know why process stopped"); + return (-1); + } + /* + * Restore the breakpoint. The saved instruction should be + * the same as the one that we were passed in. + */ + if (proc_bkptset(phdl, pc, &samesaved) < 0) { + DPRINTFX("ERROR: couldn't restore breakpoint"); + return (-1); + } + assert(samesaved == saved); + + return (0); +} diff --git a/lib/libproc/proc_create.c b/lib/libproc/proc_create.c new file mode 100644 index 000000000000..0b10b186b8ca --- /dev/null +++ b/lib/libproc/proc_create.c @@ -0,0 +1,265 @@ +/*- + * SPDX-License-Identifier: BSD-2-Clause + * + * Copyright (c) 2008 John Birrell (jb@freebsd.org) + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include <sys/types.h> +#include <sys/sysctl.h> +#include <sys/user.h> +#include <sys/wait.h> + +#include <err.h> +#include <errno.h> +#include <fcntl.h> +#include <limits.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#include <libelf.h> +#include <libprocstat.h> + +#include "_libproc.h" + +extern char * const *environ; + +static int getelfclass(int); +static int proc_init(pid_t, int, int, struct proc_handle **); + +static int +getelfclass(int fd) +{ + GElf_Ehdr ehdr; + Elf *e; + int class; + + class = ELFCLASSNONE; + + if ((e = elf_begin(fd, ELF_C_READ, NULL)) == NULL) + goto out; + if (gelf_getehdr(e, &ehdr) == NULL) + goto out; + class = ehdr.e_ident[EI_CLASS]; +out: + (void)elf_end(e); + return (class); +} + +static int +proc_init(pid_t pid, int flags, int status, struct proc_handle **pphdl) +{ + struct kinfo_proc *kp; + struct proc_handle *phdl; + int error, class, count, fd; + + error = ENOMEM; + if ((phdl = malloc(sizeof(*phdl))) == NULL) + goto out; + + memset(phdl, 0, sizeof(*phdl)); + phdl->public.pid = pid; + phdl->flags = flags; + phdl->status = status; + phdl->procstat = procstat_open_sysctl(); + if (phdl->procstat == NULL) + goto out; + + /* Obtain a path to the executable. */ + if ((kp = procstat_getprocs(phdl->procstat, KERN_PROC_PID, pid, + &count)) == NULL) + goto out; + error = procstat_getpathname(phdl->procstat, kp, phdl->execpath, + sizeof(phdl->execpath)); + procstat_freeprocs(phdl->procstat, kp); + if (error != 0) + goto out; + + /* Use it to determine the data model for the process. */ + if ((fd = open(phdl->execpath, O_RDONLY)) < 0) { + error = errno; + goto out; + } + class = getelfclass(fd); + switch (class) { + case ELFCLASS64: + phdl->model = PR_MODEL_LP64; + break; + case ELFCLASS32: + phdl->model = PR_MODEL_ILP32; + break; + case ELFCLASSNONE: + default: + error = EINVAL; + break; + } + (void)close(fd); + +out: + *pphdl = phdl; + return (error); +} + +int +proc_attach(pid_t pid, int flags, struct proc_handle **pphdl) +{ + struct proc_handle *phdl; + int error, status; + + if (pid == 0 || (pid == getpid() && (flags & PATTACH_RDONLY) == 0)) + return (EINVAL); + if (elf_version(EV_CURRENT) == EV_NONE) + return (ENOENT); + + /* + * Allocate memory for the process handle, a structure containing + * all things related to the process. + */ + error = proc_init(pid, flags, PS_RUN, &phdl); + if (error != 0) + goto out; + + if ((flags & PATTACH_RDONLY) == 0) { + if (ptrace(PT_ATTACH, proc_getpid(phdl), 0, 0) != 0) { + error = errno; + DPRINTF("ERROR: cannot ptrace child process %d", pid); + goto out; + } + + /* Wait for the child process to stop. */ + if (waitpid(pid, &status, WUNTRACED) == -1) { + error = errno; + DPRINTF("ERROR: child process %d didn't stop as expected", pid); + goto out; + } + + /* Check for an unexpected status. */ + if (!WIFSTOPPED(status)) + DPRINTFX("ERROR: child process %d status 0x%x", pid, status); + else + phdl->status = PS_STOP; + + if ((flags & PATTACH_NOSTOP) != 0) + proc_continue(phdl); + } + +out: + if (error != 0 && phdl != NULL) { + proc_free(phdl); + phdl = NULL; + } + *pphdl = phdl; + return (error); +} + +int +proc_create(const char *file, char * const *argv, char * const *envp, + proc_child_func *pcf, void *child_arg, struct proc_handle **pphdl) +{ + struct proc_handle *phdl; + int error, status; + pid_t pid; + + if (elf_version(EV_CURRENT) == EV_NONE) + return (ENOENT); + + error = 0; + phdl = NULL; + + if ((pid = fork()) == -1) + error = errno; + else if (pid == 0) { + /* The child expects to be traced. */ + if (ptrace(PT_TRACE_ME, 0, 0, 0) != 0) + _exit(1); + + if (pcf != NULL) + (*pcf)(child_arg); + + if (envp != NULL) + environ = envp; + + execvp(file, argv); + + _exit(2); + /* NOTREACHED */ + } else { + /* Wait for the child process to stop. */ + if (waitpid(pid, &status, WUNTRACED) == -1) { + error = errno; + DPRINTF("ERROR: child process %d didn't stop as expected", pid); + goto bad; + } + + /* Check for an unexpected status. */ + if (!WIFSTOPPED(status)) { + error = ENOENT; + DPRINTFX("ERROR: child process %d status 0x%x", pid, status); + goto bad; + } + + /* The parent owns the process handle. */ + error = proc_init(pid, 0, PS_IDLE, &phdl); + if (error == 0) + phdl->status = PS_STOP; + +bad: + if (error != 0 && phdl != NULL) { + proc_free(phdl); + phdl = NULL; + } + } + *pphdl = phdl; + return (error); +} + +void +proc_free(struct proc_handle *phdl) +{ + struct file_info *file; + size_t i; + + for (i = 0; i < phdl->nmappings; i++) { + file = phdl->mappings[i].file; + if (file != NULL && --file->refs == 0) { + if (file->elf != NULL) { + (void)elf_end(file->elf); + (void)close(file->fd); + if (file->symtab.nsyms > 0) + free(file->symtab.index); + if (file->dynsymtab.nsyms > 0) + free(file->dynsymtab.index); + } + free(file); + } + } + if (phdl->maparrsz > 0) + free(phdl->mappings); + if (phdl->procstat != NULL) + procstat_close(phdl->procstat); + if (phdl->rdap != NULL) + rd_delete(phdl->rdap); + free(phdl); +} diff --git a/lib/libproc/proc_regs.c b/lib/libproc/proc_regs.c new file mode 100644 index 000000000000..8d41233ec108 --- /dev/null +++ b/lib/libproc/proc_regs.c @@ -0,0 +1,144 @@ +/*- + * SPDX-License-Identifier: BSD-2-Clause + * + * Copyright (c) 2010 The FreeBSD Foundation + * + * This software was developed by Rui Paulo under sponsorship from the + * FreeBSD Foundation. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include <sys/types.h> +#include <sys/ptrace.h> + +#include <err.h> +#include <stdio.h> +#include <string.h> +#include <errno.h> + +#include "_libproc.h" + +int +proc_regget(struct proc_handle *phdl, proc_reg_t reg, unsigned long *regvalue) +{ + struct reg regs; + + if (phdl->status == PS_DEAD || phdl->status == PS_UNDEAD || + phdl->status == PS_IDLE) { + errno = ENOENT; + return (-1); + } + memset(®s, 0, sizeof(regs)); + if (ptrace(PT_GETREGS, proc_getpid(phdl), (caddr_t)®s, 0) < 0) + return (-1); + switch (reg) { + case REG_PC: +#if defined(__aarch64__) + *regvalue = regs.elr; +#elif defined(__amd64__) + *regvalue = regs.r_rip; +#elif defined(__arm__) + *regvalue = regs.r_pc; +#elif defined(__i386__) + *regvalue = regs.r_eip; +#elif defined(__powerpc__) + *regvalue = regs.pc; +#elif defined(__riscv) + *regvalue = regs.sepc; +#endif + break; + case REG_SP: +#if defined(__aarch64__) + *regvalue = regs.sp; +#elif defined(__amd64__) + *regvalue = regs.r_rsp; +#elif defined(__arm__) + *regvalue = regs.r_sp; +#elif defined(__i386__) + *regvalue = regs.r_esp; +#elif defined(__powerpc__) + *regvalue = regs.fixreg[1]; +#elif defined(__riscv) + *regvalue = regs.sp; +#endif + break; + default: + DPRINTFX("ERROR: no support for reg number %d", reg); + return (-1); + } + + return (0); +} + +int +proc_regset(struct proc_handle *phdl, proc_reg_t reg, unsigned long regvalue) +{ + struct reg regs; + + if (phdl->status == PS_DEAD || phdl->status == PS_UNDEAD || + phdl->status == PS_IDLE) { + errno = ENOENT; + return (-1); + } + if (ptrace(PT_GETREGS, proc_getpid(phdl), (caddr_t)®s, 0) < 0) + return (-1); + switch (reg) { + case REG_PC: +#if defined(__aarch64__) + regs.elr = regvalue; +#elif defined(__amd64__) + regs.r_rip = regvalue; +#elif defined(__arm__) + regs.r_pc = regvalue; +#elif defined(__i386__) + regs.r_eip = regvalue; +#elif defined(__powerpc__) + regs.pc = regvalue; +#elif defined(__riscv) + regs.sepc = regvalue; +#endif + break; + case REG_SP: +#if defined(__aarch64__) + regs.sp = regvalue; +#elif defined(__amd64__) + regs.r_rsp = regvalue; +#elif defined(__arm__) + regs.r_sp = regvalue; +#elif defined(__i386__) + regs.r_esp = regvalue; +#elif defined(__powerpc__) + regs.fixreg[1] = regvalue; +#elif defined(__riscv) + regs.sp = regvalue; +#endif + break; + default: + DPRINTFX("ERROR: no support for reg number %d", reg); + return (-1); + } + if (ptrace(PT_SETREGS, proc_getpid(phdl), (caddr_t)®s, 0) < 0) + return (-1); + + return (0); +} diff --git a/lib/libproc/proc_rtld.c b/lib/libproc/proc_rtld.c new file mode 100644 index 000000000000..1d6fc732933a --- /dev/null +++ b/lib/libproc/proc_rtld.c @@ -0,0 +1,135 @@ +/*- + * SPDX-License-Identifier: BSD-2-Clause + * + * Copyright (c) 2010 The FreeBSD Foundation + * + * This software was developed by Rui Paulo under sponsorship from the + * FreeBSD Foundation. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include <sys/cdefs.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include <rtld_db.h> + +#include "_libproc.h" + +static void rdl2prmap(const rd_loadobj_t *, prmap_t *); + +static int +map_iter(const rd_loadobj_t *lop, void *arg) +{ + struct file_info *file; + struct map_info *mapping, *tmp; + struct proc_handle *phdl; + size_t i; + + phdl = arg; + if (phdl->nmappings >= phdl->maparrsz) { + phdl->maparrsz *= 2; + tmp = reallocarray(phdl->mappings, phdl->maparrsz, + sizeof(*phdl->mappings)); + if (tmp == NULL) + return (-1); + phdl->mappings = tmp; + } + + mapping = &phdl->mappings[phdl->nmappings]; + rdl2prmap(lop, &mapping->map); + if (strcmp(lop->rdl_path, phdl->execpath) == 0 && + (lop->rdl_prot & RD_RDL_X) != 0) + phdl->exec_map = phdl->nmappings; + + file = NULL; + if (lop->rdl_path[0] != '\0') { + /* Look for an existing mapping of the same file. */ + for (i = 0; i < phdl->nmappings; i++) + if (strcmp(mapping->map.pr_mapname, + phdl->mappings[i].map.pr_mapname) == 0) { + file = phdl->mappings[i].file; + break; + } + + if (file == NULL) { + file = malloc(sizeof(*file)); + if (file == NULL) + return (-1); + file->elf = NULL; + file->fd = -1; + file->refs = 1; + } else + file->refs++; + } + mapping->file = file; + phdl->nmappings++; + return (0); +} + +static void +rdl2prmap(const rd_loadobj_t *rdl, prmap_t *map) +{ + + map->pr_vaddr = rdl->rdl_saddr; + map->pr_size = rdl->rdl_eaddr - rdl->rdl_saddr; + map->pr_offset = rdl->rdl_offset; + map->pr_mflags = 0; + if (rdl->rdl_prot & RD_RDL_R) + map->pr_mflags |= MA_READ; + if (rdl->rdl_prot & RD_RDL_W) + map->pr_mflags |= MA_WRITE; + if (rdl->rdl_prot & RD_RDL_X) + map->pr_mflags |= MA_EXEC; + (void)strlcpy(map->pr_mapname, rdl->rdl_path, + sizeof(map->pr_mapname)); +} + +rd_agent_t * +proc_rdagent(struct proc_handle *phdl) +{ + + if (phdl->rdap == NULL && phdl->status != PS_UNDEAD && + phdl->status != PS_IDLE) { + if ((phdl->rdap = rd_new(phdl)) == NULL) + return (NULL); + + phdl->maparrsz = 64; + phdl->mappings = calloc(phdl->maparrsz, + sizeof(*phdl->mappings)); + if (phdl->mappings == NULL) + return (phdl->rdap); + if (rd_loadobj_iter(phdl->rdap, map_iter, phdl) != RD_OK) + return (NULL); + } + return (phdl->rdap); +} + +void +proc_updatesyms(struct proc_handle *phdl) +{ + + memset(phdl->mappings, 0, sizeof(*phdl->mappings) * phdl->maparrsz); + rd_loadobj_iter(phdl->rdap, map_iter, phdl); +} diff --git a/lib/libproc/proc_sym.c b/lib/libproc/proc_sym.c new file mode 100644 index 000000000000..be932b12a24f --- /dev/null +++ b/lib/libproc/proc_sym.c @@ -0,0 +1,714 @@ +/*- + * SPDX-License-Identifier: BSD-2-Clause + * + * Copyright (c) 2016-2017 Mark Johnston <markj@FreeBSD.org> + * Copyright (c) 2010 The FreeBSD Foundation + * Copyright (c) 2008 John Birrell (jb@freebsd.org) + * All rights reserved. + * + * Portions of this software were developed by Rui Paulo under sponsorship + * from the FreeBSD Foundation. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include <sys/types.h> +#ifndef NO_CTF +#include <sys/ctf.h> +#include <sys/ctf_api.h> +#endif +#include <sys/user.h> + +#include <assert.h> +#include <err.h> +#include <fcntl.h> +#include <libgen.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#ifndef NO_CTF +#include <libctf.h> +#endif +#include <libutil.h> + +#include <zlib.h> +#include "_libproc.h" + +#define PATH_DEBUG_DIR "/usr/lib/debug" + +#ifdef NO_CTF +typedef struct ctf_file ctf_file_t; +#endif + +#ifndef NO_CXA_DEMANGLE +extern char *__cxa_demangle(const char *, char *, size_t *, int *); +#endif /* NO_CXA_DEMANGLE */ + +static int +crc32_file(int fd, uint32_t *crc) +{ + char buf[MAXPHYS]; + ssize_t nr; + + *crc = crc32(0L, Z_NULL, 0); + while ((nr = read(fd, buf, sizeof(buf))) > 0) { + *crc = crc32(*crc, (char *)buf, nr); + } + return (!!nr); +} + +static void +demangle(const char *symbol, char *buf, size_t len) +{ +#ifndef NO_CXA_DEMANGLE + char *dembuf; + + if (symbol[0] == '_' && symbol[1] == 'Z' && symbol[2]) { + dembuf = __cxa_demangle(symbol, NULL, NULL, NULL); + if (!dembuf) + goto fail; + strlcpy(buf, dembuf, len); + free(dembuf); + return; + } +fail: +#endif /* NO_CXA_DEMANGLE */ + strlcpy(buf, symbol, len); +} + +struct symsort_thunk { + Elf *e; + struct symtab *symtab; +}; + +static int +symvalcmp(const void *a1, const void *a2, void *_thunk) +{ + GElf_Sym sym1, sym2; + struct symsort_thunk *thunk; + const char *s1, *s2; + u_int i1, i2; + int bind1, bind2; + + i1 = *(const u_int *)a1; + i2 = *(const u_int *)a2; + thunk = _thunk; + + (void)gelf_getsym(thunk->symtab->data, i1, &sym1); + (void)gelf_getsym(thunk->symtab->data, i2, &sym2); + + if (sym1.st_value != sym2.st_value) + return (sym1.st_value < sym2.st_value ? -1 : 1); + + /* Prefer non-local symbols. */ + bind1 = GELF_ST_BIND(sym1.st_info); + bind2 = GELF_ST_BIND(sym2.st_info); + if (bind1 != bind2) { + if (bind1 == STB_LOCAL && bind2 != STB_LOCAL) + return (-1); + if (bind1 != STB_LOCAL && bind2 == STB_LOCAL) + return (1); + } + + s1 = elf_strptr(thunk->e, thunk->symtab->stridx, sym1.st_name); + s2 = elf_strptr(thunk->e, thunk->symtab->stridx, sym2.st_name); + if (s1 != NULL && s2 != NULL) { + /* Prefer symbols without a leading '$'. */ + if (*s1 == '$') + return (-1); + if (*s2 == '$') + return (1); + + /* Prefer symbols with fewer leading underscores. */ + for (; *s1 == '_' && *s2 == '_'; s1++, s2++) + ; + if (*s1 == '_') + return (-1); + if (*s2 == '_') + return (1); + } + + return (0); +} + +static int +load_symtab(Elf *e, struct symtab *symtab, u_long sh_type) +{ + GElf_Ehdr ehdr; + GElf_Shdr shdr; + struct symsort_thunk thunk; + Elf_Scn *scn; + u_int nsyms; + + if (gelf_getehdr(e, &ehdr) == NULL) + return (-1); + + scn = NULL; + while ((scn = elf_nextscn(e, scn)) != NULL) { + (void)gelf_getshdr(scn, &shdr); + if (shdr.sh_type == sh_type) + break; + } + if (scn == NULL) + return (-1); + + nsyms = shdr.sh_size / shdr.sh_entsize; + if (nsyms > (1 << 20)) + return (-1); + + if ((symtab->data = elf_getdata(scn, NULL)) == NULL) + return (-1); + + symtab->index = calloc(nsyms, sizeof(u_int)); + if (symtab->index == NULL) + return (-1); + for (u_int i = 0; i < nsyms; i++) + symtab->index[i] = i; + symtab->nsyms = nsyms; + symtab->stridx = shdr.sh_link; + + thunk.e = e; + thunk.symtab = symtab; + qsort_r(symtab->index, nsyms, sizeof(u_int), symvalcmp, &thunk); + + return (0); +} + +static void +load_symtabs(struct file_info *file) +{ + + file->symtab.nsyms = file->dynsymtab.nsyms = 0; + (void)load_symtab(file->elf, &file->symtab, SHT_SYMTAB); + (void)load_symtab(file->elf, &file->dynsymtab, SHT_DYNSYM); +} + +static int +open_debug_file(char *path, const char *debugfile, uint32_t crc) +{ + size_t n; + uint32_t compcrc; + int fd; + + fd = -1; + if ((n = strlcat(path, "/", PATH_MAX)) >= PATH_MAX) + return (fd); + if (strlcat(path, debugfile, PATH_MAX) >= PATH_MAX) + goto out; + if ((fd = open(path, O_RDONLY | O_CLOEXEC)) < 0) + goto out; + if (crc32_file(fd, &compcrc) != 0 || crc != compcrc) { + DPRINTFX("ERROR: CRC32 mismatch for %s", path); + (void)close(fd); + fd = -1; + } +out: + path[n] = '\0'; + return (fd); +} + +/* + * Obtain an ELF descriptor for the specified mapped object. If a GNU debuglink + * section is present, a descriptor for the corresponding debug file is + * returned. + */ +static int +open_object(struct map_info *mapping) +{ + char path[PATH_MAX]; + GElf_Shdr shdr; + Elf *e, *e2; + Elf_Data *data; + Elf_Scn *scn; + struct file_info *file; + prmap_t *map; + const char *debugfile, *scnname; + size_t ndx; + uint32_t crc; + int fd, fd2; + + if (mapping->map.pr_mapname[0] == '\0') + return (-1); /* anonymous object */ + if (mapping->file->elf != NULL) + return (0); /* already loaded */ + + file = mapping->file; + map = &mapping->map; + if ((fd = open(map->pr_mapname, O_RDONLY | O_CLOEXEC)) < 0) { + DPRINTF("ERROR: open %s failed", map->pr_mapname); + return (-1); + } + if ((e = elf_begin(fd, ELF_C_READ, NULL)) == NULL) { + DPRINTFX("ERROR: elf_begin() failed: %s", elf_errmsg(-1)); + goto err; + } + if (gelf_getehdr(e, &file->ehdr) != &file->ehdr) { + DPRINTFX("ERROR: elf_getehdr() failed: %s", elf_errmsg(-1)); + goto err; + } + + scn = NULL; + while ((scn = elf_nextscn(e, scn)) != NULL) { + if (gelf_getshdr(scn, &shdr) != &shdr) { + DPRINTFX("ERROR: gelf_getshdr failed: %s", + elf_errmsg(-1)); + goto err; + } + if (shdr.sh_type != SHT_PROGBITS) + continue; + if (elf_getshdrstrndx(e, &ndx) != 0) { + DPRINTFX("ERROR: elf_getshdrstrndx failed: %s", + elf_errmsg(-1)); + goto err; + } + if ((scnname = elf_strptr(e, ndx, shdr.sh_name)) == NULL) + continue; + + if (strcmp(scnname, ".gnu_debuglink") == 0) + break; + } + if (scn == NULL) + goto internal; + + if ((data = elf_getdata(scn, NULL)) == NULL) { + DPRINTFX("ERROR: elf_getdata failed: %s", elf_errmsg(-1)); + goto err; + } + + /* + * The data contains a null-terminated file name followed by a 4-byte + * CRC. + */ + if (data->d_size < sizeof(crc) + 1) { + DPRINTFX("ERROR: debuglink section is too small (%zd bytes)", + (ssize_t)data->d_size); + goto internal; + } + if (strnlen(data->d_buf, data->d_size) >= data->d_size - sizeof(crc)) { + DPRINTFX("ERROR: no null-terminator in gnu_debuglink section"); + goto internal; + } + + debugfile = data->d_buf; + memcpy(&crc, (char *)data->d_buf + data->d_size - sizeof(crc), + sizeof(crc)); + + /* + * Search for the debug file using the algorithm described in the gdb + * documentation: + * - look in the directory containing the object, + * - look in the subdirectory ".debug" of the directory containing the + * object, + * - look in the global debug directories (currently /usr/lib/debug). + */ + (void)strlcpy(path, map->pr_mapname, sizeof(path)); + (void)dirname(path); + + if ((fd2 = open_debug_file(path, debugfile, crc)) >= 0) + goto external; + + if (strlcat(path, "/.debug", sizeof(path)) < sizeof(path) && + (fd2 = open_debug_file(path, debugfile, crc)) >= 0) + goto external; + + (void)snprintf(path, sizeof(path), PATH_DEBUG_DIR); + if (strlcat(path, map->pr_mapname, sizeof(path)) < sizeof(path)) { + (void)dirname(path); + if ((fd2 = open_debug_file(path, debugfile, crc)) >= 0) + goto external; + } + +internal: + /* We didn't find a debug file, just return the object's descriptor. */ + file->elf = e; + file->fd = fd; + load_symtabs(file); + return (0); + +external: + if ((e2 = elf_begin(fd2, ELF_C_READ, NULL)) == NULL) { + DPRINTFX("ERROR: elf_begin failed: %s", elf_errmsg(-1)); + (void)close(fd2); + goto err; + } + (void)elf_end(e); + (void)close(fd); + file->elf = e2; + file->fd = fd2; + load_symtabs(file); + return (0); + +err: + if (e != NULL) + (void)elf_end(e); + (void)close(fd); + return (-1); +} + +char * +proc_objname(struct proc_handle *p, uintptr_t addr, char *objname, + size_t objnamesz) +{ + prmap_t *map; + size_t i; + + if (p->nmappings == 0) + if (proc_rdagent(p) == NULL) + return (NULL); + for (i = 0; i < p->nmappings; i++) { + map = &p->mappings[i].map; + if (addr >= map->pr_vaddr && + addr < map->pr_vaddr + map->pr_size) { + strlcpy(objname, map->pr_mapname, objnamesz); + return (objname); + } + } + return (NULL); +} + +int +proc_iter_objs(struct proc_handle *p, proc_map_f *func, void *cd) +{ + char last[MAXPATHLEN], path[MAXPATHLEN], *base; + prmap_t *map; + size_t i; + int error; + + if (p->nmappings == 0) + if (proc_rdagent(p) == NULL) + return (-1); + + error = 0; + memset(last, 0, sizeof(last)); + for (i = 0; i < p->nmappings; i++) { + map = &p->mappings[i].map; + strlcpy(path, map->pr_mapname, sizeof(path)); + base = basename(path); + /* + * We shouldn't call the callback twice with the same object. + * To do that we are assuming the fact that if there are + * repeated object names (i.e. different mappings for the + * same object) they occur next to each other. + */ + if (strcmp(base, last) == 0) + continue; + if ((error = (*func)(cd, map, base)) != 0) + break; + strlcpy(last, path, sizeof(last)); + } + return (error); +} + +static struct map_info * +_proc_addr2map(struct proc_handle *p, uintptr_t addr) +{ + struct map_info *mapping; + size_t i; + + if (p->nmappings == 0) + if (proc_rdagent(p) == NULL) + return (NULL); + for (i = 0; i < p->nmappings; i++) { + mapping = &p->mappings[i]; + if (addr >= mapping->map.pr_vaddr && + addr < mapping->map.pr_vaddr + mapping->map.pr_size) + return (mapping); + } + return (NULL); +} + +prmap_t * +proc_addr2map(struct proc_handle *p, uintptr_t addr) +{ + + return (&_proc_addr2map(p, addr)->map); +} + +/* + * Look up the symbol at addr using a binary search, returning a copy of the + * symbol and its name. + */ +static int +lookup_symbol_by_addr(Elf *e, struct symtab *symtab, uintptr_t addr, + const char **namep, GElf_Sym *symp) +{ + GElf_Sym sym; + Elf_Data *data; + const char *s; + u_int i, min, max, mid; + + if (symtab->nsyms == 0) + return (ENOENT); + + data = symtab->data; + min = 0; + max = symtab->nsyms - 1; + + while (min <= max) { + mid = (max + min) / 2; + (void)gelf_getsym(data, symtab->index[mid], &sym); + if (addr >= sym.st_value && addr < sym.st_value + sym.st_size) + break; + + if (addr < sym.st_value) + max = mid - 1; + else + min = mid + 1; + } + if (min > max) + return (ENOENT); + + /* + * Advance until we find the matching symbol with largest index. + */ + for (i = mid; i < symtab->nsyms; i++) { + (void)gelf_getsym(data, symtab->index[i], &sym); + if (addr < sym.st_value || addr >= sym.st_value + sym.st_size) + break; + } + (void)gelf_getsym(data, symtab->index[i - 1], symp); + s = elf_strptr(e, symtab->stridx, symp->st_name); + if (s != NULL && namep != NULL) + *namep = s; + return (0); +} + +int +proc_addr2sym(struct proc_handle *p, uintptr_t addr, char *name, + size_t namesz, GElf_Sym *symcopy) +{ + struct file_info *file; + struct map_info *mapping; + const char *s; + uintptr_t off; + int error; + + if ((mapping = _proc_addr2map(p, addr)) == NULL) { + DPRINTFX("ERROR: proc_addr2map failed to resolve 0x%jx", (uintmax_t)addr); + return (-1); + } + if (open_object(mapping) != 0) { + DPRINTFX("ERROR: failed to open object %s", + mapping->map.pr_mapname); + return (-1); + } + + file = mapping->file; + off = file->ehdr.e_type == ET_DYN ? + mapping->map.pr_vaddr - mapping->map.pr_offset : 0; + if (addr < off) + return (ENOENT); + addr -= off; + + error = lookup_symbol_by_addr(file->elf, &file->dynsymtab, addr, &s, + symcopy); + if (error == ENOENT) + error = lookup_symbol_by_addr(file->elf, &file->symtab, addr, + &s, symcopy); + if (error == 0) { + symcopy->st_value += off; + demangle(s, name, namesz); + } + return (error); +} + +static struct map_info * +_proc_name2map(struct proc_handle *p, const char *name) +{ + char path[MAXPATHLEN], *base; + struct map_info *mapping; + size_t i, len; + + if ((len = strlen(name)) == 0) + return (NULL); + if (p->nmappings == 0) + if (proc_rdagent(p) == NULL) + return (NULL); + for (i = 0; i < p->nmappings; i++) { + mapping = &p->mappings[i]; + (void)strlcpy(path, mapping->map.pr_mapname, sizeof(path)); + base = basename(path); + if (strcmp(base, name) == 0) + return (mapping); + } + /* If we didn't find a match, try matching prefixes of the basename. */ + for (i = 0; i < p->nmappings; i++) { + mapping = &p->mappings[i]; + strlcpy(path, mapping->map.pr_mapname, sizeof(path)); + base = basename(path); + if (strncmp(base, name, len) == 0) + return (mapping); + } + if (strcmp(name, "a.out") == 0) + return (_proc_addr2map(p, + p->mappings[p->exec_map].map.pr_vaddr)); + return (NULL); +} + +prmap_t * +proc_name2map(struct proc_handle *p, const char *name) +{ + + return (&_proc_name2map(p, name)->map); +} + +/* + * Look up the symbol with the given name and return a copy of it. + */ +static int +lookup_symbol_by_name(Elf *elf, struct symtab *symtab, const char *symbol, + GElf_Sym *symcopy, prsyminfo_t *si) +{ + GElf_Sym sym; + Elf_Data *data; + char *s; + int i; + + if (symtab->nsyms == 0) + return (ENOENT); + data = symtab->data; + for (i = 0; gelf_getsym(data, i, &sym) != NULL; i++) { + s = elf_strptr(elf, symtab->stridx, sym.st_name); + if (s != NULL && strcmp(s, symbol) == 0) { + memcpy(symcopy, &sym, sizeof(*symcopy)); + if (si != NULL) + si->prs_id = i; + return (0); + } + } + return (ENOENT); +} + +int +proc_name2sym(struct proc_handle *p, const char *object, const char *symbol, + GElf_Sym *symcopy, prsyminfo_t *si) +{ + struct file_info *file; + struct map_info *mapping; + uintptr_t off; + int error; + + if ((mapping = _proc_name2map(p, object)) == NULL) { + DPRINTFX("ERROR: proc_name2map failed to resolve %s", object); + return (-1); + } + if (open_object(mapping) != 0) { + DPRINTFX("ERROR: failed to open object %s", + mapping->map.pr_mapname); + return (-1); + } + + file = mapping->file; + off = file->ehdr.e_type == ET_DYN ? + mapping->map.pr_vaddr - mapping->map.pr_offset : 0; + + error = lookup_symbol_by_name(file->elf, &file->dynsymtab, symbol, + symcopy, si); + if (error == ENOENT) + error = lookup_symbol_by_name(file->elf, &file->symtab, symbol, + symcopy, si); + if (error == 0) + symcopy->st_value += off; + return (error); +} + +ctf_file_t * +proc_name2ctf(struct proc_handle *p, const char *name) +{ +#ifndef NO_CTF + ctf_file_t *ctf; + prmap_t *map; + int error; + + if ((map = proc_name2map(p, name)) == NULL) + return (NULL); + + ctf = ctf_open(map->pr_mapname, &error); + return (ctf); +#else + (void)p; + (void)name; + return (NULL); +#endif +} + +int +proc_iter_symbyaddr(struct proc_handle *p, const char *object, int which, + int mask, proc_sym_f *func, void *cd) +{ + GElf_Sym sym; + struct file_info *file; + struct map_info *mapping; + struct symtab *symtab; + const char *s; + int error, i; + + if ((mapping = _proc_name2map(p, object)) == NULL) { + DPRINTFX("ERROR: proc_name2map failed to resolve %s", object); + return (-1); + } + if (open_object(mapping) != 0) { + DPRINTFX("ERROR: failed to open object %s", + mapping->map.pr_mapname); + return (-1); + } + + file = mapping->file; + symtab = which == PR_SYMTAB ? &file->symtab : &file->dynsymtab; + if (symtab->nsyms == 0) + return (-1); + + error = 0; + for (i = 0; gelf_getsym(symtab->data, i, &sym) != NULL; i++) { + if (GELF_ST_BIND(sym.st_info) == STB_LOCAL && + (mask & BIND_LOCAL) == 0) + continue; + if (GELF_ST_BIND(sym.st_info) == STB_GLOBAL && + (mask & BIND_GLOBAL) == 0) + continue; + if (GELF_ST_BIND(sym.st_info) == STB_WEAK && + (mask & BIND_WEAK) == 0) + continue; + if (GELF_ST_TYPE(sym.st_info) == STT_NOTYPE && + (mask & TYPE_NOTYPE) == 0) + continue; + if (GELF_ST_TYPE(sym.st_info) == STT_OBJECT && + (mask & TYPE_OBJECT) == 0) + continue; + if (GELF_ST_TYPE(sym.st_info) == STT_FUNC && + (mask & TYPE_FUNC) == 0) + continue; + if (GELF_ST_TYPE(sym.st_info) == STT_SECTION && + (mask & TYPE_SECTION) == 0) + continue; + if (GELF_ST_TYPE(sym.st_info) == STT_FILE && + (mask & TYPE_FILE) == 0) + continue; + s = elf_strptr(file->elf, symtab->stridx, sym.st_name); + if (file->ehdr.e_type == ET_DYN) + sym.st_value += mapping->map.pr_vaddr; + if ((error = (*func)(cd, &sym, s)) != 0) + break; + } + return (error); +} diff --git a/lib/libproc/proc_util.c b/lib/libproc/proc_util.c new file mode 100644 index 000000000000..a062b9a1f461 --- /dev/null +++ b/lib/libproc/proc_util.c @@ -0,0 +1,242 @@ +/*- + * SPDX-License-Identifier: BSD-2-Clause + * + * Copyright (c) 2010 The FreeBSD Foundation + * Copyright (c) 2008 John Birrell (jb@freebsd.org) + * All rights reserved. + * + * Portions of this software were developed by Rui Paulo under sponsorship + * from the FreeBSD Foundation. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include <sys/types.h> +#include <sys/ptrace.h> +#include <sys/wait.h> + +#include <err.h> +#include <errno.h> +#include <signal.h> +#include <stdio.h> +#include <string.h> +#include <unistd.h> + +#include "_libproc.h" + +int +proc_clearflags(struct proc_handle *phdl, int mask) +{ + + if (phdl == NULL) + return (EINVAL); + + phdl->flags &= ~mask; + + return (0); +} + +/* + * NB: we return -1 as the Solaris libproc Psetrun() function. + */ +int +proc_continue(struct proc_handle *phdl) +{ + int pending; + + if (phdl == NULL) + return (-1); + + if (phdl->status == PS_STOP && WSTOPSIG(phdl->wstat) != SIGTRAP) + pending = WSTOPSIG(phdl->wstat); + else + pending = 0; + if (ptrace(PT_CONTINUE, proc_getpid(phdl), (caddr_t)(uintptr_t)1, + pending) != 0) + return (-1); + + phdl->status = PS_RUN; + + return (0); +} + +int +proc_detach(struct proc_handle *phdl, int reason) +{ + int status; + int request; + pid_t pid; + + if (phdl == NULL) + return (EINVAL); + if (reason == PRELEASE_HANG) + return (EINVAL); + if ((phdl->flags & PATTACH_RDONLY) != 0) + goto free; + request = (reason == PRELEASE_KILL) ? PT_KILL : PT_DETACH; + pid = proc_getpid(phdl); + if (ptrace(request, pid, 0, 0) != 0 && errno == EBUSY) { + kill(pid, SIGSTOP); + waitpid(pid, &status, WUNTRACED); + ptrace(request, pid, 0, 0); + kill(pid, SIGCONT); + } +free: + proc_free(phdl); + return (0); +} + +int +proc_getflags(struct proc_handle *phdl) +{ + + if (phdl == NULL) + return (-1); + + return (phdl->flags); +} + +int +proc_setflags(struct proc_handle *phdl, int mask) +{ + + if (phdl == NULL) + return (EINVAL); + + phdl->flags |= mask; + + return (0); +} + +int +proc_state(struct proc_handle *phdl) +{ + + if (phdl == NULL) + return (-1); + + return (phdl->status); +} + +int +proc_getmodel(struct proc_handle *phdl) +{ + + if (phdl == NULL) + return (-1); + + return (phdl->model); +} + +int +proc_wstatus(struct proc_handle *phdl) +{ + int status; + + if (phdl == NULL) + return (-1); + if (waitpid(proc_getpid(phdl), &status, WUNTRACED) < 0) { + if (errno != EINTR) + DPRINTF("waitpid"); + return (-1); + } + if (WIFSTOPPED(status)) + phdl->status = PS_STOP; + if (WIFEXITED(status) || WIFSIGNALED(status)) + phdl->status = PS_UNDEAD; + phdl->wstat = status; + + return (phdl->status); +} + +int +proc_getwstat(struct proc_handle *phdl) +{ + + if (phdl == NULL) + return (-1); + + return (phdl->wstat); +} + +char * +proc_signame(int sig, char *name, size_t namesz) +{ + char buf[SIG2STR_MAX]; + + if (sig2str(sig, buf) == 0) + (void)snprintf(name, namesz, "SIG%s", buf); + else + (void)snprintf(name, namesz, "SIG#%d", sig); + + return (name); +} + +int +proc_read(struct proc_handle *phdl, void *buf, size_t size, size_t addr) +{ + struct ptrace_io_desc piod; + + if (phdl == NULL) + return (-1); + piod.piod_op = PIOD_READ_D; + piod.piod_len = size; + piod.piod_addr = (void *)buf; + piod.piod_offs = (void *)addr; + + if (ptrace(PT_IO, proc_getpid(phdl), (caddr_t)&piod, 0) < 0) + return (-1); + return (piod.piod_len); +} + +const lwpstatus_t * +proc_getlwpstatus(struct proc_handle *phdl) +{ + struct ptrace_lwpinfo lwpinfo; + lwpstatus_t *psp = &phdl->lwps; + siginfo_t *siginfo; + + if (phdl == NULL) + return (NULL); + if (ptrace(PT_LWPINFO, proc_getpid(phdl), (caddr_t)&lwpinfo, + sizeof(lwpinfo)) < 0) + return (NULL); + siginfo = &lwpinfo.pl_siginfo; + if (lwpinfo.pl_event == PL_EVENT_SIGNAL && + (lwpinfo.pl_flags & PL_FLAG_SI) != 0) { + if (siginfo->si_signo == SIGTRAP && + (siginfo->si_code == TRAP_BRKPT || + siginfo->si_code == TRAP_TRACE)) { + psp->pr_why = PR_FAULTED; + psp->pr_what = FLTBPT; + } else { + psp->pr_why = PR_SIGNALLED; + psp->pr_what = siginfo->si_signo; + } + } else if (lwpinfo.pl_flags & PL_FLAG_SCE) { + psp->pr_why = PR_SYSENTRY; + } else if (lwpinfo.pl_flags & PL_FLAG_SCX) { + psp->pr_why = PR_SYSEXIT; + } + + return (psp); +} diff --git a/lib/libproc/tests/Makefile b/lib/libproc/tests/Makefile new file mode 100644 index 000000000000..9467cc634884 --- /dev/null +++ b/lib/libproc/tests/Makefile @@ -0,0 +1,14 @@ +ATF_TESTS_C+= proc_test + +PROGS= target_prog +SRCS_target_prog= target_prog.c +BINDIR_target_prog= ${TESTSDIR} + +LIBADD= elf proc rtld_db util + +# Ensure that symbols aren't stripped from the test program, as they're needed +# for testing symbol lookup. +STRIP= +CFLAGS.target_prog.c+= -O0 + +.include <bsd.test.mk> diff --git a/lib/libproc/tests/Makefile.depend b/lib/libproc/tests/Makefile.depend new file mode 100644 index 000000000000..7b0ee806c4a7 --- /dev/null +++ b/lib/libproc/tests/Makefile.depend @@ -0,0 +1,26 @@ +# Autogenerated - do NOT edit! + +DIRDEPS = \ + cddl/lib/libctf \ + gnu/lib/csu \ + include \ + include/xlocale \ + lib/${CSU_DIR} \ + lib/atf/libatf-c \ + lib/libc \ + lib/libcompiler_rt \ + lib/libcxxrt \ + lib/libelf \ + lib/libkvm \ + lib/libproc \ + lib/libprocstat \ + lib/librtld_db \ + lib/libutil \ + lib/libz \ + + +.include <dirdeps.mk> + +.if ${DEP_RELDIR} == ${_DEP_RELDIR} +# local dependencies - needed for -jN in clean tree +.endif diff --git a/lib/libproc/tests/proc_test.c b/lib/libproc/tests/proc_test.c new file mode 100644 index 000000000000..e7fc90f29905 --- /dev/null +++ b/lib/libproc/tests/proc_test.c @@ -0,0 +1,479 @@ +/*- + * Copyright (c) 2014-2017 Mark Johnston <markj@FreeBSD.org> + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include <sys/types.h> +#include <sys/wait.h> + +#include <libgen.h> +#include <stdio.h> +#include <stdint.h> +#include <stdlib.h> +#include <string.h> +#include <atf-c.h> +#include <libelf.h> +#include <libproc.h> + +static const char *aout_object = "a.out"; +static const char *ldelf_object = "ld-elf.so.1"; +static const char *target_prog_file = "target_prog"; + +/* + * Run the test program. If the sig parameter is set to true, the test program + * will deliver SIGUSR1 to itself during execution. + */ +static struct proc_handle * +start_prog(const struct atf_tc *tc, bool sig) +{ + char *argv[3]; + struct proc_handle *phdl; + int error; + + asprintf(&argv[0], "%s/%s", atf_tc_get_config_var(tc, "srcdir"), + target_prog_file); + ATF_REQUIRE(argv[0] != NULL); + + if (sig) { + argv[1] = strdup("-s"); + argv[2] = NULL; + } else { + argv[1] = NULL; + } + + error = proc_create(argv[0], argv, NULL, NULL, NULL, &phdl); + ATF_REQUIRE_EQ_MSG(error, 0, "failed to run '%s'", target_prog_file); + ATF_REQUIRE(phdl != NULL); + + free(argv[0]); + free(argv[1]); + + return (phdl); +} + +static void +set_bkpt(struct proc_handle *phdl, uintptr_t addr, u_long *saved) +{ + int error; + + error = proc_bkptset(phdl, addr, saved); + ATF_REQUIRE_EQ_MSG(error, 0, "failed to set breakpoint at 0x%jx", + (uintmax_t)addr); +} + +static void +remove_bkpt(struct proc_handle *phdl, uintptr_t addr, u_long val) +{ + int error; + + error = proc_bkptdel(phdl, addr, val); + ATF_REQUIRE_EQ_MSG(error, 0, + "failed to delete breakpoint at 0x%jx", (uintmax_t)addr); + + error = proc_regset(phdl, REG_PC, addr); + ATF_REQUIRE_EQ_MSG(error, 0, "failed to reset program counter"); +} + +/* + * Wait for the specified process to hit a breakpoint at the specified symbol. + */ +static void +verify_bkpt(struct proc_handle *phdl, GElf_Sym *sym, const char *symname, + const char *mapname) +{ + char *name, *mapname_copy, *mapbname; + GElf_Sym tsym; + prmap_t *map; + size_t namesz; + u_long addr; + int error, state; + + state = proc_wstatus(phdl); + ATF_REQUIRE_EQ_MSG(state, PS_STOP, "process has state %d", state); + + /* Get the program counter and decrement it. */ + error = proc_regget(phdl, REG_PC, &addr); + ATF_REQUIRE_EQ_MSG(error, 0, "failed to obtain PC for '%s'", + target_prog_file); + proc_bkptregadj(&addr); + + /* + * Make sure the PC matches the expected value obtained from the symbol + * definition we looked up earlier. + */ + ATF_CHECK_EQ_MSG(addr, sym->st_value, + "program counter 0x%lx doesn't match expected value 0x%jx", + addr, (uintmax_t)sym->st_value); + + /* + * Ensure we can look up the r_debug_state symbol using its starting + * address and that the resulting symbol matches the one we found using + * a name lookup. + */ + namesz = strlen(symname) + 1; + name = malloc(namesz); + ATF_REQUIRE(name != NULL); + + error = proc_addr2sym(phdl, addr, name, namesz, &tsym); + ATF_REQUIRE_EQ_MSG(error, 0, "failed to look up symbol at 0x%lx", addr); + ATF_REQUIRE_EQ(memcmp(sym, &tsym, sizeof(*sym)), 0); + ATF_REQUIRE_EQ(strcmp(symname, name), 0); + free(name); + + map = proc_addr2map(phdl, addr); + ATF_REQUIRE_MSG(map != NULL, "failed to look up map for address 0x%lx", + addr); + mapname_copy = strdup(map->pr_mapname); + mapbname = basename(mapname_copy); + ATF_REQUIRE_EQ_MSG(strcmp(mapname, mapbname), 0, + "expected map name '%s' doesn't match '%s'", mapname, mapbname); + free(mapname_copy); +} + +ATF_TC(map_alias_name2map); +ATF_TC_HEAD(map_alias_name2map, tc) +{ + atf_tc_set_md_var(tc, "descr", + "Callers are supposed to be able to use \"a.out\" as an alias for " + "the program executable. Make sure that proc_name2map() handles " + "this properly."); +} +ATF_TC_BODY(map_alias_name2map, tc) +{ + struct proc_handle *phdl; + prmap_t *map1, *map2; + + phdl = start_prog(tc, false); + + /* Initialize the rtld_db handle. */ + (void)proc_rdagent(phdl); + + /* Ensure that "target_prog" and "a.out" return the same map. */ + map1 = proc_name2map(phdl, target_prog_file); + ATF_REQUIRE_MSG(map1 != NULL, "failed to look up map for '%s'", + target_prog_file); + map2 = proc_name2map(phdl, aout_object); + ATF_REQUIRE_MSG(map2 != NULL, "failed to look up map for '%s'", + aout_object); + ATF_CHECK_EQ(strcmp(map1->pr_mapname, map2->pr_mapname), 0); + + ATF_CHECK_EQ_MSG(proc_continue(phdl), 0, "failed to resume execution"); + + proc_detach(phdl, 0); +} + +ATF_TC(map_prefix_name2map); +ATF_TC_HEAD(map_prefix_name2map, tc) +{ + atf_tc_set_md_var(tc, "descr", + "Verify that proc_name2map() returns prefix matches of the " + "basename of loaded objects if no full matches are found."); +} +ATF_TC_BODY(map_prefix_name2map, tc) +{ + struct proc_handle *phdl; + prmap_t *map1, *map2; + + phdl = start_prog(tc, false); + + /* Initialize the rtld_db handle. */ + (void)proc_rdagent(phdl); + + /* Make sure that "ld-elf" and "ld-elf.so" return the same map. */ + map1 = proc_name2map(phdl, "ld-elf"); + ATF_REQUIRE_MSG(map1 != NULL, "failed to look up map for 'ld-elf'"); + map2 = proc_name2map(phdl, "ld-elf.so"); + ATF_REQUIRE_MSG(map2 != NULL, "failed to look up map for 'ld-elf.so'"); + ATF_CHECK_EQ(strcmp(map1->pr_mapname, map2->pr_mapname), 0); + + ATF_CHECK_EQ_MSG(proc_continue(phdl), 0, "failed to resume execution"); + + proc_detach(phdl, 0); +} + +ATF_TC(map_alias_name2sym); +ATF_TC_HEAD(map_alias_name2sym, tc) +{ + atf_tc_set_md_var(tc, "descr", + "Callers are supposed to be able to use \"a.out\" as an alias for " + "the program executable. Make sure that proc_name2sym() handles " + "this properly."); +} +ATF_TC_BODY(map_alias_name2sym, tc) +{ + GElf_Sym sym1, sym2; + prsyminfo_t si1, si2; + struct proc_handle *phdl; + int error; + + phdl = start_prog(tc, false); + + /* Initialize the rtld_db handle. */ + (void)proc_rdagent(phdl); + + /* + * Make sure that "target_prog:main" and "a.out:main" return the same + * symbol. + */ + error = proc_name2sym(phdl, target_prog_file, "main", &sym1, &si1); + ATF_REQUIRE_EQ_MSG(error, 0, "failed to look up 'main' via %s", + target_prog_file); + error = proc_name2sym(phdl, aout_object, "main", &sym2, &si2); + ATF_REQUIRE_EQ_MSG(error, 0, "failed to look up 'main' via %s", + aout_object); + + ATF_CHECK_EQ(memcmp(&sym1, &sym2, sizeof(sym1)), 0); + ATF_CHECK_EQ(si1.prs_id, si2.prs_id); + + ATF_CHECK_EQ_MSG(proc_continue(phdl), 0, "failed to resume execution"); + + proc_detach(phdl, 0); +} + +ATF_TC(symbol_lookup); +ATF_TC_HEAD(symbol_lookup, tc) +{ + atf_tc_set_md_var(tc, "descr", + "Look up a couple of well-known symbols in the test program, place " + "breakpoints on them, and verify that we hit the breakpoints. Also " + "make sure that we can use the breakpoint address to look up the " + "corresponding symbol."); +} +ATF_TC_BODY(symbol_lookup, tc) +{ + GElf_Sym main_sym, r_debug_state_sym; + struct proc_handle *phdl; + u_long saved; + int error; + + phdl = start_prog(tc, false); + + error = proc_name2sym(phdl, target_prog_file, "main", &main_sym, NULL); + ATF_REQUIRE_EQ_MSG(error, 0, "failed to look up 'main'"); + + error = proc_name2sym(phdl, ldelf_object, "r_debug_state", + &r_debug_state_sym, NULL); + ATF_REQUIRE_EQ_MSG(error, 0, "failed to look up 'r_debug_state'"); + + set_bkpt(phdl, r_debug_state_sym.st_value, &saved); + ATF_CHECK_EQ_MSG(proc_continue(phdl), 0, "failed to resume execution"); + verify_bkpt(phdl, &r_debug_state_sym, "r_debug_state", ldelf_object); + remove_bkpt(phdl, r_debug_state_sym.st_value, saved); + + set_bkpt(phdl, main_sym.st_value, &saved); + ATF_CHECK_EQ_MSG(proc_continue(phdl), 0, "failed to resume execution"); + verify_bkpt(phdl, &main_sym, "main", target_prog_file); + remove_bkpt(phdl, main_sym.st_value, saved); + + ATF_CHECK_EQ_MSG(proc_continue(phdl), 0, "failed to resume execution"); + + proc_detach(phdl, 0); +} + +ATF_TC(symbol_lookup_fail); +ATF_TC_HEAD(symbol_lookup_fail, tc) +{ + atf_tc_set_md_var(tc, "descr", + "Verify that proc_addr2sym() returns an error when given an offset " + "that it cannot resolve."); +} +ATF_TC_BODY(symbol_lookup_fail, tc) +{ + char symname[32]; + GElf_Sym sym; + struct proc_handle *phdl; + prmap_t *map; + int error; + + phdl = start_prog(tc, false); + + /* Initialize the rtld_db handle. */ + (void)proc_rdagent(phdl); + + map = proc_name2map(phdl, target_prog_file); + ATF_REQUIRE_MSG(map != NULL, "failed to look up map for '%s'", + target_prog_file); + + /* + * We shouldn't be able to find symbols at the beginning of a mapped + * file. + */ + error = proc_addr2sym(phdl, map->pr_vaddr, symname, sizeof(symname), + &sym); + ATF_REQUIRE_MSG(error != 0, "unexpectedly found a symbol"); + + ATF_CHECK_EQ_MSG(proc_continue(phdl), 0, "failed to resume execution"); + + proc_detach(phdl, 0); +} + +ATF_TC(signal_forward); +ATF_TC_HEAD(signal_forward, tc) +{ + atf_tc_set_md_var(tc, "descr", + "Run the test program in a mode which causes it to send a signal " + "to itself. Make sure that we intercept the signal and that " + "proc_continue() forwards it to the process."); +} +ATF_TC_BODY(signal_forward, tc) +{ + struct proc_handle *phdl; + int state, status; + + phdl = start_prog(tc, true); + ATF_CHECK_EQ_MSG(proc_continue(phdl), 0, "failed to resume execution"); + + /* The process should have been interrupted by a signal. */ + state = proc_wstatus(phdl); + ATF_REQUIRE_EQ_MSG(state, PS_STOP, "process has unexpected state %d", + state); + + /* Continue execution and allow the signal to be delivered. */ + ATF_CHECK_EQ_MSG(proc_continue(phdl), 0, "failed to resume execution"); + + /* + * Make sure the process exited with status 0. If it didn't receive the + * SIGUSR1 that it sent to itself, it'll exit with a non-zero exit + * status, causing the test to fail. + */ + state = proc_wstatus(phdl); + ATF_REQUIRE_EQ_MSG(state, PS_UNDEAD, "process has unexpected state %d", + state); + + status = proc_getwstat(phdl); + ATF_REQUIRE(status >= 0); + ATF_REQUIRE(WIFEXITED(status)); + ATF_REQUIRE_EQ(WEXITSTATUS(status), 0); + + proc_detach(phdl, 0); +} + +ATF_TC(symbol_sort_local); +ATF_TC_HEAD(symbol_sort_local, tc) +{ + atf_tc_set_md_var(tc, "descr", + "Ensure that proc_addr2sym() returns the non-local alias when " + "the address resolves to multiple symbols."); +} +ATF_TC_BODY(symbol_sort_local, tc) +{ + char symname[32]; + GElf_Sym bar_sym; + struct proc_handle *phdl; + int error; + + phdl = start_prog(tc, true); + + error = proc_name2sym(phdl, target_prog_file, "bar", &bar_sym, NULL); + ATF_REQUIRE_MSG(error == 0, "failed to look up 'bar' in %s", + target_prog_file); + ATF_REQUIRE(GELF_ST_BIND(bar_sym.st_info) == STB_LOCAL); + + error = proc_addr2sym(phdl, bar_sym.st_value, symname, sizeof(symname), + &bar_sym); + ATF_REQUIRE_MSG(error == 0, "failed to resolve 'bar' by addr"); + + ATF_REQUIRE_MSG(strcmp(symname, "baz") == 0, + "unexpected symbol name '%s'", symname); + ATF_REQUIRE(GELF_ST_BIND(bar_sym.st_info) == STB_GLOBAL); + + proc_detach(phdl, 0); +} + +ATF_TC(symbol_sort_prefix); +ATF_TC_HEAD(symbol_sort_prefix, tc) +{ + atf_tc_set_md_var(tc, "descr", + "Ensure that proc_addr2sym() returns the alias whose name is not " + "prefixed with '$' if one exists."); +} +ATF_TC_BODY(symbol_sort_prefix, tc) +{ + char symname[32]; + GElf_Sym qux_sym; + struct proc_handle *phdl; + int error; + + phdl = start_prog(tc, true); + + error = proc_name2sym(phdl, target_prog_file, "$qux", &qux_sym, NULL); + ATF_REQUIRE_MSG(error == 0, "failed to look up '$qux' in %s", + target_prog_file); + + error = proc_addr2sym(phdl, qux_sym.st_value, symname, sizeof(symname), + &qux_sym); + ATF_REQUIRE_MSG(error == 0, "failed to resolve 'qux' by addr"); + + ATF_REQUIRE_MSG(strcmp(symname, "qux") == 0, + "unexpected symbol name '%s'", symname); + + proc_detach(phdl, 0); +} + +ATF_TC(symbol_sort_underscore); +ATF_TC_HEAD(symbol_sort_underscore, tc) +{ + atf_tc_set_md_var(tc, "descr", + "Ensure that proc_addr2sym() returns the alias with fewest leading " + "underscores in the name when the address resolves to multiple " + "symbols."); +} +ATF_TC_BODY(symbol_sort_underscore, tc) +{ + char symname[32]; + GElf_Sym foo_sym; + struct proc_handle *phdl; + int error; + + phdl = start_prog(tc, true); + + error = proc_name2sym(phdl, target_prog_file, "foo", &foo_sym, NULL); + ATF_REQUIRE_MSG(error == 0, "failed to look up 'foo' in %s", + target_prog_file); + + error = proc_addr2sym(phdl, foo_sym.st_value, symname, sizeof(symname), + &foo_sym); + ATF_REQUIRE_MSG(error == 0, "failed to resolve 'foo' by addr"); + + ATF_REQUIRE_MSG(strcmp(symname, "foo") == 0, + "unexpected symbol name '%s'", symname); + + proc_detach(phdl, 0); +} + +ATF_TP_ADD_TCS(tp) +{ + + ATF_TP_ADD_TC(tp, map_alias_name2map); + ATF_TP_ADD_TC(tp, map_prefix_name2map); + ATF_TP_ADD_TC(tp, map_alias_name2sym); + ATF_TP_ADD_TC(tp, symbol_lookup); + ATF_TP_ADD_TC(tp, symbol_lookup_fail); + ATF_TP_ADD_TC(tp, signal_forward); + ATF_TP_ADD_TC(tp, symbol_sort_local); + ATF_TP_ADD_TC(tp, symbol_sort_prefix); + ATF_TP_ADD_TC(tp, symbol_sort_underscore); + + return (atf_no_error()); +} diff --git a/lib/libproc/tests/target_prog.c b/lib/libproc/tests/target_prog.c new file mode 100644 index 000000000000..7e54dad6501a --- /dev/null +++ b/lib/libproc/tests/target_prog.c @@ -0,0 +1,80 @@ +/*- + * Copyright (c) 2014 Mark Johnston <markj@FreeBSD.org> + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include <sys/cdefs.h> +#include <err.h> +#include <signal.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +static volatile sig_atomic_t saw; + +static void +usr1(int sig __unused) +{ + + saw = 1; +} + +void foo(void); +void qux(void); + +void +foo(void) +{ +} +__weak_reference(foo, _foo); + +static void +bar(void) +{ +} +__strong_reference(bar, baz); + +void +qux(void) +{ +} +__strong_reference(qux, $qux); + +int +main(int argc, char **argv) +{ + + bar(); /* force the symbol to be emitted */ + + if (argc == 1) + return (EXIT_SUCCESS); + if (argc == 2 && strcmp(argv[1], "-s") == 0) { + if (signal(SIGUSR1, usr1) == SIG_ERR) + err(1, "signal"); + if (kill(getpid(), SIGUSR1) != 0) + err(1, "kill"); + return (saw == 1 ? EXIT_SUCCESS : EXIT_FAILURE); + } + return (EXIT_FAILURE); +} |