From: Daniel Jacobowitz Date: Tue, 11 Jun 2002 17:32:40 +0000 (+0000) Subject: 2002-06-11 Daniel Jacobowitz X-Git-Url: https://git.libre-soc.org/?a=commitdiff_plain;h=0d62e5e8077eecf77e9b7b5dc0d2689d051a3ab3;p=binutils-gdb.git 2002-06-11 Daniel Jacobowitz * 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. --- diff --git a/gdb/gdbserver/Makefile.in b/gdb/gdbserver/Makefile.in index 2c8cd3366b4..bc9256b79cd 100644 --- a/gdb/gdbserver/Makefile.in +++ b/gdb/gdbserver/Makefile.in @@ -126,6 +126,7 @@ OBS = inferiors.o regcache.o remote-utils.o server.o signals.o target.o \ 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. @@ -231,6 +232,7 @@ MAKEOVERRIDES= ## 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 @@ -239,10 +241,12 @@ server_h = $(srcdir)/server.h $(regcache_h) config.h $(srcdir)/target.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) @@ -253,6 +257,8 @@ i387-fp.o: i387-fp.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) diff --git a/gdb/gdbserver/acconfig.h b/gdb/gdbserver/acconfig.h index 968feb8b436..f0464b0a280 100644 --- a/gdb/gdbserver/acconfig.h +++ b/gdb/gdbserver/acconfig.h @@ -7,3 +7,18 @@ /* Define if the target supports PTRACE_GETFPXREGS for extended register access. */ #undef HAVE_PTRACE_GETFPXREGS + +/* Define if has prgregset_t. */ +#undef HAVE_PRGREGSET_T + +/* Define if has prfpregset_t. */ +#undef HAVE_PRFPREGSET_T + +/* Define if has lwpid_t. */ +#undef HAVE_LWPID_T + +/* Define if has psaddr_t. */ +#undef HAVE_PSADDR_T + +/* Define if the prfpregset_t type is broken. */ +#undef PRFPREGSET_T_BROKEN diff --git a/gdb/gdbserver/acinclude.m4 b/gdb/gdbserver/acinclude.m4 new file mode 100644 index 00000000000..bbfa86f16c5 --- /dev/null +++ b/gdb/gdbserver/acinclude.m4 @@ -0,0 +1,41 @@ +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" + ]]) +)]) diff --git a/gdb/gdbserver/aclocal.m4 b/gdb/gdbserver/aclocal.m4 index 24b9ced2835..2fc6cf883da 100644 --- a/gdb/gdbserver/aclocal.m4 +++ b/gdb/gdbserver/aclocal.m4 @@ -10,91 +10,45 @@ dnl but WITHOUT ANY WARRANTY, to the extent permitted by law; without 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 -#include -#include -#include -/* 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" + ]]) +)]) diff --git a/gdb/gdbserver/config.in b/gdb/gdbserver/config.in index 9d553f220be..cdaeb8dfe39 100644 --- a/gdb/gdbserver/config.in +++ b/gdb/gdbserver/config.in @@ -1,4 +1,4 @@ -/* 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 @@ -13,12 +13,24 @@ register access. */ #undef HAVE_PTRACE_GETFPXREGS +/* Define if the prfpregset_t type is broken. */ +#undef PRFPREGSET_T_BROKEN + +/* Define if you have the header file. */ +#undef HAVE_LINUX_ELF_H + +/* Define if you have the header file. */ +#undef HAVE_PROC_SERVICE_H + /* Define if you have the header file. */ #undef HAVE_SGTTY_H /* Define if you have the header file. */ #undef HAVE_STRING_H +/* Define if you have the header file. */ +#undef HAVE_SYS_PROCFS_H + /* Define if you have the header file. */ #undef HAVE_SYS_REG_H @@ -27,3 +39,22 @@ /* Define if you have the header file. */ #undef HAVE_TERMIOS_H + +/* Define if you have the header file. */ +#undef HAVE_THREAD_DB_H + +/* Define if has lwpid_t. */ +#undef HAVE_LWPID_T + +/* Define if has psaddr_t. */ +#undef HAVE_PSADDR_T + +/* Define if has prgregset_t. */ +#undef HAVE_PRGREGSET_T + +/* Define if has prfpregset_t. */ +#undef HAVE_PRFPREGSET_T + +/* Define if has elf_fpregset_t. */ +#undef HAVE_ELF_FPREGSET_T + diff --git a/gdb/gdbserver/configure b/gdb/gdbserver/configure index 758d48383f1..d2575c0cc92 100755 --- a/gdb/gdbserver/configure +++ b/gdb/gdbserver/configure @@ -28,6 +28,7 @@ program_suffix=NONE program_transform_name=s,x,x, silent= site= +sitefile= srcdir= target=NONE verbose= @@ -142,6 +143,7 @@ Configuration: --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 @@ -312,6 +314,11 @@ EOF -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=*) @@ -477,12 +484,16 @@ fi 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 @@ -526,7 +537,7 @@ fi # 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 @@ -556,7 +567,7 @@ if test -z "$CC"; then # 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 @@ -607,7 +618,7 @@ fi # 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 @@ -639,7 +650,7 @@ fi 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. @@ -650,12 +661,12 @@ cross_compiling=$ac_cv_prog_cc_cross 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 @@ -681,12 +692,12 @@ if test $ac_cv_prog_cc_works = no; 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 @@ -695,7 +706,7 @@ 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 @@ -714,7 +725,7 @@ ac_test_CFLAGS="${CFLAGS+set}" 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 @@ -793,7 +804,7 @@ else { echo "configure: error: can not run $ac_config_sub" 1>&2; exit 1; } 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 @@ -814,7 +825,7 @@ host_os=`echo $host | sed 's/^\([^-]*\)-\([^-]*\)-\(.*\)$/\3/'` 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 @@ -832,7 +843,7 @@ target_os=`echo $target | sed 's/^\([^-]*\)-\([^-]*\)-\(.*\)$/\3/'` 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 @@ -867,7 +878,7 @@ test "$host_alias" != "$target_alias" && # 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 @@ -921,7 +932,7 @@ test -z "$INSTALL_DATA" && INSTALL_DATA='${INSTALL} -m 644' 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= @@ -936,13 +947,13 @@ else # On the NeXT, cc -E runs the code through the compiler's parser, # not just through cpp. cat > conftest.$ac_ext < 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 : @@ -953,13 +964,13 @@ else rm -rf conftest* CPP="${CC-cc} -E -traditional-cpp" cat > conftest.$ac_ext < 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 : @@ -970,13 +981,13 @@ else rm -rf conftest* CPP="${CC-cc} -nologo -E" cat > conftest.$ac_ext < 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 : @@ -1001,12 +1012,12 @@ fi 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 < #include @@ -1014,7 +1025,7 @@ else #include 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* @@ -1031,7 +1042,7 @@ rm -f 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 @@ -1049,7 +1060,7 @@ fi 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 @@ -1070,7 +1081,7 @@ if test "$cross_compiling" = yes; then : else cat > conftest.$ac_ext < #define ISLOWER(c) ('a' <= (c) && (c) <= 'z') @@ -1081,7 +1092,7 @@ if (XOR (islower (i), ISLOWER (i)) || toupper (i) != TOUPPER (i)) exit(2); 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 @@ -1105,21 +1116,21 @@ EOF 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 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* @@ -1157,19 +1168,19 @@ fi 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 < 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 @@ -1190,19 +1201,19 @@ EOF 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 < 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 @@ -1223,7 +1234,328 @@ EOF 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 < +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 < +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 < +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 < +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 < + 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 < +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 <&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 <&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" + + @@ -1381,6 +1713,8 @@ s%@INSTALL_SCRIPT@%$INSTALL_SCRIPT%g 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 diff --git a/gdb/gdbserver/configure.in b/gdb/gdbserver/configure.in index db7e301934b..7c94d408adf 100644 --- a/gdb/gdbserver/configure.in +++ b/gdb/gdbserver/configure.in @@ -30,7 +30,8 @@ AC_PROG_INSTALL 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 @@ -62,9 +63,67 @@ if test "${srv_linux_regsets}" = "yes"; then 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 + 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 diff --git a/gdb/gdbserver/configure.srv b/gdb/gdbserver/configure.srv index 3268cd77207..28dc2aad1f6 100644 --- a/gdb/gdbserver/configure.srv +++ b/gdb/gdbserver/configure.srv @@ -21,11 +21,13 @@ case "${target}" 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" @@ -38,10 +40,12 @@ case "${target}" in 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" @@ -54,6 +58,7 @@ case "${target}" in 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" diff --git a/gdb/gdbserver/i387-fp.c b/gdb/gdbserver/i387-fp.c index 3d1d6a6fd5c..19a9929debc 100644 --- a/gdb/gdbserver/i387-fp.c +++ b/gdb/gdbserver/i387-fp.c @@ -20,6 +20,7 @@ Boston, MA 02111-1307, USA. */ #include "server.h" +#include "i387-fp.h" int num_xmm_registers = 8; @@ -108,7 +109,7 @@ i387_cache_to_fsave (void *buf) } void -i387_fsave_to_cache (void *buf) +i387_fsave_to_cache (const void *buf) { struct i387_fsave *fp = (struct i387_fsave *) buf; int i; @@ -240,7 +241,7 @@ i387_ftag (struct i387_fxsave *fp, int regno) } 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; @@ -287,4 +288,3 @@ i387_fxsave_to_cache (void *buf) val = (fp->fop) & 0x7FF; supply_register_by_name ("fop", &val); } - diff --git a/gdb/gdbserver/i387-fp.h b/gdb/gdbserver/i387-fp.h index 90fe4ca6eb7..d28c4228830 100644 --- a/gdb/gdbserver/i387-fp.h +++ b/gdb/gdbserver/i387-fp.h @@ -23,10 +23,10 @@ #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; diff --git a/gdb/gdbserver/inferiors.c b/gdb/gdbserver/inferiors.c index 774798deae3..68c91c4efa9 100644 --- a/gdb/gdbserver/inferiors.c +++ b/gdb/gdbserver/inferiors.c @@ -25,81 +25,175 @@ #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; } diff --git a/gdb/gdbserver/linux-arm-low.c b/gdb/gdbserver/linux-arm-low.c index 2958fdf3129..07e2792837c 100644 --- a/gdb/gdbserver/linux-arm-low.c +++ b/gdb/gdbserver/linux-arm-low.c @@ -45,9 +45,49 @@ arm_cannot_fetch_register (int regno) 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, }; diff --git a/gdb/gdbserver/linux-i386-low.c b/gdb/gdbserver/linux-i386-low.c index 71264321341..b79b601ae8f 100644 --- a/gdb/gdbserver/linux-i386-low.c +++ b/gdb/gdbserver/linux-i386-low.c @@ -72,7 +72,7 @@ i386_fill_gregset (void *buf) } static void -i386_store_gregset (void *buf) +i386_store_gregset (const void *buf) { int i; @@ -89,7 +89,7 @@ i386_fill_fpregset (void *buf) } static void -i386_store_fpregset (void *buf) +i386_store_fpregset (const void *buf) { i387_fsave_to_cache (buf); } @@ -101,7 +101,7 @@ i386_fill_fpxregset (void *buf) } static void -i386_store_fpxregset (void *buf) +i386_store_fpxregset (const void *buf) { i387_fxsave_to_cache (buf); } @@ -109,14 +109,17 @@ i386_store_fpxregset (void *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 */ @@ -124,25 +127,38 @@ struct regset_info target_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 = { @@ -150,8 +166,11 @@ 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, }; diff --git a/gdb/gdbserver/linux-low.c b/gdb/gdbserver/linux-low.c index 6cfe0d5aea5..c272fed2142 100644 --- a/gdb/gdbserver/linux-low.c +++ b/gdb/gdbserver/linux-low.c @@ -35,9 +35,32 @@ #include #include -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 @@ -48,12 +71,64 @@ static int use_regsets_p = 1; 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. */ @@ -61,7 +136,7 @@ struct inferior_linux_data static int linux_create_inferior (char *program, char **allargs) { - struct inferior_linux_data *tdata; + void *new_process; int pid; pid = fork (); @@ -72,6 +147,8 @@ linux_create_inferior (char *program, char **allargs) { ptrace (PTRACE_TRACEME, 0, 0, 0); + signal (SIGRTMIN + 1, SIG_DFL); + execv (program, allargs); fprintf (stderr, "Cannot exec %s: %s.\n", program, @@ -80,22 +157,18 @@ linux_create_inferior (char *program, char **allargs) _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) { @@ -103,143 +176,712 @@ linux_attach (int pid) 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) @@ -262,6 +904,7 @@ fetch_register (int regno) { CORE_ADDR regaddr; register int i; + char *buf; if (regno >= the_low_target.num_regs) return; @@ -271,10 +914,11 @@ fetch_register (int regno) 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) @@ -288,6 +932,8 @@ fetch_register (int regno) goto error_exit; } } + supply_register (regno, buf); + error_exit:; } @@ -310,6 +956,7 @@ usr_store_inferior_registers (int regno) { CORE_ADDR regaddr; int i; + char *buf; if (regno >= 0) { @@ -323,11 +970,13 @@ usr_store_inferior_registers (int regno) 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) @@ -345,7 +994,7 @@ usr_store_inferior_registers (int regno) } else for (regno = 0; regno < the_low_target.num_regs; regno++) - store_inferior_registers (regno); + usr_store_inferior_registers (regno); } #endif /* HAVE_LINUX_USRREGS */ @@ -354,7 +1003,7 @@ usr_store_inferior_registers (int regno) #ifdef HAVE_LINUX_REGSETS static int -regsets_fetch_inferior_registers (void) +regsets_fetch_inferior_registers () { struct regset_info *regset; @@ -392,7 +1041,10 @@ regsets_fetch_inferior_registers (void) } 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); @@ -402,7 +1054,7 @@ regsets_fetch_inferior_registers (void) } static int -regsets_store_inferior_registers (void) +regsets_store_inferior_registers () { struct regset_info *regset; @@ -528,6 +1180,11 @@ linux_write_memory (CORE_ADDR memaddr, const char *myaddr, int len) 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, @@ -562,7 +1219,40 @@ linux_write_memory (CORE_ADDR memaddr, const char *myaddr, int len) 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); } @@ -578,13 +1268,24 @@ static struct target_ops linux_target_ops = { 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 (); } diff --git a/gdb/gdbserver/linux-low.h b/gdb/gdbserver/linux-low.h index b484982e15e..bae76b77bdc 100644 --- a/gdb/gdbserver/linux-low.h +++ b/gdb/gdbserver/linux-low.h @@ -19,12 +19,21 @@ 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 @@ -39,11 +48,67 @@ struct linux_target_ops 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); diff --git a/gdb/gdbserver/linux-mips-low.c b/gdb/gdbserver/linux-mips-low.c index f721ec94eeb..51e74cef140 100644 --- a/gdb/gdbserver/linux-mips-low.c +++ b/gdb/gdbserver/linux-mips-low.c @@ -96,9 +96,60 @@ mips_cannot_store_register (int regno) 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, }; diff --git a/gdb/gdbserver/linux-ppc-low.c b/gdb/gdbserver/linux-ppc-low.c index 7cb315a304a..2bb0f50034e 100644 --- a/gdb/gdbserver/linux-ppc-low.c +++ b/gdb/gdbserver/linux-ppc-low.c @@ -64,9 +64,53 @@ ppc_cannot_fetch_register (int regno) 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, }; diff --git a/gdb/gdbserver/linux-sh-low.c b/gdb/gdbserver/linux-sh-low.c index cdc390d2332..ee84561f17c 100644 --- a/gdb/gdbserver/linux-sh-low.c +++ b/gdb/gdbserver/linux-sh-low.c @@ -57,9 +57,49 @@ sh_cannot_fetch_register (int regno) 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, }; diff --git a/gdb/gdbserver/linux-x86-64-low.c b/gdb/gdbserver/linux-x86-64-low.c index e1248904aba..1f80d990cc9 100644 --- a/gdb/gdbserver/linux-x86-64-low.c +++ b/gdb/gdbserver/linux-x86-64-low.c @@ -71,10 +71,12 @@ x86_64_store_fpregset (void *buf) 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 = { diff --git a/gdb/gdbserver/proc-service.c b/gdb/gdbserver/proc-service.c new file mode 100644 index 00000000000..becf565529b --- /dev/null +++ b/gdb/gdbserver/proc-service.c @@ -0,0 +1,256 @@ +/* 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 + via . */ +#ifdef HAVE_LINUX_ELF_H +#include +#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; +} + + diff --git a/gdb/gdbserver/regcache.c b/gdb/gdbserver/regcache.c index 701d09232f8..67139098802 100644 --- a/gdb/gdbserver/regcache.c +++ b/gdb/gdbserver/regcache.c @@ -27,6 +27,7 @@ struct inferior_regcache_data { + int registers_valid; char *registers; }; @@ -38,7 +39,7 @@ static int num_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; @@ -47,17 +48,50 @@ get_regcache (struct inferior_info *inf) 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; @@ -67,15 +101,19 @@ create_register_cache (struct inferior_info *inferior) 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 @@ -99,7 +137,7 @@ set_register_cache (struct reg *regs, int n) 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); } @@ -108,7 +146,7 @@ void 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) { @@ -155,10 +193,10 @@ register_size (int n) 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); } @@ -166,7 +204,7 @@ register_data (int n) 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 @@ -178,7 +216,13 @@ supply_register_by_name (const char *name, const void *buf) 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 diff --git a/gdb/gdbserver/regcache.h b/gdb/gdbserver/regcache.h index 362288ee049..930bd9cbfe6 100644 --- a/gdb/gdbserver/regcache.h +++ b/gdb/gdbserver/regcache.h @@ -21,15 +21,20 @@ #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. */ @@ -48,8 +53,6 @@ int registers_length (void); struct reg *find_register_by_number (int n); -char *register_data (int n); - int register_size (int n); int find_regno (const char *name); @@ -62,6 +65,8 @@ void supply_register_by_name (const char *name, const void *buf); 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 */ diff --git a/gdb/gdbserver/remote-utils.c b/gdb/gdbserver/remote-utils.c index 14734f161bd..c610c4c0e7c 100644 --- a/gdb/gdbserver/remote-utils.c +++ b/gdb/gdbserver/remote-utils.c @@ -42,6 +42,10 @@ struct ui_file *gdb_stdlog; 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. */ @@ -296,10 +300,17 @@ putpkt (char *buf) } 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) @@ -310,6 +321,10 @@ putpkt (char *buf) 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] != '+'); @@ -346,7 +361,7 @@ input_interrupt (int unused) return; } - kill (signal_pid, SIGINT); + kill ((*the_target->signal_pid) (), SIGINT); } } @@ -411,7 +426,11 @@ getpkt (char *buf) 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; } @@ -441,12 +460,19 @@ getpkt (char *buf) } 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; } @@ -499,8 +525,6 @@ convert_ascii_to_int (char *from, char *to, int n) 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) @@ -508,13 +532,46 @@ outreg (int regno, char *buf) *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) { @@ -538,12 +595,23 @@ 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; @@ -620,7 +688,11 @@ look_up_one_symbol (const char *name, CORE_ADDR *addrp) { /* 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; } diff --git a/gdb/gdbserver/server.c b/gdb/gdbserver/server.c index a31547c9b39..b674ed0070f 100644 --- a/gdb/gdbserver/server.c +++ b/gdb/gdbserver/server.c @@ -23,9 +23,12 @@ 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 @@ -33,11 +36,12 @@ start_inferior (char *argv[], char *statusptr) { /* 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 @@ -48,7 +52,7 @@ attach_inferior (int pid, char *statusptr, unsigned char *sigptr) if (myattach (pid) != 0) return -1; - *sigptr = mywait (statusptr); + *sigptr = mywait (statusptr, 0); return 0; } @@ -59,6 +63,8 @@ extern int remote_debug; 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) @@ -68,6 +74,29 @@ handle_query (char *own_buf) 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; @@ -188,12 +217,16 @@ main (int argc, char *argv[]) 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. */ @@ -202,11 +235,12 @@ main (int argc, char *argv[]) } 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': @@ -227,8 +261,9 @@ main (int argc, char *argv[]) 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': @@ -237,18 +272,21 @@ main (int argc, char *argv[]) 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': diff --git a/gdb/gdbserver/server.h b/gdb/gdbserver/server.h index 32b90b5bcc7..746502b3d63 100644 --- a/gdb/gdbserver/server.h +++ b/gdb/gdbserver/server.h @@ -54,8 +54,21 @@ 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" @@ -67,27 +80,41 @@ struct inferior_info; 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; @@ -103,6 +130,8 @@ void enable_async_io (void); 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, diff --git a/gdb/gdbserver/target.c b/gdb/gdbserver/target.c index 53a4c1ef55a..1c2860a11e9 100644 --- a/gdb/gdbserver/target.c +++ b/gdb/gdbserver/target.c @@ -25,6 +25,38 @@ 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) { @@ -33,10 +65,41 @@ 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 diff --git a/gdb/gdbserver/target.h b/gdb/gdbserver/target.h index 6d06b9fd4ab..c6aeee6f562 100644 --- a/gdb/gdbserver/target.h +++ b/gdb/gdbserver/target.h @@ -104,6 +104,11 @@ struct target_ops 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; @@ -125,17 +130,18 @@ void set_target_ops (struct target_ops *); #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 */ diff --git a/gdb/gdbserver/thread-db.c b/gdb/gdbserver/thread-db.c new file mode 100644 index 00000000000..f3d57a54d62 --- /dev/null +++ b/gdb/gdbserver/thread-db.c @@ -0,0 +1,342 @@ +/* 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 +#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 + via . */ +#ifdef HAVE_LINUX_ELF_H +#include +#endif +#endif + +#include "../gdb_proc_service.h" + +/* Structure that identifies the child process for the + 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; +}