/* 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 . */ #include "server.h" #include "gdbthread.h" #include "nto-low.h" #include "hostio.h" #include "debug.h" #include #include #include #include #include #include #include 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 &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); }