binutils-gdb/gdb/gdbserver/nto-low.c
Sergio Durigan Junior 2090129c36 Share fork_inferior et al with gdbserver
This is the most important (and the biggest, sorry) patch of the
series.  It moves fork_inferior from gdb/fork-child.c to
nat/fork-inferior.c and makes all the necessary adjustments to both
GDB and gdbserver to make sure everything works OK.

There is no "most important change" with this patch; all changes are
made in a progressive way, making sure that gdbserver had the
necessary features while not breaking GDB at the same time.

I decided to go ahead and implement a partial support for starting the
inferior with a shell on gdbserver, although the full feature comes in
the next patch.  The user won't have the option to disable the
startup-with-shell, and also won't be able to change which shell
gdbserver will use (other than setting the $SHELL environment
variable, that is).

Everything is working as expected, and no regressions were present
during the tests.

gdb/ChangeLog:
2017-06-07  Sergio Durigan Junior  <sergiodj@redhat.com>
	    Pedro Alves  <palves@redhat.com>

	* Makefile.in (HFILES_NO_SRCDIR): Add "common/common-inferior.h"
	and "nat/fork-inferior.h".
	* common/common-inferior.h: New file, with contents from
	"gdb/inferior.h".
	* commom/common-utils.c: Include "common-utils.h".
	(stringify_argv): New function.
	* common/common-utils.h (stringify_argv): New prototype.
	* configure.nat: Add "fork-inferior.o" as a dependency for
	"*linux*", "fbsd*" and "nbsd*" hosts.
	* corefile.c (get_exec_file): Update comment.
	* darwin-nat.c (darwin_ptrace_him): Call "gdb_startup_inferior"
	instead of "startup_inferior".
	(darwin_create_inferior): Call "add_thread_silent" after
	"fork_inferior".
	* fork-child.c: Cleanup unnecessary includes.
	(SHELL_FILE): Move to "common/common-fork-child.c".
	(environ): Likewise.
	(exec_wrapper): Initialize.
	(get_exec_wrapper): New function.
	(breakup_args): Move to "common/common-fork-child.c"; rename to
	"breakup_args_for_exec".
	(escape_bang_in_quoted_argument): Move to
	"common/common-fork-child.c".
	(saved_ui): New variable.
	(prefork_hook): New function.
	(postfork_hook): Likewise.
	(postfork_child_hook): Likewise.
	(gdb_startup_inferior): Likewise.
	(fork_inferior): Move to "common/common-fork-child.c".  Update
	function to support gdbserver.
	(startup_inferior): Likewise.
	* gdbcore.h (get_exec_file): Remove declaration.
	* gnu-nat.c (gnu_create_inferior): Call "gdb_startup_inferior"
	instead of "startup_inferior".  Call "add_thread_silent" after
	"fork_inferior".
	* inf-ptrace.c: Include "nat/fork-inferior.h" and "utils.h".
	(inf_ptrace_create_inferior): Call "gdb_startup_inferior"
	instead of "startup_inferior".  Call "add_thread_silent" after
	"fork_inferior".
	* inferior.h: Include "common-inferior.h".
	(trace_start_error): Move to "common/common-utils.h".
	(trace_start_error_with_name): Likewise.
	(fork_inferior): Move prototype to "nat/fork-inferior.h".
	(startup_inferior): Likewise.
	(gdb_startup_inferior): New prototype.
	* nat/fork-inferior.c: New file, with contents from "fork-child.c".
	* nat/fork-inferior.h: New file.
	* procfs.c (procfs_init_inferior): Call "gdb_startup_inferior"
	instead of "startup_inferior".  Call "add_thread_silent" after
	"fork_inferior".
	* target.h (target_terminal_init): Move prototype to
	"target/target.h".
	(target_terminal_inferior): Likewise.
	(target_terminal_ours): Likewise.
	* target/target.h (target_terminal_init): New prototype, moved
	from "target.h".
	(target_terminal_inferior): Likewise.
	(target_terminal_ours): Likewise.
	* utils.c (gdb_flush_out_err): New function.

