From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: (qmail 8062 invoked by alias); 28 Oct 2002 22:01:13 -0000 Mailing-List: contact gdb-patches-help@sources.redhat.com; run by ezmlm Precedence: bulk List-Subscribe: List-Archive: List-Post: List-Help: , Sender: gdb-patches-owner@sources.redhat.com Received: (qmail 8006 invoked from network); 28 Oct 2002 22:01:08 -0000 Received: from unknown (HELO e34.co.us.ibm.com) (32.97.110.132) by sources.redhat.com with SMTP; 28 Oct 2002 22:01:08 -0000 Received: from westrelay01.boulder.ibm.com (westrelay01.boulder.ibm.com [9.17.194.22]) by e34.co.us.ibm.com (8.12.2/8.12.2) with ESMTP id g9SM10gU043156 for ; Mon, 28 Oct 2002 17:01:03 -0500 Received: from dmfet (dmfet.austin.ibm.com [9.53.216.34]) by westrelay01.boulder.ibm.com (8.12.3/NCO/VER6.4) with ESMTP id g9SM0x3A028542 for ; Mon, 28 Oct 2002 15:00:59 -0700 Received: from smoser (helo=localhost) by dmfet with local-esmtp (Exim 3.35 #1 (Debian)) id 186HwR-0001Os-00 for ; Mon, 28 Oct 2002 16:00:59 -0600 Date: Mon, 28 Oct 2002 14:01:00 -0000 From: Scott Moser X-X-Sender: smoser@dmfet To: gdb-patches@sources.redhat.com Subject: [PATCH] plugin patch Message-ID: MIME-Version: 1.0 Content-Type: MULTIPART/MIXED; BOUNDARY="889791192-1721647947-1035228590=:19693" Content-ID: X-SW-Source: 2002-10/txt/msg00577.txt.bz2 This message is in MIME format. The first part should be readable text, while the remaining parts are likely unreadable without MIME-aware tools. Send mail to mime@docserver.cac.washington.edu for more info. --889791192-1721647947-1035228590=:19693 Content-Type: TEXT/PLAIN; CHARSET=US-ASCII Content-ID: Content-length: 19691 Below is a patch to add plugin support to GDB. It exports a fairly simple programmable interface for people to extend the functionality of GDB via runtime loaded shared libraries in ways that may not fit with the direction of the main GDB tree (not cross-platform, not stable, niche audience...). Also attached are two example plugins. They've been tested on linux and AIX, and some time ago on freebsd. Changelog entry: 2002-10-28 Scott Moser * Makefile.in (plugin.o): Add build line for plugin.c * configure.in: Add --enable-plugin option * config/powerpc/aix.mh: add -Wl,-bexpall on aix build * plugin.c: new file, plugin implementation * plugin.h: new file, header file for plugin.c * gdbplugin.h: new file for plugin use gdb plugin architecture overview - gdb user commands - plugin load takes a filename and loads it as a gdb plugin. searches the plugin-path if needed - plugin description takes a filename or number (of a loaded plugin) and calls its plugin_description function. if not implemented returns "description N/A" - plugin name takes a filename or number (of a loaded plugin) and calls its plugin_name function. if not implemented returns "name N/A" - plugin commands takes a filename or number (of a loaded plugin) and calls its plugin_commands function. if not implmemented returns "commands N/A" - plugin list takes no arguments. lists the currently loaded plugins. - plugin search [ verbose ] trys to open each file in the current plugin-path as a gdb plugin. lists filename and plugin-name for each plugin found. if 'verbose' is passed in, also call's plugins' 'description' - set plugin-path takes a string and sets it to the ":" delimited search path - functions implemented by gdb plugins - plugin_init (required) prototype: int plugin_init(const char * version, char * args, int tty) - version is character string describing GDB's version - args are user specified arguments after the filename in 'plugin load' - tty is gdb's tty - returns true or false on success/failure of load - It is expected that in this function, the plugin will perform actions like: - on systems where '-rdynamic'-type symbol resolution isn't available, use dlsym() to access the functions it requires within gdb (or, potentially, other plugins). For example, the functions "add_cmd", "add_set_cmd", etc. - call "add_cmd" to add its plugin-specific commands to the gdb command set - call "add_set_cmd" to enable plugin-specific variables called at load of a plugin - plugin_name prototype: const char * plugin_name(void) - takes no arguments - returns a character string (short) "name" for the plugin. called by 'plugin "name|list|search"' - plugin_commands prototype: const char * plugin_commands(void) - takes no arguments - returns a string describing the list of commands registered (or that will be registered) by the plugin. called by 'plugin commands' - plugin_description prototype: const char * plugin_description(void) - takes no arguments - returns a character string "description" of the command. called by 'plugin "name|list|search verbose"' - implementation notes - expect users to call 'plugin_commands', 'plugin_description', and 'plugin_name' before plugin_init is called. For example, by 'plugin search' - plugin_init will only be called on load. - if you check the version passed in to decide if you'll run or not, consider offering a 'force' option to load even if you don't think you should - issues: - colon ":" is the search path separator. this may cause problems on windows with its C:\ like filenames. At this point, the plugin design will not work on windows anyway, as windows requires explicitly exporting functions for them to be accessible outside of main (no -rdynamic) - uses printf_filtered - the version string format changes between releases and cvs Scott Moser Software Engineer; Linux Technology Center IBM Corp., Austin, Tx (512) 838-1533 T/L: 678-1533 ssmoser@us.ibm.com , internal zip: 9812 diff -uprN ../src-20021028.sparce/gdb/Makefile.in gdb/Makefile.in --- ../src-20021028.sparce/gdb/Makefile.in 2002-10-28 13:53:34.000000000 -0600 +++ gdb/Makefile.in 2002-10-28 14:46:43.000000000 -0600 @@ -1968,6 +1968,9 @@ parse.o: parse.c $(defs_h) $(gdb_string_ $(frame_h) $(expression_h) $(value_h) $(command_h) $(language_h) \ $(parser_defs_h) $(gdbcmd_h) $(symfile_h) $(inferior_h) \ $(doublest_h) $(builtin_regs_h) $(gdb_assert_h) +plugin.o: plugin.c $(defs_h) $(frame_h) $(inferior_h) $(target_h) $(gdbcmd_h) \ + $(language_h) $(symfile_h) $(objfiles_h) $(completer_h) $(value_h) \ + $(gdb_string_h) $(gdbcore_h) $(gdb_stat_h) $(xcoffsolib_h) ppc-bdm.o: ppc-bdm.c $(defs_h) $(gdbcore_h) $(gdb_string_h) $(frame_h) \ $(inferior_h) $(bfd_h) $(symfile_h) $(target_h) $(gdbcmd_h) \ $(objfiles_h) $(gdb_stabs_h) $(serial_h) $(ocd_h) $(ppc_tdep_h) \ diff -uprN ../src-20021028.sparce/gdb/config/powerpc/aix.mh gdb/config/powerpc/aix.mh --- ../src-20021028.sparce/gdb/config/powerpc/aix.mh 2002-10-28 13:53:34.000000000 -0600 +++ gdb/config/powerpc/aix.mh 2002-10-28 13:26:31.000000000 -0600 @@ -6,6 +6,7 @@ NAT_FILE= nm-aix.h NATDEPFILES= fork-child.o infptrace.o inftarg.o corelow.o rs6000-nat.o \ xcoffread.o xcoffsolib.o +LOADLIBES= -Wl,-bexpall # When compiled with cc, for debugging, this argument should be passed. # We have no idea who our current compiler is though, so we skip it. # MH_CFLAGS = -bnodelcsect diff -uprN ../src-20021028.sparce/gdb/configure.in gdb/configure.in --- ../src-20021028.sparce/gdb/configure.in 2002-10-28 13:53:34.000000000 -0600 +++ gdb/configure.in 2002-10-28 14:44:44.000000000 -0600 @@ -665,6 +665,30 @@ case ${enable_gdbmi} in ;; esac +dnl Enable gdb plugin interface +AC_ARG_ENABLE(plugin, +[ --enable-plugin Enable GDB plugin interface], +[ + case "${enable_plugin}" in + yes ) if test x$gdb_cv_os_cygwin = xyes; then + AC_MSG_ERROR(plugin will not work on win32; disabled) + fi ;; + no) ;; + "") enable_plugin=yes ;; + *) + AC_MSG_ERROR(Bad value for --enable-plugin: ${enableval}) + ;; + esac +], +[enable_plugin=yes]) +case ${enable_plugin} in + "yes" ) + CONFIG_OBS="${CONFIG_OBS} plugin.o" + CONFIG_SRCS="${CONFIG_SRCS} plugin.c" + CONFIG_INITS="${CONFIG_INITS} plugin.c" + ;; +esac + # Configure UI_OUT by default (before 5.2 it can be disabled) # It must be configured if gdbmi is configured diff -uprN ../src-20021028.sparce/gdb/gdbplugin.h gdb/gdbplugin.h --- ../src-20021028.sparce/gdb/gdbplugin.h 1969-12-31 18:00:00.000000000 -0600 +++ gdb/gdbplugin.h 2002-10-28 13:26:31.000000000 -0600 @@ -0,0 +1,11 @@ +/* this file exists as a convienence for gdb plugin programming + * it will hopefully include all the gdb header files that a plugin + * developer would want to use + * */ +#include "defs.h" +#include "gdbcmd.h" +#include "gdbcore.h" +#include "completer.h" +#include "value.h" +#include "valprint.h" +#include "language.h" diff -uprN ../src-20021028.sparce/gdb/plugin.c gdb/plugin.c --- ../src-20021028.sparce/gdb/plugin.c 1969-12-31 18:00:00.000000000 -0600 +++ gdb/plugin.c 2002-10-28 13:26:31.000000000 -0600 @@ -0,0 +1,468 @@ +#include "plugin.h" +#include "defs.h" +#include "gdbcmd.h" +#include "gdbcore.h" +#include "completer.h" +#include "dlfcn.h" +#include "dirent.h" +#include "version.h" +#include +#include + +static struct plugin* newPlugin(void *, const char *); +static struct plugin* findPlugin(const char *); +static struct plugin* findPluginByNum(int); +static const char * searchPluginPath(const char * ); +static void * openPlugin(const char * ,int, char *, int ); +static void * getPluginHandle(const char *); +static const char * callPluginInfoFunction(void *,char *,const char *fallback); + +static char *plugin_path; + +static struct plugin * pluginList; + +void +plugin_list(int tty) +{ + /* list the plugins currently loaded */ + const struct plugin * curp=pluginList; + if(curp==NULL) + { + printf_filtered("no plugins loaded\n"); + return; + } + while(curp!=NULL) + { + printf_filtered("%i %s %s\n", curp->num, curp->filename, plugin_name(curp->handle)); + curp=curp->next; + } + return; +} + +const char * +plugin_commands(void * handle) +{ + return callPluginInfoFunction(handle,"plugin_commands","commands N/A"); +} + +const char * +plugin_name(void * handle) +{ + return callPluginInfoFunction(handle,"plugin_name","name N/A"); +} + +const char * +plugin_description(void * handle) +{ + return callPluginInfoFunction(handle,"plugin_description","description N/A"); +} + +const char * +callPluginInfoFunction(void * handle,char * funcname,const char *fallback) +{ + const char * (*func)(void); + const char * ret=NULL; + if(handle && funcname) + { + func=dlsym(handle,funcname); + if(func) + { + ret=func(); + } + } + return (ret) ? ret : fallback; +} + +void +plugin_search(const char * str, int tty, int verbose) +{ + /* searches the current path for files with plugin_init() defined. */ + if(plugin_path!=NULL) + { + const char *tok; + char *spath=(char*)malloc((strlen(plugin_path)+1)*sizeof(char)); + if(spath==NULL) + return; + strcpy(spath,plugin_path); + tok=strtok(spath,":"); + while(tok!=NULL) + { + struct dirent *direntry; + DIR * dir=opendir(tok); + if (dir!=NULL) + { + while((direntry=readdir(dir))!=NULL) + { +#if (defined(DT_REG) && defined(DT_LNK) && defined(DT_UNKNOWN)) + if (direntry->d_type==DT_REG || direntry->d_type==DT_LNK || direntry->d_type==DT_UNKNOWN) + { +#endif + void *handle=NULL; + char *filename=(char*)malloc((strlen(tok)+strlen(direntry->d_name)+2)*sizeof(char)); + if(filename==NULL) + { + free(spath); + return; + } + sprintf(filename,"%s/%s",tok,direntry->d_name); + handle=openPlugin(filename,0,(char *)NULL, tty); + if(handle!=NULL) + { + printf_filtered("%s : %s\n",filename,plugin_name(handle)); + if(verbose) + { + printf_filtered("\t%s\n",plugin_description(handle)); + } + dlclose(handle); + } + free(filename); +#if (defined(DT_REG) && defined(DT_LNK) && defined(DT_UNKNOWN)) + } +#endif + } + } + tok=strtok(NULL,":"); + } + } + else + { + printf_filtered("plugin-path not set\n"); + } + return; +} + +void +plugin_load(const char * filename, char * args, int tty) +{ + /* load a plugin named filename, (searching plugin-path if set ) + * with args given + * */ + void *handle=NULL; + + handle=openPlugin(filename,1,args, tty); + if(handle==NULL) + { + const char *tempfile; + tempfile=searchPluginPath(filename); + if(tempfile==NULL) + { + printf_filtered("plugin load: couldn't find %s\n",filename); + return; + } + handle=openPlugin(tempfile,1,args,tty); + if(handle==NULL) + { + printf_filtered("plugin load: couldn't load %s\n",tempfile); + return; + } + } + + newPlugin(handle,filename); + return; +} + +void * +getPluginHandle(const char * filename) +{ + struct plugin * cur; + void * handle; + cur=findPlugin(filename); + if(cur!=NULL) + { + handle=cur->handle; + } + else + { + const char * fullpath; + fullpath=searchPluginPath(filename); + handle=openPlugin(fullpath,0,(char *)NULL, 0); + } + return(handle); +} + +struct plugin * +newPlugin(void * handle, const char * filename) +{ + /* add this plugin at the beginning of the list */ + struct plugin* newP; + newP=(struct plugin*)malloc(sizeof(struct plugin)+strlen(filename)*sizeof(char)+1); + if(newP==NULL) + { + printf_filtered("Failed to allocate space for plugin %s\n",filename); + return NULL; + } + newP->handle=handle; + newP->next=pluginList; + newP->filename=(char*)(newP+1); + strcpy(newP->filename,filename); + if(newP->next!=NULL) + { + newP->num=(int)((newP->next)->num)+1; + } + else + { + newP->num=0; + } + pluginList=newP; + return newP; +} + +struct plugin * +findPluginByNum(int num) +{ + struct plugin * cur; + + for ( cur = pluginList; cur != NULL; cur = cur->next ) + { + if (cur->num == num) break; + } + return cur; +} + +struct plugin * +findPlugin(const char * filename) +{ + /* find a plugin by filename from the plugin_list */ + struct plugin * cur; + + for ( cur = pluginList; cur != NULL; cur = cur->next ) + { + if (strcmp(filename,cur->filename) == 0) break; + } + return cur; +} + +void * +openPlugin(const char * filename,int init, char * args, int tty) +{ + /* opens a named filename. if init is true, will call its init function + * and pass in args and tty + * */ + void * handle; + int (*initfunc)(const char *, char *, int); + + if(filename==NULL) + return(NULL); + + handle=dlopen(filename,RTLD_LAZY); + if(handle==NULL) + return(NULL); + + if(!init) + return(handle); + + initfunc=dlsym(handle,"plugin_init"); + + if(initfunc==NULL) + { + printf_filtered("couldn't find plugin_init in plugin %s\n",filename); + dlclose(handle); + return(NULL); + } + + if(initfunc(version,args,tty)) + { + printf_filtered("successfully loaded plugin %s\n",filename); + return(handle); + } + else + { + printf_filtered("plugin_init failed %s\n",filename); + /* + * don't do a dlclose here to avoid possible segfault if + * failed load didn't clean itself up correctly + */ + } + return(NULL); +} + + +const char * +searchPluginPath(const char * filename) +{ + /* returns a full path to a file if it exists in search path */ + if(filename!=NULL) + { + int found; + struct stat buf; + char *tok; + char *spath; + if(!stat(filename,&buf)) + { + return(filename); + } + if(plugin_path!=NULL) + { + spath=(char*)malloc((strlen(plugin_path)+1)*sizeof(char)); + if(spath==NULL) + { + return(NULL); + } + strcpy(spath,plugin_path); + tok=strtok(spath,":"); + while(tok!=NULL) + { + char *fp=(char*)malloc(((strlen(filename)+strlen(tok)+2))*sizeof(char)); + if(fp==NULL) + { + free(spath); + return(NULL); + } + sprintf(fp,"%s/%s",tok,filename); + if(!stat(fp,&buf)) + { + free(spath); + return(fp); + } + free(fp); + tok=strtok(NULL, ":"); + } + free(spath); + } + } + return(NULL); +} + +void +plugin_command (char *arg, int from_tty) +{ + /* top level 'plugin' command handler */ + char * cmdtype; + char * filename; + char * cmdargs = NULL; + int start; + int arglen; + int plugNum; + char *endptr; + struct plugin *p; + void * dlhandle; + const char * retstr; + + if(arg==NULL) + { + printf_filtered("plugin: need second level command (see 'help plugin')\n"); + return; + } + + if (!strcmp(arg,"search verbose")) + { + plugin_search("",from_tty,1); + return; + } + else if (!strcmp(arg,"search")) + { + plugin_search("",from_tty,0); + return; + } + else if (!strcmp(arg,"list")) + { + plugin_list(from_tty); + return; + } + + // all remaining plugin commands require args + cmdtype=arg; + if ((endptr = strchr(arg,' ')) == NULL) + { + printf_filtered("plugin: Error no argument to plugin command \"%s\"\n",cmdtype); + return ; + } + *endptr = '\0'; + filename = endptr + 1; + while(filename[0]!='\0' && filename[0]==' ') filename++; + + if(filename[0]=='\0') + return ; + + if(filename[0]=='"' && ((endptr=strchr(filename+1,'"'))!=NULL)) + { + filename++; + *endptr='\0'; + cmdargs=endptr+1; + if(endptr[1]=='\0') cmdargs=NULL; + } + else + { + if((endptr=strchr(filename,' '))!=NULL) + { + *endptr = '\0'; + cmdargs=endptr+1; + } + } + + if(!strcmp(cmdtype,"load")) + { + plugin_load(filename,cmdargs,from_tty); + return; + } + + plugNum=(int)strtol(filename,&endptr,10); + + if(endptr!=NULL && (*endptr)=='\0') + { + p=findPluginByNum(plugNum); + if(p==NULL) + { + printf_filtered("no plugin number %i loaded (\"plugin list\" for list)\n",plugNum); + return; + } + dlhandle=p->handle; + } + else + { + dlhandle=getPluginHandle(filename); + } + + if (!strncmp(cmdtype,"commands",4)) + { + retstr=plugin_commands(dlhandle); + } + else if (!strcmp(cmdtype,"name")) + { + retstr=plugin_name(dlhandle); + } + else if (!strncmp(cmdtype,"desc",4)) + { + retstr=plugin_description(dlhandle); + } + else + { + printf_filtered("%s: not a plugin command\n",cmdtype); + return; + } + + if(retstr!=NULL) + { + printf_filtered("%s\n",retstr); + } + else + { + printf_filtered("command \"%s\" not implemented in plugin %s\n",cmdtype,filename); + } +} + +void +_initialize_plugin(void) +{ + struct cmd_list_element *c; + + c = add_cmd ("plugin", class_files, plugin_command, + "manage GDB plugins\n\ +a GDB plugin can add functionality to GDB and make use of GDB functionality\n\ +without modifiying the core to GDB.\n\ +load [args] - loads and initializes a plugin, [ with args ]\n\ +desc[ription] - print description for file/num\n\ +name - print name for file/num\n\ +commands - list commands given by file/num\n\ +search [verbose] - search current plugin-path for gdb plugins [ show desc ]\n\ +list - list currently loaded plugins\n\ +" ,&cmdlist); + set_cmd_completer (c, filename_completer); + + add_show_from_set(add_set_cmd("plugin-path",class_support, + var_string_noescape, (char*) &plugin_path, + "Set plugin search path\n", &setlist), &showlist); + + pluginList=NULL; +} + diff -uprN ../src-20021028.sparce/gdb/plugin.h gdb/plugin.h --- ../src-20021028.sparce/gdb/plugin.h 1969-12-31 18:00:00.000000000 -0600 +++ gdb/plugin.h 2002-10-28 13:26:31.000000000 -0600 @@ -0,0 +1,21 @@ +#if !defined (PLUGIN_H) +#define PLUGIN_H 1 + +extern void plugin_load(const char *, char *, int); +extern void plugin_command (char *, int); +extern void plugin_list(int); +extern void plugin_search(const char * , int ,int ); +extern const char * plugin_name(void *); +extern const char * plugin_description(void*); +extern const char * plugin_commands(void*); + +extern char *plugin_path; + +struct plugin { + void * handle; + int num; + struct plugin * next; + char * filename; +}; + +#endif /* !defined (PLUGIN_H) */ --889791192-1721647947-1035228590=:19693 Content-Type: APPLICATION/OCTET-STREAM; NAME="exPlugin.tar.gz" Content-Transfer-Encoding: BASE64 Content-ID: Content-Description: Content-Disposition: ATTACHMENT; FILENAME="exPlugin.tar.gz" Content-length: 3831 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 --889791192-1721647947-1035228590=:19693--