/* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License, Version 1.0 only * (the "License"). You may not use this file except in compliance * with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright 2004 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ #pragma ident "%Z%%M% %I% %E% SMI" /* * Interfaces to sync up with run time linker (rtld) at process start up time * and at dlopen() and dlclose() time * In Solaris 2.6, librtld_db.so should replace this functionality. Issues * to solve before libtnfctl.so can use librtld_db.so: * 1. Should libtnfctl.so be usable before Solaris 2.6 - If so, cannot use * librtld_db.so * 2. libtnfctl.so will have to provide in order to use * librtld_db.so. If libtnfctl.so is now linked into a debugger that * also provides , how will the two co-exist - will the * linker get confused, or not ? */ #include #include #include #include #include #include #include #include #include #include "tnfctl.h" #include "prb_proc_int.h" #include "dbg.h" static prb_status_t prb_rtld_setup(prb_proc_ctl_t *proc_p, boolean_t *synced); static prb_status_t prb_rtld_wait(prb_proc_ctl_t *proc_p); static prb_status_t bpt(prb_proc_ctl_t *proc_p, uintptr_t addr); static prb_status_t unbpt(prb_proc_ctl_t *proc_p, uintptr_t addr); /* ---------------------------------------------------------------- */ /* ----------------------- Public Functions ----------------------- */ /* ---------------------------------------------------------------- */ /* * prb_rtld_stalk() - setup for a breakpoint when rtld has opened or closed a * shared object. */ prb_status_t prb_rtld_stalk(prb_proc_ctl_t *proc_p) { prb_status_t prbstat = PRB_STATUS_OK; DBG_TNF_PROBE_0(prb_rtld_stalk_1, "libtnfctl", "sunw%verbosity 2"); if (!proc_p->bptaddr) { Elf3264_Dyn dentry; struct r_debug r_dbg; if (proc_p->dbgaddr == 0) { DBG((void) fprintf(stderr, "prb_rtld_stalk: dbgaddr not set\n")); return (PRB_STATUS_BADARG); } prbstat = prb_proc_read(proc_p, proc_p->dbgaddr, &dentry, sizeof (dentry)); if (prbstat || !dentry.d_un.d_ptr) { DBG((void) fprintf(stderr, "prb_rtld_stalk: error in d_un.d_ptr\n")); return (prbstat); } /* read in the debug struct that it points to */ prbstat = prb_proc_read(proc_p, dentry.d_un.d_ptr, &r_dbg, sizeof (r_dbg)); if (prbstat) return (prbstat); proc_p->bptaddr = r_dbg.r_brk; } /* plant a breakpoint trap in the pointed to function */ prbstat = bpt(proc_p, proc_p->bptaddr); if (prbstat) return (prbstat); /* setup process to stop when breakpoint encountered */ prbstat = prb_proc_tracebpt(proc_p, B_TRUE); return (prbstat); } /* * prb_rtld_unstalk() - remove rtld breakpoint */ prb_status_t prb_rtld_unstalk(prb_proc_ctl_t *proc_p) { prb_status_t prbstat; DBG_TNF_PROBE_0(prb_rtld_unstalk_1, "libtnfctl", "sunw%verbosity 2"); /* turn off BPT tracing while out of the water ... */ prbstat = prb_proc_tracebpt(proc_p, B_FALSE); prbstat = unbpt(proc_p, proc_p->bptaddr); return (prbstat); } /* * prb_rtld_advance() - we've hit a breakpoint, replace the original * instruction, istep, put the breakpoint back ... */ prb_status_t prb_rtld_advance(prb_proc_ctl_t *proc_p) { prb_status_t prbstat; DBG_TNF_PROBE_0(prb_rtld_advance_1, "libtnfctl", "sunw%verbosity 2"); prbstat = prb_proc_clrbptflt(proc_p); if (prbstat) return (prbstat); prbstat = unbpt(proc_p, proc_p->bptaddr); if (prbstat) return (prbstat); prbstat = prb_proc_istepbpt(proc_p); if (prbstat) return (prbstat); prbstat = bpt(proc_p, proc_p->bptaddr); if (prbstat) return (prbstat); return (PRB_STATUS_OK); } /* * checks if process has reached rtld_sync point or not i.e. has rltld * loaded in libraries or not ? If not, it lets process run until * rtld has mapped in all libraries (no user code would have been * executed, including .init sections) */ prb_status_t prb_rtld_sync_if_needed(prb_proc_ctl_t *proc_p) { prb_status_t prbstat = PRB_STATUS_OK; boolean_t synced = B_FALSE; prbstat = prb_rtld_setup(proc_p, &synced); if (prbstat) return (prbstat); if (synced == B_FALSE) { /* wait on target to sync up after rtld maps in all .so's */ prbstat = prb_rtld_wait(proc_p); if (prbstat) return (prbstat); } return (prbstat); } /* ---------------------------------------------------------------- */ /* ----------------------- Private Functions ---------------------- */ /* ---------------------------------------------------------------- */ /* * prb_rtld_setup() - turns on the flag in the rtld structure so that rtld * executes a getpid() stystem call after it done mapping all shared objects * but before it executes any init code. */ static prb_status_t prb_rtld_setup(prb_proc_ctl_t *proc_p, boolean_t *synced) { prb_status_t prbstat = PRB_STATUS_OK; Elf3264_Dyn dentry; DBG_TNF_PROBE_0(prb_rtld_setup_1, "libtnfctl", "sunw%verbosity 2"); if (proc_p->dbgaddr == 0) { DBG((void) fprintf(stderr, "prb_rtld_setup: dbgaddr not set\n")); return (PRB_STATUS_BADARG); } prbstat = prb_proc_read(proc_p, proc_p->dbgaddr, &dentry, sizeof (dentry)); if (prbstat) { DBG((void) fprintf(stderr, "prb_rtld_setup: error in d_un.d_ptr\n")); return (prbstat); } if ((dentry.d_un.d_ptr == 0) || (dentry.d_un.d_ptr == 1)) { *synced = B_FALSE; } else { *synced = B_TRUE; return (PRB_STATUS_OK); } /* modify it - i.e. request rtld to do getpid() */ dentry.d_un.d_ptr = 1; prbstat = prb_proc_write(proc_p, proc_p->dbgaddr, &dentry, sizeof (dentry)); return (prbstat); } /* * prb_rtld_wait() - waits on target to execute getpid() */ static prb_status_t prb_rtld_wait(prb_proc_ctl_t *proc_p) { prb_proc_state_t pstate; prb_status_t prbstat; DBG_TNF_PROBE_0(prb_rtld_wait_1, "libtnfctl", "sunw%verbosity 2"); /* stop on exit of getpid() */ prbstat = prb_proc_exit(proc_p, SYS_getpid, PRB_SYS_ADD); if (prbstat) { DBG((void) fprintf(stderr, "prb_rtld_wait: couldn't set up child to stop on " "exit of getpid(): %s\n", prb_status_str(prbstat))); return (prbstat); } /* stop on entry of exit() - i.e. exec failed */ prbstat = prb_proc_entry(proc_p, SYS_exit, PRB_SYS_ADD); if (prbstat) { DBG((void) fprintf(stderr, "prb_rtld_wait: couldn't set up child to stop on " "entry of exit(): %s\n", prb_status_str(prbstat))); return (prbstat); } /* continue target and wait for it to stop */ prbstat = prb_proc_cont(proc_p); if (prbstat) { DBG((void) fprintf(stderr, "prb_rtld_wait: couldn't continue target process: %s\n", prb_status_str(prbstat))); return (prbstat); } /* wait for target to stop */ prbstat = prb_proc_wait(proc_p, B_FALSE, NULL); if (prbstat) { DBG((void) fprintf(stderr, "prb_rtld_wait: couldn't wait on target process: %s\n", prb_status_str(prbstat))); return (prbstat); } /* make sure it did stop on getpid() */ prbstat = prb_proc_state(proc_p, &pstate); if (prbstat) { DBG((void) fprintf(stderr, "prb_rtld_wait: couldn't get state of target: %s\n", prb_status_str(prbstat))); return (prbstat); } if (pstate.ps_issysentry && (pstate.ps_syscallnum == SYS_exit)) { DBG((void) fprintf(stderr, "prb_rtld_wait: target exited\n")); return (prb_status_map(EACCES)); } /* catch any other errors */ if (!(pstate.ps_issysexit && (pstate.ps_syscallnum == SYS_getpid))) { DBG((void) fprintf(stderr, "prb_rtld_wait: target didn't stop on getpid\n")); return (PRB_STATUS_BADSYNC); } /* clear wait on getpid */ prbstat = prb_proc_exit(proc_p, SYS_getpid, PRB_SYS_DEL); if (prbstat) { DBG((void) fprintf(stderr, "prb_rtld_wait: couldn't clear child to stop on " "exit of getpid(): %s\n", prb_status_str(prbstat))); return (prbstat); } /* clear wait on exit */ prbstat = prb_proc_entry(proc_p, SYS_exit, PRB_SYS_DEL); if (prbstat) { DBG((void) fprintf(stderr, "prb_rtld_wait: couldn't clear child to stop on " "entry of exit(): %s\n", prb_status_str(prbstat))); return (prbstat); } /* start-stop the process to clear it out of the system call */ prbstat = prb_proc_prstop(proc_p); if (prbstat) { DBG((void) fprintf(stderr, "prb_rtld_wait: couldn't prstop child: %s\n", prb_status_str(prbstat))); return (prbstat); } return (PRB_STATUS_OK); } #if defined(__sparc) #define INS_BPT 0x91d02001 #elif defined(__i386) || defined(__amd64) #define INS_BPT 0xcc #else #error What is your breakpoint instruction? #endif /* * plants a breakpoint at the specified location in * the target process, and saves the existing instruction. */ static prb_status_t bpt(prb_proc_ctl_t *proc_p, uintptr_t addr) { prb_status_t prbstat; bptsave_t instr; if (!proc_p->bpt_inserted) { DBG_TNF_PROBE_1(bpt_1, "libtnfctl", "sunw%verbosity 2", tnf_opaque, bpt_planted_at, addr); prbstat = prb_proc_read(proc_p, addr, &(proc_p->saveinstr), sizeof (proc_p->saveinstr)); if (prbstat) return (prbstat); DBG_TNF_PROBE_1(bpt_2, "libtnfctl", "sunw%verbosity 2", tnf_opaque, saved_instr, (unsigned)proc_p->saveinstr); instr = INS_BPT; prbstat = prb_proc_write(proc_p, addr, &instr, sizeof (instr)); if (prbstat) return (prbstat); proc_p->bpt_inserted = B_TRUE; } return (PRB_STATUS_OK); } /* * removes a breakpoint at the specified location in * the target process, and replaces it with the original instruction. */ prb_status_t unbpt(prb_proc_ctl_t *proc_p, uintptr_t addr) { prb_status_t prbstat; if (proc_p->bpt_inserted) { DBG_TNF_PROBE_2(unbpt_1, "libtnfctl", "sunw%verbosity 2", tnf_opaque, unplanting_at, addr, tnf_opaque, saved_instr, (unsigned)proc_p->saveinstr); prbstat = prb_proc_write(proc_p, addr, &(proc_p->saveinstr), sizeof (proc_p->saveinstr)); if (prbstat) return (prbstat); proc_p->bpt_inserted = B_FALSE; } return (PRB_STATUS_OK); }