gdb/gdbserver/ChangeLog:
2017-06-07  Sergio Durigan Junior  <sergiodj@redhat.com>
	    Pedro Alves  <palves@redhat.com>

	* Makefile.in (SFILES): Add "nat/fork-inferior.o".
	* configure: Regenerate.
	* configure.srv (srv_linux_obj): Add "fork-child.o" and
	"fork-inferior.o".
	(i[34567]86-*-lynxos*): Likewise.
	(spu*-*-*): Likewise.
	* fork-child.c: New file.
	* linux-low.c: Include "common-inferior.h", "nat/fork-inferior.h"
	and "environ.h".
	(linux_ptrace_fun): New function.
	(linux_create_inferior): Adjust function prototype to reflect
	change on "target.h".  Adjust function code to use
	"fork_inferior".
	(linux_request_interrupt): Delete "signal_pid".
	* lynx-low.c: Include "common-inferior.h" and "nat/fork-inferior.h".
	(lynx_ptrace_fun): New function.
	(lynx_create_inferior): Adjust function prototype to reflect
	change on "target.h".  Adjust function code to use
	"fork_inferior".
	* nto-low.c (nto_create_inferior): Adjust function prototype and
	code to reflect change on "target.h".  Update comments.
	* server.c: Include "common-inferior.h", "nat/fork-inferior.h",
	"common-terminal.h" and "environ.h".
	(terminal_fd): Moved to fork-child.c.
	(old_foreground_pgrp): Likewise.
	(restore_old_foreground_pgrp): Likewise.
	(last_status): Make it global.
	(last_ptid): Likewise.
	(our_environ): New variable.
	(startup_with_shell): Likewise.
	(program_name): Likewise.
	(program_argv): Rename to...
	(program_args): ...this.
	(wrapper_argv): New variable.
	(start_inferior): Delete function.
	(get_exec_wrapper): New function.
	(get_exec_file): Likewise.
	(get_environ): Likewise.
	(prefork_hook): Likewise.
	(post_fork_inferior): Likewise.
	(postfork_hook): Likewise.
	(postfork_child_hook): Likewise.
	(handle_v_run): Update code to deal with arguments coming from the
	remote host.  Update calls from "start_inferior" to
	"create_inferior".
	(captured_main): Likewise.  Initialize environment variable.  Call
	"have_job_control".
	* server.h (post_fork_inferior): New prototype.
	(get_environ): Likewise.
	(last_status): Declare.
	(last_ptid): Likewise.
	(signal_pid): Likewise.
	* spu-low.c: Include "common-inferior.h" and "nat/fork-inferior.h".
	(spu_ptrace_fun): New function.
	(spu_create_inferior): Adjust function prototype to reflect change
	on "target.h".  Adjust function code to use "fork_inferior".
	* target.c (target_terminal_init): New function.
	(target_terminal_inferior): Likewise.
	(target_terminal_ours): Likewise.
	* target.h: Include <vector>.
	(struct target_ops) <create_inferior>: Update prototype.
	(create_inferior): Update macro.
	* utils.c (gdb_flush_out_err): New function.
	* win32-low.c (win32_create_inferior): Adjust function prototype
	and code to reflect change on "target.h".

gdb/testsuite/ChangeLog:
2017-06-07  Sergio Durigan Junior  <sergiodj@redhat.com>

	* gdb.server/non-existing-program.exp: Update regex in order to
	reflect the fact that gdbserver is now using fork_inferior (with a
	shell) to startup the inferior.
2017-06-07 19:56:09 -04:00

1033 lines
25 KiB
C

