/**[txh]******************************************************************** Copyright (c) 2004-2009 by Salvador E. Tropea. Covered by the GPL license. Module: Connect. Comments: This module handles the dialog with gdb, including starting and stopping gdb. @

GDB Bug workaround for "file -readnow": I tried to workaround a bug using it but looks like this option also have bugs!!!! so I have to use the command line option --readnow. It also have a bug!!!! when the binary is changed and gdb must reload it this option is ignored. So it looks like we have no solution but 3 gdb bugs in a row. ***************************************************************************/ #include "platform.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include "gdbmi.h" #ifndef _GNU_SOURCE #define _GNU_SOURCE #endif #ifndef TEMP_FAILURE_RETRY #define TEMP_FAILURE_RETRY(a) (a) #endif int mi_error=MI_OK; char *mi_error_from_gdb=NULL; static char *gdb_exe=NULL; static char *xterm_exe=NULL; static char *gdb_start=NULL; static char *gdb_conn=NULL; static char *main_func=NULL; static char disable_psym_search_workaround=0; mi_h *mi_alloc_h() { mi_h *h=(mi_h *)calloc(1,sizeof(mi_h)); if (!h) { mi_error=MI_OUT_OF_MEMORY; return NULL; } h->to_gdb[0]=h->to_gdb[1]=h->from_gdb[0]=h->from_gdb[1]=-1; h->pid=-1; return h; } int mi_check_running_pid(pid_t pid) { int status; if (pid<=0) return 0; /* If waitpid returns the number of our child means it communicated to as a termination status. */ if (waitpid(pid,&status,WNOHANG)==pid) { pid=0; return 0; } return 1; } int mi_check_running(mi_h *h) { return !h->died && mi_check_running_pid(h->pid); } void mi_kill_child(pid_t pid) { kill(pid,SIGTERM); usleep(100000); if (mi_check_running_pid(pid)) { int status; kill(pid,SIGKILL); waitpid(pid,&status,0); } } void mi_free_h(mi_h **handle) { mi_h *h=*handle; if (h->to_gdb[0]>=0) close(h->to_gdb[0]); if (h->to) fclose(h->to); else if (h->to_gdb[1]>=0) close(h->to_gdb[1]); if (h->from) fclose(h->from); else if (h->from_gdb[0]>=0) close(h->from_gdb[0]); if (h->from_gdb[1]>=0) close(h->from_gdb[1]); if (mi_check_running(h)) {/* GDB is running! */ mi_kill_child(h->pid); } if (h->line) free(h->line); mi_free_output(h->po); free(h->catched_console); free(h); *handle=NULL; } void mi_set_nonblk(int h) { int flf; flf=fcntl(h,F_GETFL,0); flf=flf | O_NONBLOCK; fcntl(h,F_SETFL,flf); } int mi_getline(mi_h *h) { char c; while (read(h->from_gdb[0],&c,1)==1) { if (h->lread>=h->llen) { h->llen=h->lread+128; h->line=(char *)realloc(h->line,h->llen); if (!h->line) { h->llen=0; h->lread=0; return -1; } } if (c=='\n') { int ret=h->lread; h->line[ret]=0; h->lread=0; return ret; } h->line[h->lread]=c; h->lread++; } return 0; } char *get_cstr(mi_output *o) { if (!o->c || o->c->type!=t_const) return NULL; return o->c->v.cstr; } int mi_get_response(mi_h *h) { int l=mi_getline(h); if (!l) return 0; if (h->from_gdb_echo) h->from_gdb_echo(h->line,h->from_gdb_echo_data); if (strncmp(h->line,"(gdb)",5)==0) {/* End of response. */ return 1; } else {/* Add to the response. */ mi_output *o; int add=1, is_exit=0; o=mi_parse_gdb_output(h->line); if (!o) return 0; /* Tunneled streams callbacks. */ if (o->type==MI_T_OUT_OF_BAND && o->stype==MI_ST_STREAM) { char *aux; add=0; switch (o->sstype) { case MI_SST_CONSOLE: aux=get_cstr(o); if (h->console) h->console(aux,h->console_data); if (h->catch_console && aux) { h->catch_console--; if (!h->catch_console) { free(h->catched_console); h->catched_console=strdup(aux); } } break; case MI_SST_TARGET: /* This one seems to be useless. */ if (h->target) h->target(get_cstr(o),h->target_data); break; case MI_SST_LOG: if (h->log) h->log(get_cstr(o),h->log_data); break; } } else if (o->type==MI_T_OUT_OF_BAND && o->stype==MI_ST_ASYNC) { if (h->async) h->async(o,h->async_data); } else if (o->type==MI_T_RESULT_RECORD && o->tclass==MI_CL_ERROR) {/* Error from gdb, record it. */ mi_error=MI_FROM_GDB; free(mi_error_from_gdb); mi_error_from_gdb=NULL; if (o->c && strcmp(o->c->var,"msg")==0 && o->c->type==t_const) mi_error_from_gdb=strdup(o->c->v.cstr); } is_exit=(o->type==MI_T_RESULT_RECORD && o->tclass==MI_CL_EXIT); /* Add to the list of responses. */ if (add) { if (h->last) h->last->next=o; else h->po=o; h->last=o; } else mi_free_output(o); /* Exit RR means gdb exited, we won't get a new prompt ;-) */ if (is_exit) return 1; } return 0; } mi_output *mi_retire_response(mi_h *h) { mi_output *ret=h->po; h->po=h->last=NULL; return ret; } mi_output *mi_get_response_blk(mi_h *h) { int r; /* Sometimes gdb dies. */ if (!mi_check_running(h)) { h->died=1; mi_error=MI_GDB_DIED; return NULL; } do { /* That's a must. If we just keep trying to read and failing things become really sloooowwww. Instead we try and if it fails we wait until something is available. TODO: Implement something with the time out, a callback to ask the application is we have to wait or not could be a good thing. */ fd_set set; struct timeval timeout; int ret; r=mi_get_response(h); if (r) return mi_retire_response(h); FD_ZERO(&set); FD_SET(h->from_gdb[0],&set); timeout.tv_sec=h->time_out; timeout.tv_usec=0; ret=TEMP_FAILURE_RETRY(select(FD_SETSIZE,&set,NULL,NULL,&timeout)); if (!ret) { if (!mi_check_running(h)) { h->died=1; mi_error=MI_GDB_DIED; return NULL; } if (h->time_out_cb) ret=h->time_out_cb(h->time_out_cb_data); if (!ret) { mi_error=MI_GDB_TIME_OUT; return NULL; } } } while (!r); return NULL; } void mi_send_commands(mi_h *h, const char *file) { FILE *f; char b[PATH_MAX]; //printf("File: %s\n",file); if (!file) return; f=fopen(file,"rt"); if (!f) return; while (!feof(f)) { if (fgets(b,PATH_MAX,f)) { //printf("Send: %s\n",b); mi_send (h, "%s", b); mi_res_simple_done(h); } } fclose(f); } void mi_send_target_commands(mi_h *h) { mi_send_commands(h,gdb_conn); } /**[txh]******************************************************************** Description: Connect to a local copy of gdb. Note that the mi_h structure is something similar to a "FILE *" for stdio. Return: A new mi_h structure or NULL on error. ***************************************************************************/ mi_h *mi_connect_local() { mi_h *h; const char *gdb=mi_get_gdb_exe(); /* Start without error. */ mi_error=MI_OK; /* Verify we have a GDB binary. */ if (access(gdb,X_OK)) { mi_error=MI_MISSING_GDB; return NULL; } /* Alloc the handle structure. */ h=mi_alloc_h(); if (!h) return h; h->time_out=MI_DEFAULT_TIME_OUT; /* Create the pipes to connect with the child. */ if (pipe(h->to_gdb) || pipe(h->from_gdb)) { mi_error=MI_PIPE_CREATE; mi_free_h(&h); return NULL; } mi_set_nonblk(h->to_gdb[1]); mi_set_nonblk(h->from_gdb[0]); /* Associate streams to the file handles. */ h->to=fdopen(h->to_gdb[1],"w"); h->from=fdopen(h->from_gdb[0],"r"); if (!h->to || !h->from) { mi_error=MI_PIPE_CREATE; mi_free_h(&h); return NULL; } /* Create the child. */ h->pid=fork(); if (h->pid==0) {/* We are the child. */ char *argv[5]; /* Connect stdin/out to the pipes. */ dup2(h->to_gdb[0],STDIN_FILENO); dup2(h->from_gdb[1],STDOUT_FILENO); /* Pass the control to gdb. */ argv[0]=(char *)gdb; /* Is that OK? */ argv[1]="--interpreter=mi"; argv[2]="--quiet"; argv[3]=disable_psym_search_workaround ? 0 : "--readnow"; argv[4]=0; execvp(argv[0],argv); /* We get here only if exec failed. */ _exit(127); } /* We are the parent. */ if (h->pid==-1) {/* Fork failed. */ mi_error=MI_FORK; mi_free_h(&h); return NULL; } if (!mi_check_running(h)) { mi_error=MI_DEBUGGER_RUN; mi_free_h(&h); return NULL; } /* Wait for the prompt. */ mi_get_response_blk(h); /* Send the start-up commands */ mi_send_commands(h,gdb_start); return h; } /**[txh]******************************************************************** Description: Close connection. You should ask gdb to quit first gmi_gdb_exit. ***************************************************************************/ void mi_disconnect(mi_h *h) { mi_free_h(&h); free(mi_error_from_gdb); mi_error_from_gdb=NULL; } void mi_set_console_cb(mi_h *h, stream_cb cb, void *data) { h->console=cb; h->console_data=data; } void mi_set_target_cb(mi_h *h, stream_cb cb, void *data) { h->target=cb; h->target_data=data; } void mi_set_log_cb(mi_h *h, stream_cb cb, void *data) { h->log=cb; h->log_data=data; } stream_cb mi_get_console_cb(mi_h *h, void **data) { if (data) *data=h->console_data; return h->console; } stream_cb mi_get_target_cb(mi_h *h, void **data) { if (data) *data=h->target_data; return h->target; } stream_cb mi_get_log_cb(mi_h *h, void **data) { if (data) *data=h->log_data; return h->log; } void mi_set_async_cb(mi_h *h, async_cb cb, void *data) { h->async=cb; h->async_data=data; } async_cb mi_get_async_cb(mi_h *h, void **data) { if (data) *data=h->async_data; return h->async; } void mi_set_to_gdb_cb(mi_h *h, stream_cb cb, void *data) { h->to_gdb_echo=cb; h->to_gdb_echo_data=data; } void mi_set_from_gdb_cb(mi_h *h, stream_cb cb, void *data) { h->from_gdb_echo=cb; h->from_gdb_echo_data=data; } stream_cb mi_get_to_gdb_cb(mi_h *h, void **data) { if (data) *data=h->to_gdb_echo_data; return h->to_gdb_echo; } stream_cb mi_get_from_gdb_cb(mi_h *h, void **data) { if (data) *data=h->from_gdb_echo_data; return h->from_gdb_echo; } void mi_set_time_out_cb(mi_h *h, tm_cb cb, void *data) { h->time_out_cb=cb; h->time_out_cb_data=data; } tm_cb mi_get_time_out_cb(mi_h *h, void **data) { if (data) *data=h->time_out_cb_data; return h->time_out_cb; } void mi_set_time_out(mi_h *h, int to) { h->time_out=to; } int mi_get_time_out(mi_h *h) { return h->time_out; } int mi_send(mi_h *h, const char *format, ...) { int ret; char *str; va_list argptr; if (h->died) return 0; va_start(argptr,format); ret=vasprintf(&str,format,argptr); va_end(argptr); if (-1 != ret) { fputs(str,h->to); fflush(h->to); if (h->to_gdb_echo) h->to_gdb_echo(str,h->to_gdb_echo_data); free(str); } else { abort (); } return ret; } void mi_clean_up_globals() { free(gdb_exe); gdb_exe=NULL; free(xterm_exe); xterm_exe=NULL; free(gdb_start); gdb_start=NULL; free(gdb_conn); gdb_conn=NULL; free(main_func); main_func=NULL; } void mi_register_exit() { static int registered=0; if (!registered) { registered=1; atexit(mi_clean_up_globals); } } void mi_set_gdb_exe(const char *name) { free(gdb_exe); gdb_exe=name ? strdup(name) : NULL; mi_register_exit(); } void mi_set_gdb_start(const char *name) { free(gdb_start); gdb_start=name ? strdup(name) : NULL; mi_register_exit(); } void mi_set_gdb_conn(const char *name) { free(gdb_conn); gdb_conn=name ? strdup(name) : NULL; mi_register_exit(); } static char *mi_search_in_path(const char *file) { char *path, *pt, *r; char test[PATH_MAX]; struct stat st; path=getenv("PATH"); if (!path) return NULL; pt=strdup(path); r=strtok(pt,PATH_SEPARATOR_STR); while (r) { strcpy(test,r); strcat(test,"/"); strcat(test,file); if (stat(test,&st)==0 && S_ISREG(st.st_mode)) { free(pt); return strdup(test); } r=strtok(NULL,PATH_SEPARATOR_STR); } free(pt); return NULL; } const char *mi_get_gdb_exe() { if (!gdb_exe) {/* Look for gdb in path */ gdb_exe=mi_search_in_path("gdb"); if (!gdb_exe) return "/usr/bin/gdb"; } return gdb_exe; } const char *mi_get_gdb_start() { return gdb_start; } const char *mi_get_gdb_conn() { return gdb_conn; } void mi_set_xterm_exe(const char *name) { free(xterm_exe); xterm_exe=name ? strdup(name) : NULL; mi_register_exit(); } const char *mi_get_xterm_exe() { if (!xterm_exe) {/* Look for xterm in path */ xterm_exe=mi_search_in_path("xterm"); if (!xterm_exe) return "/usr/bin/X11/xterm"; } return xterm_exe; } void mi_set_main_func(const char *name) { free(main_func); main_func=name ? strdup(name) : NULL; mi_register_exit(); } const char *mi_get_main_func() { if (main_func) return main_func; return "main"; } /**[txh]******************************************************************** Description: Opens a new xterm to be used by the child process to debug. Return: A new mi_aux_term structure, you can use gmi_end_aux_term to release it. ***************************************************************************/ mi_aux_term *gmi_start_xterm() { char nsh[14]="/tmp/shXXXXXX"; char ntt[14]="/tmp/ttXXXXXX"; const char *xterm; struct stat st; int hsh, htt=-1; mi_aux_term *res=NULL; FILE *f; pid_t pid; char buf[PATH_MAX]; /* Verify we have an X terminal. */ xterm=mi_get_xterm_exe(); if (access(xterm,X_OK)) { mi_error=MI_MISSING_XTERM; return NULL; } /* Create 2 temporals. */ hsh=mkstemp(nsh); if (hsh==-1) { mi_error=MI_CREATE_TEMPORAL; return NULL; } htt=mkstemp(ntt); if (htt==-1) { close(hsh); unlink(nsh); mi_error=MI_CREATE_TEMPORAL; return NULL; } close(htt); /* Create the script. */ f=fdopen(hsh,"w"); if (!f) { close(hsh); unlink(nsh); unlink(ntt); mi_error=MI_CREATE_TEMPORAL; return NULL; } fprintf(f,"#!/bin/sh\n"); fprintf(f,"tty > %s\n",ntt); fprintf(f,"rm %s\n",nsh); fprintf(f,"sleep 365d\n"); fclose(f); /* Spawn xterm. */ /* Create the child. */ pid=fork(); if (pid==0) {/* We are the child. */ char *argv[5]; /* Pass the control to gdb. */ argv[0]=(char *)mi_get_xterm_exe(); /* Is that ok? */ argv[1]="-e"; argv[2]="/bin/sh"; argv[3]=nsh; argv[4]=0; execvp(argv[0],argv); /* We get here only if exec failed. */ unlink(nsh); unlink(ntt); _exit(127); } /* We are the parent. */ if (pid==-1) {/* Fork failed. */ unlink(nsh); unlink(ntt); mi_error=MI_FORK; return NULL; } /* Wait until the shell is deleted. */ while (stat(nsh,&st)==0) usleep(1000); /* Try to read the tty name. */ f=fopen(ntt,"rt"); if (f) { if (fgets(buf,PATH_MAX,f)) { char *s; /* Strip the \n. */ for (s=buf; *s && *s!='\n'; s++); *s=0; res=(mi_aux_term *)malloc(sizeof(mi_aux_term)); if (res) { res->pid=pid; res->tty=strdup(buf); } } fclose(f); } unlink(ntt); return res; } void mi_free_aux_term(mi_aux_term *t) { if (!t) return; free(t->tty); free(t); } /**[txh]******************************************************************** Description: Closes the auxiliar terminal and releases the allocated memory. ***************************************************************************/ void gmi_end_aux_term(mi_aux_term *t) { if (!t) return; if (t->pid!=-1 && mi_check_running_pid(t->pid)) mi_kill_child(t->pid); mi_free_aux_term(t); } /**[txh]******************************************************************** Description: Forces the MI version. Currently the library can't detect it so you must force it manually. GDB 5.x implemented MI v1 and 6.x v2. ***************************************************************************/ void mi_force_version(mi_h *h, unsigned vMajor, unsigned vMiddle, unsigned vMinor) { h->version=MI_VERSION2U(vMajor,vMiddle,vMinor); } /**[txh]******************************************************************** Description: Dis/Enables the workaround for a bug in gdb. ***************************************************************************/ void mi_set_workaround(unsigned wa, int enable) { switch (wa) { case MI_PSYM_SEARCH: disable_psym_search_workaround=enable ? 0 : 1; break; } } /**[txh]******************************************************************** Description: Finds if the workaround for a bug in gdb is enabled. Return: !=0 if enabled. ***************************************************************************/ int mi_get_workaround(unsigned wa) { switch (wa) { case MI_PSYM_SEARCH: return disable_psym_search_workaround==0; } return 0; }