aboutsummaryrefslogtreecommitdiff
path: root/lib/libproc
diff options
context:
space:
mode:
Diffstat (limited to 'lib/libproc')
-rw-r--r--lib/libproc/Makefile41
-rw-r--r--lib/libproc/Makefile.depend20
-rw-r--r--lib/libproc/Makefile.depend.options10
-rw-r--r--lib/libproc/Makefile.inc1
-rw-r--r--lib/libproc/_libproc.h89
-rw-r--r--lib/libproc/libproc.h168
-rw-r--r--lib/libproc/proc_bkpt.c262
-rw-r--r--lib/libproc/proc_create.c265
-rw-r--r--lib/libproc/proc_regs.c144
-rw-r--r--lib/libproc/proc_rtld.c135
-rw-r--r--lib/libproc/proc_sym.c714
-rw-r--r--lib/libproc/proc_util.c242
-rw-r--r--lib/libproc/tests/Makefile14
-rw-r--r--lib/libproc/tests/Makefile.depend26
-rw-r--r--lib/libproc/tests/proc_test.c479
-rw-r--r--lib/libproc/tests/target_prog.c80
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(&regs, 0, sizeof(regs));
+ if (ptrace(PT_GETREGS, proc_getpid(phdl), (caddr_t)&regs, 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)&regs, 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)&regs, 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);
+}