* gdbserver/thread-db.c: New file.
* gdbserver/proc-service.c: New file.
* gdbserver/acinclude.m4: New file.
* gdbserver/Makefile.in: Add GDBSERVER_LIBS, gdb_proc_service_h,
proc-service.o, and thread-db.o.
(linux-low.o): Add USE_THREAD_DB.
* gdbserver/acconfig.h: Add HAVE_PRGREGSET_T, HAVE_PRFPREGSET_T,
HAVE_LWPID_T, HAVE_PSADDR_T, and PRFPREGSET_T_BROKEN.
* gdbserver/aclocal.m4: Regenerated.
* gdbserver/config.in: Regenerated.
* gdbserver/configure: Regenerated.
* gdbserver/configure.in: Check for proc_service.h, sys/procfs.h,
thread_db.h, and linux/elf.h headrs.
Check for lwpid_t, psaddr_t, prgregset_t, prfpregset_t, and
PRFPREGSET_T_BROKEN. Introduce srv_thread_depfiles and USE_THREAD_DB.
Check for -lthread_db and thread support.
* gdbserver/configure.srv: Enable thread_db support for ARM, i386, MIPS,
PowerPC, and SuperH.
* gdbserver/i387-fp.c: Constify arguments.
* gdbserver/i387-fp.h: Likewise.
* gdbserver/inferiors.c: (struct thread_info): Renamed from
`struct inferior_info'. Remove PID member. Use generic inferior
list header. All uses updated.
(inferiors, signal_pid): Removed.
(all_threads): New variable.
(get_thread): Define.
(add_inferior_to_list): New function.
(for_each_inferior): New function.
(change_inferior_id): New function.
(add_inferior): Removed.
(remove_inferior): New function.
(add_thread): New function.
(free_one_thread): New function.
(remove_thread): New function.
(clear_inferiors): Use for_each_inferior and free_one_thread.
(find_inferior): New function.
(find_inferior_id): New function.
(inferior_target_data): Update argument type.
(set_inferior_target_data): Likewise.
(inferior_regcache_data): Likewise.
(set_inferior_regcache_data): Likewise.
* gdbserver/linux-low.c (linux_bp_reinsert): Remove.
(all_processes, stopping_threads, using_thrads)
(struct pending_signals, debug_threads, pid_of): New.
(inferior_pid): Replace with macro.
(struct inferior_linux_data): Remove.
(get_stop_pc, add_process): New functions.
(linux_create_inferior): Restore SIGRTMIN+1 before calling exec.
Use add_process and add_thread.
(linux_attach_lwp): New function, based on old linux_attach. Use
add_process and add_thread. Set stop_expected for new threads.
(linux_attach): New function.
(linux_kill_one_process): New function.
(linux_kill): Kill all LWPs.
(linux_thread_alive): Use find_inferior_id.
(check_removed_breakpoints, status_pending_p): New functions.
(linux_wait_for_process): Renamed from linux_wait_for_one_inferior.
Update. Use WNOHANG. Wait for cloned processes also. Update process
struct for the found process.
(linux_wait_for_event): New function.
(linux_wait): Use it. Support LWPs.
(send_sigstop, wait_for_sigstop, stop_all_processes)
(linux_resume_one_process, linux_continue_one_process): New functions.
(linux_resume): Support LWPs.
(REGISTER_RAW_SIZE): Remove.
(fetch_register): Use register_size instead. Call supply_register.
(usr_store_inferior_registers): Likewise. Call collect_register.
Fix recursive case.
(regsets_fetch_inferior_registers): Improve error message.
(regsets_store_inferior_registers): Add debugging.
(linux_look_up_symbols): Call thread_db_init if USE_THREAD_DB.
(unstopped_p, linux_signal_pid): New functions.
(linux_target_ops): Add linux_signal_pid.
(linux_init_signals): New function.
(initialize_low): Call it. Initialize using_threads.
* gdbserver/regcache.c (inferior_regcache_data): Add valid
flag.
(get_regcache): Fetch registers lazily. Add fetch argument
and update all callers.
(regcache_invalidate_one, regcache_invalidate): New
functions.
(new_register_cache): Renamed from create_register_cache.
Return the new regcache.
(free_register_cache): Change argument to a void *.
(registers_to_string, registers_from_string): Call get_regcache
with fetch flag set.
(register_data): Make static. Pass fetch flag to get_regcache.
(supply_register): Call get_regcache with fetch flag clear.
(collect_register): Call get_regcache with fetch flag set.
(collect_register_as_string): New function.
* gdbserver/regcache.h: Update.
* gdbserver/remote-utils.c (putpkt): Flush after debug output and use
stderr.
Handle input interrupts while waiting for an ACK.
(input_interrupt): Use signal_pid method.
(getpkt): Flush after debug output and use stderr.
(outreg): Use collect_register_as_string.
(new_thread_notify, dead_thread_notify): New functions.
(prepare_resume_reply): Check using_threads. Set thread_from_wait
and general_thread.
(look_up_one_symbol): Flush after debug output.
* gdbserver/server.c (step_thread, server_waiting): New variables.
(start_inferior): Don't use signal_pid. Update call to mywait.
(attach_inferior): Update call to mywait.
(handle_query): Handle qfThreadInfo and qsThreadInfo.
(main): Don't fetch/store registers explicitly. Use
set_desired_inferior. Support proposed ``Hs'' packet. Update
calls to mywait.
* gdbserver/server.h: Update.
(struct inferior_list, struct_inferior_list_entry): New.
* gdbserver/target.c (set_desired_inferior): New.
(write_inferior_memory): Constify.
(mywait): New function.
* gdbserver/target.h: Update.
(struct target_ops): New signal_pid method.
(mywait): Removed macro, added prototype.
* gdbserver/linux-low.h (regset_func): Removed.
(regset_fill_func, regset_store_func): New.
(enum regset_type): New.
(struct regset_info): Add type field. Use new operation types.
(struct linux_target_ops): stop_pc renamed to get_pc.
Add decr_pc_after_break and breakpoint_at.
(get_process, get_thread_proess, get_process_thread)
(strut process_info, all_processes, linux_attach_lwp)
(thread_db_init): New.
* gdbserver/linux-arm-low.c (arm_get_pc, arm_set_pc,
arm_breakpoint, arm_breakpoint_len, arm_breakpoint_at): New.
(the_low_target): Add new members.
* gdbserver/linux-i386-low.c (i386_store_gregset, i386_store_fpregset)
(i386_store_fpxregset): Constify.
(target_regsets): Add new kind identifier.
(i386_get_pc): Renamed from i386_stop_pc. Simplify.
(i386_set_pc): Add debugging.
(i386_breakpoint_at): New function.
(the_low_target): Add new members.
* gdbserver/linux-mips-low.c (mips_get_pc, mips_set_pc)
(mips_breakpoint, mips_breakpoint_len, mips_reinsert_addr)
(mips_breakpoint_at): New.
(the_low_target): Add new members.
* gdbserver/linux-ppc-low.c (ppc_get_pc, ppc_set_pc)
(ppc_breakpoint, ppc_breakpoint_len, ppc_breakpoint_at): New.
(the_low_target): Add new members.
* gdbserver/linux-sh-low.c (sh_get_pc, sh_set_pc)
(sh_breakpoint, sh_breakpoint_len, sh_breakpoint_at): New.
(the_low_target): Add new members.
* gdbserver/linux-x86-64-low.c (target_regsets): Add new kind
identifier.
utils.o \
mem-break.o \
$(DEPFILES)
+GDBSERVER_LIBS = @GDBSERVER_LIBS@
# Prevent Sun make from putting in the machine type. Setting
# TARGET_ARCH to nothing works for SunOS 3, 4.0, but not for 4.1.
## with no dependencies and no actions.
unexport CHILLFLAGS CHILL_LIB CHILL_FOR_TARGET :
+gdb_proc_service_h = $(srcdir)/../gdb_proc_service.h $(srcdir)/../gregset.h
regdat_sh = $(srcdir)/../regformats/regdat.sh
regdef_h = $(srcdir)/../regformats/regdef.h
regcache_h = $(srcdir)/regcache.h
inferiors.o: inferiors.c $(server_h)
mem-break.o: mem-break.c $(server_h)
+proc-service.o: proc-service.c $(server_h) $(gdb_proc_service_h)
regcache.o: regcache.c $(server_h) $(regdef_h)
remote-utils.o: remote-utils.c terminal.h $(server_h)
server.o: server.c $(server_h)
target.o: target.c $(server_h)
+thread-db.o: thread-db.c $(server_h) $(gdb_proc_service_h)
utils.o: utils.c $(server_h)
signals.o: ../signals/signals.c $(server_h)
linux_low_h = $(srcdir)/linux-low.h
linux-low.o: linux-low.c $(linux_low_h) $(server_h)
+ $(CC) -c $(CPPFLAGS) $(INTERNAL_CFLAGS) $< @USE_THREAD_DB@
+
linux-arm-low.o: linux-arm-low.c $(linux_low_h) $(server_h)
linux-i386-low.o: linux-i386-low.c $(linux_low_h) $(server_h)
linux-ia64-low.o: linux-ia64-low.c $(linux_low_h) $(server_h)
/* Define if the target supports PTRACE_GETFPXREGS for extended
register access. */
#undef HAVE_PTRACE_GETFPXREGS
+
+/* Define if <sys/procfs.h> has prgregset_t. */
+#undef HAVE_PRGREGSET_T
+
+/* Define if <sys/procfs.h> has prfpregset_t. */
+#undef HAVE_PRFPREGSET_T
+
+/* Define if <sys/procfs.h> has lwpid_t. */
+#undef HAVE_LWPID_T
+
+/* Define if <sys/procfs.h> has psaddr_t. */
+#undef HAVE_PSADDR_T
+
+/* Define if the prfpregset_t type is broken. */
+#undef PRFPREGSET_T_BROKEN
--- /dev/null
+dnl gdb/gdbserver/configure.in uses BFD_HAVE_SYS_PROCFS_TYPE.
+sinclude(../../bfd/acinclude.m4)
+
+AC_DEFUN([SRV_CHECK_THREAD_DB],
+[AC_CACHE_CHECK([for libthread_db],[srv_cv_thread_db],
+ [old_LIBS="$LIBS"
+ LIBS="$LIBS -lthread_db"
+ AC_TRY_LINK(
+ [void ps_pglobal_lookup() {}
+ void ps_pdread() {}
+ void ps_pdwrite() {}
+ void ps_lgetregs() {}
+ void ps_lsetregs() {}
+ void ps_lgetfpregs() {}
+ void ps_lsetfpregs() {}
+ void ps_getpid() {}],
+ [td_ta_new();],
+ [srv_cv_thread_db="-lthread_db"],
+ [srv_cv_thread_db=no
+
+ if test "$prefix" = "/usr" || test "$prefix" = "NONE"; then
+ thread_db="/lib/libthread_db.so.1"
+ else
+ thread_db='$prefix/lib/libthread_db.so.1'
+ fi
+ LIBS="$old_LIBS `eval echo "$thread_db"`"
+ AC_TRY_LINK(
+ [void ps_pglobal_lookup() {}
+ void ps_pdread() {}
+ void ps_pdwrite() {}
+ void ps_lgetregs() {}
+ void ps_lsetregs() {}
+ void ps_lgetfpregs() {}
+ void ps_lsetfpregs() {}
+ void ps_getpid() {}],
+ [td_ta_new();],
+ [srv_cv_thread_db="$thread_db"],
+ [srv_cv_thread_db=no])
+ LIBS="$old_LIBS"
+ ]])
+)])
dnl even the implied warranty of MERCHANTABILITY or FITNESS FOR A
dnl PARTICULAR PURPOSE.
+dnl gdb/gdbserver/configure.in uses BFD_HAVE_SYS_PROCFS_TYPE.
+sinclude(../../bfd/acinclude.m4)
-# serial 1
+AC_DEFUN([SRV_CHECK_THREAD_DB],
+[AC_CACHE_CHECK([for libthread_db],[srv_cv_thread_db],
+ [old_LIBS="$LIBS"
+ LIBS="$LIBS -lthread_db"
+ AC_TRY_LINK(
+ [void ps_pglobal_lookup() {}
+ void ps_pdread() {}
+ void ps_pdwrite() {}
+ void ps_lgetregs() {}
+ void ps_lsetregs() {}
+ void ps_lgetfpregs() {}
+ void ps_lsetfpregs() {}
+ void ps_getpid() {}],
+ [td_ta_new();],
+ [srv_cv_thread_db="-lthread_db"],
+ [srv_cv_thread_db=no
-# @defmac AC_PROG_CC_STDC
-# @maindex PROG_CC_STDC
-# @ovindex CC
-# If the C compiler in not in ANSI C mode by default, try to add an option
-# to output variable @code{CC} to make it so. This macro tries various
-# options that select ANSI C on some system or another. It considers the
-# compiler to be in ANSI C mode if it handles function prototypes correctly.
-#
-# If you use this macro, you should check after calling it whether the C
-# compiler has been set to accept ANSI C; if not, the shell variable
-# @code{am_cv_prog_cc_stdc} is set to @samp{no}. If you wrote your source
-# code in ANSI C, you can make an un-ANSIfied copy of it by using the
-# program @code{ansi2knr}, which comes with Ghostscript.
-# @end defmac
-
-AC_DEFUN(AM_PROG_CC_STDC,
-[AC_REQUIRE([AC_PROG_CC])
-AC_BEFORE([$0], [AC_C_INLINE])
-AC_BEFORE([$0], [AC_C_CONST])
-dnl Force this before AC_PROG_CPP. Some cpp's, eg on HPUX, require
-dnl a magic option to avoid problems with ANSI preprocessor commands
-dnl like #elif.
-dnl FIXME: can't do this because then AC_AIX won't work due to a
-dnl circular dependency.
-dnl AC_BEFORE([$0], [AC_PROG_CPP])
-AC_MSG_CHECKING(for ${CC-cc} option to accept ANSI C)
-AC_CACHE_VAL(am_cv_prog_cc_stdc,
-[am_cv_prog_cc_stdc=no
-ac_save_CC="$CC"
-# Don't try gcc -ansi; that turns off useful extensions and
-# breaks some systems' header files.
-# AIX -qlanglvl=ansi
-# Ultrix and OSF/1 -std1
-# HP-UX -Aa -D_HPUX_SOURCE
-# SVR4 -Xc -D__EXTENSIONS__
-for ac_arg in "" -qlanglvl=ansi -std1 "-Aa -D_HPUX_SOURCE" "-Xc -D__EXTENSIONS__"
-do
- CC="$ac_save_CC $ac_arg"
- AC_TRY_COMPILE(
-[#include <stdarg.h>
-#include <stdio.h>
-#include <sys/types.h>
-#include <sys/stat.h>
-/* Most of the following tests are stolen from RCS 5.7's src/conf.sh. */
-struct buf { int x; };
-FILE * (*rcsopen) (struct buf *, struct stat *, int);
-static char *e (p, i)
- char **p;
- int i;
-{
- return p[i];
-}
-static char *f (char * (*g) (char **, int), char **p, ...)
-{
- char *s;
- va_list v;
- va_start (v,p);
- s = g (p, va_arg (v,int));
- va_end (v);
- return s;
-}
-int test (int i, double x);
-struct s1 {int (*f) (int a);};
-struct s2 {int (*f) (double a);};
-int pairnames (int, char **, FILE *(*)(struct buf *, struct stat *, int), int, int);
-int argc;
-char **argv;
-], [
-return f (e, argv, 0) != argv[0] || f (e, argv, 1) != argv[1];
-],
-[am_cv_prog_cc_stdc="$ac_arg"; break])
-done
-CC="$ac_save_CC"
-])
-if test -z "$am_cv_prog_cc_stdc"; then
- AC_MSG_RESULT([none needed])
-else
- AC_MSG_RESULT($am_cv_prog_cc_stdc)
-fi
-case "x$am_cv_prog_cc_stdc" in
- x|xno) ;;
- *) CC="$CC $am_cv_prog_cc_stdc" ;;
-esac
-])
+ if test "$prefix" = "/usr" || test "$prefix" = "NONE"; then
+ thread_db="/lib/libthread_db.so.1"
+ else
+ thread_db='$prefix/lib/libthread_db.so.1'
+ fi
+ LIBS="$old_LIBS `eval echo "$thread_db"`"
+ AC_TRY_LINK(
+ [void ps_pglobal_lookup() {}
+ void ps_pdread() {}
+ void ps_pdwrite() {}
+ void ps_lgetregs() {}
+ void ps_lsetregs() {}
+ void ps_lgetfpregs() {}
+ void ps_lsetfpregs() {}
+ void ps_getpid() {}],
+ [td_ta_new();],
+ [srv_cv_thread_db="$thread_db"],
+ [srv_cv_thread_db=no])
+ LIBS="$old_LIBS"
+ ]])
+)])
-/* config.in. Generated automatically from configure.in by autoheader 2.13. */
+/* config.in. Generated automatically from configure.in by autoheader. */
/* Define if you have the ANSI C header files. */
#undef STDC_HEADERS
register access. */
#undef HAVE_PTRACE_GETFPXREGS
+/* Define if the prfpregset_t type is broken. */
+#undef PRFPREGSET_T_BROKEN
+
+/* Define if you have the <linux/elf.h> header file. */
+#undef HAVE_LINUX_ELF_H
+
+/* Define if you have the <proc_service.h> header file. */
+#undef HAVE_PROC_SERVICE_H
+
/* Define if you have the <sgtty.h> header file. */
#undef HAVE_SGTTY_H
/* Define if you have the <string.h> header file. */
#undef HAVE_STRING_H
+/* Define if you have the <sys/procfs.h> header file. */
+#undef HAVE_SYS_PROCFS_H
+
/* Define if you have the <sys/reg.h> header file. */
#undef HAVE_SYS_REG_H
/* Define if you have the <termios.h> header file. */
#undef HAVE_TERMIOS_H
+
+/* Define if you have the <thread_db.h> header file. */
+#undef HAVE_THREAD_DB_H
+
+/* Define if <sys/procfs.h> has lwpid_t. */
+#undef HAVE_LWPID_T
+
+/* Define if <sys/procfs.h> has psaddr_t. */
+#undef HAVE_PSADDR_T
+
+/* Define if <sys/procfs.h> has prgregset_t. */
+#undef HAVE_PRGREGSET_T
+
+/* Define if <sys/procfs.h> has prfpregset_t. */
+#undef HAVE_PRFPREGSET_T
+
+/* Define if <sys/procfs.h> has elf_fpregset_t. */
+#undef HAVE_ELF_FPREGSET_T
+
program_transform_name=s,x,x,
silent=
site=
+sitefile=
srcdir=
target=NONE
verbose=
--help print this message
--no-create do not create output files
--quiet, --silent do not print \`checking...' messages
+ --site-file=FILE use FILE as the site file
--version print the version of autoconf that created configure
Directory and file names:
--prefix=PREFIX install architecture-independent files in PREFIX
-site=* | --site=* | --sit=*)
site="$ac_optarg" ;;
+ -site-file | --site-file | --site-fil | --site-fi | --site-f)
+ ac_prev=sitefile ;;
+ -site-file=* | --site-file=* | --site-fil=* | --site-fi=* | --site-f=*)
+ sitefile="$ac_optarg" ;;
+
-srcdir | --srcdir | --srcdi | --srcd | --src | --sr)
ac_prev=srcdir ;;
-srcdir=* | --srcdir=* | --srcdi=* | --srcd=* | --src=* | --sr=*)
srcdir=`echo "${srcdir}" | sed 's%\([^/]\)/*$%\1%'`
# Prefer explicitly selected file to automatically selected ones.
-if test -z "$CONFIG_SITE"; then
- if test "x$prefix" != xNONE; then
- CONFIG_SITE="$prefix/share/config.site $prefix/etc/config.site"
- else
- CONFIG_SITE="$ac_default_prefix/share/config.site $ac_default_prefix/etc/config.site"
+if test -z "$sitefile"; then
+ if test -z "$CONFIG_SITE"; then
+ if test "x$prefix" != xNONE; then
+ CONFIG_SITE="$prefix/share/config.site $prefix/etc/config.site"
+ else
+ CONFIG_SITE="$ac_default_prefix/share/config.site $ac_default_prefix/etc/config.site"
+ fi
fi
+else
+ CONFIG_SITE="$sitefile"
fi
for ac_site_file in $CONFIG_SITE; do
if test -r "$ac_site_file"; then
# Extract the first word of "gcc", so it can be a program name with args.
set dummy gcc; ac_word=$2
echo $ac_n "checking for $ac_word""... $ac_c" 1>&6
-echo "configure:530: checking for $ac_word" >&5
+echo "configure:541: checking for $ac_word" >&5
if eval "test \"`echo '$''{'ac_cv_prog_CC'+set}'`\" = set"; then
echo $ac_n "(cached) $ac_c" 1>&6
else
# Extract the first word of "cc", so it can be a program name with args.
set dummy cc; ac_word=$2
echo $ac_n "checking for $ac_word""... $ac_c" 1>&6
-echo "configure:560: checking for $ac_word" >&5
+echo "configure:571: checking for $ac_word" >&5
if eval "test \"`echo '$''{'ac_cv_prog_CC'+set}'`\" = set"; then
echo $ac_n "(cached) $ac_c" 1>&6
else
# Extract the first word of "cl", so it can be a program name with args.
set dummy cl; ac_word=$2
echo $ac_n "checking for $ac_word""... $ac_c" 1>&6
-echo "configure:611: checking for $ac_word" >&5
+echo "configure:622: checking for $ac_word" >&5
if eval "test \"`echo '$''{'ac_cv_prog_CC'+set}'`\" = set"; then
echo $ac_n "(cached) $ac_c" 1>&6
else
fi
echo $ac_n "checking whether the C compiler ($CC $CFLAGS $LDFLAGS) works""... $ac_c" 1>&6
-echo "configure:643: checking whether the C compiler ($CC $CFLAGS $LDFLAGS) works" >&5
+echo "configure:654: checking whether the C compiler ($CC $CFLAGS $LDFLAGS) works" >&5
ac_ext=c
# CFLAGS is not in ac_cpp because -g, -O, etc. are not valid cpp options.
cat > conftest.$ac_ext << EOF
-#line 654 "configure"
+#line 665 "configure"
#include "confdefs.h"
main(){return(0);}
EOF
-if { (eval echo configure:659: \"$ac_link\") 1>&5; (eval $ac_link) 2>&5; } && test -s conftest${ac_exeext}; then
+if { (eval echo configure:670: \"$ac_link\") 1>&5; (eval $ac_link) 2>&5; } && test -s conftest${ac_exeext}; then
ac_cv_prog_cc_works=yes
# If we can't run a trivial program, we are probably using a cross compiler.
if (./conftest; exit) 2>/dev/null; then
{ echo "configure: error: installation or configuration problem: C compiler cannot create executables." 1>&2; exit 1; }
fi
echo $ac_n "checking whether the C compiler ($CC $CFLAGS $LDFLAGS) is a cross-compiler""... $ac_c" 1>&6
-echo "configure:685: checking whether the C compiler ($CC $CFLAGS $LDFLAGS) is a cross-compiler" >&5
+echo "configure:696: checking whether the C compiler ($CC $CFLAGS $LDFLAGS) is a cross-compiler" >&5
echo "$ac_t""$ac_cv_prog_cc_cross" 1>&6
cross_compiling=$ac_cv_prog_cc_cross
echo $ac_n "checking whether we are using GNU C""... $ac_c" 1>&6
-echo "configure:690: checking whether we are using GNU C" >&5
+echo "configure:701: checking whether we are using GNU C" >&5
if eval "test \"`echo '$''{'ac_cv_prog_gcc'+set}'`\" = set"; then
echo $ac_n "(cached) $ac_c" 1>&6
else
yes;
#endif
EOF
-if { ac_try='${CC-cc} -E conftest.c'; { (eval echo configure:699: \"$ac_try\") 1>&5; (eval $ac_try) 2>&5; }; } | egrep yes >/dev/null 2>&1; then
+if { ac_try='${CC-cc} -E conftest.c'; { (eval echo configure:710: \"$ac_try\") 1>&5; (eval $ac_try) 2>&5; }; } | egrep yes >/dev/null 2>&1; then
ac_cv_prog_gcc=yes
else
ac_cv_prog_gcc=no
ac_save_CFLAGS="$CFLAGS"
CFLAGS=
echo $ac_n "checking whether ${CC-cc} accepts -g""... $ac_c" 1>&6
-echo "configure:718: checking whether ${CC-cc} accepts -g" >&5
+echo "configure:729: checking whether ${CC-cc} accepts -g" >&5
if eval "test \"`echo '$''{'ac_cv_prog_cc_g'+set}'`\" = set"; then
echo $ac_n "(cached) $ac_c" 1>&6
else
fi
echo $ac_n "checking host system type""... $ac_c" 1>&6
-echo "configure:797: checking host system type" >&5
+echo "configure:808: checking host system type" >&5
host_alias=$host
case "$host_alias" in
echo "$ac_t""$host" 1>&6
echo $ac_n "checking target system type""... $ac_c" 1>&6
-echo "configure:818: checking target system type" >&5
+echo "configure:829: checking target system type" >&5
target_alias=$target
case "$target_alias" in
echo "$ac_t""$target" 1>&6
echo $ac_n "checking build system type""... $ac_c" 1>&6
-echo "configure:836: checking build system type" >&5
+echo "configure:847: checking build system type" >&5
build_alias=$build
case "$build_alias" in
# SVR4 /usr/ucb/install, which tries to use the nonexistent group "staff"
# ./install, which can be erroneously created by make from ./install.sh.
echo $ac_n "checking for a BSD compatible install""... $ac_c" 1>&6
-echo "configure:871: checking for a BSD compatible install" >&5
+echo "configure:882: checking for a BSD compatible install" >&5
if test -z "$INSTALL"; then
if eval "test \"`echo '$''{'ac_cv_path_install'+set}'`\" = set"; then
echo $ac_n "(cached) $ac_c" 1>&6
echo $ac_n "checking how to run the C preprocessor""... $ac_c" 1>&6
-echo "configure:925: checking how to run the C preprocessor" >&5
+echo "configure:936: checking how to run the C preprocessor" >&5
# On Suns, sometimes $CPP names a directory.
if test -n "$CPP" && test -d "$CPP"; then
CPP=
# On the NeXT, cc -E runs the code through the compiler's parser,
# not just through cpp.
cat > conftest.$ac_ext <<EOF
-#line 940 "configure"
+#line 951 "configure"
#include "confdefs.h"
#include <assert.h>
Syntax Error
EOF
ac_try="$ac_cpp conftest.$ac_ext >/dev/null 2>conftest.out"
-{ (eval echo configure:946: \"$ac_try\") 1>&5; (eval $ac_try) 2>&5; }
+{ (eval echo configure:957: \"$ac_try\") 1>&5; (eval $ac_try) 2>&5; }
ac_err=`grep -v '^ *+' conftest.out | grep -v "^conftest.${ac_ext}\$"`
if test -z "$ac_err"; then
:
rm -rf conftest*
CPP="${CC-cc} -E -traditional-cpp"
cat > conftest.$ac_ext <<EOF
-#line 957 "configure"
+#line 968 "configure"
#include "confdefs.h"
#include <assert.h>
Syntax Error
EOF
ac_try="$ac_cpp conftest.$ac_ext >/dev/null 2>conftest.out"
-{ (eval echo configure:963: \"$ac_try\") 1>&5; (eval $ac_try) 2>&5; }
+{ (eval echo configure:974: \"$ac_try\") 1>&5; (eval $ac_try) 2>&5; }
ac_err=`grep -v '^ *+' conftest.out | grep -v "^conftest.${ac_ext}\$"`
if test -z "$ac_err"; then
:
rm -rf conftest*
CPP="${CC-cc} -nologo -E"
cat > conftest.$ac_ext <<EOF
-#line 974 "configure"
+#line 985 "configure"
#include "confdefs.h"
#include <assert.h>
Syntax Error
EOF
ac_try="$ac_cpp conftest.$ac_ext >/dev/null 2>conftest.out"
-{ (eval echo configure:980: \"$ac_try\") 1>&5; (eval $ac_try) 2>&5; }
+{ (eval echo configure:991: \"$ac_try\") 1>&5; (eval $ac_try) 2>&5; }
ac_err=`grep -v '^ *+' conftest.out | grep -v "^conftest.${ac_ext}\$"`
if test -z "$ac_err"; then
:
echo "$ac_t""$CPP" 1>&6
echo $ac_n "checking for ANSI C header files""... $ac_c" 1>&6
-echo "configure:1005: checking for ANSI C header files" >&5
+echo "configure:1016: checking for ANSI C header files" >&5
if eval "test \"`echo '$''{'ac_cv_header_stdc'+set}'`\" = set"; then
echo $ac_n "(cached) $ac_c" 1>&6
else
cat > conftest.$ac_ext <<EOF
-#line 1010 "configure"
+#line 1021 "configure"
#include "confdefs.h"
#include <stdlib.h>
#include <stdarg.h>
#include <float.h>
EOF
ac_try="$ac_cpp conftest.$ac_ext >/dev/null 2>conftest.out"
-{ (eval echo configure:1018: \"$ac_try\") 1>&5; (eval $ac_try) 2>&5; }
+{ (eval echo configure:1029: \"$ac_try\") 1>&5; (eval $ac_try) 2>&5; }
ac_err=`grep -v '^ *+' conftest.out | grep -v "^conftest.${ac_ext}\$"`
if test -z "$ac_err"; then
rm -rf conftest*
if test $ac_cv_header_stdc = yes; then
# SunOS 4.x string.h does not declare mem*, contrary to ANSI.
cat > conftest.$ac_ext <<EOF
-#line 1035 "configure"
+#line 1046 "configure"
#include "confdefs.h"
#include <string.h>
EOF
if test $ac_cv_header_stdc = yes; then
# ISC 2.0.2 stdlib.h does not declare free, contrary to ANSI.
cat > conftest.$ac_ext <<EOF
-#line 1053 "configure"
+#line 1064 "configure"
#include "confdefs.h"
#include <stdlib.h>
EOF
:
else
cat > conftest.$ac_ext <<EOF
-#line 1074 "configure"
+#line 1085 "configure"
#include "confdefs.h"
#include <ctype.h>
#define ISLOWER(c) ('a' <= (c) && (c) <= 'z')
exit (0); }
EOF
-if { (eval echo configure:1085: \"$ac_link\") 1>&5; (eval $ac_link) 2>&5; } && test -s conftest${ac_exeext} && (./conftest; exit) 2>/dev/null
+if { (eval echo configure:1096: \"$ac_link\") 1>&5; (eval $ac_link) 2>&5; } && test -s conftest${ac_exeext} && (./conftest; exit) 2>/dev/null
then
:
else
fi
-for ac_hdr in sgtty.h termio.h termios.h sys/reg.h string.h
+for ac_hdr in sgtty.h termio.h termios.h sys/reg.h string.h proc_service.h sys/procfs.h thread_db.h linux/elf.h
do
ac_safe=`echo "$ac_hdr" | sed 'y%./+-%__p_%'`
echo $ac_n "checking for $ac_hdr""... $ac_c" 1>&6
-echo "configure:1113: checking for $ac_hdr" >&5
+echo "configure:1124: checking for $ac_hdr" >&5
if eval "test \"`echo '$''{'ac_cv_header_$ac_safe'+set}'`\" = set"; then
echo $ac_n "(cached) $ac_c" 1>&6
else
cat > conftest.$ac_ext <<EOF
-#line 1118 "configure"
+#line 1129 "configure"
#include "confdefs.h"
#include <$ac_hdr>
EOF
ac_try="$ac_cpp conftest.$ac_ext >/dev/null 2>conftest.out"
-{ (eval echo configure:1123: \"$ac_try\") 1>&5; (eval $ac_try) 2>&5; }
+{ (eval echo configure:1134: \"$ac_try\") 1>&5; (eval $ac_try) 2>&5; }
ac_err=`grep -v '^ *+' conftest.out | grep -v "^conftest.${ac_ext}\$"`
if test -z "$ac_err"; then
rm -rf conftest*
if test "${srv_linux_regsets}" = "yes"; then
echo $ac_n "checking for PTRACE_GETREGS""... $ac_c" 1>&6
-echo "configure:1161: checking for PTRACE_GETREGS" >&5
+echo "configure:1172: checking for PTRACE_GETREGS" >&5
if eval "test \"`echo '$''{'gdbsrv_cv_have_ptrace_getregs'+set}'`\" = set"; then
echo $ac_n "(cached) $ac_c" 1>&6
else
cat > conftest.$ac_ext <<EOF
-#line 1166 "configure"
+#line 1177 "configure"
#include "confdefs.h"
#include <sys/ptrace.h>
int main() {
PTRACE_GETREGS;
; return 0; }
EOF
-if { (eval echo configure:1173: \"$ac_compile\") 1>&5; (eval $ac_compile) 2>&5; }; then
+if { (eval echo configure:1184: \"$ac_compile\") 1>&5; (eval $ac_compile) 2>&5; }; then
rm -rf conftest*
gdbsrv_cv_have_ptrace_getregs=yes
else
fi
echo $ac_n "checking for PTRACE_GETFPXREGS""... $ac_c" 1>&6
-echo "configure:1194: checking for PTRACE_GETFPXREGS" >&5
+echo "configure:1205: checking for PTRACE_GETFPXREGS" >&5
if eval "test \"`echo '$''{'gdbsrv_cv_have_ptrace_getfpxregs'+set}'`\" = set"; then
echo $ac_n "(cached) $ac_c" 1>&6
else
cat > conftest.$ac_ext <<EOF
-#line 1199 "configure"
+#line 1210 "configure"
#include "confdefs.h"
#include <sys/ptrace.h>
int main() {
PTRACE_GETFPXREGS;
; return 0; }
EOF
-if { (eval echo configure:1206: \"$ac_compile\") 1>&5; (eval $ac_compile) 2>&5; }; then
+if { (eval echo configure:1217: \"$ac_compile\") 1>&5; (eval $ac_compile) 2>&5; }; then
rm -rf conftest*
gdbsrv_cv_have_ptrace_getfpxregs=yes
else
fi
fi
-GDBSERVER_DEPFILES="$srv_regobj $srv_tgtobj"
+if test "$ac_cv_header_sys_procfs_h" = yes; then
+ echo $ac_n "checking for lwpid_t in sys/procfs.h""... $ac_c" 1>&6
+echo "configure:1240: checking for lwpid_t in sys/procfs.h" >&5
+ if eval "test \"`echo '$''{'bfd_cv_have_sys_procfs_type_lwpid_t'+set}'`\" = set"; then
+ echo $ac_n "(cached) $ac_c" 1>&6
+else
+ cat > conftest.$ac_ext <<EOF
+#line 1245 "configure"
+#include "confdefs.h"
+
+#define _SYSCALL32
+#include <sys/procfs.h>
+int main() {
+lwpid_t avar
+; return 0; }
+EOF
+if { (eval echo configure:1254: \"$ac_compile\") 1>&5; (eval $ac_compile) 2>&5; }; then
+ rm -rf conftest*
+ bfd_cv_have_sys_procfs_type_lwpid_t=yes
+else
+ echo "configure: failed program was:" >&5
+ cat conftest.$ac_ext >&5
+ rm -rf conftest*
+ bfd_cv_have_sys_procfs_type_lwpid_t=no
+
+fi
+rm -f conftest*
+fi
+
+ if test $bfd_cv_have_sys_procfs_type_lwpid_t = yes; then
+ cat >> confdefs.h <<\EOF
+#define HAVE_LWPID_T 1
+EOF
+
+ fi
+ echo "$ac_t""$bfd_cv_have_sys_procfs_type_lwpid_t" 1>&6
+
+ echo $ac_n "checking for psaddr_t in sys/procfs.h""... $ac_c" 1>&6
+echo "configure:1276: checking for psaddr_t in sys/procfs.h" >&5
+ if eval "test \"`echo '$''{'bfd_cv_have_sys_procfs_type_psaddr_t'+set}'`\" = set"; then
+ echo $ac_n "(cached) $ac_c" 1>&6
+else
+ cat > conftest.$ac_ext <<EOF
+#line 1281 "configure"
+#include "confdefs.h"
+
+#define _SYSCALL32
+#include <sys/procfs.h>
+int main() {
+psaddr_t avar
+; return 0; }
+EOF
+if { (eval echo configure:1290: \"$ac_compile\") 1>&5; (eval $ac_compile) 2>&5; }; then
+ rm -rf conftest*
+ bfd_cv_have_sys_procfs_type_psaddr_t=yes
+else
+ echo "configure: failed program was:" >&5
+ cat conftest.$ac_ext >&5
+ rm -rf conftest*
+ bfd_cv_have_sys_procfs_type_psaddr_t=no
+
+fi
+rm -f conftest*
+fi
+
+ if test $bfd_cv_have_sys_procfs_type_psaddr_t = yes; then
+ cat >> confdefs.h <<\EOF
+#define HAVE_PSADDR_T 1
+EOF
+
+ fi
+ echo "$ac_t""$bfd_cv_have_sys_procfs_type_psaddr_t" 1>&6
+
+ echo $ac_n "checking for prgregset_t in sys/procfs.h""... $ac_c" 1>&6
+echo "configure:1312: checking for prgregset_t in sys/procfs.h" >&5
+ if eval "test \"`echo '$''{'bfd_cv_have_sys_procfs_type_prgregset_t'+set}'`\" = set"; then
+ echo $ac_n "(cached) $ac_c" 1>&6
+else
+ cat > conftest.$ac_ext <<EOF
+#line 1317 "configure"
+#include "confdefs.h"
+
+#define _SYSCALL32
+#include <sys/procfs.h>
+int main() {
+prgregset_t avar
+; return 0; }
+EOF
+if { (eval echo configure:1326: \"$ac_compile\") 1>&5; (eval $ac_compile) 2>&5; }; then
+ rm -rf conftest*
+ bfd_cv_have_sys_procfs_type_prgregset_t=yes
+else
+ echo "configure: failed program was:" >&5
+ cat conftest.$ac_ext >&5
+ rm -rf conftest*
+ bfd_cv_have_sys_procfs_type_prgregset_t=no
+
+fi
+rm -f conftest*
+fi
+
+ if test $bfd_cv_have_sys_procfs_type_prgregset_t = yes; then
+ cat >> confdefs.h <<\EOF
+#define HAVE_PRGREGSET_T 1
+EOF
+
+ fi
+ echo "$ac_t""$bfd_cv_have_sys_procfs_type_prgregset_t" 1>&6
+
+ echo $ac_n "checking for prfpregset_t in sys/procfs.h""... $ac_c" 1>&6
+echo "configure:1348: checking for prfpregset_t in sys/procfs.h" >&5
+ if eval "test \"`echo '$''{'bfd_cv_have_sys_procfs_type_prfpregset_t'+set}'`\" = set"; then
+ echo $ac_n "(cached) $ac_c" 1>&6
+else
+ cat > conftest.$ac_ext <<EOF
+#line 1353 "configure"
+#include "confdefs.h"
+
+#define _SYSCALL32
+#include <sys/procfs.h>
+int main() {
+prfpregset_t avar
+; return 0; }
+EOF
+if { (eval echo configure:1362: \"$ac_compile\") 1>&5; (eval $ac_compile) 2>&5; }; then
+ rm -rf conftest*
+ bfd_cv_have_sys_procfs_type_prfpregset_t=yes
+else
+ echo "configure: failed program was:" >&5
+ cat conftest.$ac_ext >&5
+ rm -rf conftest*
+ bfd_cv_have_sys_procfs_type_prfpregset_t=no
+
+fi
+rm -f conftest*
+fi
+
+ if test $bfd_cv_have_sys_procfs_type_prfpregset_t = yes; then
+ cat >> confdefs.h <<\EOF
+#define HAVE_PRFPREGSET_T 1
+EOF
+
+ fi
+ echo "$ac_t""$bfd_cv_have_sys_procfs_type_prfpregset_t" 1>&6
+
+
+
+
+ if test $bfd_cv_have_sys_procfs_type_prfpregset_t = yes; then
+ echo $ac_n "checking whether prfpregset_t type is broken""... $ac_c" 1>&6
+echo "configure:1388: checking whether prfpregset_t type is broken" >&5
+ if eval "test \"`echo '$''{'gdb_cv_prfpregset_t_broken'+set}'`\" = set"; then
+ echo $ac_n "(cached) $ac_c" 1>&6
+else
+ if test "$cross_compiling" = yes; then
+ gdb_cv_prfpregset_t_broken=yes
+else
+ cat > conftest.$ac_ext <<EOF
+#line 1396 "configure"
+#include "confdefs.h"
+#include <sys/procfs.h>
+ int main ()
+ {
+ if (sizeof (prfpregset_t) == sizeof (void *))
+ return 1;
+ return 0;
+ }
+EOF
+if { (eval echo configure:1406: \"$ac_link\") 1>&5; (eval $ac_link) 2>&5; } && test -s conftest${ac_exeext} && (./conftest; exit) 2>/dev/null
+then
+ gdb_cv_prfpregset_t_broken=no
+else
+ echo "configure: failed program was:" >&5
+ cat conftest.$ac_ext >&5
+ rm -fr conftest*
+ gdb_cv_prfpregset_t_broken=yes
+fi
+rm -fr conftest*
+fi
+
+fi
+
+ echo "$ac_t""$gdb_cv_prfpregset_t_broken" 1>&6
+ if test $gdb_cv_prfpregset_t_broken = yes; then
+ cat >> confdefs.h <<\EOF
+#define PRFPREGSET_T_BROKEN 1
+EOF
+
+ fi
+ fi
+
+ echo $ac_n "checking for elf_fpregset_t in sys/procfs.h""... $ac_c" 1>&6
+echo "configure:1430: checking for elf_fpregset_t in sys/procfs.h" >&5
+ if eval "test \"`echo '$''{'bfd_cv_have_sys_procfs_type_elf_fpregset_t'+set}'`\" = set"; then
+ echo $ac_n "(cached) $ac_c" 1>&6
+else
+ cat > conftest.$ac_ext <<EOF
+#line 1435 "configure"
+#include "confdefs.h"
+
+#define _SYSCALL32
+#include <sys/procfs.h>
+int main() {
+elf_fpregset_t avar
+; return 0; }
+EOF
+if { (eval echo configure:1444: \"$ac_compile\") 1>&5; (eval $ac_compile) 2>&5; }; then
+ rm -rf conftest*
+ bfd_cv_have_sys_procfs_type_elf_fpregset_t=yes
+else
+ echo "configure: failed program was:" >&5
+ cat conftest.$ac_ext >&5
+ rm -rf conftest*
+ bfd_cv_have_sys_procfs_type_elf_fpregset_t=no
+
+fi
+rm -f conftest*
+fi
+
+ if test $bfd_cv_have_sys_procfs_type_elf_fpregset_t = yes; then
+ cat >> confdefs.h <<\EOF
+#define HAVE_ELF_FPREGSET_T 1
+EOF
+
+ fi
+ echo "$ac_t""$bfd_cv_have_sys_procfs_type_elf_fpregset_t" 1>&6
+
+fi
+
+srv_thread_depfiles=
+srv_libs=
+USE_THREAD_DB=
+
+if test "$srv_linux_thread_db" = "yes"; then
+ echo $ac_n "checking for libthread_db""... $ac_c" 1>&6
+echo "configure:1473: checking for libthread_db" >&5
+if eval "test \"`echo '$''{'srv_cv_thread_db'+set}'`\" = set"; then
+ echo $ac_n "(cached) $ac_c" 1>&6
+else
+ old_LIBS="$LIBS"
+ LIBS="$LIBS -lthread_db"
+ cat > conftest.$ac_ext <<EOF
+#line 1480 "configure"
+#include "confdefs.h"
+void ps_pglobal_lookup() {}
+ void ps_pdread() {}
+ void ps_pdwrite() {}
+ void ps_lgetregs() {}
+ void ps_lsetregs() {}
+ void ps_lgetfpregs() {}
+ void ps_lsetfpregs() {}
+ void ps_getpid() {}
+int main() {
+td_ta_new();
+; return 0; }
+EOF
+if { (eval echo configure:1494: \"$ac_link\") 1>&5; (eval $ac_link) 2>&5; } && test -s conftest${ac_exeext}; then
+ rm -rf conftest*
+ srv_cv_thread_db="-lthread_db"
+else
+ echo "configure: failed program was:" >&5
+ cat conftest.$ac_ext >&5
+ rm -rf conftest*
+ srv_cv_thread_db=no
+
+ if test "$prefix" = "/usr" || test "$prefix" = "NONE"; then
+ thread_db="/lib/libthread_db.so.1"
+ else
+ thread_db='$prefix/lib/libthread_db.so.1'
+ fi
+ LIBS="$old_LIBS `eval echo "$thread_db"`"
+ cat > conftest.$ac_ext <<EOF
+#line 1510 "configure"
+#include "confdefs.h"
+void ps_pglobal_lookup() {}
+ void ps_pdread() {}
+ void ps_pdwrite() {}
+ void ps_lgetregs() {}
+ void ps_lsetregs() {}
+ void ps_lgetfpregs() {}
+ void ps_lsetfpregs() {}
+ void ps_getpid() {}
+int main() {
+td_ta_new();
+; return 0; }
+EOF
+if { (eval echo configure:1524: \"$ac_link\") 1>&5; (eval $ac_link) 2>&5; } && test -s conftest${ac_exeext}; then
+ rm -rf conftest*
+ srv_cv_thread_db="$thread_db"
+else
+ echo "configure: failed program was:" >&5
+ cat conftest.$ac_ext >&5
+ rm -rf conftest*
+ srv_cv_thread_db=no
+fi
+rm -f conftest*
+ LIBS="$old_LIBS"
+
+fi
+
+echo "$ac_t""$srv_cv_thread_db" 1>&6
+
+fi
+rm -f conftest*
+ if test "$srv_cv_thread_db" = no; then
+ echo "configure: warning: Could not find libthread_db." 1>&2
+ echo "configure: warning: Disabling thread support in gdbserver." 1>&2
+ srv_linux_thread_db=no
+ else
+ srv_libs="$srv_cv_thread_db"
+ fi
+fi
+
+if test "$srv_linux_thread_db" = "yes"; then
+ srv_thread_depfiles="thread-db.o proc-service.o"
+ USE_THREAD_DB="-DUSE_THREAD_DB"
+fi
+
+GDBSERVER_DEPFILES="$srv_regobj $srv_tgtobj $srv_thread_depfiles"
+GDBSERVER_LIBS="$srv_libs"
+
+
s%@INSTALL_DATA@%$INSTALL_DATA%g
s%@CPP@%$CPP%g
s%@GDBSERVER_DEPFILES@%$GDBSERVER_DEPFILES%g
+s%@GDBSERVER_LIBS@%$GDBSERVER_LIBS%g
+s%@USE_THREAD_DB@%$USE_THREAD_DB%g
CEOF
EOF
AC_HEADER_STDC
-AC_CHECK_HEADERS(sgtty.h termio.h termios.h sys/reg.h string.h)
+AC_CHECK_HEADERS(sgtty.h termio.h termios.h sys/reg.h string.h dnl
+ proc_service.h sys/procfs.h thread_db.h linux/elf.h)
. ${srcdir}/configure.srv
fi
fi
-GDBSERVER_DEPFILES="$srv_regobj $srv_tgtobj"
+if test "$ac_cv_header_sys_procfs_h" = yes; then
+ BFD_HAVE_SYS_PROCFS_TYPE(lwpid_t)
+ BFD_HAVE_SYS_PROCFS_TYPE(psaddr_t)
+ BFD_HAVE_SYS_PROCFS_TYPE(prgregset_t)
+ BFD_HAVE_SYS_PROCFS_TYPE(prfpregset_t)
+
+ dnl Check for broken prfpregset_t type
+
+ dnl For Linux/i386, glibc 2.1.3 was released with a bogus
+ dnl prfpregset_t type (it's a typedef for the pointer to a struct
+ dnl instead of the struct itself). We detect this here, and work
+ dnl around it in gdb_proc_service.h.
+
+ if test $bfd_cv_have_sys_procfs_type_prfpregset_t = yes; then
+ AC_MSG_CHECKING(whether prfpregset_t type is broken)
+ AC_CACHE_VAL(gdb_cv_prfpregset_t_broken,
+ [AC_TRY_RUN([#include <sys/procfs.h>
+ int main ()
+ {
+ if (sizeof (prfpregset_t) == sizeof (void *))
+ return 1;
+ return 0;
+ }],
+ gdb_cv_prfpregset_t_broken=no,
+ gdb_cv_prfpregset_t_broken=yes,
+ gdb_cv_prfpregset_t_broken=yes)])
+ AC_MSG_RESULT($gdb_cv_prfpregset_t_broken)
+ if test $gdb_cv_prfpregset_t_broken = yes; then
+ AC_DEFINE(PRFPREGSET_T_BROKEN)
+ fi
+ fi
+
+ BFD_HAVE_SYS_PROCFS_TYPE(elf_fpregset_t)
+fi
+
+srv_thread_depfiles=
+srv_libs=
+USE_THREAD_DB=
+
+if test "$srv_linux_thread_db" = "yes"; then
+ SRV_CHECK_THREAD_DB
+ if test "$srv_cv_thread_db" = no; then
+ AC_WARN([Could not find libthread_db.])
+ AC_WARN([Disabling thread support in gdbserver.])
+ srv_linux_thread_db=no
+ else
+ srv_libs="$srv_cv_thread_db"
+ fi
+fi
+
+if test "$srv_linux_thread_db" = "yes"; then
+ srv_thread_depfiles="thread-db.o proc-service.o"
+ USE_THREAD_DB="-DUSE_THREAD_DB"
+fi
+
+GDBSERVER_DEPFILES="$srv_regobj $srv_tgtobj $srv_thread_depfiles"
+GDBSERVER_LIBS="$srv_libs"
AC_SUBST(GDBSERVER_DEPFILES)
+AC_SUBST(GDBSERVER_LIBS)
+AC_SUBST(USE_THREAD_DB)
AC_OUTPUT(Makefile,
[case x$CONFIG_HEADERS in
arm*-*-linux*) srv_regobj=reg-arm.o
srv_tgtobj="linux-low.o linux-arm-low.o"
srv_linux_usrregs=yes
+ srv_linux_thread_db=yes
;;
i[3456]86-*-linux*) srv_regobj=reg-i386-linux.o
srv_tgtobj="linux-low.o linux-i386-low.o i387-fp.o"
srv_linux_usrregs=yes
srv_linux_regsets=yes
+ srv_linux_thread_db=yes
;;
ia64-*-linux*) srv_regobj=reg-ia64.o
srv_tgtobj="linux-low.o linux-ia64-low.o"
mips*-*-linux*) srv_regobj=reg-mips.o
srv_tgtobj="linux-low.o linux-mips-low.o"
srv_linux_usrregs=yes
+ srv_linux_thread_db=yes
;;
powerpc*-*-linux*) srv_regobj=reg-ppc.o
srv_tgtobj="linux-low.o linux-ppc-low.o"
srv_linux_usrregs=yes
+ srv_linux_thread_db=yes
;;
s390-*-linux*) srv_regobj=reg-s390.o
srv_tgtobj="linux-low.o linux-s390-low.o"
sh*-*-linux*) srv_regobj=reg-sh.o
srv_tgtobj="linux-low.o linux-sh-low.o"
srv_linux_usrregs=yes
+ srv_linux_thread_db=yes
;;
x86_64-*-linux*) srv_regobj=reg-x86-64.o
srv_tgtobj="linux-low.o linux-x86-64-low.o i387-fp.o"
Boston, MA 02111-1307, USA. */
#include "server.h"
+#include "i387-fp.h"
int num_xmm_registers = 8;
}
void
-i387_fsave_to_cache (void *buf)
+i387_fsave_to_cache (const void *buf)
{
struct i387_fsave *fp = (struct i387_fsave *) buf;
int i;
}
void
-i387_fxsave_to_cache (void *buf)
+i387_fxsave_to_cache (const void *buf)
{
struct i387_fxsave *fp = (struct i387_fxsave *) buf;
int i, top;
val = (fp->fop) & 0x7FF;
supply_register_by_name ("fop", &val);
}
-
#define I387_FP_H
void i387_cache_to_fsave (void *buf);
-void i387_fsave_to_cache (void *buf);
+void i387_fsave_to_cache (const void *buf);
void i387_cache_to_fxsave (void *buf);
-void i387_fxsave_to_cache (void *buf);
+void i387_fxsave_to_cache (const void *buf);
extern int num_xmm_registers;
#include "server.h"
-struct inferior_info
+struct thread_info
{
- int pid;
+ struct inferior_list_entry entry;
void *target_data;
void *regcache_data;
- struct inferior_info *next;
};
-static struct inferior_info *inferiors;
-struct inferior_info *current_inferior;
-int signal_pid;
+struct inferior_list all_threads;
+
+struct thread_info *current_inferior;
+
+#define get_thread(inf) ((struct thread_info *)(inf))
+
+void
+add_inferior_to_list (struct inferior_list *list,
+ struct inferior_list_entry *new_inferior)
+{
+ new_inferior->next = NULL;
+ if (list->tail != NULL)
+ list->tail->next = new_inferior;
+ else
+ list->head = new_inferior;
+ list->tail = new_inferior;
+}
+
+void
+for_each_inferior (struct inferior_list *list,
+ void (*action) (struct inferior_list_entry *))
+{
+ struct inferior_list_entry *cur = list->head, *next;
+
+ while (cur != NULL)
+ {
+ next = cur->next;
+ (*action) (cur);
+ cur = next;
+ }
+}
void
-add_inferior (int pid)
+change_inferior_id (struct inferior_list *list,
+ int new_id)
{
- struct inferior_info *new_inferior
- = (struct inferior_info *) malloc (sizeof (*new_inferior));
+ if (list->head != list->tail)
+ error ("tried to change thread ID after multiple threads are created");
- memset (new_inferior, 0, sizeof (*new_inferior));
+ list->head->id = new_id;
+}
- new_inferior->pid = pid;
+void
+remove_inferior (struct inferior_list *list,
+ struct inferior_list_entry *entry)
+{
+ struct inferior_list_entry **cur;
- new_inferior->next = inferiors;
- inferiors = new_inferior;
+ if (list->head == entry)
+ {
+ list->head = entry->next;
+ if (list->tail == entry)
+ list->tail = list->head;
+ return;
+ }
+
+ cur = &list->head;
+ while (*cur && (*cur)->next != entry)
+ cur = &(*cur)->next;
+
+ if (*cur == NULL)
+ return;
+ (*cur)->next = entry->next;
+
+ if (list->tail == entry)
+ list->tail = *cur;
+}
+
+void
+add_thread (int thread_id, void *target_data)
+{
+ struct thread_info *new_thread
+ = (struct thread_info *) malloc (sizeof (*new_thread));
+
+ memset (new_thread, 0, sizeof (*new_thread));
+
+ new_thread->entry.id = thread_id;
+
+ add_inferior_to_list (&all_threads, & new_thread->entry);
+
if (current_inferior == NULL)
- current_inferior = inferiors;
+ current_inferior = new_thread;
- create_register_cache (new_inferior);
+ new_thread->target_data = target_data;
+ set_inferior_regcache_data (new_thread, new_register_cache ());
+}
- if (signal_pid == 0)
- signal_pid = pid;
+static void
+free_one_thread (struct inferior_list_entry *inf)
+{
+ struct thread_info *thread = get_thread (inf);
+ free_register_cache (inferior_regcache_data (thread));
+ free (thread);
+}
+
+void
+remove_thread (struct thread_info *thread)
+{
+ remove_inferior (&all_threads, (struct inferior_list_entry *) thread);
+ free_one_thread (&thread->entry);
}
void
clear_inferiors (void)
{
- struct inferior_info *inf = inferiors, *next_inf;
+ for_each_inferior (&all_threads, free_one_thread);
+
+ all_threads.head = all_threads.tail = NULL;
+}
+
+struct inferior_list_entry *
+find_inferior (struct inferior_list *list,
+ int (*func) (struct inferior_list_entry *, void *), void *arg)
+{
+ struct inferior_list_entry *inf = list->head;
- while (inf)
+ while (inf != NULL)
{
- next_inf = inf->next;
+ if ((*func) (inf, arg))
+ return inf;
+ inf = inf->next;
+ }
- if (inf->target_data)
- free (inf->target_data);
- if (inf->regcache_data)
- free_register_cache (inf);
+ return NULL;
+}
- free (inf);
- inf = next_inf;
+struct inferior_list_entry *
+find_inferior_id (struct inferior_list *list, int id)
+{
+ struct inferior_list_entry *inf = list->head;
+
+ while (inf != NULL)
+ {
+ if (inf->id == id)
+ return inf;
+ inf = inf->next;
}
- inferiors = NULL;
+ return NULL;
}
void *
-inferior_target_data (struct inferior_info *inferior)
+inferior_target_data (struct thread_info *inferior)
{
return inferior->target_data;
}
void
-set_inferior_target_data (struct inferior_info *inferior, void *data)
+set_inferior_target_data (struct thread_info *inferior, void *data)
{
inferior->target_data = data;
}
void *
-inferior_regcache_data (struct inferior_info *inferior)
+inferior_regcache_data (struct thread_info *inferior)
{
return inferior->regcache_data;
}
void
-set_inferior_regcache_data (struct inferior_info *inferior, void *data)
+set_inferior_regcache_data (struct thread_info *inferior, void *data)
{
inferior->regcache_data = data;
}
return (regno >= arm_num_regs);
}
+static CORE_ADDR
+arm_get_pc ()
+{
+ unsigned long pc;
+ collect_register_by_name ("pc", &pc);
+ return pc;
+}
+
+static void
+arm_set_pc (CORE_ADDR pc)
+{
+ unsigned long newpc = pc;
+ supply_register_by_name ("pc", &newpc);
+}
+
+/* Correct in either endianness. We do not support Thumb yet. */
+static const unsigned long arm_breakpoint = 0xef9f0001;
+#define arm_breakpoint_len 4
+
+static int
+arm_breakpoint_at (CORE_ADDR where)
+{
+ unsigned long insn;
+
+ (*the_target->read_memory) (where, (char *) &insn, 4);
+ if (insn == arm_breakpoint)
+ return 1;
+
+ /* If necessary, recognize more trap instructions here. GDB only uses the
+ one. */
+ return 0;
+}
+
struct linux_target_ops the_low_target = {
arm_num_regs,
arm_regmap,
arm_cannot_fetch_register,
arm_cannot_store_register,
+ arm_get_pc,
+ arm_set_pc,
+ (const char *) &arm_breakpoint,
+ arm_breakpoint_len,
+ NULL,
+ 0,
+ arm_breakpoint_at,
};
}
static void
-i386_store_gregset (void *buf)
+i386_store_gregset (const void *buf)
{
int i;
}
static void
-i386_store_fpregset (void *buf)
+i386_store_fpregset (const void *buf)
{
i387_fsave_to_cache (buf);
}
}
static void
-i386_store_fpxregset (void *buf)
+i386_store_fpxregset (const void *buf)
{
i387_fxsave_to_cache (buf);
}
struct regset_info target_regsets[] = {
{ PTRACE_GETREGS, PTRACE_SETREGS, sizeof (elf_gregset_t),
+ GENERAL_REGS,
i386_fill_gregset, i386_store_gregset },
#ifdef HAVE_PTRACE_GETFPXREGS
{ PTRACE_GETFPXREGS, PTRACE_SETFPXREGS, sizeof (elf_fpxregset_t),
+ EXTENDED_REGS,
i386_fill_fpxregset, i386_store_fpxregset },
#endif
{ PTRACE_GETFPREGS, PTRACE_SETFPREGS, sizeof (elf_fpregset_t),
+ FP_REGS,
i386_fill_fpregset, i386_store_fpregset },
- { 0, 0, -1, NULL, NULL }
+ { 0, 0, -1, -1, NULL, NULL }
};
#endif /* HAVE_LINUX_REGSETS */
static const char i386_breakpoint[] = { 0xCC };
#define i386_breakpoint_len 1
+extern int debug_threads;
+
static CORE_ADDR
-i386_stop_pc ()
+i386_get_pc ()
{
unsigned long pc;
- /* Overkill */
- fetch_inferior_registers (0);
-
collect_register_by_name ("eip", &pc);
- return pc - 1;
+
+ if (debug_threads)
+ fprintf (stderr, "stop pc (before any decrement) is %08lx\n", pc);
+ return pc;
}
static void
i386_set_pc (CORE_ADDR newpc)
{
+ if (debug_threads)
+ fprintf (stderr, "set pc to %08lx\n", (long) newpc);
supply_register_by_name ("eip", &newpc);
+}
+
+static int
+i386_breakpoint_at (CORE_ADDR pc)
+{
+ unsigned char c;
+
+ read_inferior_memory (pc, &c, 1);
+ if (c == 0xCC)
+ return 1;
- /* Overkill */
- store_inferior_registers (0);
+ return 0;
}
struct linux_target_ops the_low_target = {
i386_regmap,
i386_cannot_fetch_register,
i386_cannot_store_register,
- i386_stop_pc,
+ i386_get_pc,
i386_set_pc,
i386_breakpoint,
i386_breakpoint_len,
+ NULL,
+ 1,
+ i386_breakpoint_at,
};
#include <stdlib.h>
#include <unistd.h>
-static CORE_ADDR linux_bp_reinsert;
+/* ``all_threads'' is keyed by the LWP ID - it should be the thread ID instead,
+ however. This requires changing the ID in place when we go from !using_threads
+ to using_threads, immediately.
+ ``all_processes'' is keyed by the process ID - which on Linux is (presently)
+ the same as the LWP ID. */
+
+struct inferior_list all_processes;
+
+/* FIXME this is a bit of a hack, and could be removed. */
+int stopping_threads;
+
+/* FIXME make into a target method? */
+int using_threads;
+
+static void linux_resume_one_process (struct inferior_list_entry *entry,
+ int step, int signal);
static void linux_resume (int step, int signal);
+static void stop_all_processes (void);
+static int linux_wait_for_event (struct thread_info *child);
+
+struct pending_signals
+{
+ int signal;
+ struct pending_signals *prev;
+};
#define PTRACE_ARG3_TYPE long
#define PTRACE_XFER_TYPE long
extern int errno;
-static int inferior_pid;
+int debug_threads = 0;
+
+#define pid_of(proc) ((proc)->head.id)
+
+/* FIXME: Delete eventually. */
+#define inferior_pid (pid_of (get_thread_process (current_inferior)))
+
+/* This function should only be called if the process got a SIGTRAP.
+ The SIGTRAP could mean several things.
+
+ On i386, where decr_pc_after_break is non-zero:
+ If we were single-stepping this process using PTRACE_SINGLESTEP,
+ we will get only the one SIGTRAP (even if the instruction we
+ stepped over was a breakpoint). The value of $eip will be the
+ next instruction.
+ If we continue the process using PTRACE_CONT, we will get a
+ SIGTRAP when we hit a breakpoint. The value of $eip will be
+ the instruction after the breakpoint (i.e. needs to be
+ decremented). If we report the SIGTRAP to GDB, we must also
+ report the undecremented PC. If we cancel the SIGTRAP, we
+ must resume at the decremented PC.
+
+ (Presumably, not yet tested) On a non-decr_pc_after_break machine
+ with hardware or kernel single-step:
+ If we single-step over a breakpoint instruction, our PC will
+ point at the following instruction. If we continue and hit a
+ breakpoint instruction, our PC will point at the breakpoint
+ instruction. */
+
+static CORE_ADDR
+get_stop_pc (void)
+{
+ CORE_ADDR stop_pc = (*the_low_target.get_pc) ();
+
+ if (get_thread_process (current_inferior)->stepping)
+ return stop_pc;
+ else
+ return stop_pc - the_low_target.decr_pc_after_break;
+}
-struct inferior_linux_data
+static void *
+add_process (int pid)
{
- int pid;
-};
+ struct process_info *process;
+
+ process = (struct process_info *) malloc (sizeof (*process));
+ memset (process, 0, sizeof (*process));
+
+ process->head.id = pid;
+
+ /* Default to tid == lwpid == pid. */
+ process->tid = pid;
+ process->lwpid = pid;
+
+ add_inferior_to_list (&all_processes, &process->head);
+
+ return process;
+}
/* Start an inferior process and returns its pid.
ALLARGS is a vector of program-name and args. */
static int
linux_create_inferior (char *program, char **allargs)
{
- struct inferior_linux_data *tdata;
+ void *new_process;
int pid;
pid = fork ();
{
ptrace (PTRACE_TRACEME, 0, 0, 0);
+ signal (SIGRTMIN + 1, SIG_DFL);
+
execv (program, allargs);
fprintf (stderr, "Cannot exec %s: %s.\n", program,
_exit (0177);
}
- add_inferior (pid);
- tdata = (struct inferior_linux_data *) malloc (sizeof (*tdata));
- tdata->pid = pid;
- set_inferior_target_data (current_inferior, tdata);
+ new_process = add_process (pid);
+ add_thread (pid, new_process);
- /* FIXME remove */
- inferior_pid = pid;
return 0;
}
/* Attach to an inferior process. */
-static int
-linux_attach (int pid)
+void
+linux_attach_lwp (int pid, int tid)
{
- struct inferior_linux_data *tdata;
+ struct process_info *new_process;
if (ptrace (PTRACE_ATTACH, pid, 0, 0) != 0)
{
errno < sys_nerr ? sys_errlist[errno] : "unknown error",
errno);
fflush (stderr);
- _exit (0177);
+
+ /* If we fail to attach to an LWP, just return. */
+ if (!using_threads)
+ _exit (0177);
+ return;
}
- add_inferior (pid);
- tdata = (struct inferior_linux_data *) malloc (sizeof (*tdata));
- tdata->pid = pid;
- set_inferior_target_data (current_inferior, tdata);
+ new_process = (struct process_info *) add_process (pid);
+ add_thread (tid, new_process);
+
+ /* The next time we wait for this LWP we'll see a SIGSTOP as PTRACE_ATTACH
+ brings it to a halt. We should ignore that SIGSTOP and resume the process
+ (unless this is the first process, in which case the flag will be cleared
+ in linux_attach).
+
+ On the other hand, if we are currently trying to stop all threads, we
+ should treat the new thread as if we had sent it a SIGSTOP. This works
+ because we are guaranteed that add_process added us to the end of the
+ list, and so the new thread has not yet reached wait_for_sigstop (but
+ will). */
+ if (! stopping_threads)
+ new_process->stop_expected = 1;
+}
+
+int
+linux_attach (int pid)
+{
+ struct process_info *process;
+
+ linux_attach_lwp (pid, pid);
+
+ /* Don't ignore the initial SIGSTOP if we just attached to this process. */
+ process = (struct process_info *) find_inferior_id (&all_processes, pid);
+ process->stop_expected = 0;
+
return 0;
}
/* Kill the inferior process. Make us have no inferior. */
static void
-linux_kill (void)
+linux_kill_one_process (struct inferior_list_entry *entry)
{
- if (inferior_pid == 0)
- return;
- ptrace (PTRACE_KILL, inferior_pid, 0, 0);
- wait (0);
- clear_inferiors ();
+ struct thread_info *thread = (struct thread_info *) entry;
+ struct process_info *process = get_thread_process (thread);
+ int wstat;
+
+ do
+ {
+ ptrace (PTRACE_KILL, pid_of (process), 0, 0);
+
+ /* Make sure it died. The loop is most likely unnecessary. */
+ wstat = linux_wait_for_event (thread);
+ } while (WIFSTOPPED (wstat));
}
/* Return nonzero if the given thread is still alive. */
+static void
+linux_kill (void)
+{
+ for_each_inferior (&all_threads, linux_kill_one_process);
+}
+
+static int
+linux_thread_alive (int tid)
+{
+ if (find_inferior_id (&all_threads, tid) != NULL)
+ return 1;
+ else
+ return 0;
+}
+
+/* Return nonzero if this process stopped at a breakpoint which
+ no longer appears to be inserted. Also adjust the PC
+ appropriately to resume where the breakpoint used to be. */
static int
-linux_thread_alive (int pid)
+check_removed_breakpoint (struct process_info *event_child)
{
+ CORE_ADDR stop_pc;
+ struct thread_info *saved_inferior;
+
+ if (event_child->pending_is_breakpoint == 0)
+ return 0;
+
+ if (debug_threads)
+ fprintf (stderr, "Checking for breakpoint.\n");
+
+ saved_inferior = current_inferior;
+ current_inferior = get_process_thread (event_child);
+
+ stop_pc = get_stop_pc ();
+
+ /* If the PC has changed since we stopped, then we shouldn't do
+ anything. This happens if, for instance, GDB handled the
+ decr_pc_after_break subtraction itself. */
+ if (stop_pc != event_child->pending_stop_pc)
+ {
+ if (debug_threads)
+ fprintf (stderr, "Ignoring, PC was changed.\n");
+
+ event_child->pending_is_breakpoint = 0;
+ current_inferior = saved_inferior;
+ return 0;
+ }
+
+ /* If the breakpoint is still there, we will report hitting it. */
+ if ((*the_low_target.breakpoint_at) (stop_pc))
+ {
+ if (debug_threads)
+ fprintf (stderr, "Ignoring, breakpoint is still present.\n");
+ current_inferior = saved_inferior;
+ return 0;
+ }
+
+ if (debug_threads)
+ fprintf (stderr, "Removed breakpoint.\n");
+
+ /* For decr_pc_after_break targets, here is where we perform the
+ decrement. We go immediately from this function to resuming,
+ and can not safely call get_stop_pc () again. */
+ if (the_low_target.set_pc != NULL)
+ (*the_low_target.set_pc) (stop_pc);
+
+ /* We consumed the pending SIGTRAP. */
+ event_child->status_pending_p = 0;
+ event_child->status_pending = 0;
+
+ current_inferior = saved_inferior;
return 1;
}
+/* Return 1 if this process has an interesting status pending. This function
+ may silently resume an inferior process. */
static int
-linux_wait_for_one_inferior (struct inferior_info *child)
+status_pending_p (struct inferior_list_entry *entry, void *dummy)
+{
+ struct process_info *process = (struct process_info *) entry;
+
+ if (process->status_pending_p)
+ if (check_removed_breakpoint (process))
+ {
+ /* This thread was stopped at a breakpoint, and the breakpoint
+ is now gone. We were told to continue (or step...) all threads,
+ so GDB isn't trying to single-step past this breakpoint.
+ So instead of reporting the old SIGTRAP, pretend we got to
+ the breakpoint just after it was removed instead of just
+ before; resume the process. */
+ linux_resume_one_process (&process->head, 0, 0);
+ return 0;
+ }
+
+ return process->status_pending_p;
+}
+
+static void
+linux_wait_for_process (struct process_info **childp, int *wstatp)
{
- struct inferior_linux_data *child_data = inferior_target_data (child);
- int pid, wstat;
+ int ret;
+ int to_wait_for = -1;
+
+ if (*childp != NULL)
+ to_wait_for = (*childp)->lwpid;
while (1)
{
- pid = waitpid (child_data->pid, &wstat, 0);
+ ret = waitpid (to_wait_for, wstatp, WNOHANG);
+
+ if (ret == -1)
+ {
+ if (errno != ECHILD)
+ perror_with_name ("waitpid");
+ }
+ else if (ret > 0)
+ break;
+
+ ret = waitpid (to_wait_for, wstatp, WNOHANG | __WCLONE);
+
+ if (ret == -1)
+ {
+ if (errno != ECHILD)
+ perror_with_name ("waitpid (WCLONE)");
+ }
+ else if (ret > 0)
+ break;
+
+ usleep (1000);
+ }
+
+ if (debug_threads
+ && (!WIFSTOPPED (*wstatp)
+ || (WSTOPSIG (*wstatp) != 32
+ && WSTOPSIG (*wstatp) != 33)))
+ fprintf (stderr, "Got an event from %d (%x)\n", ret, *wstatp);
+
+ if (to_wait_for == -1)
+ *childp = (struct process_info *) find_inferior_id (&all_processes, ret);
+
+ (*childp)->stopped = 1;
+ (*childp)->pending_is_breakpoint = 0;
+
+ if (debug_threads
+ && WIFSTOPPED (*wstatp))
+ {
+ current_inferior = (struct thread_info *)
+ find_inferior_id (&all_threads, (*childp)->tid);
+ /* For testing only; i386_stop_pc prints out a diagnostic. */
+ if (the_low_target.get_pc != NULL)
+ get_stop_pc ();
+ }
+}
- if (pid != child_data->pid)
- perror_with_name ("wait");
+static int
+linux_wait_for_event (struct thread_info *child)
+{
+ CORE_ADDR stop_pc;
+ struct process_info *event_child;
+ int wstat;
+
+ /* Check for a process with a pending status. */
+ /* It is possible that the user changed the pending task's registers since
+ it stopped. We correctly handle the change of PC if we hit a breakpoint
+ (in check_removed_breakpoints); signals should be reported anyway. */
+ if (child == NULL)
+ {
+ event_child = (struct process_info *)
+ find_inferior (&all_processes, status_pending_p, NULL);
+ if (debug_threads && event_child)
+ fprintf (stderr, "Got a pending child %d\n", event_child->lwpid);
+ }
+ else
+ {
+ event_child = get_thread_process (child);
+ if (event_child->status_pending_p
+ && check_removed_breakpoint (event_child))
+ event_child = NULL;
+ }
- /* If this target supports breakpoints, see if we hit one. */
- if (the_low_target.stop_pc != NULL
- && WIFSTOPPED (wstat)
- && WSTOPSIG (wstat) == SIGTRAP)
+ if (event_child != NULL)
+ {
+ if (event_child->status_pending_p)
{
- CORE_ADDR stop_pc;
+ if (debug_threads)
+ fprintf (stderr, "Got an event from pending child %d (%04x)\n",
+ event_child->lwpid, event_child->status_pending);
+ wstat = event_child->status_pending;
+ event_child->status_pending_p = 0;
+ event_child->status_pending = 0;
+ current_inferior = get_process_thread (event_child);
+ return wstat;
+ }
+ }
+
+ /* We only enter this loop if no process has a pending wait status. Thus
+ any action taken in response to a wait status inside this loop is
+ responding as soon as we detect the status, not after any pending
+ events. */
+ while (1)
+ {
+ if (child == NULL)
+ event_child = NULL;
+ else
+ event_child = get_thread_process (child);
+
+ linux_wait_for_process (&event_child, &wstat);
+
+ if (event_child == NULL)
+ error ("event from unknown child");
- if (linux_bp_reinsert != 0)
+ current_inferior = (struct thread_info *)
+ find_inferior_id (&all_threads, event_child->tid);
+
+ if (using_threads)
+ {
+ /* Check for thread exit. */
+ if (! WIFSTOPPED (wstat))
{
- reinsert_breakpoint (linux_bp_reinsert);
- linux_bp_reinsert = 0;
- linux_resume (0, 0);
+ if (debug_threads)
+ fprintf (stderr, "Thread %d (LWP %d) exiting\n",
+ event_child->tid, event_child->head.id);
+
+ /* If the last thread is exiting, just return. */
+ if (all_threads.head == all_threads.tail)
+ return wstat;
+
+ dead_thread_notify (event_child->tid);
+
+ remove_inferior (&all_processes, &event_child->head);
+ free (event_child);
+ remove_thread (current_inferior);
+ current_inferior = (struct thread_info *) all_threads.head;
+
+ /* If we were waiting for this particular child to do something...
+ well, it did something. */
+ if (child != NULL)
+ return wstat;
+
+ /* Wait for a more interesting event. */
continue;
}
- fetch_inferior_registers (0);
- stop_pc = (*the_low_target.stop_pc) ();
+ if (WIFSTOPPED (wstat)
+ && WSTOPSIG (wstat) == SIGSTOP
+ && event_child->stop_expected)
+ {
+ if (debug_threads)
+ fprintf (stderr, "Expected stop.\n");
+ event_child->stop_expected = 0;
+ linux_resume_one_process (&event_child->head,
+ event_child->stepping, 0);
+ continue;
+ }
- if (check_breakpoints (stop_pc) != 0)
+ /* FIXME drow/2002-06-09: Get signal numbers from the inferior's
+ thread library? */
+ if (WIFSTOPPED (wstat)
+ && (WSTOPSIG (wstat) == SIGRTMIN
+ || WSTOPSIG (wstat) == SIGRTMIN + 1))
{
- if (the_low_target.set_pc != NULL)
- (*the_low_target.set_pc) (stop_pc);
+ if (debug_threads)
+ fprintf (stderr, "Ignored signal %d for %d (LWP %d).\n",
+ WSTOPSIG (wstat), event_child->tid,
+ event_child->head.id);
+ linux_resume_one_process (&event_child->head,
+ event_child->stepping,
+ WSTOPSIG (wstat));
+ continue;
+ }
+ }
- if (the_low_target.breakpoint_reinsert_addr == NULL)
- {
- linux_bp_reinsert = stop_pc;
- uninsert_breakpoint (stop_pc);
- linux_resume (1, 0);
- }
- else
- {
- reinsert_breakpoint_by_bp
- (stop_pc, (*the_low_target.breakpoint_reinsert_addr) ());
- linux_resume (0, 0);
- }
+ /* If this event was not handled above, and is not a SIGTRAP, report
+ it. */
+ if (!WIFSTOPPED (wstat) || WSTOPSIG (wstat) != SIGTRAP)
+ return wstat;
- continue;
+ /* If this target does not support breakpoints, we simply report the
+ SIGTRAP; it's of no concern to us. */
+ if (the_low_target.get_pc == NULL)
+ return wstat;
+
+ stop_pc = get_stop_pc ();
+
+ /* bp_reinsert will only be set if we were single-stepping.
+ Notice that we will resume the process after hitting
+ a gdbserver breakpoint; single-stepping to/over one
+ is not supported (yet). */
+ if (event_child->bp_reinsert != 0)
+ {
+ if (debug_threads)
+ fprintf (stderr, "Reinserted breakpoint.\n");
+ reinsert_breakpoint (event_child->bp_reinsert);
+ event_child->bp_reinsert = 0;
+
+ /* Clear the single-stepping flag and SIGTRAP as we resume. */
+ linux_resume_one_process (&event_child->head, 0, 0);
+ continue;
+ }
+
+ if (debug_threads)
+ fprintf (stderr, "Hit a (non-reinsert) breakpoint.\n");
+
+ if (check_breakpoints (stop_pc) != 0)
+ {
+ /* We hit one of our own breakpoints. We mark it as a pending
+ breakpoint, so that check_removed_breakpoints () will do the PC
+ adjustment for us at the appropriate time. */
+ event_child->pending_is_breakpoint = 1;
+ event_child->pending_stop_pc = stop_pc;
+
+ /* Now we need to put the breakpoint back. We continue in the event
+ loop instead of simply replacing the breakpoint right away,
+ in order to not lose signals sent to the thread that hit the
+ breakpoint. Unfortunately this increases the window where another
+ thread could sneak past the removed breakpoint. For the current
+ use of server-side breakpoints (thread creation) this is
+ acceptable; but it needs to be considered before this breakpoint
+ mechanism can be used in more general ways. For some breakpoints
+ it may be necessary to stop all other threads, but that should
+ be avoided where possible.
+
+ If breakpoint_reinsert_addr is NULL, that means that we can
+ use PTRACE_SINGLESTEP on this platform. Uninsert the breakpoint,
+ mark it for reinsertion, and single-step.
+
+ Otherwise, call the target function to figure out where we need
+ our temporary breakpoint, create it, and continue executing this
+ process. */
+ if (the_low_target.breakpoint_reinsert_addr == NULL)
+ {
+ event_child->bp_reinsert = stop_pc;
+ uninsert_breakpoint (stop_pc);
+ linux_resume_one_process (&event_child->head, 1, 0);
+ }
+ else
+ {
+ reinsert_breakpoint_by_bp
+ (stop_pc, (*the_low_target.breakpoint_reinsert_addr) ());
+ linux_resume_one_process (&event_child->head, 0, 0);
}
+
+ continue;
+ }
+
+ /* If we were single-stepping, we definitely want to report the
+ SIGTRAP. The single-step operation has completed, so also
+ clear the stepping flag; in general this does not matter,
+ because the SIGTRAP will be reported to the client, which
+ will give us a new action for this thread, but clear it for
+ consistency anyway. It's safe to clear the stepping flag
+ because the only consumer of get_stop_pc () after this point
+ is check_removed_breakpoints, and pending_is_breakpoint is not
+ set. It might be wiser to use a step_completed flag instead. */
+ if (event_child->stepping)
+ {
+ event_child->stepping = 0;
+ return wstat;
+ }
+
+ /* A SIGTRAP that we can't explain. It may have been a breakpoint.
+ Check if it is a breakpoint, and if so mark the process information
+ accordingly. This will handle both the necessary fiddling with the
+ PC on decr_pc_after_break targets and suppressing extra threads
+ hitting a breakpoint if two hit it at once and then GDB removes it
+ after the first is reported. Arguably it would be better to report
+ multiple threads hitting breakpoints simultaneously, but the current
+ remote protocol does not allow this. */
+ if ((*the_low_target.breakpoint_at) (stop_pc))
+ {
+ event_child->pending_is_breakpoint = 1;
+ event_child->pending_stop_pc = stop_pc;
}
return wstat;
}
+
/* NOTREACHED */
return 0;
}
-/* Wait for process, returns status */
+/* Wait for process, returns status. */
static unsigned char
linux_wait (char *status)
{
int w;
+ struct thread_info *child = NULL;
+
+retry:
+ /* If we were only supposed to resume one thread, only wait for
+ that thread - if it's still alive. If it died, however - which
+ can happen if we're coming from the thread death case below -
+ then we need to make sure we restart the other threads. We could
+ pick a thread at random or restart all; restarting all is less
+ arbitrary. */
+ if (cont_thread > 0)
+ {
+ child = (struct thread_info *) find_inferior_id (&all_threads,
+ cont_thread);
+
+ /* No stepping, no signal - unless one is pending already, of course. */
+ if (child == NULL)
+ linux_resume (0, 0);
+ }
enable_async_io ();
- w = linux_wait_for_one_inferior (current_inferior);
+ w = linux_wait_for_event (child);
+ stop_all_processes ();
disable_async_io ();
- if (WIFEXITED (w))
+ /* If we are waiting for a particular child, and it exited,
+ linux_wait_for_event will return its exit status. Similarly if
+ the last child exited. If this is not the last child, however,
+ do not report it as exited until there is a 'thread exited' response
+ available in the remote protocol. Instead, just wait for another event.
+ This should be safe, because if the thread crashed we will already
+ have reported the termination signal to GDB; that should stop any
+ in-progress stepping operations, etc.
+
+ Report the exit status of the last thread to exit. This matches
+ LinuxThreads' behavior. */
+
+ if (all_threads.head == all_threads.tail)
{
- fprintf (stderr, "\nChild exited with retcode = %x \n", WEXITSTATUS (w));
- *status = 'W';
- clear_inferiors ();
- return ((unsigned char) WEXITSTATUS (w));
+ if (WIFEXITED (w))
+ {
+ fprintf (stderr, "\nChild exited with retcode = %x \n", WEXITSTATUS (w));
+ *status = 'W';
+ clear_inferiors ();
+ return ((unsigned char) WEXITSTATUS (w));
+ }
+ else if (!WIFSTOPPED (w))
+ {
+ fprintf (stderr, "\nChild terminated with signal = %x \n", WTERMSIG (w));
+ clear_inferiors ();
+ *status = 'X';
+ return ((unsigned char) WTERMSIG (w));
+ }
}
- else if (!WIFSTOPPED (w))
+ else
{
- fprintf (stderr, "\nChild terminated with signal = %x \n", WTERMSIG (w));
- clear_inferiors ();
- *status = 'X';
- return ((unsigned char) WTERMSIG (w));
+ if (!WIFSTOPPED (w))
+ goto retry;
}
- fetch_inferior_registers (0);
-
*status = 'T';
return ((unsigned char) WSTOPSIG (w));
}
+static void
+send_sigstop (struct inferior_list_entry *entry)
+{
+ struct process_info *process = (struct process_info *) entry;
+
+ if (process->stopped)
+ return;
+
+ /* If we already have a pending stop signal for this process, don't
+ send another. */
+ if (process->stop_expected)
+ {
+ process->stop_expected = 0;
+ return;
+ }
+
+ if (debug_threads)
+ fprintf (stderr, "Sending sigstop to process %d\n", process->head.id);
+
+ kill (process->head.id, SIGSTOP);
+ process->sigstop_sent = 1;
+}
+
+static void
+wait_for_sigstop (struct inferior_list_entry *entry)
+{
+ struct process_info *process = (struct process_info *) entry;
+ struct thread_info *saved_inferior, *thread;
+ int wstat, saved_tid;
+
+ if (process->stopped)
+ return;
+
+ saved_inferior = current_inferior;
+ saved_tid = ((struct inferior_list_entry *) saved_inferior)->id;
+ thread = (struct thread_info *) find_inferior_id (&all_threads,
+ process->tid);
+ wstat = linux_wait_for_event (thread);
+
+ /* If we stopped with a non-SIGSTOP signal, save it for later
+ and record the pending SIGSTOP. If the process exited, just
+ return. */
+ if (WIFSTOPPED (wstat)
+ && WSTOPSIG (wstat) != SIGSTOP)
+ {
+ if (debug_threads)
+ fprintf (stderr, "Stopped with non-sigstop signal\n");
+ process->status_pending_p = 1;
+ process->status_pending = wstat;
+ process->stop_expected = 1;
+ }
+
+ if (linux_thread_alive (saved_tid))
+ current_inferior = saved_inferior;
+ else
+ {
+ if (debug_threads)
+ fprintf (stderr, "Previously current thread died.\n");
+
+ /* Set a valid thread as current. */
+ set_desired_inferior (0);
+ }
+}
+
+static void
+stop_all_processes (void)
+{
+ stopping_threads = 1;
+ for_each_inferior (&all_processes, send_sigstop);
+ for_each_inferior (&all_processes, wait_for_sigstop);
+ stopping_threads = 0;
+}
+
/* Resume execution of the inferior process.
If STEP is nonzero, single-step it.
If SIGNAL is nonzero, give it that signal. */
static void
-linux_resume (int step, int signal)
+linux_resume_one_process (struct inferior_list_entry *entry,
+ int step, int signal)
{
+ struct process_info *process = (struct process_info *) entry;
+ struct thread_info *saved_inferior;
+
+ if (process->stopped == 0)
+ return;
+
+ /* If we have pending signals or status, and a new signal, enqueue the
+ signal. Also enqueue the signal if we are waiting to reinsert a
+ breakpoint; it will be picked up again below. */
+ if (signal != 0
+ && (process->status_pending_p || process->pending_signals != NULL
+ || process->bp_reinsert != 0))
+ {
+ struct pending_signals *p_sig;
+ p_sig = malloc (sizeof (*p_sig));
+ p_sig->prev = process->pending_signals;
+ p_sig->signal = signal;
+ process->pending_signals = p_sig;
+ }
+
+ if (process->status_pending_p)
+ return;
+
+ saved_inferior = current_inferior;
+ current_inferior = get_process_thread (process);
+
+ if (debug_threads)
+ fprintf (stderr, "Resuming process %d (%s, signal %d, stop %s)\n", inferior_pid,
+ step ? "step" : "continue", signal,
+ process->stop_expected ? "expected" : "not expected");
+
+ /* This bit needs some thinking about. If we get a signal that
+ we must report while a single-step reinsert is still pending,
+ we often end up resuming the thread. It might be better to
+ (ew) allow a stack of pending events; then we could be sure that
+ the reinsert happened right away and not lose any signals.
+
+ Making this stack would also shrink the window in which breakpoints are
+ uninserted (see comment in linux_wait_for_process) but not enough for
+ complete correctness, so it won't solve that problem. It may be
+ worthwhile just to solve this one, however. */
+ if (process->bp_reinsert != 0)
+ {
+ if (debug_threads)
+ fprintf (stderr, " pending reinsert at %08lx", (long)process->bp_reinsert);
+ if (step == 0)
+ fprintf (stderr, "BAD - reinserting but not stepping.\n");
+ step = 1;
+
+ /* Postpone any pending signal. It was enqueued above. */
+ signal = 0;
+ }
+
+ check_removed_breakpoint (process);
+
+ if (debug_threads && the_low_target.get_pc != NULL)
+ {
+ fprintf (stderr, " ");
+ (long) (*the_low_target.get_pc) ();
+ }
+
+ /* If we have pending signals, consume one unless we are trying to reinsert
+ a breakpoint. */
+ if (process->pending_signals != NULL && process->bp_reinsert == 0)
+ {
+ struct pending_signals **p_sig;
+
+ p_sig = &process->pending_signals;
+ while ((*p_sig)->prev != NULL)
+ p_sig = &(*p_sig)->prev;
+
+ signal = (*p_sig)->signal;
+ free (*p_sig);
+ *p_sig = NULL;
+ }
+
+ regcache_invalidate_one ((struct inferior_list_entry *)
+ get_process_thread (process));
errno = 0;
- ptrace (step ? PTRACE_SINGLESTEP : PTRACE_CONT, inferior_pid, 1, signal);
+ process->stopped = 0;
+ process->stepping = step;
+ ptrace (step ? PTRACE_SINGLESTEP : PTRACE_CONT, process->lwpid, 0, signal);
+
+ current_inferior = saved_inferior;
if (errno)
perror_with_name ("ptrace");
}
+/* This function is called once per process other than the first
+ one. The first process we are told the signal to continue
+ with, and whether to step or continue; for all others, any
+ existing signals will be marked in status_pending_p to be
+ reported momentarily, and we preserve the stepping flag. */
+static void
+linux_continue_one_process (struct inferior_list_entry *entry)
+{
+ struct process_info *process;
-#ifdef HAVE_LINUX_USRREGS
+ process = (struct process_info *) entry;
+ linux_resume_one_process (entry, process->stepping, 0);
+}
+
+static void
+linux_resume (int step, int signal)
+{
+ struct process_info *process;
+
+ process = get_thread_process (current_inferior);
+
+ /* If the current process has a status pending, this signal will
+ be enqueued and sent later. */
+ linux_resume_one_process (&process->head, step, signal);
-#define REGISTER_RAW_SIZE(regno) register_size((regno))
+ if (cont_thread == 0 || cont_thread == -1)
+ for_each_inferior (&all_processes, linux_continue_one_process);
+}
+
+#ifdef HAVE_LINUX_USRREGS
int
register_addr (int regnum)
{
CORE_ADDR regaddr;
register int i;
+ char *buf;
if (regno >= the_low_target.num_regs)
return;
regaddr = register_addr (regno);
if (regaddr == -1)
return;
- for (i = 0; i < REGISTER_RAW_SIZE (regno); i += sizeof (PTRACE_XFER_TYPE))
+ buf = alloca (register_size (regno));
+ for (i = 0; i < register_size (regno); i += sizeof (PTRACE_XFER_TYPE))
{
errno = 0;
- *(PTRACE_XFER_TYPE *) (register_data (regno) + i) =
+ *(PTRACE_XFER_TYPE *) (buf + i) =
ptrace (PTRACE_PEEKUSER, inferior_pid, (PTRACE_ARG3_TYPE) regaddr, 0);
regaddr += sizeof (PTRACE_XFER_TYPE);
if (errno != 0)
goto error_exit;
}
}
+ supply_register (regno, buf);
+
error_exit:;
}
{
CORE_ADDR regaddr;
int i;
+ char *buf;
if (regno >= 0)
{
if (regaddr == -1)
return;
errno = 0;
- for (i = 0; i < REGISTER_RAW_SIZE (regno); i += sizeof (PTRACE_XFER_TYPE))
+ buf = alloca (register_size (regno));
+ collect_register (regno, buf);
+ for (i = 0; i < register_size (regno); i += sizeof (PTRACE_XFER_TYPE))
{
errno = 0;
ptrace (PTRACE_POKEUSER, inferior_pid, (PTRACE_ARG3_TYPE) regaddr,
- *(int *) (register_data (regno) + i));
+ *(int *) (buf + i));
if (errno != 0)
{
if ((*the_low_target.cannot_store_register) (regno) == 0)
}
else
for (regno = 0; regno < the_low_target.num_regs; regno++)
- store_inferior_registers (regno);
+ usr_store_inferior_registers (regno);
}
#endif /* HAVE_LINUX_USRREGS */
#ifdef HAVE_LINUX_REGSETS
static int
-regsets_fetch_inferior_registers (void)
+regsets_fetch_inferior_registers ()
{
struct regset_info *regset;
}
else
{
- perror ("Warning: ptrace(regsets_fetch_inferior_registers)");
+ char s[256];
+ sprintf (s, "ptrace(regsets_fetch_inferior_registers) PID=%d",
+ inferior_pid);
+ perror (s);
}
}
regset->store_function (buf);
}
static int
-regsets_store_inferior_registers (void)
+regsets_store_inferior_registers ()
{
struct regset_info *regset;
register PTRACE_XFER_TYPE *buffer = (PTRACE_XFER_TYPE *) alloca (count * sizeof (PTRACE_XFER_TYPE));
extern int errno;
+ if (debug_threads)
+ {
+ fprintf (stderr, "Writing %02x to %08lx\n", (unsigned)myaddr[0], (long)memaddr);
+ }
+
/* Fill start and end extra bytes of buffer with existing memory data. */
buffer[0] = ptrace (PTRACE_PEEKTEXT, inferior_pid,
static void
linux_look_up_symbols (void)
{
- /* Don't need to look up any symbols yet. */
+#ifdef USE_THREAD_DB
+ if (using_threads)
+ return;
+
+ using_threads = thread_db_init ();
+#endif
+}
+
+/* Return 1 if this process is not stopped. */
+static int
+unstopped_p (struct inferior_list_entry *entry, void *dummy)
+{
+ struct process_info *process = (struct process_info *) entry;
+
+ if (process->stopped)
+ return 0;
+
+ return 1;
+}
+
+static int
+linux_signal_pid ()
+{
+ struct inferior_list_entry *process;
+
+ process = find_inferior (&all_processes, unstopped_p, NULL);
+
+ if (process == NULL)
+ {
+ warning ("no unstopped process");
+ return inferior_pid;
+ }
+
+ return pid_of ((struct process_info *) process);
}
\f
linux_read_memory,
linux_write_memory,
linux_look_up_symbols,
+ linux_signal_pid,
};
+static void
+linux_init_signals ()
+{
+ /* FIXME drow/2002-06-09: As above, we should check with LinuxThreads
+ to find what the cancel signal actually is. */
+ signal (SIGRTMIN+1, SIG_IGN);
+}
+
void
initialize_low (void)
{
+ using_threads = 0;
set_target_ops (&linux_target_ops);
set_breakpoint_data (the_low_target.breakpoint,
the_low_target.breakpoint_len);
init_registers ();
+ linux_init_signals ();
}
Boston, MA 02111-1307, USA. */
#ifdef HAVE_LINUX_REGSETS
-typedef void (*regset_func) (void *);
+typedef void (*regset_fill_func) (void *);
+typedef void (*regset_store_func) (const void *);
+enum regset_type {
+ GENERAL_REGS,
+ FP_REGS,
+ EXTENDED_REGS,
+};
+
struct regset_info
{
int get_request, set_request;
int size;
- regset_func fill_function, store_function;
+ enum regset_type type;
+ regset_fill_func fill_function;
+ regset_store_func store_function;
};
extern struct regset_info target_regsets[];
#endif
store the register, and 2 if failure to store the register
is acceptable. */
int (*cannot_store_register) (int);
- CORE_ADDR (*stop_pc) (void);
+ CORE_ADDR (*get_pc) (void);
void (*set_pc) (CORE_ADDR newpc);
const char *breakpoint;
int breakpoint_len;
CORE_ADDR (*breakpoint_reinsert_addr) (void);
+
+
+ int decr_pc_after_break;
+ int (*breakpoint_at) (CORE_ADDR pc);
};
extern struct linux_target_ops the_low_target;
+
+#define get_process(inf) ((struct process_info *)(inf))
+#define get_thread_process(thr) (get_process (inferior_target_data (thr)))
+#define get_process_thread(proc) ((struct thread_info *) \
+ find_inferior_id (&all_threads, \
+ get_process (proc)->tid))
+
+struct process_info
+{
+ struct inferior_list_entry head;
+ int thread_known;
+ int lwpid;
+ int tid;
+
+ /* If this flag is set, the next SIGSTOP will be ignored (the process will
+ be immediately resumed). */
+ int stop_expected;
+
+ /* If this flag is set, the process is known to be stopped right now (stop
+ event already received in a wait()). */
+ int stopped;
+
+ /* If this flag is set, we have sent a SIGSTOP to this process and are
+ waiting for it to stop. */
+ int sigstop_sent;
+
+ /* If this flag is set, STATUS_PENDING is a waitstatus that has not yet
+ been reported. */
+ int status_pending_p;
+ int status_pending;
+
+ /* If this flag is set, the pending status is a (GDB-placed) breakpoint. */
+ int pending_is_breakpoint;
+ CORE_ADDR pending_stop_pc;
+
+ /* If this is non-zero, it is a breakpoint to be reinserted at our next
+ stop (SIGTRAP stops only). */
+ CORE_ADDR bp_reinsert;
+
+ /* If this flag is set, the last continue operation on this process
+ was a single-step. */
+ int stepping;
+
+ /* If this is non-zero, it points to a chain of signals which need to
+ be delivered to this process. */
+ struct pending_signals *pending_signals;
+};
+extern struct inferior_list all_processes;
+
+void linux_attach_lwp (int pid, int tid);
+
+int thread_db_init (void);
return 0;
}
+static CORE_ADDR
+mips_get_pc ()
+{
+ unsigned long pc;
+ collect_register_by_name ("pc", &pc);
+ return pc;
+}
+
+static void
+mips_set_pc (CORE_ADDR pc)
+{
+ unsigned long newpc = pc;
+ supply_register_by_name ("pc", &newpc);
+}
+
+/* Correct in either endianness. */
+static const unsigned long mips_breakpoint = 0x0005000d;
+#define mips_breakpoint_len 4
+
+/* We only place breakpoints in empty marker functions, and thread locking
+ is outside of the function. So rather than importing software single-step,
+ we can just run until exit. */
+static CORE_ADDR
+mips_reinsert_addr ()
+{
+ unsigned long pc;
+ collect_register_by_name ("ra", &pc);
+ return pc;
+}
+
+static int
+mips_breakpoint_at (CORE_ADDR where)
+{
+ unsigned long insn;
+
+ (*the_target->read_memory) (where, (char *) &insn, 4);
+ if (insn == mips_breakpoint)
+ return 1;
+
+ /* If necessary, recognize more trap instructions here. GDB only uses the
+ one. */
+ return 0;
+}
+
struct linux_target_ops the_low_target = {
mips_num_regs,
mips_regmap,
mips_cannot_fetch_register,
mips_cannot_store_register,
+ mips_get_pc,
+ mips_set_pc,
+ (const char *) &mips_breakpoint,
+ mips_breakpoint_len,
+ mips_reinsert_addr,
+ 0,
+ mips_breakpoint_at,
};
return 0;
}
+static CORE_ADDR
+ppc_get_pc (void)
+{
+ unsigned long pc;
+
+ collect_register_by_name ("pc", &pc);
+ return (CORE_ADDR) pc;
+}
+
+static void
+ppc_set_pc (CORE_ADDR pc)
+{
+ unsigned long newpc = pc;
+
+ supply_register_by_name ("pc", &newpc);
+}
+
+/* Correct in either endianness. Note that this file is
+ for PowerPC only, not PowerPC64.
+ This instruction is "twge r2, r2", which GDB uses as a software
+ breakpoint. */
+static const unsigned long ppc_breakpoint = 0x7d821008;
+#define ppc_breakpoint_len 4
+
+static int
+ppc_breakpoint_at (CORE_ADDR where)
+{
+ unsigned long insn;
+
+ (*the_target->read_memory) (where, (char *) &insn, 4);
+ if (insn == ppc_breakpoint)
+ return 1;
+ /* If necessary, recognize more trap instructions here. GDB only uses the
+ one. */
+ return 0;
+}
+
struct linux_target_ops the_low_target = {
ppc_num_regs,
ppc_regmap,
ppc_cannot_fetch_register,
ppc_cannot_store_register,
+ ppc_get_pc,
+ ppc_set_pc,
+ (const char *) &ppc_breakpoint,
+ ppc_breakpoint_len,
+ NULL,
+ 0,
+ ppc_breakpoint_at,
};
return 0;
}
+static CORE_ADDR
+sh_get_pc ()
+{
+ unsigned long pc;
+ collect_register_by_name ("pc", &pc);
+ return pc;
+}
+
+static void
+sh_set_pc (CORE_ADDR pc)
+{
+ unsigned long newpc = pc;
+ supply_register_by_name ("pc", &newpc);
+}
+
+/* Correct in either endianness, obviously. */
+static const unsigned short sh_breakpoint = 0xc3c3;
+#define sh_breakpoint_len 2
+
+static int
+sh_breakpoint_at (CORE_ADDR where)
+{
+ unsigned short insn;
+
+ (*the_target->read_memory) (where, (char *) &insn, 2);
+ if (insn == sh_breakpoint)
+ return 1;
+
+ /* If necessary, recognize more trap instructions here. GDB only uses the
+ one. */
+ return 0;
+}
+
struct linux_target_ops the_low_target = {
sh_num_regs,
sh_regmap,
sh_cannot_fetch_register,
sh_cannot_store_register,
+ sh_get_pc,
+ sh_set_pc,
+ (const char *) &sh_breakpoint,
+ sh_breakpoint_len,
+ NULL,
+ 0,
+ sh_breakpoint_at,
};
struct regset_info target_regsets[] = {
{ PTRACE_GETREGS, PTRACE_SETREGS, sizeof (elf_gregset_t),
+ GENERAL_REGS,
x86_64_fill_gregset, x86_64_store_gregset },
{ PTRACE_GETFPREGS, PTRACE_SETFPREGS, sizeof (elf_fpregset_t),
+ FP_REGS,
x86_64_fill_fpregset, x86_64_store_fpregset },
- { 0, 0, -1, NULL, NULL }
+ { 0, 0, -1, -1, NULL, NULL }
};
struct linux_target_ops the_low_target = {
--- /dev/null
+/* libthread_db helper functions for the remote server for GDB.
+ Copyright 2002
+ Free Software Foundation, Inc.
+
+ Contributed by MontaVista Software.
+
+ 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 2 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, write to the Free Software
+ Foundation, Inc., 59 Temple Place - Suite 330,
+ Boston, MA 02111-1307, USA. */
+
+#include "server.h"
+
+/* This file is currently tied to GNU/Linux. It should scale well to
+ another libthread_db implementation, with the approriate gdbserver
+ hooks, but for now this means we can use GNU/Linux's target data. */
+
+#include "linux-low.h"
+
+/* Correct for all GNU/Linux targets (for quite some time). */
+#define GDB_GREGSET_T elf_gregset_t
+#define GDB_FPREGSET_T elf_fpregset_t
+
+#ifndef HAVE_ELF_FPREGSET_T
+/* Make sure we have said types. Not all platforms bring in <linux/elf.h>
+ via <sys/procfs.h>. */
+#ifdef HAVE_LINUX_ELF_H
+#include <linux/elf.h>
+#endif
+#endif
+
+#include "../gdb_proc_service.h"
+
+typedef struct ps_prochandle *gdb_ps_prochandle_t;
+typedef void *gdb_ps_read_buf_t;
+typedef const void *gdb_ps_write_buf_t;
+typedef size_t gdb_ps_size_t;
+
+/* FIXME redo this right */
+#if 0
+#ifndef HAVE_LINUX_REGSETS
+#error HAVE_LINUX_REGSETS required!
+#else
+static struct regset_info *
+gregset_info(void)
+{
+ int i = 0;
+
+ while (target_regsets[i].size != -1)
+ {
+ if (target_regsets[i].type == GENERAL_REGS)
+ break;
+ i++;
+ }
+
+ return &target_regsets[i];
+}
+
+static struct regset_info *
+fpregset_info(void)
+{
+ int i = 0;
+
+ while (target_regsets[i].size != -1)
+ {
+ if (target_regsets[i].type == FP_REGS)
+ break;
+ i++;
+ }
+
+ return &target_regsets[i];
+}
+#endif
+#endif
+
+/* Search for the symbol named NAME within the object named OBJ within
+ the target process PH. If the symbol is found the address of the
+ symbol is stored in SYM_ADDR. */
+
+ps_err_e
+ps_pglobal_lookup (gdb_ps_prochandle_t ph, const char *obj,
+ const char *name, paddr_t *sym_addr)
+{
+ CORE_ADDR addr;
+
+ if (look_up_one_symbol (name, &addr) == 0)
+ return PS_NOSYM;
+
+ *sym_addr = (paddr_t) (unsigned long) addr;
+ return PS_OK;
+}
+
+/* Read SIZE bytes from the target process PH at address ADDR and copy
+ them into BUF. */
+
+ps_err_e
+ps_pdread (gdb_ps_prochandle_t ph, paddr_t addr,
+ gdb_ps_read_buf_t buf, gdb_ps_size_t size)
+{
+ read_inferior_memory (addr, buf, size);
+ return PS_OK;
+}
+
+/* Write SIZE bytes from BUF into the target process PH at address ADDR. */
+
+ps_err_e
+ps_pdwrite (gdb_ps_prochandle_t ph, paddr_t addr,
+ gdb_ps_write_buf_t buf, gdb_ps_size_t size)
+{
+ return write_inferior_memory (addr, buf, size);
+}
+
+/* Get the general registers of LWP LWPID within the target process PH
+ and store them in GREGSET. */
+
+ps_err_e
+ps_lgetregs (gdb_ps_prochandle_t ph, lwpid_t lwpid, prgregset_t gregset)
+{
+#if 0
+ struct thread_info *reg_inferior, *save_inferior;
+ void *regcache;
+
+ reg_inferior = (struct thread_info *) find_inferior_id (&all_threads,
+ lwpid);
+ if (reg_inferior == NULL)
+ return PS_ERR;
+
+ save_inferior = current_inferior;
+ current_inferior = reg_inferior;
+
+ regcache = new_register_cache ();
+ the_target->fetch_registers (0, regcache);
+ gregset_info()->fill_function (gregset, regcache);
+ free_register_cache (regcache);
+
+ current_inferior = save_inferior;
+ return PS_OK;
+#endif
+ /* FIXME */
+ return PS_ERR;
+}
+
+/* Set the general registers of LWP LWPID within the target process PH
+ from GREGSET. */
+
+ps_err_e
+ps_lsetregs (gdb_ps_prochandle_t ph, lwpid_t lwpid, const prgregset_t gregset)
+{
+#if 0
+ struct thread_info *reg_inferior, *save_inferior;
+ void *regcache;
+
+ reg_inferior = (struct thread_info *) find_inferior_id (&all_threads, lwpid);
+ if (reg_inferior == NULL)
+ return PS_ERR;
+
+ save_inferior = current_inferior;
+ current_inferior = reg_inferior;
+
+ regcache = new_register_cache ();
+ gregset_info()->store_function (gregset, regcache);
+ the_target->store_registers (0, regcache);
+ free_register_cache (regcache);
+
+ current_inferior = save_inferior;
+
+ return PS_OK;
+#endif
+ /* FIXME */
+ return PS_ERR;
+}
+
+/* Get the floating-point registers of LWP LWPID within the target
+ process PH and store them in FPREGSET. */
+
+ps_err_e
+ps_lgetfpregs (gdb_ps_prochandle_t ph, lwpid_t lwpid,
+ gdb_prfpregset_t *fpregset)
+{
+#if 0
+ struct thread_info *reg_inferior, *save_inferior;
+ void *regcache;
+
+ reg_inferior = (struct thread_info *) find_inferior_id (&all_threads, lwpid);
+ if (reg_inferior == NULL)
+ return PS_ERR;
+
+ save_inferior = current_inferior;
+ current_inferior = reg_inferior;
+
+ regcache = new_register_cache ();
+ the_target->fetch_registers (0, regcache);
+ fpregset_info()->fill_function (fpregset, regcache);
+ free_register_cache (regcache);
+
+ current_inferior = save_inferior;
+
+ return PS_OK;
+#endif
+ /* FIXME */
+ return PS_ERR;
+}
+
+/* Set the floating-point registers of LWP LWPID within the target
+ process PH from FPREGSET. */
+
+ps_err_e
+ps_lsetfpregs (gdb_ps_prochandle_t ph, lwpid_t lwpid,
+ const gdb_prfpregset_t *fpregset)
+{
+#if 0
+ struct thread_info *reg_inferior, *save_inferior;
+ void *regcache;
+
+ reg_inferior = (struct thread_info *) find_inferior_id (&all_threads, lwpid);
+ if (reg_inferior == NULL)
+ return PS_ERR;
+
+ save_inferior = current_inferior;
+ current_inferior = reg_inferior;
+
+ regcache = new_register_cache ();
+ fpregset_info()->store_function (fpregset, regcache);
+ the_target->store_registers (0, regcache);
+ free_register_cache (regcache);
+
+ current_inferior = save_inferior;
+
+ return PS_OK;
+#endif
+ /* FIXME */
+ return PS_ERR;
+}
+
+/* Return overall process id of the target PH. Special for GNU/Linux
+ -- not used on Solaris. */
+
+pid_t
+ps_getpid (gdb_ps_prochandle_t ph)
+{
+ return ph->pid;
+}
+
+
struct inferior_regcache_data
{
+ int registers_valid;
char *registers;
};
const char **gdbserver_expedite_regs;
static struct inferior_regcache_data *
-get_regcache (struct inferior_info *inf)
+get_regcache (struct thread_info *inf, int fetch)
{
struct inferior_regcache_data *regcache;
if (regcache == NULL)
fatal ("no register cache");
+ /* FIXME - fetch registers for INF */
+ if (fetch && regcache->registers_valid == 0)
+ {
+ fetch_inferior_registers (0);
+ regcache->registers_valid = 1;
+ }
+
return regcache;
}
+void
+regcache_invalidate_one (struct inferior_list_entry *entry)
+{
+ struct thread_info *thread = (struct thread_info *) entry;
+ struct inferior_regcache_data *regcache;
+
+ regcache = (struct inferior_regcache_data *) inferior_regcache_data (thread);
+
+ if (regcache->registers_valid)
+ {
+ struct thread_info *saved_inferior = current_inferior;
+
+ current_inferior = thread;
+ store_inferior_registers (-1);
+ current_inferior = saved_inferior;
+ }
+
+ regcache->registers_valid = 0;
+}
+
+void
+regcache_invalidate ()
+{
+ for_each_inferior (&all_threads, regcache_invalidate_one);
+}
+
int
registers_length (void)
{
return 2 * register_bytes;
}
-void
-create_register_cache (struct inferior_info *inferior)
+void *
+new_register_cache (void)
{
struct inferior_regcache_data *regcache;
if (regcache->registers == NULL)
fatal ("Could not allocate register cache.");
- set_inferior_regcache_data (inferior, regcache);
+ regcache->registers_valid = 0;
+
+ return regcache;
}
void
-free_register_cache (struct inferior_info *inferior)
+free_register_cache (void *regcache_p)
{
- free (get_regcache (current_inferior)->registers);
- free (get_regcache (current_inferior));
- set_inferior_regcache_data (inferior, NULL);
+ struct inferior_regcache_data *regcache
+ = (struct inferior_regcache_data *) regcache_p;
+
+ free (regcache->registers);
+ free (regcache);
}
void
void
registers_to_string (char *buf)
{
- char *registers = get_regcache (current_inferior)->registers;
+ char *registers = get_regcache (current_inferior, 1)->registers;
convert_int_to_ascii (registers, buf, register_bytes);
}
registers_from_string (char *buf)
{
int len = strlen (buf);
- char *registers = get_regcache (current_inferior)->registers;
+ char *registers = get_regcache (current_inferior, 1)->registers;
if (len != register_bytes * 2)
{
return reg_defs[n].size / 8;
}
-char *
-register_data (int n)
+static char *
+register_data (int n, int fetch)
{
- char *registers = get_regcache (current_inferior)->registers;
+ char *registers = get_regcache (current_inferior, fetch)->registers;
return registers + (reg_defs[n].offset / 8);
}
void
supply_register (int n, const void *buf)
{
- memcpy (register_data (n), buf, register_size (n));
+ memcpy (register_data (n, 0), buf, register_size (n));
}
void
void
collect_register (int n, void *buf)
{
- memcpy (buf, register_data (n), register_size (n));
+ memcpy (buf, register_data (n, 1), register_size (n));
+}
+
+void
+collect_register_as_string (int n, char *buf)
+{
+ convert_int_to_ascii (register_data (n, 1), buf, register_size (n));
}
void
#ifndef REGCACHE_H
#define REGCACHE_H
-struct inferior_info;
+struct inferior_list_entry;
/* Create a new register cache for INFERIOR. */
-void create_register_cache (struct inferior_info *inferior);
+void *new_register_cache (void);
/* Release all memory associated with the register cache for INFERIOR. */
-void free_register_cache (struct inferior_info *inferior);
+void free_register_cache (void *regcache);
+
+/* Invalidate cached registers for one or all threads. */
+
+void regcache_invalidate_one (struct inferior_list_entry *);
+void regcache_invalidate (void);
/* Convert all registers to a string in the currently specified remote
format. */
struct reg *find_register_by_number (int n);
-char *register_data (int n);
-
int register_size (int n);
int find_regno (const char *name);
void collect_register (int n, void *buf);
+void collect_register_as_string (int n, char *buf);
+
void collect_register_by_name (const char *name, void *buf);
#endif /* REGCACHE_H */
static int remote_desc;
+/* FIXME headerize? */
+extern int using_threads;
+extern int debug_threads;
+
/* Open a connection to a remote debugger.
NAME is the filename used for communication. */
}
if (remote_debug)
- printf ("putpkt (\"%s\"); [looking for ack]\n", buf2);
+ {
+ fprintf (stderr, "putpkt (\"%s\"); [looking for ack]\n", buf2);
+ fflush (stderr);
+ }
cc = read (remote_desc, buf3, 1);
if (remote_debug)
- printf ("[received '%c' (0x%x)]\n", buf3[0], buf3[0]);
+ {
+ fprintf (stderr, "[received '%c' (0x%x)]\n", buf3[0], buf3[0]);
+ fflush (stderr);
+ }
+
if (cc <= 0)
{
if (cc == 0)
free (buf2);
return -1;
}
+
+ /* Check for an input interrupt while we're here. */
+ if (buf3[0] == '\003')
+ kill ((*the_target->signal_pid) (), SIGINT);
}
while (buf3[0] != '+');
return;
}
- kill (signal_pid, SIGINT);
+ kill ((*the_target->signal_pid) (), SIGINT);
}
}
if (c == '$')
break;
if (remote_debug)
- printf ("[getpkt: discarding char '%c']\n", c);
+ {
+ fprintf (stderr, "[getpkt: discarding char '%c']\n", c);
+ fflush (stderr);
+ }
+
if (c < 0)
return -1;
}
}
if (remote_debug)
- printf ("getpkt (\"%s\"); [sending ack] \n", buf);
+ {
+ fprintf (stderr, "getpkt (\"%s\"); [sending ack] \n", buf);
+ fflush (stderr);
+ }
write (remote_desc, "+", 1);
if (remote_debug)
- printf ("[sent ack]\n");
+ {
+ fprintf (stderr, "[sent ack]\n");
+ fflush (stderr);
+ }
+
return bp - buf;
}
static char *
outreg (int regno, char *buf)
{
- int regsize = register_size (regno);
-
if ((regno >> 12) != 0)
*buf++ = tohex ((regno >> 12) & 0xf);
if ((regno >> 8) != 0)
*buf++ = tohex ((regno >> 4) & 0xf);
*buf++ = tohex (regno & 0xf);
*buf++ = ':';
- convert_int_to_ascii (register_data (regno), buf, regsize);
- buf += 2 * regsize;
+ collect_register_as_string (regno, buf);
+ buf += 2 * register_size (regno);
*buf++ = ';';
return buf;
}
+void
+new_thread_notify (int id)
+{
+ char own_buf[256];
+
+ /* The `n' response is not yet part of the remote protocol. Do nothing. */
+ if (1)
+ return;
+
+ if (server_waiting == 0)
+ return;
+
+ sprintf (own_buf, "n%x", id);
+ disable_async_io ();
+ putpkt (own_buf);
+ enable_async_io ();
+}
+
+void
+dead_thread_notify (int id)
+{
+ char own_buf[256];
+
+ /* The `x' response is not yet part of the remote protocol. Do nothing. */
+ if (1)
+ return;
+
+ sprintf (own_buf, "x%x", id);
+ disable_async_io ();
+ putpkt (own_buf);
+ enable_async_io ();
+}
+
void
prepare_resume_reply (char *buf, char status, unsigned char signo)
{
regp ++;
}
- /* If the debugger hasn't used any thread features, don't burden it with
- threads. If we didn't check this, GDB 4.13 and older would choke. */
- if (cont_thread != 0)
+ /* Formerly, if the debugger had not used any thread features we would not
+ burden it with a thread status response. This was for the benefit of
+ GDB 4.13 and older. However, in recent GDB versions the check
+ (``if (cont_thread != 0)'') does not have the desired effect because of
+ sillyness in the way that the remote protocol handles specifying a thread.
+ Since thread support relies on qSymbol support anyway, assume GDB can handle
+ threads. */
+
+ if (using_threads)
{
+ /* FIXME right place to set this? */
+ thread_from_wait = ((struct inferior_list_entry *)current_inferior)->id;
+ if (debug_threads)
+ fprintf (stderr, "Writing resume reply for %d\n\n", thread_from_wait);
if (old_thread_from_wait != thread_from_wait)
{
+ general_thread = thread_from_wait;
sprintf (buf, "thread:%x;", thread_from_wait);
buf += strlen (buf);
old_thread_from_wait = thread_from_wait;
{
/* Malformed response. */
if (remote_debug)
- fprintf (stderr, "Malformed response to qSymbol, ignoring.\n");
+ {
+ fprintf (stderr, "Malformed response to qSymbol, ignoring.\n");
+ fflush (stderr);
+ }
+
return -1;
}
int cont_thread;
int general_thread;
+int step_thread;
int thread_from_wait;
int old_thread_from_wait;
int extended_protocol;
+int server_waiting;
+
jmp_buf toplevel;
static unsigned char
{
/* FIXME Check error? Or turn to void. */
create_inferior (argv[0], argv);
- /* FIXME Print pid properly. */
- fprintf (stderr, "Process %s created; pid = %d\n", argv[0], signal_pid);
+
+ fprintf (stderr, "Process %s created; pid = %d\n", argv[0],
+ all_threads.head->id);
/* Wait till we are at 1st instruction in program, return signal number. */
- return mywait (statusptr);
+ return mywait (statusptr, 0);
}
static int
if (myattach (pid) != 0)
return -1;
- *sigptr = mywait (statusptr);
+ *sigptr = mywait (statusptr, 0);
return 0;
}
void
handle_query (char *own_buf)
{
+ static struct inferior_list_entry *thread_ptr;
+
if (strcmp ("qSymbol::", own_buf) == 0)
{
if (the_target->look_up_symbols != NULL)
return;
}
+ if (strcmp ("qfThreadInfo", own_buf) == 0)
+ {
+ thread_ptr = all_threads.head;
+ sprintf (own_buf, "m%x", thread_ptr->id);
+ thread_ptr = thread_ptr->next;
+ return;
+ }
+
+ if (strcmp ("qsThreadInfo", own_buf) == 0)
+ {
+ if (thread_ptr != NULL)
+ {
+ sprintf (own_buf, "m%x", thread_ptr->id);
+ thread_ptr = thread_ptr->next;
+ return;
+ }
+ else
+ {
+ sprintf (own_buf, "l");
+ return;
+ }
+ }
+
/* Otherwise we didn't know what packet it was. Say we didn't
understand it. */
own_buf[0] = 0;
case 'g':
general_thread = strtol (&own_buf[2], NULL, 16);
write_ok (own_buf);
- fetch_inferior_registers (0);
+ set_desired_inferior (1);
break;
case 'c':
cont_thread = strtol (&own_buf[2], NULL, 16);
write_ok (own_buf);
break;
+ case 's':
+ step_thread = strtol (&own_buf[2], NULL, 16);
+ write_ok (own_buf);
+ break;
default:
/* Silently ignore it so that gdb can extend the protocol
without compatibility headaches. */
}
break;
case 'g':
+ set_desired_inferior (1);
registers_to_string (own_buf);
break;
case 'G':
+ set_desired_inferior (1);
registers_from_string (&own_buf[1]);
- store_inferior_registers (-1);
write_ok (own_buf);
break;
case 'm':
signal = target_signal_to_host (sig);
else
signal = 0;
+ set_desired_inferior (0);
myresume (0, signal);
- signal = mywait (&status);
+ signal = mywait (&status, 1);
prepare_resume_reply (own_buf, status, signal);
break;
case 'S':
signal = target_signal_to_host (sig);
else
signal = 0;
+ set_desired_inferior (0);
myresume (1, signal);
- signal = mywait (&status);
+ signal = mywait (&status, 1);
prepare_resume_reply (own_buf, status, signal);
break;
case 'c':
+ set_desired_inferior (0);
myresume (0, 0);
- signal = mywait (&status);
+ signal = mywait (&status, 1);
prepare_resume_reply (own_buf, status, signal);
break;
case 's':
+ set_desired_inferior (0);
myresume (1, 0);
- signal = mywait (&status);
+ signal = mywait (&status, 1);
prepare_resume_reply (own_buf, status, signal);
break;
case 'k':
least the size of a (void *). */
typedef long long CORE_ADDR;
-/* Opaque inferior process information. */
-struct inferior_info;
+/* Generic information for tracking a list of ``inferiors'' - threads,
+ processes, etc. */
+struct inferior_list
+{
+ struct inferior_list_entry *head;
+ struct inferior_list_entry *tail;
+};
+struct inferior_list_entry
+{
+ int id;
+ struct inferior_list_entry *next;
+};
+
+/* Opaque type for user-visible threads. */
+struct thread_info;
#include "regcache.h"
#include "gdb/signals.h"
void initialize_low ();
-/* Target-specific variables */
-
-extern char *registers;
-
/* From inferiors.c. */
-extern struct inferior_info *current_inferior;
-extern int signal_pid;
-void add_inferior (int pid);
+extern struct inferior_list all_threads;
+void add_inferior_to_list (struct inferior_list *list,
+ struct inferior_list_entry *new_inferior);
+void for_each_inferior (struct inferior_list *list,
+ void (*action) (struct inferior_list_entry *));
+extern struct thread_info *current_inferior;
+void remove_inferior (struct inferior_list *list,
+ struct inferior_list_entry *entry);
+void remove_thread (struct thread_info *thread);
+void add_thread (int thread_id, void *target_data);
void clear_inferiors (void);
-void *inferior_target_data (struct inferior_info *);
-void set_inferior_target_data (struct inferior_info *, void *);
-void *inferior_regcache_data (struct inferior_info *);
-void set_inferior_regcache_data (struct inferior_info *, void *);
+struct inferior_list_entry *find_inferior
+ (struct inferior_list *,
+ int (*func) (struct inferior_list_entry *,
+ void *),
+ void *arg);
+struct inferior_list_entry *find_inferior_id (struct inferior_list *list,
+ int id);
+void *inferior_target_data (struct thread_info *);
+void set_inferior_target_data (struct thread_info *, void *);
+void *inferior_regcache_data (struct thread_info *);
+void set_inferior_regcache_data (struct thread_info *, void *);
+void change_inferior_id (struct inferior_list *list,
+ int new_id);
/* Public variables in server.c */
extern int cont_thread;
extern int general_thread;
+extern int step_thread;
extern int thread_from_wait;
extern int old_thread_from_wait;
+extern int server_waiting;
extern jmp_buf toplevel;
void disable_async_io (void);
void convert_ascii_to_int (char *from, char *to, int n);
void convert_int_to_ascii (char *from, char *to, int n);
+void new_thread_notify (int id);
+void dead_thread_notify (int id);
void prepare_resume_reply (char *buf, char status, unsigned char sig);
void decode_m_packet (char *from, CORE_ADDR * mem_addr_ptr,
struct target_ops *the_target;
+void
+set_desired_inferior (int use_general)
+{
+ struct thread_info *found;
+
+ if (use_general == 1)
+ {
+ found = (struct thread_info *) find_inferior_id (&all_threads,
+ general_thread);
+ }
+ else
+ {
+ found = NULL;
+
+ /* If we are continuing any (all) thread(s), use step_thread
+ to decide which thread to step and/or send the specified
+ signal to. */
+ if (step_thread > 0 && (cont_thread == 0 || cont_thread == -1))
+ found = (struct thread_info *) find_inferior_id (&all_threads,
+ step_thread);
+
+ if (found == NULL)
+ found = (struct thread_info *) find_inferior_id (&all_threads,
+ cont_thread);
+ }
+
+ if (found == NULL)
+ current_inferior = (struct thread_info *) all_threads.head;
+ else
+ current_inferior = found;
+}
+
void
read_inferior_memory (CORE_ADDR memaddr, char *myaddr, int len)
{
}
int
-write_inferior_memory (CORE_ADDR memaddr, char *myaddr, int len)
+write_inferior_memory (CORE_ADDR memaddr, const char *myaddr, int len)
+{
+ /* Lacking cleanups, there is some potential for a memory leak if the
+ write fails and we go through error(). Make sure that no more than
+ one buffer is ever pending by making BUFFER static. */
+ static char *buffer = 0;
+ int res;
+
+ if (buffer != NULL)
+ free (buffer);
+
+ buffer = malloc (len);
+ memcpy (buffer, myaddr, len);
+ check_mem_write (memaddr, buffer, len);
+ res = (*the_target->write_memory) (memaddr, buffer, len);
+ free (buffer);
+ buffer = NULL;
+
+ return res;
+}
+
+unsigned char
+mywait (char *statusp, int connected_wait)
{
- check_mem_write (memaddr, myaddr, len);
- return (*the_target->write_memory) (memaddr, myaddr, len);
+ unsigned char ret;
+
+ if (connected_wait)
+ server_waiting = 1;
+
+ ret = (*the_target->wait) (statusp);
+
+ if (connected_wait)
+ server_waiting = 0;
+
+ return ret;
}
void
symbols. */
void (*look_up_symbols) (void);
+
+ /* Return the PID we should send a signal to. Used for asynchronous
+ interrupts (user hitting Control-C). */
+
+ int (*signal_pid) (void);
};
extern struct target_ops *the_target;
#define myresume(step,signo) \
(*the_target->resume) (step, signo)
-#define mywait(statusp) \
- (*the_target->wait) (statusp)
-
#define fetch_inferior_registers(regno) \
(*the_target->fetch_registers) (regno)
#define store_inferior_registers(regno) \
(*the_target->store_registers) (regno)
+unsigned char mywait (char *statusp, int connected_wait);
+
void read_inferior_memory (CORE_ADDR memaddr, char *myaddr, int len);
-int write_inferior_memory (CORE_ADDR memaddr, char *myaddr, int len);
+int write_inferior_memory (CORE_ADDR memaddr, const char *myaddr, int len);
+
+void set_desired_inferior (int id);
#endif /* TARGET_H */
--- /dev/null
+/* Thread management interface, for the remote server for GDB.
+ Copyright 2002
+ Free Software Foundation, Inc.
+
+ Contributed by MontaVista Software.
+
+ 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 2 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, write to the Free Software
+ Foundation, Inc., 59 Temple Place - Suite 330,
+ Boston, MA 02111-1307, USA. */
+
+#include "server.h"
+
+#include "linux-low.h"
+
+extern int debug_threads;
+
+#ifdef HAVE_THREAD_DB_H
+#include <thread_db.h>
+#endif
+
+/* Correct for all GNU/Linux targets (for quite some time). */
+#define GDB_GREGSET_T elf_gregset_t
+#define GDB_FPREGSET_T elf_fpregset_t
+
+#ifndef HAVE_ELF_FPREGSET_T
+/* Make sure we have said types. Not all platforms bring in <linux/elf.h>
+ via <sys/procfs.h>. */
+#ifdef HAVE_LINUX_ELF_H
+#include <linux/elf.h>
+#endif
+#endif
+
+#include "../gdb_proc_service.h"
+
+/* Structure that identifies the child process for the
+ <proc_service.h> interface. */
+static struct ps_prochandle proc_handle;
+
+/* Connection to the libthread_db library. */
+static td_thragent_t *thread_agent;
+
+static int find_new_threads_callback (const td_thrhandle_t *th_p, void *data);
+
+static char *
+thread_db_err_str (td_err_e err)
+{
+ static char buf[64];
+
+ switch (err)
+ {
+ case TD_OK:
+ return "generic 'call succeeded'";
+ case TD_ERR:
+ return "generic error";
+ case TD_NOTHR:
+ return "no thread to satisfy query";
+ case TD_NOSV:
+ return "no sync handle to satisfy query";
+ case TD_NOLWP:
+ return "no LWP to satisfy query";
+ case TD_BADPH:
+ return "invalid process handle";
+ case TD_BADTH:
+ return "invalid thread handle";
+ case TD_BADSH:
+ return "invalid synchronization handle";
+ case TD_BADTA:
+ return "invalid thread agent";
+ case TD_BADKEY:
+ return "invalid key";
+ case TD_NOMSG:
+ return "no event message for getmsg";
+ case TD_NOFPREGS:
+ return "FPU register set not available";
+ case TD_NOLIBTHREAD:
+ return "application not linked with libthread";
+ case TD_NOEVENT:
+ return "requested event is not supported";
+ case TD_NOCAPAB:
+ return "capability not available";
+ case TD_DBERR:
+ return "debugger service failed";
+ case TD_NOAPLIC:
+ return "operation not applicable to";
+ case TD_NOTSD:
+ return "no thread-specific data for this thread";
+ case TD_MALLOC:
+ return "malloc failed";
+ case TD_PARTIALREG:
+ return "only part of register set was written/read";
+ case TD_NOXREGS:
+ return "X register set not available for this thread";
+ default:
+ snprintf (buf, sizeof (buf), "unknown thread_db error '%d'", err);
+ return buf;
+ }
+}
+
+#if 0
+static char *
+thread_db_state_str (td_thr_state_e state)
+{
+ static char buf[64];
+
+ switch (state)
+ {
+ case TD_THR_STOPPED:
+ return "stopped by debugger";
+ case TD_THR_RUN:
+ return "runnable";
+ case TD_THR_ACTIVE:
+ return "active";
+ case TD_THR_ZOMBIE:
+ return "zombie";
+ case TD_THR_SLEEP:
+ return "sleeping";
+ case TD_THR_STOPPED_ASLEEP:
+ return "stopped by debugger AND blocked";
+ default:
+ snprintf (buf, sizeof (buf), "unknown thread_db state %d", state);
+ return buf;
+ }
+}
+#endif
+
+static void
+thread_db_create_event (CORE_ADDR where)
+{
+ td_event_msg_t msg;
+ td_err_e err;
+ struct inferior_linux_data *tdata;
+
+ if (debug_threads)
+ fprintf (stderr, "Thread creation event.\n");
+
+ tdata = inferior_target_data (current_inferior);
+
+ /* FIXME: This assumes we don't get another event.
+ In the LinuxThreads implementation, this is safe,
+ because all events come from the manager thread
+ (except for its own creation, of course). */
+ err = td_ta_event_getmsg (thread_agent, &msg);
+ if (err != TD_OK)
+ fprintf (stderr, "thread getmsg err: %s\n",
+ thread_db_err_str (err));
+
+ /* msg.event == TD_EVENT_CREATE */
+
+ find_new_threads_callback (msg.th_p, NULL);
+}
+
+#if 0
+static void
+thread_db_death_event (CORE_ADDR where)
+{
+ if (debug_threads)
+ fprintf (stderr, "Thread death event.\n");
+}
+#endif
+
+static int
+thread_db_enable_reporting ()
+{
+ td_thr_events_t events;
+ td_notify_t notify;
+ td_err_e err;
+
+ /* Set the process wide mask saying which events we're interested in. */
+ td_event_emptyset (&events);
+ td_event_addset (&events, TD_CREATE);
+
+#if 0
+ /* This is reported to be broken in glibc 2.1.3. A different approach
+ will be necessary to support that. */
+ td_event_addset (&events, TD_DEATH);
+#endif
+
+ err = td_ta_set_event (thread_agent, &events);
+ if (err != TD_OK)
+ {
+ warning ("Unable to set global thread event mask: %s",
+ thread_db_err_str (err));
+ return 0;
+ }
+
+ /* Get address for thread creation breakpoint. */
+ err = td_ta_event_addr (thread_agent, TD_CREATE, ¬ify);
+ if (err != TD_OK)
+ {
+ warning ("Unable to get location for thread creation breakpoint: %s",
+ thread_db_err_str (err));
+ return 0;
+ }
+ set_breakpoint_at ((CORE_ADDR) (unsigned long) notify.u.bptaddr,
+ thread_db_create_event);
+
+#if 0
+ /* Don't concern ourselves with reported thread deaths, only
+ with actual thread deaths (via wait). */
+
+ /* Get address for thread death breakpoint. */
+ err = td_ta_event_addr (thread_agent, TD_DEATH, ¬ify);
+ if (err != TD_OK)
+ {
+ warning ("Unable to get location for thread death breakpoint: %s",
+ thread_db_err_str (err));
+ return;
+ }
+ set_breakpoint_at ((CORE_ADDR) (unsigned long) notify.u.bptaddr,
+ thread_db_death_event);
+#endif
+
+ return 1;
+}
+
+static void
+maybe_attach_thread (const td_thrhandle_t *th_p, td_thrinfo_t *ti_p)
+{
+ td_err_e err;
+ struct thread_info *inferior;
+ struct process_info *process;
+
+ /* If we are attaching to our first thread, things are a little
+ different. */
+ if (all_threads.head == all_threads.tail)
+ {
+ inferior = (struct thread_info *) all_threads.head;
+ process = get_thread_process (inferior);
+ if (process->thread_known == 0)
+ {
+ /* Switch to indexing the threads list by TID. */
+ change_inferior_id (&all_threads, ti_p->ti_tid);
+ goto found;
+ }
+ }
+
+ inferior = (struct thread_info *) find_inferior_id (&all_threads,
+ ti_p->ti_tid);
+ if (inferior != NULL)
+ return;
+
+ if (debug_threads)
+ fprintf (stderr, "Attaching to thread %ld (LWP %d)\n",
+ ti_p->ti_tid, ti_p->ti_lid);
+ linux_attach_lwp (ti_p->ti_lid, ti_p->ti_tid);
+ inferior = (struct thread_info *) find_inferior_id (&all_threads,
+ ti_p->ti_tid);
+ if (inferior == NULL)
+ {
+ warning ("Could not attach to thread %ld (LWP %d)\n",
+ ti_p->ti_tid, ti_p->ti_lid);
+ return;
+ }
+
+ process = inferior_target_data (inferior);
+
+found:
+ new_thread_notify (ti_p->ti_tid);
+
+ process->tid = ti_p->ti_tid;
+ process->lwpid = ti_p->ti_lid;
+
+ process->thread_known = 1;
+ err = td_thr_event_enable (th_p, 1);
+ if (err != TD_OK)
+ error ("Cannot enable thread event reporting for %d: %s",
+ ti_p->ti_lid, thread_db_err_str (err));
+}
+
+static int
+find_new_threads_callback (const td_thrhandle_t *th_p, void *data)
+{
+ td_thrinfo_t ti;
+ td_err_e err;
+
+ err = td_thr_get_info (th_p, &ti);
+ if (err != TD_OK)
+ error ("Cannot get thread info: %s", thread_db_err_str (err));
+
+ /* Check for zombies. */
+ if (ti.ti_state == TD_THR_UNKNOWN || ti.ti_state == TD_THR_ZOMBIE)
+ return 0;
+
+ maybe_attach_thread (th_p, &ti);
+
+ return 0;
+}
+
+static void
+thread_db_find_new_threads (void)
+{
+ td_err_e err;
+
+ /* Iterate over all user-space threads to discover new threads. */
+ err = td_ta_thr_iter (thread_agent, find_new_threads_callback, NULL,
+ TD_THR_ANY_STATE, TD_THR_LOWEST_PRIORITY,
+ TD_SIGNO_MASK, TD_THR_ANY_USER_FLAGS);
+ if (err != TD_OK)
+ error ("Cannot find new threads: %s", thread_db_err_str (err));
+}
+
+int
+thread_db_init ()
+{
+ int err;
+
+ proc_handle.pid = ((struct inferior_list_entry *)current_inferior)->id;
+
+ err = td_ta_new (&proc_handle, &thread_agent);
+ switch (err)
+ {
+ case TD_NOLIBTHREAD:
+ /* No thread library was detected. */
+ return 0;
+
+ case TD_OK:
+ /* The thread library was detected. */
+
+ if (thread_db_enable_reporting () == 0)
+ return 0;
+ thread_db_find_new_threads ();
+ return 1;
+
+ default:
+ warning ("error initializing thread_db library.");
+ }
+
+ return 0;
+}