/* Copyright (C) 2009-2019 Free Software Foundation, Inc. This file is part of GDB. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "server.h" #include "target.h" #include "netbsd-low.h" #include #include #include #include #include #include #include #include #include "gdbsupport/gdb_wait.h" #include "gdbsupport/filestuff.h" #include "gdbsupport/common-inferior.h" #include "nat/fork-inferior.h" #include "hostio.h" int using_threads = 1; const struct target_desc *netbsd_tdesc; /* Per-process private data. */ struct process_info_private { /* The PTID obtained from the last wait performed on this process. Initialized to null_ptid until the first wait is performed. */ ptid_t last_wait_event_ptid; /* &_r_debug. 0 if not yet determined. -1 if no PT_DYNAMIC in Phdrs. */ CORE_ADDR r_debug; }; /* Print a debug trace on standard output if debug_threads is set. */ static void netbsd_debug (const char *string, ...) { va_list args; if (!debug_threads) return; va_start (args, string); fprintf (stderr, "DEBUG(netbsd): "); vfprintf (stderr, string, args); fprintf (stderr, "\n"); va_end (args); } /* Build a ptid_t given a PID and a NetBSD TID. */ static ptid_t netbsd_ptid_t (pid_t pid, lwpid_t tid) { /* brobecker/2010-06-21: It looks like the LWP field in ptids should be distinct for each thread (see write_ptid where it writes the thread ID from the LWP). So instead of storing the NetBSD tid in the tid field of the ptid, we store it in the lwp field. */ return ptid_t (pid, tid, 0); } /* Return the process ID of the given PTID. This function has little reason to exist, it's just a wrapper around ptid_get_pid. But since we have a getter function for the lynxos ptid, it feels cleaner to have a getter for the pid as well. */ static int netbsd_ptid_get_pid (ptid_t ptid) { return ptid.pid (); } /* Return the NetBSD tid of the given PTID. */ #if 0 static long netbsd_ptid_get_tid (ptid_t ptid) { /* See lynx_ptid_t: The NetBSD tid is stored inside the lwp field of the ptid. */ return ptid.lwp (); } #endif /* For a given PTID, return the associated PID as known by the NetBSD ptrace layer. */ #if 0 static pid_t netbsd_ptrace_pid_from_ptid (ptid_t ptid) { return netbsd_ptid_get_pid (ptid); } #endif /* Return a string image of the ptrace REQUEST number. */ static const char * ptrace_request_to_str (int request) { #define CASE(X) case X: return #X switch (request) { CASE(PT_TRACE_ME); CASE(PT_READ_I); CASE(PT_READ_D); CASE(PT_WRITE_I); CASE(PT_WRITE_D); CASE(PT_CONTINUE); CASE(PT_KILL); CASE(PT_ATTACH); CASE(PT_DETACH); CASE(PT_IO); CASE(PT_DUMPCORE); CASE(PT_LWPINFO); CASE(PT_SYSCALL); CASE(PT_SYSCALLEMU); CASE(PT_SET_EVENT_MASK); CASE(PT_GET_EVENT_MASK); CASE(PT_GET_PROCESS_STATE); CASE(PT_SET_SIGINFO); CASE(PT_GET_SIGINFO); CASE(PT_RESUME); CASE(PT_SUSPEND); #ifdef PT_STEP CASE(PT_STEP); #endif #ifdef PT_GETREGS CASE(PT_GETREGS); #endif #ifdef PT_SETREGS CASE(PT_SETREGS); #endif #ifdef PT_GETFPREGS CASE(PT_GETFPREGS); #endif #ifdef PT_SETFPREGS CASE(PT_SETFPREGS); #endif #ifdef PT_GETDBREGS CASE(PT_GETDBREGS); #endif #ifdef PT_SETDBREGS CASE(PT_SETDBREGS); #endif #ifdef PT_SETSTEP CASE(PT_SETSTEP); #endif #ifdef PT_CLEARSTEP CASE(PT_CLEARSTEP); #endif #ifdef PT_GETXSTATE CASE(PT_GETXSTATE); #endif #ifdef PT_SETXSTATE CASE(PT_SETXSTATE); #endif #ifdef PT_GETXMMREGS CASE(PT_GETXMMREGS); #endif #ifdef PT_SETXMMREGS CASE(PT_SETXMMREGS); #endif #ifdef PT_GETVECREGS CASE(PT_GETVECREGS); #endif #ifdef PT_SETVECREGS CASE(PT_SETVECREGS); #endif } #undef CASE return ""; } static const char * netbsd_wait_kind_to_str (int kind) { #define CASE(X) case X: return #X switch (kind) { CASE(TARGET_WAITKIND_EXITED); CASE(TARGET_WAITKIND_STOPPED); CASE(TARGET_WAITKIND_SIGNALLED); CASE(TARGET_WAITKIND_LOADED); CASE(TARGET_WAITKIND_FORKED); CASE(TARGET_WAITKIND_VFORKED); CASE(TARGET_WAITKIND_EXECD); CASE(TARGET_WAITKIND_VFORK_DONE); CASE(TARGET_WAITKIND_SYSCALL_ENTRY); CASE(TARGET_WAITKIND_SYSCALL_RETURN); CASE(TARGET_WAITKIND_IGNORE); CASE(TARGET_WAITKIND_NO_HISTORY); CASE(TARGET_WAITKIND_NO_RESUMED); CASE(TARGET_WAITKIND_THREAD_CREATED); CASE(TARGET_WAITKIND_THREAD_EXITED); } #undef CASE return ""; } /* A wrapper around ptrace that allows us to print debug traces of ptrace calls if debug traces are activated. */ static int netbsd_ptrace (int request, pid_t pid, void *addr, int data) { int result; int saved_errno; netbsd_debug ("[%d] PTRACE (%s, pid=%d, addr=%p, " "data=%#x)", getpid(), ptrace_request_to_str (request), pid, addr, data); saved_errno = errno; errno = 0; result = ptrace (request, pid, addr, data); netbsd_debug (" -> %d (=%#x errno=%d)\n", result, result, errno); errno = saved_errno; return result; } /* Call add_process with the given parameters, and initializes the process' private data. */ static struct process_info * netbsd_add_process (int pid, int attached) { struct process_info *proc; proc = add_process (pid, attached); proc->tdesc = netbsd_tdesc; proc->priv = XCNEW (struct process_info_private); proc->priv->last_wait_event_ptid = null_ptid; return proc; } /* Callback used by fork_inferior to start tracing the inferior. */ static void netbsd_ptrace_fun () { /* Switch child to its own process group so that signals won't directly affect GDBserver. */ if (setpgid (0, 0) < 0) trace_start_error_with_name ("setpgid"); if (netbsd_ptrace (PT_TRACE_ME, 0, NULL, 0) < 0) trace_start_error_with_name ("netbsd_ptrace"); /* If GDBserver is connected to gdb via stdio, redirect the inferior's stdout to stderr so that inferior i/o doesn't corrupt the connection. Also, redirect stdin to /dev/null. */ if (remote_connection_is_stdio ()) { if (close (0) < 0) trace_start_error_with_name ("close"); if (open ("/dev/null", O_RDONLY) < 0) trace_start_error_with_name ("open"); if (dup2 (2, 1) < 0) trace_start_error_with_name ("dup2"); if (write (2, "stdin/stdout redirected\n", sizeof ("stdin/stdout redirected\n") - 1) < 0) { /* Errors ignored. */; } } } static void netbsd_add_threads_sysctl (pid_t pid) { struct kinfo_lwp *kl; int mib[5]; size_t i, nlwps; size_t size; mib[0] = CTL_KERN; mib[1] = KERN_LWP; mib[2] = pid; mib[3] = sizeof(struct kinfo_lwp); mib[4] = 0; if (sysctl(mib, 5, NULL, &size, NULL, 0) == -1 || size == 0) trace_start_error_with_name ("sysctl"); mib[4] = size / sizeof(size_t); kl = (struct kinfo_lwp *) xmalloc (size); if (kl == NULL) trace_start_error_with_name ("malloc"); if (sysctl(mib, 5, kl, &size, NULL, 0) == -1 || size == 0) trace_start_error_with_name ("sysctl"); nlwps = size / sizeof(struct kinfo_lwp); for (i = 0; i < nlwps; i++) { ptid_t ptid = netbsd_ptid_t (pid, kl[i].l_lid); netbsd_debug ("Registering thread (pid=%d, lwpid=%d)\n", pid, kl[i].l_lid); add_thread (ptid, NULL); } xfree(kl); } /* Implement the create_inferior method of the target_ops vector. */ static int netbsd_create_inferior (const char *program, const std::vector &program_args) { pid_t pid; std::string str_program_args = stringify_argv (program_args); netbsd_debug ("netbsd_create_inferior ()"); pid = fork_inferior (program, str_program_args.c_str (), get_environ ()->envp (), netbsd_ptrace_fun, NULL, NULL, NULL, NULL); netbsd_add_process (pid, 0); netbsd_add_threads_sysctl (pid); post_fork_inferior (pid, program); return pid; } /* Assuming we've just attached to a running inferior whose pid is PID, add all threads running in that process. */ static void netbsd_add_threads_after_attach (pid_t pid) { struct ptrace_lwpinfo pl; int val; pl.pl_lwpid = 0; while ((val = netbsd_ptrace(PT_LWPINFO, pid, (void *)&pl, sizeof(pl))) != -1 && pl.pl_lwpid != 0) { ptid_t thread_ptid = netbsd_ptid_t (pid, pl.pl_lwpid); if (!find_thread_ptid (thread_ptid)) { netbsd_debug ("New thread: (pid = %d, tid = %d)", pid, pl.pl_lwpid); add_thread (thread_ptid, NULL); } } } /* Implement the attach target_ops method. */ static int netbsd_attach (unsigned long pid) { if (netbsd_ptrace (PT_ATTACH, pid, NULL, 0) != 0) error ("Cannot attach to process %lu: %s (%d)\n", pid, strerror (errno), errno); netbsd_add_process (pid, 1); netbsd_add_threads_after_attach (pid); return 0; } #if 0 /* Resume the execution of the given PTID. */ static void netbsd_continue (ptid_t ptid) { struct thread_resume resume_info; resume_info.thread = ptid; resume_info.kind = resume_continue; resume_info.sig = 0; netbsd_resume (&resume_info, 1); } #endif /* Implement the resume target_ops method. */ static void netbsd_resume (struct thread_resume *resume_info, size_t n) { ptid_t ptid = resume_info[0].thread; const int signal = resume_info[0].sig; if (ptid == minus_one_ptid) ptid = ptid_of (current_thread); regcache_invalidate_pid (ptid.pid ()); if (resume_info[0].kind == resume_step) { if (n == 1) { struct ptrace_lwpinfo pl; int val; pl.pl_lwpid = 0; while ((val = netbsd_ptrace(PT_LWPINFO, ptid.pid(), (void *)&pl, sizeof(pl))) != -1 && pl.pl_lwpid != 0) { if (pl.pl_lwpid == ptid.lwp()) { netbsd_ptrace (PT_SETSTEP, ptid.pid(), NULL, pl.pl_lwpid); netbsd_ptrace (PT_RESUME, ptid.pid(), NULL, pl.pl_lwpid); } else { netbsd_ptrace (PT_CLEARSTEP, ptid.pid(), NULL, pl.pl_lwpid); netbsd_ptrace (PT_SUSPEND, ptid.pid(), NULL, pl.pl_lwpid); } } } else { struct ptrace_lwpinfo pl; int val; pl.pl_lwpid = 0; while ((val = netbsd_ptrace(PT_LWPINFO, ptid.pid(), (void *)&pl, sizeof(pl))) != -1 && pl.pl_lwpid != 0) { netbsd_ptrace (PT_SETSTEP, ptid.pid(), NULL, pl.pl_lwpid); netbsd_ptrace (PT_RESUME, ptid.pid(), NULL, pl.pl_lwpid); } } } else { struct ptrace_lwpinfo pl; int val; pl.pl_lwpid = 0; while ((val = netbsd_ptrace(PT_LWPINFO, ptid.pid(), (void *)&pl, sizeof(pl))) != -1 && pl.pl_lwpid != 0) { netbsd_ptrace (PT_CLEARSTEP, ptid.pid(), NULL, pl.pl_lwpid); netbsd_ptrace (PT_RESUME, ptid.pid(), NULL, pl.pl_lwpid); } } errno = 0; netbsd_ptrace (PT_CONTINUE, ptid.pid(), (void *)1, signal); if (errno) perror_with_name ("ptrace"); } /* A wrapper around waitpid that handles the various idiosyncrasies of NetBSD waitpid. */ static int netbsd_waitpid (int pid, int *stat_loc, int options) { int ret; do { ret = waitpid (pid, stat_loc, options); } while (ret == -1 && errno == EINTR); return ret; } /* Return the name of a file that can be opened to get the symbols for the child process identified by PID. */ static char * pid_to_exec_file (pid_t pid) { static const int name[] = { CTL_KERN, KERN_PROC_ARGS, pid, KERN_PROC_PATHNAME, }; static char path[MAXPATHLEN]; size_t len; len = sizeof(path); if (sysctl(name, __arraycount(name), path, &len, NULL, 0) == -1) return NULL; return path; } static void netbsd_enable_event_reporting (pid_t pid) { ptrace_event_t event; ptrace (PT_GET_EVENT_MASK, pid, &event, sizeof(event)); event.pe_set_event |= PTRACE_FORK; event.pe_set_event |= PTRACE_VFORK; event.pe_set_event |= PTRACE_VFORK_DONE; event.pe_set_event |= PTRACE_LWP_CREATE; event.pe_set_event |= PTRACE_LWP_EXIT; event.pe_set_event |= PTRACE_POSIX_SPAWN; netbsd_ptrace (PT_SET_EVENT_MASK, pid, &event, sizeof(event)); } /* Implement the wait target_ops method. */ static ptid_t netbsd_wait_1 (ptid_t ptid, struct target_waitstatus *ourstatus, int target_options) { pid_t pid; int status; if (ptid == minus_one_ptid) ptid = ptid_of (current_thread); pid = netbsd_ptid_get_pid (ptid); int options = 0; if (target_options & TARGET_WNOHANG) options |= WNOHANG; pid_t wpid = netbsd_waitpid (pid, &status, options); if (wpid == 0) { gdb_assert (target_options & TARGET_WNOHANG); ourstatus->kind = TARGET_WAITKIND_IGNORE; return null_ptid; } gdb_assert (wpid != -1); if (WIFEXITED (status)) { ourstatus->kind = TARGET_WAITKIND_EXITED; ourstatus->value.integer = WEXITSTATUS (status); return ptid; } if (WIFSIGNALED (status)) { ourstatus->kind = TARGET_WAITKIND_SIGNALLED; ourstatus->value.sig = gdb_signal_from_host (WTERMSIG (status)); return ptid; } if (WIFCONTINUED(status)) { ourstatus->kind = TARGET_WAITKIND_IGNORE; return null_ptid; } if (WIFSTOPPED (status)) { ptrace_state_t pst; ptrace_siginfo_t psi, child_psi; pid_t child, wchild; ptid_t child_ptid; lwpid_t lwp; { struct process_info *proc; /* Architecture-specific setup after inferior is running. */ proc = find_process_pid (wpid); if (proc->tdesc == NULL) { /* This needs to happen after we have attached to the inferior and it is stopped for the first time, but before we access any inferior registers. */ the_low_target.arch_setup (); } } ourstatus->kind = TARGET_WAITKIND_STOPPED; ourstatus->value.sig = gdb_signal_from_host (WSTOPSIG (status)); // Find the lwp that caused the wait status change if (netbsd_ptrace (PT_GET_SIGINFO, wpid, &psi, sizeof(psi)) == -1) perror_with_name (("ptrace")); /* For whole-process signals pick random thread */ if (psi.psi_lwpid == 0) { // XXX: Is this always valid? lwp = lwpid_of (current_thread); } else { lwp = psi.psi_lwpid; } ptid_t wptid = netbsd_ptid_t (wpid, lwp); if (!find_thread_ptid (wptid)) { add_thread (wptid, NULL); } switch (psi.psi_siginfo.si_signo) { case SIGTRAP: switch (psi.psi_siginfo.si_code) { #if 0 case TRAP_BRKPT: // lp->stop_reason = TARGET_STOPPED_BY_SW_BREAKPOINT; break; case TRAP_DBREG: // if (hardware_breakpoint_inserted_here_p (get_regcache_aspace (regcache), pc)) // lp->stop_reason = TARGET_STOPPED_BY_HW_BREAKPOINT; // else // lp->stop_reason = TARGET_STOPPED_BY_WATCHPOINT; break; case TRAP_TRACE: // lp->stop_reason = TARGET_STOPPED_BY_SINGLE_STEP; break; #endif case TRAP_SCE: ourstatus->kind = TARGET_WAITKIND_SYSCALL_ENTRY; ourstatus->value.syscall_number = psi.psi_siginfo.si_sysnum; break; case TRAP_SCX: ourstatus->kind = TARGET_WAITKIND_SYSCALL_RETURN; ourstatus->value.syscall_number = psi.psi_siginfo.si_sysnum; break; case TRAP_EXEC: ourstatus->kind = TARGET_WAITKIND_EXECD; ourstatus->value.execd_pathname = xstrdup(pid_to_exec_file (wpid)); break; case TRAP_LWP: case TRAP_CHLD: if (netbsd_ptrace (PT_GET_PROCESS_STATE, wpid, &pst, sizeof(pst)) == -1) perror_with_name (("ptrace")); switch (pst.pe_report_event) { case PTRACE_FORK: case PTRACE_VFORK: if (pst.pe_report_event == PTRACE_FORK) ourstatus->kind = TARGET_WAITKIND_FORKED; else ourstatus->kind = TARGET_WAITKIND_VFORKED; child = pst.pe_other_pid; wchild = netbsd_waitpid (child, &status, 0); if (wchild == -1) perror_with_name (("waitpid")); gdb_assert (wchild == child); if (!WIFSTOPPED(status)) { /* Abnormal situation (SIGKILLed?).. bail out */ ourstatus->kind = TARGET_WAITKIND_SPURIOUS; return wptid; } if (netbsd_ptrace (PT_GET_SIGINFO, child, &child_psi, sizeof(child_psi)) == -1) perror_with_name (("ptrace")); if (child_psi.psi_siginfo.si_signo != SIGTRAP) { /* Abnormal situation.. bail out */ ourstatus->kind = TARGET_WAITKIND_SPURIOUS; return wptid; } if (child_psi.psi_siginfo.si_code != TRAP_CHLD) { /* Abnormal situation.. bail out */ ourstatus->kind = TARGET_WAITKIND_SPURIOUS; return wptid; } child_ptid = netbsd_ptid_t (child, child_psi.psi_lwpid); netbsd_enable_event_reporting (child_ptid.pid ()); ourstatus->value.related_pid = child_ptid; break; case PTRACE_VFORK_DONE: ourstatus->kind = TARGET_WAITKIND_VFORK_DONE; break; case PTRACE_LWP_CREATE: wptid = netbsd_ptid_t (wpid, pst.pe_lwp); if (!find_thread_ptid (wptid)) { add_thread (wptid, NULL); } ourstatus->kind = TARGET_WAITKIND_THREAD_CREATED; break; case PTRACE_LWP_EXIT: wptid = netbsd_ptid_t (wpid, pst.pe_lwp); thread_info *thread = find_thread_ptid (wptid); if (!thread) { /* Dead child reported after attach? */ ourstatus->kind = TARGET_WAITKIND_SPURIOUS; return wptid; } remove_thread (thread); ourstatus->kind = TARGET_WAITKIND_THREAD_EXITED; #if 0 if (netbsd_ptrace (PT_CONTINUE, pid, (void *)1, 0) == -1) perror_with_name (("ptrace")); #endif break; } break; } break; } return wptid; } return null_ptid; } /* A wrapper around netbsd_wait_1 that also prints debug traces when such debug traces have been activated. */ static ptid_t netbsd_wait (ptid_t ptid, struct target_waitstatus *status, int options) { ptid_t new_ptid; netbsd_debug ("netbsd_wait (pid = %d, %s)", netbsd_ptid_get_pid (ptid), options & TARGET_WNOHANG ? "WNOHANG" : "" ); new_ptid = netbsd_wait_1 (ptid, status, options); netbsd_debug (" -> (pid=%d, status->kind = %s)", netbsd_ptid_get_pid (new_ptid), netbsd_wait_kind_to_str(status->kind)); return new_ptid; } /* Implement the kill target_ops method. */ static int netbsd_kill (process_info *process) { pid_t pid = process->pid; ptid_t ptid = netbsd_ptid_t (pid, 0); struct target_waitstatus status; netbsd_ptrace (PT_KILL, pid, NULL, 0); netbsd_wait (ptid, &status, 0); the_target->mourn (process); return 0; } /* Implement the detach target_ops method. */ static int netbsd_detach (process_info *process) { pid_t pid = process->pid; netbsd_ptrace (PT_DETACH, pid, (void *)1, 0); the_target->mourn (process); return 0; } /* Implement the mourn target_ops method. */ static void netbsd_mourn (struct process_info *proc) { for_each_thread (proc->pid, remove_thread); /* Free our private data. */ free (proc->priv); proc->priv = NULL; remove_process (proc); } /* Implement the join target_ops method. */ static void netbsd_join (int pid) { /* The PTRACE_DETACH is sufficient to detach from the process. So no need to do anything extra. */ } /* Implement the thread_alive target_ops method. */ static int netbsd_thread_alive (ptid_t ptid) { /* The list of threads is updated at the end of each wait, so it should be up to date. No need to re-fetch it. */ return (find_thread_ptid (ptid) != NULL); } /* Implement the fetch_registers target_ops method. */ static void netbsd_fetch_registers (struct regcache *regcache, int regno) { struct netbsd_regset_info *regset = netbsd_target_regsets; ptid_t inferior_ptid = ptid_of (current_thread); netbsd_debug ("netbsd_fetch_registers (regno = %d)", regno); while (regset->size >= 0) { char *buf; int res; buf = (char *)xmalloc (regset->size); res = netbsd_ptrace (regset->get_request, inferior_ptid.pid(), buf, inferior_ptid.lwp()); if (res < 0) perror ("ptrace"); regset->store_function (regcache, buf); free (buf); regset++; } } /* Implement the store_registers target_ops method. */ static void netbsd_store_registers (struct regcache *regcache, int regno) { struct netbsd_regset_info *regset = netbsd_target_regsets; ptid_t inferior_ptid = ptid_of (current_thread); netbsd_debug ("netbsd_store_registers (regno = %d)", regno); while (regset->size >= 0) { char *buf; int res; buf = (char *)xmalloc (regset->size); res = netbsd_ptrace (regset->get_request, inferior_ptid.pid(), buf, inferior_ptid.lwp()); if (res == 0) { /* Then overlay our cached registers on that. */ regset->fill_function (regcache, buf); /* Only now do we write the register set. */ res = netbsd_ptrace (regset->set_request, inferior_ptid.pid(), buf, inferior_ptid.lwp()); } if (res < 0) perror ("ptrace"); free (buf); regset++; } } /* Implement the read_memory target_ops method. */ static int netbsd_read_memory (CORE_ADDR memaddr, unsigned char *myaddr, int size) { struct ptrace_io_desc io; io.piod_op = PIOD_READ_D; io.piod_len = size; ptid_t inferior_ptid = ptid_of (current_thread); int bytes_read = 0; if (size == 0) { /* Zero length write always succeeds. */ return 0; } do { io.piod_offs = (void *)(memaddr + bytes_read); io.piod_addr = myaddr + bytes_read; int rv = netbsd_ptrace (PT_IO, inferior_ptid.pid(), &io, 0); if (rv == -1) return errno; if (io.piod_len == 0) return 0; bytes_read += io.piod_len; io.piod_len = size - bytes_read; } while (bytes_read < size); return 0; } /* Implement the write_memory target_ops method. */ static int netbsd_write_memory (CORE_ADDR memaddr, const unsigned char *myaddr, int size) { struct ptrace_io_desc io; io.piod_op = PIOD_WRITE_D; io.piod_len = size; ptid_t inferior_ptid = ptid_of (current_thread); int bytes_written = 0; if (size == 0) { /* Zero length write always succeeds. */ return 0; } do { io.piod_addr = (void *)(myaddr + bytes_written); io.piod_offs = (void *)(memaddr + bytes_written); int rv = netbsd_ptrace (PT_IO, inferior_ptid.pid(), &io, 0); if (rv == -1) return errno; if (io.piod_len == 0) return 0; bytes_written += io.piod_len; io.piod_len = size - bytes_written; } while (bytes_written < size); return 0; } /* Implement the kill_request target_ops method. */ static void netbsd_request_interrupt (void) { ptid_t inferior_ptid = ptid_of (get_first_thread ()); kill (netbsd_ptid_get_pid (inferior_ptid), SIGINT); } /* Copy LEN bytes from inferior's auxiliary vector starting at OFFSET to debugger memory starting at MYADDR. */ static int netbsd_read_auxv (CORE_ADDR offset, unsigned char *myaddr, unsigned int len) { struct ptrace_io_desc pio; pid_t pid = pid_of (current_thread); pio.piod_op = PIOD_READ_AUXV; pio.piod_offs = (void *)(intptr_t)offset; pio.piod_addr = myaddr; pio.piod_len = len; if (netbsd_ptrace (PT_IO, pid, &pio, 0) == -1) return 0; return pio.piod_len; } static int netbsd_supports_z_point_type (char z_type) { switch (z_type) { case Z_PACKET_SW_BP: #if 0 case Z_PACKET_HW_BP: case Z_PACKET_WRITE_WP: case Z_PACKET_READ_WP: case Z_PACKET_ACCESS_WP: #endif return 1; default: return 0; } } /* Insert {break/watch}point at address ADDR. SIZE is not used. */ static int netbsd_insert_point (enum raw_bkpt_type type, CORE_ADDR addr, int size, struct raw_breakpoint *bp) { netbsd_debug ("%s type:%c addr: 0x%08lx len:%d\n", __func__, (int)type, addr, size); switch (type) { case raw_bkpt_type_sw: return insert_memory_breakpoint (bp); case raw_bkpt_type_hw: case raw_bkpt_type_write_wp: case raw_bkpt_type_read_wp: case raw_bkpt_type_access_wp: default: return 1; /* Not supported. */ } } /* Remove {break/watch}point at address ADDR. SIZE is not used. */ static int netbsd_remove_point (enum raw_bkpt_type type, CORE_ADDR addr, int size, struct raw_breakpoint *bp) { netbsd_debug ("%s type:%c addr: 0x%08lx len:%d\n", __func__, (int)type, addr, size); switch (type) { case raw_bkpt_type_sw: return remove_memory_breakpoint (bp); case raw_bkpt_type_hw: case raw_bkpt_type_write_wp: case raw_bkpt_type_read_wp: case raw_bkpt_type_access_wp: default: return 1; /* Not supported. */ } } /* Implement the to_stopped_by_sw_breakpoint target_ops method. */ static int netbsd_stopped_by_sw_breakpoint (void) { ptrace_siginfo_t psi; pid_t pid = pid_of (current_thread); if (netbsd_ptrace (PT_GET_SIGINFO, pid, &psi, sizeof(psi)) == -1) return -1; // XXX return psi.psi_siginfo.si_signo == SIGTRAP && psi.psi_siginfo.si_code == TRAP_TRACE; } /* Implement the to_supports_stopped_by_sw_breakpoint target_ops method. */ static int netbsd_supports_stopped_by_sw_breakpoint (void) { return 1; } static int netbsd_supports_non_stop (void) { return 0; } static int netbsd_supports_multi_process (void) { return 0; /* XXX */ } /* Check if fork events are supported. */ static int netbsd_supports_fork_events (void) { return 1; } /* Check if vfork events are supported. */ static int netbsd_supports_vfork_events (void) { return 1; } /* Check if exec events are supported. */ static int netbsd_supports_exec_events (void) { return 1; } static int netbsd_supports_disable_randomization (void) { return 0; } /* Extract &phdr and num_phdr in the inferior. Return 0 on success. */ static int get_phdr_phnum_from_proc_auxv (const int pid, const int is_elf64, CORE_ADDR *phdr_memaddr, int *num_phdr) { struct ptrace_io_desc pio; const int auxv_size = is_elf64 ? sizeof (Aux64Info) : sizeof (Aux32Info); char *auxv_buf; char *buf; const size_t auxv_buf_size = 100 * sizeof(Aux64Info); auxv_buf = (char *)xmalloc(auxv_buf_size); pio.piod_op = PIOD_READ_AUXV; pio.piod_offs = 0; pio.piod_addr = auxv_buf; pio.piod_len = auxv_buf_size; netbsd_ptrace (PT_IO, pid, &pio, 0); *phdr_memaddr = 0; *num_phdr = 0; for (buf = auxv_buf; buf < (auxv_buf + auxv_buf_size); buf += auxv_size) { if (is_elf64) { Aux64Info *const aux = (Aux64Info *) buf; switch (aux->a_type) { case AT_PHDR: *phdr_memaddr = aux->a_v; break; case AT_PHNUM: *num_phdr = aux->a_v; break; } } else { Aux32Info *const aux = (Aux32Info *) buf; switch (aux->a_type) { case AT_PHDR: *phdr_memaddr = aux->a_v; break; case AT_PHNUM: *num_phdr = aux->a_v; break; } } if (*phdr_memaddr != 0 && *num_phdr != 0) break; } xfree (auxv_buf); if (*phdr_memaddr == 0 || *num_phdr == 0) { warning ("Unexpected missing AT_PHDR and/or AT_PHNUM: " "phdr_memaddr = %ld, phdr_num = %d", (long) *phdr_memaddr, *num_phdr); return 2; } return 0; } /* Return &_DYNAMIC (via PT_DYNAMIC) in the inferior, or 0 if not present. */ static CORE_ADDR get_dynamic (const int pid, const int is_elf64) { CORE_ADDR phdr_memaddr, relocation; int num_phdr, i; unsigned char *phdr_buf; const int phdr_size = is_elf64 ? sizeof (Elf64_Phdr) : sizeof (Elf32_Phdr); if (get_phdr_phnum_from_proc_auxv (pid, is_elf64, &phdr_memaddr, &num_phdr)) return 0; gdb_assert (num_phdr < 100); /* Basic sanity check. */ phdr_buf = (unsigned char *) alloca (num_phdr * phdr_size); if (netbsd_read_memory (phdr_memaddr, phdr_buf, num_phdr * phdr_size)) return 0; /* Compute relocation: it is expected to be 0 for "regular" executables, non-zero for PIE ones. */ relocation = -1; for (i = 0; relocation == -1 && i < num_phdr; i++) if (is_elf64) { Elf64_Phdr *const p = (Elf64_Phdr *) (phdr_buf + i * phdr_size); if (p->p_type == PT_PHDR) relocation = phdr_memaddr - p->p_vaddr; } else { Elf32_Phdr *const p = (Elf32_Phdr *) (phdr_buf + i * phdr_size); if (p->p_type == PT_PHDR) relocation = phdr_memaddr - p->p_vaddr; } if (relocation == -1) { /* PT_PHDR is optional, but necessary for PIE in general. Fortunately any real world executables, including PIE executables, have always PT_PHDR present. PT_PHDR is not present in some shared libraries or in fpc (Free Pascal 2.4) binaries but neither of those have a need for or present DT_DEBUG anyway (fpc binaries are statically linked). Therefore if there exists DT_DEBUG there is always also PT_PHDR. GDB could find RELOCATION also from AT_ENTRY - e_entry. */ return 0; } for (i = 0; i < num_phdr; i++) { if (is_elf64) { Elf64_Phdr *const p = (Elf64_Phdr *) (phdr_buf + i * phdr_size); if (p->p_type == PT_DYNAMIC) return p->p_vaddr + relocation; } else { Elf32_Phdr *const p = (Elf32_Phdr *) (phdr_buf + i * phdr_size); if (p->p_type == PT_DYNAMIC) return p->p_vaddr + relocation; } } return 0; } /* Return &_r_debug in the inferior, or -1 if not present. Return value can be 0 if the inferior does not yet have the library list initialized. We look for DT_MIPS_RLD_MAP first. MIPS executables use this instead of DT_DEBUG, although they sometimes contain an unused DT_DEBUG entry too. */ static CORE_ADDR get_r_debug (const int pid, const int is_elf64) { CORE_ADDR dynamic_memaddr; const int dyn_size = is_elf64 ? sizeof (Elf64_Dyn) : sizeof (Elf32_Dyn); unsigned char buf[sizeof (Elf64_Dyn)]; /* The larger of the two. */ CORE_ADDR map = -1; dynamic_memaddr = get_dynamic (pid, is_elf64); if (dynamic_memaddr == 0) return map; while (netbsd_read_memory (dynamic_memaddr, buf, dyn_size) == 0) { if (is_elf64) { Elf64_Dyn *const dyn = (Elf64_Dyn *) buf; #if defined DT_MIPS_RLD_MAP || defined DT_MIPS_RLD_MAP_REL union { Elf64_Xword map; unsigned char buf[sizeof (Elf64_Xword)]; } rld_map; #endif #ifdef DT_MIPS_RLD_MAP if (dyn->d_tag == DT_MIPS_RLD_MAP) { if (netbsd_read_memory (dyn->d_un.d_val, rld_map.buf, sizeof (rld_map.buf)) == 0) return rld_map.map; else break; } #endif /* DT_MIPS_RLD_MAP */ #ifdef DT_MIPS_RLD_MAP_REL if (dyn->d_tag == DT_MIPS_RLD_MAP_REL) { if (netbsd_read_memory (dyn->d_un.d_val + dynamic_memaddr, rld_map.buf, sizeof (rld_map.buf)) == 0) return rld_map.map; else break; } #endif /* DT_MIPS_RLD_MAP_REL */ if (dyn->d_tag == DT_DEBUG && map == -1) map = dyn->d_un.d_val; if (dyn->d_tag == DT_NULL) break; } else { Elf32_Dyn *const dyn = (Elf32_Dyn *) buf; #if defined DT_MIPS_RLD_MAP || defined DT_MIPS_RLD_MAP_REL union { Elf32_Word map; unsigned char buf[sizeof (Elf32_Word)]; } rld_map; #endif #ifdef DT_MIPS_RLD_MAP if (dyn->d_tag == DT_MIPS_RLD_MAP) { if (netbsd_read_memory (dyn->d_un.d_val, rld_map.buf, sizeof (rld_map.buf)) == 0) return rld_map.map; else break; } #endif /* DT_MIPS_RLD_MAP */ #ifdef DT_MIPS_RLD_MAP_REL if (dyn->d_tag == DT_MIPS_RLD_MAP_REL) { if (netbsd_read_memory (dyn->d_un.d_val + dynamic_memaddr, rld_map.buf, sizeof (rld_map.buf)) == 0) return rld_map.map; else break; } #endif /* DT_MIPS_RLD_MAP_REL */ if (dyn->d_tag == DT_DEBUG && map == -1) map = dyn->d_un.d_val; if (dyn->d_tag == DT_NULL) break; } dynamic_memaddr += dyn_size; } return map; } /* Read one pointer from MEMADDR in the inferior. */ static int read_one_ptr (CORE_ADDR memaddr, CORE_ADDR *ptr, int ptr_size) { int ret; /* Go through a union so this works on either big or little endian hosts, when the inferior's pointer size is smaller than the size of CORE_ADDR. It is assumed the inferior's endianness is the same of the superior's. */ union { CORE_ADDR core_addr; unsigned int ui; unsigned char uc; } addr; ret = netbsd_read_memory (memaddr, &addr.uc, ptr_size); if (ret == 0) { if (ptr_size == sizeof (CORE_ADDR)) *ptr = addr.core_addr; else if (ptr_size == sizeof (unsigned int)) *ptr = addr.ui; else gdb_assert_not_reached ("unhandled pointer size"); } return ret; } struct link_map_offsets { /* Offset and size of r_debug.r_version. */ int r_version_offset; /* Offset and size of r_debug.r_map. */ int r_map_offset; /* Offset to l_addr field in struct link_map. */ int l_addr_offset; /* Offset to l_name field in struct link_map. */ int l_name_offset; /* Offset to l_ld field in struct link_map. */ int l_ld_offset; /* Offset to l_next field in struct link_map. */ int l_next_offset; /* Offset to l_prev field in struct link_map. */ int l_prev_offset; }; /* Return non-zero if HEADER is a 64-bit ELF file. */ static int elf_64_header_p (const Elf64_Ehdr *header, unsigned int *machine) { if (header->e_ident[EI_MAG0] == ELFMAG0 && header->e_ident[EI_MAG1] == ELFMAG1 && header->e_ident[EI_MAG2] == ELFMAG2 && header->e_ident[EI_MAG3] == ELFMAG3) { *machine = header->e_machine; return header->e_ident[EI_CLASS] == ELFCLASS64; } *machine = EM_NONE; return -1; } /* Return non-zero if FILE is a 64-bit ELF file, zero if the file is not a 64-bit ELF file, and -1 if the file is not accessible or doesn't exist. */ static int elf_64_file_p (const char *file, unsigned int *machine) { Elf64_Ehdr header; int fd; fd = open (file, O_RDONLY); if (fd < 0) return -1; if (read (fd, &header, sizeof (header)) != sizeof (header)) { close (fd); return 0; } close (fd); return elf_64_header_p (&header, machine); } /* Construct qXfer:libraries-svr4:read reply. */ static int netbsd_qxfer_libraries_svr4 (const char *annex, unsigned char *readbuf, unsigned const char *writebuf, CORE_ADDR offset, int len) { struct process_info_private *const priv = current_process ()->priv; char filename[PATH_MAX]; int pid, is_elf64; static const struct link_map_offsets lmo_32bit_offsets = { 0, /* r_version offset. */ 4, /* r_debug.r_map offset. */ 0, /* l_addr offset in link_map. */ 4, /* l_name offset in link_map. */ 8, /* l_ld offset in link_map. */ 12, /* l_next offset in link_map. */ 16 /* l_prev offset in link_map. */ }; static const struct link_map_offsets lmo_64bit_offsets = { 0, /* r_version offset. */ 8, /* r_debug.r_map offset. */ 0, /* l_addr offset in link_map. */ 8, /* l_name offset in link_map. */ 16, /* l_ld offset in link_map. */ 24, /* l_next offset in link_map. */ 32 /* l_prev offset in link_map. */ }; const struct link_map_offsets *lmo; unsigned int machine; int ptr_size; CORE_ADDR lm_addr = 0, lm_prev = 0; CORE_ADDR l_name, l_addr, l_ld, l_next, l_prev; int header_done = 0; if (writebuf != NULL) return -2; if (readbuf == NULL) return -1; pid = pid_of (current_thread); xsnprintf (filename, sizeof filename, "/proc/%d/exe", pid); is_elf64 = elf_64_file_p (filename, &machine); lmo = is_elf64 ? &lmo_64bit_offsets : &lmo_32bit_offsets; ptr_size = is_elf64 ? 8 : 4; while (annex[0] != '\0') { const char *sep; CORE_ADDR *addrp; int name_len; sep = strchr (annex, '='); if (sep == NULL) break; name_len = sep - annex; if (name_len == 5 && startswith (annex, "start")) addrp = &lm_addr; else if (name_len == 4 && startswith (annex, "prev")) addrp = &lm_prev; else { annex = strchr (sep, ';'); if (annex == NULL) break; annex++; continue; } annex = decode_address_to_semicolon (addrp, sep + 1); } if (lm_addr == 0) { int r_version = 0; if (priv->r_debug == 0) priv->r_debug = get_r_debug (pid, is_elf64); /* We failed to find DT_DEBUG. Such situation will not change for this inferior - do not retry it. Report it to GDB as E01, see for the reasons at the GDB solib-svr4.c side. */ if (priv->r_debug == (CORE_ADDR) -1) return -1; if (priv->r_debug != 0) { if (netbsd_read_memory (priv->r_debug + lmo->r_version_offset, (unsigned char *) &r_version, sizeof (r_version)) != 0 || r_version != 1) { warning ("unexpected r_debug version %d", r_version); } else if (read_one_ptr (priv->r_debug + lmo->r_map_offset, &lm_addr, ptr_size) != 0) { warning ("unable to read r_map from 0x%lx", (long) priv->r_debug + lmo->r_map_offset); } } } std::string document = "l_name_offset, &l_name, ptr_size) == 0 && read_one_ptr (lm_addr + lmo->l_addr_offset, &l_addr, ptr_size) == 0 && read_one_ptr (lm_addr + lmo->l_ld_offset, &l_ld, ptr_size) == 0 && read_one_ptr (lm_addr + lmo->l_prev_offset, &l_prev, ptr_size) == 0 && read_one_ptr (lm_addr + lmo->l_next_offset, &l_next, ptr_size) == 0) { unsigned char libname[PATH_MAX]; if (lm_prev != l_prev) { warning ("Corrupted shared library list: 0x%lx != 0x%lx", (long) lm_prev, (long) l_prev); break; } /* Ignore the first entry even if it has valid name as the first entry corresponds to the main executable. The first entry should not be skipped if the dynamic loader was loaded late by a static executable (see solib-svr4.c parameter ignore_first). But in such case the main executable does not have PT_DYNAMIC present and this function already exited above due to failed get_r_debug. */ if (lm_prev == 0) string_appendf (document, " main-lm=\"0x%lx\"", (unsigned long) lm_addr); else { /* Not checking for error because reading may stop before we've got PATH_MAX worth of characters. */ libname[0] = '\0'; netbsd_read_memory (l_name, libname, sizeof (libname) - 1); libname[sizeof (libname) - 1] = '\0'; if (libname[0] != '\0') { if (!header_done) { /* Terminate `", (unsigned long) lm_addr, (unsigned long) l_addr, (unsigned long) l_ld); } } lm_prev = lm_addr; lm_addr = l_next; } if (!header_done) { /* Empty list; terminate ` document_len) len = document_len; memcpy (readbuf, document.data () + offset, len); return len; } /* Implementation of the target_ops method "sw_breakpoint_from_kind". */ static const gdb_byte * netbsd_sw_breakpoint_from_kind (int kind, int *size) { static gdb_byte brkpt[PTRACE_BREAKPOINT_SIZE]; *size = PTRACE_BREAKPOINT_SIZE; memcpy(brkpt, PTRACE_BREAKPOINT, PTRACE_BREAKPOINT_SIZE); return brkpt; } static int netbsd_supports_catch_syscall (void) { return 1; } /* The NetBSD target_ops vector. */ static struct target_ops netbsd_target_ops = { netbsd_create_inferior, NULL, /* post_create_inferior */ netbsd_attach, netbsd_kill, netbsd_detach, netbsd_mourn, netbsd_join, netbsd_thread_alive, netbsd_resume, netbsd_wait, netbsd_fetch_registers, netbsd_store_registers, NULL, /* prepare_to_access_memory */ NULL, /* done_accessing_memory */ netbsd_read_memory, netbsd_write_memory, NULL, /* look_up_symbols */ netbsd_request_interrupt, netbsd_read_auxv, netbsd_supports_z_point_type, netbsd_insert_point, netbsd_remove_point, netbsd_stopped_by_sw_breakpoint, netbsd_supports_stopped_by_sw_breakpoint, NULL, /* stopped_by_hw_breakpoint */ NULL, /* supports_stopped_by_hw_breakpoint */ target_can_do_hardware_single_step, NULL, /* stopped_by_watchpoint */ NULL, /* stopped_data_address */ NULL, /* read_offsets */ NULL, /* get_tls_address */ NULL, /* qxfer_spu */ hostio_last_error_from_errno, NULL, /* qxfer_osdata */ NULL, /* qxfer_siginfo */ netbsd_supports_non_stop, NULL, /* async */ NULL, /* start_non_stop */ netbsd_supports_multi_process, netbsd_supports_fork_events, netbsd_supports_vfork_events, netbsd_supports_exec_events, NULL, /* handle_new_gdb_connection */ NULL, /* handle_monitor_command */ NULL, /* core_of_thread */ NULL, /* read_loadmap */ NULL, /* process_qsupported */ NULL, /* supports_tracepoints */ NULL, /* read_pc */ NULL, /* write_pc */ NULL, /* thread_stopped */ NULL, /* get_tib_address */ NULL, /* pause_all */ NULL, /* unpause_all */ NULL, /* stabilize_threads */ NULL, /* install_fast_tracepoint_jump_pad */ NULL, /* emit_ops */ netbsd_supports_disable_randomization, NULL, /* get_min_fast_tracepoint_insn_len */ netbsd_qxfer_libraries_svr4, NULL, /* supports_agent */ NULL, /* enable_btrace */ NULL, /* disable_btrace */ NULL, /* read_btrace */ NULL, /* read_btrace_conf */ NULL, /* supports_range_stepping */ NULL, /* pid_to_exec_file */ NULL, /* multifs_open */ NULL, /* multifs_unlink */ NULL, /* multifs_readlink */ NULL, /* breakpoint_kind_from_pc */ netbsd_sw_breakpoint_from_kind, NULL, /* thread_name */ NULL, /* breakpoint_kind_from_current_state */ NULL, /* supports_software_single_step */ netbsd_supports_catch_syscall, NULL, /* get_ipa_tdesc_idx */ NULL, /* thread_handle */ }; void initialize_low (void) { set_target_ops (&netbsd_target_ops); the_low_target.arch_setup (); }