mirror of
https://sourceware.org/git/binutils-gdb.git
synced 2024-11-24 18:44:20 +08:00
6d91ce9a65
This provides threadsafety. Unfortunately, since libinproctrace.so does not link to gnulib, we can't use it there, especially since it still includes the gnulib headers (so it is difficult to directly call the system strerror_r). gdb/ChangeLog: 2019-11-26 Christian Biesinger <cbiesinger@google.com> * linux-nat.c (detach_one_lwp): Call safe_strerror instead of strerror. * nto-procfs.c (nto_procfs_target::create_inferior): Likewise. * windows-nat.c (windows_nat_target::create_inferior): Likewise. gdb/gdbserver/ChangeLog: 2019-11-26 Christian Biesinger <cbiesinger@google.com> * debug.c (debug_set_output): Call safe_strerror instead of strerror. * linux-low.c (attach_proc_task_lwp_callback): Likewise. (linux_kill_one_lwp): Likewise. (linux_detach_one_lwp): Likewise. (linux_wait_for_event_filtered): Likewise. (store_register): Likewise. * lynx-low.c (lynx_attach): Likewise. * mem-break.c (insert_memory_breakpoint): Likewise. (remove_memory_breakpoint): Likewise. (delete_fast_tracepoint_jump): Likewise. (set_fast_tracepoint_jump): Likewise. (uninsert_fast_tracepoint_jumps_at): Likewise. (reinsert_fast_tracepoint_jumps_at): Likewise. * nto-low.c (nto_xfer_memory): Likewise. (nto_resume): Likewise. Change-Id: I9e259cdcaa6e11bbcc4ee6bdc5b7127d73e11abe
1027 lines
24 KiB
C
1027 lines
24 KiB
C
/* QNX Neutrino specific low level interface, for the remote server
|
|
for GDB.
|
|
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 <http://www.gnu.org/licenses/>. */
|
|
|
|
|
|
#include "server.h"
|
|
#include "gdbthread.h"
|
|
#include "nto-low.h"
|
|
#include "hostio.h"
|
|
#include "debug.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>
|
|
|
|
|
|
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.pid (),
|
|
ptid.lwp ());
|
|
if (nto_inferior.ctl_fd != -1
|
|
&& ptid != null_ptid
|
|
&& ptid != minus_one_ptid)
|
|
{
|
|
pthread_t tid = ptid.lwp ();
|
|
|
|
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_t (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_t (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_t (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.lwp ());
|
|
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, safe_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 (process_info *proc)
|
|
{
|
|
int pid = proc->pid;
|
|
|
|
TRACE ("%s %d\n", __func__, pid);
|
|
kill (pid, SIGKILL);
|
|
do_detach ();
|
|
return 0;
|
|
}
|
|
|
|
/* Detach from process PID. */
|
|
|
|
static int
|
|
nto_detach (process_info *proc)
|
|
{
|
|
TRACE ("%s %d\n", __func__, proc->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.pid (),
|
|
ptid.lwp ());
|
|
if (SignalKill (0, ptid.pid (), ptid.lwp (),
|
|
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, safe_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.pid (), &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_t (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;
|
|
|
|
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_t ptid = ptid_of (current_thread);
|
|
if (!nto_set_thread (ptid))
|
|
return;
|
|
|
|
if (devctl (nto_inferior.ctl_fd, DCMD_PROC_GETGREG, &greg, sizeof (greg),
|
|
®size) == 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;
|
|
|
|
TRACE ("%s (regno:%d)\n", __func__, regno);
|
|
|
|
if (current_thread == NULL)
|
|
{
|
|
TRACE ("current_thread is NULL\n");
|
|
return;
|
|
}
|
|
ptid_t ptid = ptid_of (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_t (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_of (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_of (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 */
|
|
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, /* 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);
|
|
}
|
|
|