/* QNX Neutrino specific low level interface, for the remote server
for GDB.
Copyright (C) 2009-2017 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 <http://www.gnu.org/licenses/>. */
#include "server.h"
#include "gdbthread.h"
#include "nto-low.h"
#include "hostio.h"
#include <limits.h>
#include <fcntl.h>
#include <spawn.h>
#include <sys/procfs.h>
#include <sys/auxv.h>
#include <sys/iomgr.h>
#include <sys/neutrino.h>
extern int using_threads;
int using_threads = 1;
const struct target_desc *nto_tdesc;
static void
nto_trace (const char *fmt, ...)
{
va_list arg_list;
if (debug_threads == 0)
return;
fprintf (stderr, "nto:");
va_start (arg_list, fmt);
vfprintf (stderr, fmt, arg_list);
va_end (arg_list);
}
#define TRACE nto_trace
/* Structure holding neutrino specific information about
inferior. */
struct nto_inferior
{
char nto_procfs_path[PATH_MAX];
int ctl_fd;
pid_t pid;
int exit_signo; /* For tracking exit status. */
};
static struct nto_inferior nto_inferior;
static void
init_nto_inferior (struct nto_inferior *nto_inferior)
{
memset (nto_inferior, 0, sizeof (struct nto_inferior));
nto_inferior->ctl_fd = -1;
nto_inferior->pid = -1;
}
static void
do_detach (void)
{
if (nto_inferior.ctl_fd != -1)
{
nto_trace ("Closing fd\n");
close (nto_inferior.ctl_fd);
init_nto_inferior (&nto_inferior);
}
}
/* Set current thread. Return 1 on success, 0 otherwise. */
static int
nto_set_thread (ptid_t ptid)
{
int res = 0;
TRACE ("%s pid: %d tid: %ld\n", __func__, ptid_get_pid (ptid),
ptid_get_lwp (ptid));
if (nto_inferior.ctl_fd != -1
&& !ptid_equal (ptid, null_ptid)
&& !ptid_equal (ptid, minus_one_ptid))
{
pthread_t tid = ptid_get_lwp (ptid);
if (EOK == devctl (nto_inferior.ctl_fd, DCMD_PROC_CURTHREAD, &tid,
sizeof (tid), 0))
res = 1;
else
TRACE ("%s: Error: failed to set current thread\n", __func__);
}
return res;
}
/* This function will determine all alive threads. Note that we do not list
dead but unjoined threads even though they are still in the process' thread
list.
NTO_INFERIOR must not be NULL. */
static void
nto_find_new_threads (struct nto_inferior *nto_inferior)
{
pthread_t tid;
TRACE ("%s pid:%d\n", __func__, nto_inferior->pid);
if (nto_inferior->ctl_fd == -1)
return;
for (tid = 1;; ++tid)
{
procfs_status status;
ptid_t ptid;
int err;
status.tid = tid;
err = devctl (nto_inferior->ctl_fd, DCMD_PROC_TIDSTATUS, &status,
sizeof (status), 0);
if (err != EOK || status.tid == 0)
break;
/* All threads in between are gone. */
while (tid != status.tid || status.state == STATE_DEAD)
{
struct thread_info *ti;
ptid = ptid_build (nto_inferior->pid, tid, 0);
ti = find_thread_ptid (ptid);
if (ti != NULL)
{
TRACE ("Removing thread %d\n", tid);
remove_thread (ti);
}
if (tid == status.tid)
break;
++tid;
}
if (status.state != STATE_DEAD)
{
TRACE ("Adding thread %d\n", tid);
ptid = ptid_build (nto_inferior->pid, tid, 0);
if (!find_thread_ptid (ptid))
add_thread (ptid, NULL);
}
}
}
/* Given pid, open procfs path. */
static pid_t
do_attach (pid_t pid)
{
procfs_status status;
struct sigevent event;
if (nto_inferior.ctl_fd != -1)
{
close (nto_inferior.ctl_fd);
init_nto_inferior (&nto_inferior);
}
xsnprintf (nto_inferior.nto_procfs_path, PATH_MAX - 1, "/proc/%d/as", pid);
nto_inferior.ctl_fd = open (nto_inferior.nto_procfs_path, O_RDWR);
if (nto_inferior.ctl_fd == -1)
{
TRACE ("Failed to open %s\n", nto_inferior.nto_procfs_path);
init_nto_inferior (&nto_inferior);
return -1;
}
if (devctl (nto_inferior.ctl_fd, DCMD_PROC_STOP, &status, sizeof (status), 0)
!= EOK)
{
do_detach ();
return -1;
}
nto_inferior.pid = pid;
/* Define a sigevent for process stopped notification. */
event.sigev_notify = SIGEV_SIGNAL_THREAD;
event.sigev_signo = SIGUSR1;
event.sigev_code = 0;
event.sigev_value.sival_ptr = NULL;
event.sigev_priority = -1;
devctl (nto_inferior.ctl_fd, DCMD_PROC_EVENT, &event, sizeof (event), 0);
if (devctl (nto_inferior.ctl_fd, DCMD_PROC_STATUS, &status, sizeof (status),
0) == EOK
&& (status.flags & _DEBUG_FLAG_STOPPED))
{
ptid_t ptid;
struct process_info *proc;
kill (pid, SIGCONT);
ptid = ptid_build (status.pid, status.tid, 0);
the_low_target.arch_setup ();
proc = add_process (status.pid, 1);
proc->tdesc = nto_tdesc;
TRACE ("Adding thread: pid=%d tid=%ld\n", status.pid,
ptid_get_lwp (ptid));
nto_find_new_threads (&nto_inferior);
}
else
{
do_detach ();
return -1;
}
return pid;
}
/* Read or write LEN bytes from/to inferior's MEMADDR memory address
into gdbservers's MYADDR buffer. Return number of bytes actually
transfered. */
static int
nto_xfer_memory (off_t memaddr, unsigned char *myaddr, int len,
int dowrite)
{
int nbytes = 0;
if (lseek (nto_inferior.ctl_fd, memaddr, SEEK_SET) == memaddr)
{
if (dowrite)
nbytes = write (nto_inferior.ctl_fd, myaddr, len);
else
nbytes = read (nto_inferior.ctl_fd, myaddr, len);
if (nbytes < 0)
nbytes = 0;
}
if (nbytes == 0)
{
int e = errno;
TRACE ("Error in %s : errno=%d (%s)\n", __func__, e, strerror (e));
}
return nbytes;
}
/* Insert or remove breakpoint or watchpoint at address ADDR.
TYPE can be one of Neutrino breakpoint types. SIZE must be 0 for
inserting the point, -1 for removing it.
Return 0 on success, 1 otherwise. */
static int
nto_breakpoint (CORE_ADDR addr, int type, int size)
{
procfs_break brk;
brk.type = type;
brk.addr = addr;
brk.size = size;
if (devctl (nto_inferior.ctl_fd, DCMD_PROC_BREAK, &brk, sizeof (brk), 0)
!= EOK)
return 1;
return 0;
}
/* Read auxiliary vector from inferior's initial stack into gdbserver's
MYADDR buffer, up to LEN bytes.
Return number of bytes read. */
static int
nto_read_auxv_from_initial_stack (CORE_ADDR initial_stack,
unsigned char *myaddr,
unsigned int len)
{
int data_ofs = 0;
int anint;
unsigned int len_read = 0;
/* Skip over argc, argv and envp... Comment from ldd.c:
The startup frame is set-up so that we have:
auxv
NULL
...
envp2
envp1 <----- void *frame + (argc + 2) * sizeof(char *)
NULL
...
argv2
argv1
argc <------ void * frame
On entry to ldd, frame gives the address of argc on the stack. */
if (nto_xfer_memory (initial_stack, (unsigned char *)&anint,
sizeof (anint), 0) != sizeof (anint))
return 0;
/* Size of pointer is assumed to be 4 bytes (32 bit arch. ) */
data_ofs += (anint + 2) * sizeof (void *); /* + 2 comes from argc itself and
NULL terminating pointer in
argv. */
/* Now loop over env table: */
while (nto_xfer_memory (initial_stack + data_ofs,
(unsigned char *)&anint, sizeof (anint), 0)
== sizeof (anint))
{
data_ofs += sizeof (anint);
if (anint == 0)
break;
}
initial_stack += data_ofs;
memset (myaddr, 0, len);
while (len_read <= len - sizeof (auxv_t))
{
auxv_t *auxv = (auxv_t *)myaddr;
/* Search backwards until we have read AT_PHDR (num. 3),
AT_PHENT (num 4), AT_PHNUM (num 5) */
if (nto_xfer_memory (initial_stack, (unsigned char *)auxv,
sizeof (auxv_t), 0) == sizeof (auxv_t))
{
if (auxv->a_type != AT_NULL)
{
auxv++;
len_read += sizeof (auxv_t);
}
if (auxv->a_type == AT_PHNUM) /* That's all we need. */
break;
initial_stack += sizeof (auxv_t);
}
else
break;
}
TRACE ("auxv: len_read: %d\n", len_read);
return len_read;
}
/* Start inferior specified by PROGRAM, using PROGRAM_ARGS as its
arguments. */
static int
nto_create_inferior (const char *program,
const std::vector<char *> &program_args)
{
struct inheritance inherit;
pid_t pid;
sigset_t set;
std::string str_program_args = stringify_argv (program_args);
TRACE ("%s %s\n", __func__, program);
/* Clear any pending SIGUSR1's but keep the behavior the same. */
signal (SIGUSR1, signal (SIGUSR1, SIG_IGN));
sigemptyset (&set);
sigaddset (&set, SIGUSR1);
sigprocmask (SIG_UNBLOCK, &set, NULL);
memset (&inherit, 0, sizeof (inherit));
inherit.flags |= SPAWN_SETGROUP | SPAWN_HOLD;
inherit.pgroup = SPAWN_NEWPGROUP;
pid = spawnp (program, 0, NULL, &inherit,
(char *) str_program_args.c_str (), 0);
sigprocmask (SIG_BLOCK, &set, NULL);
if (pid == -1)
return -1;
if (do_attach (pid) != pid)
return -1;
return pid;
}
/* Attach to process PID. */
static int
nto_attach (unsigned long pid)
{
TRACE ("%s %ld\n", __func__, pid);
if (do_attach (pid) != pid)
error ("Unable to attach to %ld\n", pid);
return 0;
}
/* Send signal to process PID. */
static int
nto_kill (int pid)
{
TRACE ("%s %d\n", __func__, pid);
kill (pid, SIGKILL);
do_detach ();
return 0;
}
/* Detach from process PID. */
static int
nto_detach (int pid)
{
TRACE ("%s %d\n", __func__, pid);
do_detach ();
return 0;
}
static void
nto_mourn (struct process_info *process)
{
remove_process (process);
}
/* Check if the given thread is alive.
Return 1 if alive, 0 otherwise. */
static int
nto_thread_alive (ptid_t ptid)
{
int res;
TRACE ("%s pid:%d tid:%d\n", __func__, ptid_get_pid (ptid),
ptid_get_lwp (ptid));
if (SignalKill (0, ptid_get_pid (ptid), ptid_get_lwp (ptid),
0, 0, 0) == -1)
res = 0;
else
res = 1;
TRACE ("%s: %s\n", __func__, res ? "yes" : "no");
return res;
}
/* Resume inferior's execution. */
static void
nto_resume (struct thread_resume *resume_info, size_t n)
{
/* We can only work in all-stop mode. */
procfs_status status;
procfs_run run;
int err;
TRACE ("%s\n", __func__);
/* Workaround for aliasing rules violation. */
sigset_t *run_fault = (sigset_t *) (void *) &run.fault;
nto_set_thread (resume_info->thread);
run.flags = _DEBUG_RUN_FAULT | _DEBUG_RUN_TRACE;
if (resume_info->kind == resume_step)
run.flags |= _DEBUG_RUN_STEP;
run.flags |= _DEBUG_RUN_ARM;
sigemptyset (run_fault);
sigaddset (run_fault, FLTBPT);
sigaddset (run_fault, FLTTRACE);
sigaddset (run_fault, FLTILL);
sigaddset (run_fault, FLTPRIV);
sigaddset (run_fault, FLTBOUNDS);
sigaddset (run_fault, FLTIOVF);
sigaddset (run_fault, FLTIZDIV);
sigaddset (run_fault, FLTFPE);
sigaddset (run_fault, FLTPAGE);
sigaddset (run_fault, FLTSTACK);
sigaddset (run_fault, FLTACCESS);
sigemptyset (&run.trace);
if (resume_info->sig)
{
int signal_to_pass;
devctl (nto_inferior.ctl_fd, DCMD_PROC_STATUS, &status, sizeof (status),
0);
signal_to_pass = resume_info->sig;
if (status.why & (_DEBUG_WHY_SIGNALLED | _DEBUG_WHY_FAULTED))
{
if (signal_to_pass != status.info.si_signo)
{
kill (status.pid, signal_to_pass);
run.flags |= _DEBUG_RUN_CLRFLT | _DEBUG_RUN_CLRSIG;
}
else /* Let it kill the program without telling us. */
sigdelset (&run.trace, signal_to_pass);
}
}
else
run.flags |= _DEBUG_RUN_CLRSIG | _DEBUG_RUN_CLRFLT;
sigfillset (&run.trace);
regcache_invalidate ();
err = devctl (nto_inferior.ctl_fd, DCMD_PROC_RUN, &run, sizeof (run), 0);
if (err != EOK)
TRACE ("Error: %d \"%s\"\n", err, strerror (err));
}
/* Wait for inferior's event.
Return ptid of thread that caused the event. */
static ptid_t
nto_wait (ptid_t ptid,
struct target_waitstatus *ourstatus, int target_options)
{
sigset_t set;
siginfo_t info;
procfs_status status;
const int trace_mask = (_DEBUG_FLAG_TRACE_EXEC | _DEBUG_FLAG_TRACE_RD
| _DEBUG_FLAG_TRACE_WR | _DEBUG_FLAG_TRACE_MODIFY);
TRACE ("%s\n", __func__);
ourstatus->kind = TARGET_WAITKIND_SPURIOUS;
sigemptyset (&set);
sigaddset (&set, SIGUSR1);
devctl (nto_inferior.ctl_fd, DCMD_PROC_STATUS, &status, sizeof (status), 0);
while (!(status.flags & _DEBUG_FLAG_ISTOP))
{
sigwaitinfo (&set, &info);
devctl (nto_inferior.ctl_fd, DCMD_PROC_STATUS, &status, sizeof (status),
0);
}
nto_find_new_threads (&nto_inferior);
if (status.flags & _DEBUG_FLAG_SSTEP)
{
TRACE ("SSTEP\n");
ourstatus->kind = TARGET_WAITKIND_STOPPED;
ourstatus->value.sig = GDB_SIGNAL_TRAP;
}
/* Was it a breakpoint? */
else if (status.flags & trace_mask)
{
TRACE ("STOPPED\n");
ourstatus->kind = TARGET_WAITKIND_STOPPED;
ourstatus->value.sig = GDB_SIGNAL_TRAP;
}
else if (status.flags & _DEBUG_FLAG_ISTOP)
{
TRACE ("ISTOP\n");
switch (status.why)
{
case _DEBUG_WHY_SIGNALLED:
TRACE (" SIGNALLED\n");
ourstatus->kind = TARGET_WAITKIND_STOPPED;
ourstatus->value.sig =
gdb_signal_from_host (status.info.si_signo);
nto_inferior.exit_signo = ourstatus->value.sig;
break;
case _DEBUG_WHY_FAULTED:
TRACE (" FAULTED\n");
ourstatus->kind = TARGET_WAITKIND_STOPPED;
if (status.info.si_signo == SIGTRAP)
{
ourstatus->value.sig = 0;
nto_inferior.exit_signo = 0;
}
else
{
ourstatus->value.sig =
gdb_signal_from_host (status.info.si_signo);
nto_inferior.exit_signo = ourstatus->value.sig;
}
break;
case _DEBUG_WHY_TERMINATED:
{
int waitval = 0;
TRACE (" TERMINATED\n");
waitpid (ptid_get_pid (ptid), &waitval, WNOHANG);
if (nto_inferior.exit_signo)
{
/* Abnormal death. */
ourstatus->kind = TARGET_WAITKIND_SIGNALLED;
ourstatus->value.sig = nto_inferior.exit_signo;
}
else
{
/* Normal death. */
ourstatus->kind = TARGET_WAITKIND_EXITED;
ourstatus->value.integer = WEXITSTATUS (waitval);
}
nto_inferior.exit_signo = 0;
break;
}
case _DEBUG_WHY_REQUESTED:
TRACE ("REQUESTED\n");
/* We are assuming a requested stop is due to a SIGINT. */
ourstatus->kind = TARGET_WAITKIND_STOPPED;
ourstatus->value.sig = GDB_SIGNAL_INT;
nto_inferior.exit_signo = 0;
break;
}
}
return ptid_build (status.pid, status.tid, 0);
}
/* Fetch inferior's registers for currently selected thread (CURRENT_INFERIOR).
If REGNO is -1, fetch all registers, or REGNO register only otherwise. */
static void
nto_fetch_registers (struct regcache *regcache, int regno)
{
int regsize;
procfs_greg greg;
ptid_t ptid;
TRACE ("%s (regno=%d)\n", __func__, regno);
if (regno >= the_low_target.num_regs)
return;
if (current_thread == NULL)
{
TRACE ("current_thread is NULL\n");
return;
}
ptid = thread_to_gdb_id (current_thread);
if (!nto_set_thread (ptid))
return;
if (devctl (nto_inferior.ctl_fd, DCMD_PROC_GETGREG, &greg, sizeof (greg),
&regsize) == EOK)
{
if (regno == -1) /* All registers. */
{
for (regno = 0; regno != the_low_target.num_regs; ++regno)
{
const unsigned int registeroffset
= the_low_target.register_offset (regno);
supply_register (regcache, regno,
((char *)&greg) + registeroffset);
}
}
else
{
const unsigned int registeroffset
= the_low_target.register_offset (regno);
if (registeroffset == -1)
return;
supply_register (regcache, regno, ((char *)&greg) + registeroffset);
}
}
else
TRACE ("ERROR reading registers from inferior.\n");
}
/* Store registers for currently selected thread (CURRENT_INFERIOR).
We always store all registers, regardless of REGNO. */
static void
nto_store_registers (struct regcache *regcache, int regno)
{
procfs_greg greg;
int err;
ptid_t ptid;
TRACE ("%s (regno:%d)\n", __func__, regno);
if (current_thread == NULL)
{
TRACE ("current_thread is NULL\n");
return;
}
ptid = thread_to_gdb_id (current_thread);
if (!nto_set_thread (ptid))
return;
memset (&greg, 0, sizeof (greg));
for (regno = 0; regno != the_low_target.num_regs; ++regno)
{
const unsigned int regoffset
= the_low_target.register_offset (regno);
collect_register (regcache, regno, ((char *)&greg) + regoffset);
}
err = devctl (nto_inferior.ctl_fd, DCMD_PROC_SETGREG, &greg, sizeof (greg),
0);
if (err != EOK)
TRACE ("Error: setting registers.\n");
}
/* Read LEN bytes from inferior's memory address MEMADDR into
gdbserver's MYADDR buffer.
Return 0 on success -1 otherwise. */
static int
nto_read_memory (CORE_ADDR memaddr, unsigned char *myaddr, int len)
{
TRACE ("%s memaddr:0x%08lx, len:%d\n", __func__, memaddr, len);
if (nto_xfer_memory (memaddr, myaddr, len, 0) != len)
{
TRACE ("Failed to read memory\n");
return -1;
}
return 0;
}
/* Write LEN bytes from gdbserver's buffer MYADDR into inferior's
memory at address MEMADDR.
Return 0 on success -1 otherwise. */
static int
nto_write_memory (CORE_ADDR memaddr, const unsigned char *myaddr, int len)
{
int len_written;
TRACE ("%s memaddr: 0x%08llx len: %d\n", __func__, memaddr, len);
if ((len_written = nto_xfer_memory (memaddr, (unsigned char *)myaddr, len,
1))
!= len)
{
TRACE ("Wanted to write: %d but written: %d\n", len, len_written);
return -1;
}
return 0;
}
/* Stop inferior. We always stop all threads. */
static void
nto_request_interrupt (void)
{
TRACE ("%s\n", __func__);
nto_set_thread (ptid_build (nto_inferior.pid, 1, 0));
if (EOK != devctl (nto_inferior.ctl_fd, DCMD_PROC_STOP, NULL, 0, 0))
TRACE ("Error stopping inferior.\n");
}
/* Read auxiliary vector from inferior's memory into gdbserver's buffer
MYADDR. We always read whole auxv.
Return number of bytes stored in MYADDR buffer, 0 if OFFSET > 0
or -1 on error. */
static int
nto_read_auxv (CORE_ADDR offset, unsigned char *myaddr, unsigned int len)
{
int err;
CORE_ADDR initial_stack;
procfs_info procinfo;
TRACE ("%s\n", __func__);
if (offset > 0)
return 0;
err = devctl (nto_inferior.ctl_fd, DCMD_PROC_INFO, &procinfo,
sizeof procinfo, 0);
if (err != EOK)
return -1;
initial_stack = procinfo.initial_stack;
return nto_read_auxv_from_initial_stack (initial_stack, myaddr, len);
}
static int
nto_supports_z_point_type (char z_type)
{
switch (z_type)
{
case Z_PACKET_SW_BP:
case Z_PACKET_HW_BP:
case Z_PACKET_WRITE_WP:
case Z_PACKET_READ_WP:
case Z_PACKET_ACCESS_WP:
return 1;
default:
return 0;
}
}
/* Insert {break/watch}point at address ADDR. SIZE is not used. */
static int
nto_insert_point (enum raw_bkpt_type type, CORE_ADDR addr,
int size, struct raw_breakpoint *bp)
{
int wtype = _DEBUG_BREAK_HW; /* Always request HW. */
TRACE ("%s type:%c addr: 0x%08lx len:%d\n", __func__, (int)type, addr, size);
switch (type)
{
case raw_bkpt_type_sw:
wtype = _DEBUG_BREAK_EXEC;
break;
case raw_bkpt_type_hw:
wtype |= _DEBUG_BREAK_EXEC;
break;
case raw_bkpt_type_write_wp:
wtype |= _DEBUG_BREAK_RW;
break;
case raw_bkpt_type_read_wp:
wtype |= _DEBUG_BREAK_RD;
break;
case raw_bkpt_type_access_wp:
wtype |= _DEBUG_BREAK_RW;
break;
default:
return 1; /* Not supported. */
}
return nto_breakpoint (addr, wtype, 0);
}
/* Remove {break/watch}point at address ADDR. SIZE is not used. */
static int
nto_remove_point (enum raw_bkpt_type type, CORE_ADDR addr,
int size, struct raw_breakpoint *bp)
{
int wtype = _DEBUG_BREAK_HW; /* Always request HW. */
TRACE ("%s type:%c addr: 0x%08lx len:%d\n", __func__, (int)type, addr, size);
switch (type)
{
case raw_bkpt_type_sw:
wtype = _DEBUG_BREAK_EXEC;
break;
case raw_bkpt_type_hw:
wtype |= _DEBUG_BREAK_EXEC;
break;
case raw_bkpt_type_write_wp:
wtype |= _DEBUG_BREAK_RW;
break;
case raw_bkpt_type_read_wp:
wtype |= _DEBUG_BREAK_RD;
break;
case raw_bkpt_type_access_wp:
wtype |= _DEBUG_BREAK_RW;
break;
default:
return 1; /* Not supported. */
}
return nto_breakpoint (addr, wtype, -1);
}
/* Check if the reason of stop for current thread (CURRENT_INFERIOR) is
a watchpoint.
Return 1 if stopped by watchpoint, 0 otherwise. */
static int
nto_stopped_by_watchpoint (void)
{
int ret = 0;
TRACE ("%s\n", __func__);
if (nto_inferior.ctl_fd != -1 && current_thread != NULL)
{
ptid_t ptid;
ptid = thread_to_gdb_id (current_thread);
if (nto_set_thread (ptid))
{
const int watchmask = _DEBUG_FLAG_TRACE_RD | _DEBUG_FLAG_TRACE_WR
| _DEBUG_FLAG_TRACE_MODIFY;
procfs_status status;
int err;
err = devctl (nto_inferior.ctl_fd, DCMD_PROC_STATUS, &status,
sizeof (status), 0);
if (err == EOK && (status.flags & watchmask))
ret = 1;
}
}
TRACE ("%s: %s\n", __func__, ret ? "yes" : "no");
return ret;
}
/* Get instruction pointer for CURRENT_INFERIOR thread.
Return inferior's instruction pointer value, or 0 on error. */
static CORE_ADDR
nto_stopped_data_address (void)
{
CORE_ADDR ret = (CORE_ADDR)0;
TRACE ("%s\n", __func__);
if (nto_inferior.ctl_fd != -1 && current_thread != NULL)
{
ptid_t ptid;
ptid = thread_to_gdb_id (current_thread);
if (nto_set_thread (ptid))
{
procfs_status status;
if (devctl (nto_inferior.ctl_fd, DCMD_PROC_STATUS, &status,
sizeof (status), 0) == EOK)
ret = status.ip;
}
}
TRACE ("%s: 0x%08lx\n", __func__, ret);
return ret;
}
/* We do not currently support non-stop. */
static int
nto_supports_non_stop (void)
{
TRACE ("%s\n", __func__);
return 0;
}
/* Implementation of the target_ops method "sw_breakpoint_from_kind". */
static const gdb_byte *
nto_sw_breakpoint_from_kind (int kind, int *size)
{
*size = the_low_target.breakpoint_len;
return the_low_target.breakpoint;
}
static struct target_ops nto_target_ops = {
nto_create_inferior,
NULL, /* post_create_inferior */
nto_attach,
nto_kill,
nto_detach,
nto_mourn,
NULL, /* nto_join */
nto_thread_alive,
nto_resume,
nto_wait,
nto_fetch_registers,
nto_store_registers,
NULL, /* prepare_to_access_memory */
NULL, /* done_accessing_memory */
nto_read_memory,
nto_write_memory,
NULL, /* nto_look_up_symbols */
nto_request_interrupt,
nto_read_auxv,
nto_supports_z_point_type,
nto_insert_point,
nto_remove_point,
NULL, /* stopped_by_sw_breakpoint */
NULL, /* supports_stopped_by_sw_breakpoint */
NULL, /* stopped_by_hw_breakpoint */
NULL, /* supports_stopped_by_hw_breakpoint */
target_can_do_hardware_single_step,
nto_stopped_by_watchpoint,
nto_stopped_data_address,
NULL, /* nto_read_offsets */
NULL, /* thread_db_set_tls_address */
NULL,
hostio_last_error_from_errno,
NULL, /* nto_qxfer_osdata */
NULL, /* xfer_siginfo */
nto_supports_non_stop,
NULL, /* async */
NULL, /* start_non_stop */
NULL, /* supports_multi_process */
NULL, /* supports_fork_events */
NULL, /* supports_vfork_events */
NULL, /* 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 */
NULL, /* supports_disable_randomization */
NULL, /* get_min_fast_tracepoint_insn_len */
NULL, /* qxfer_libraries_svr4 */
NULL, /* support_agent */
NULL, /* support_btrace */
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 */
nto_sw_breakpoint_from_kind,
};
/* Global function called by server.c. Initializes QNX Neutrino
gdbserver. */
void
initialize_low (void)
{
sigset_t set;
TRACE ("%s\n", __func__);
set_target_ops (&nto_target_ops);
/* We use SIGUSR1 to gain control after we block waiting for a process.
We use sigwaitevent to wait. */
sigemptyset (&set);
sigaddset (&set, SIGUSR1);
sigprocmask (SIG_BLOCK, &set, NULL);
}