| /****************************************************************************** |
| * Copyright (c) 2005 The Regents of the University of California. |
| * This material was produced under U.S. Government contract W-7405-ENG-36 |
| * for Los Alamos National Laboratory, which is operated by the University |
| * of California for the U.S. Department of Energy. The U.S. Government has |
| * rights to use, reproduce, and distribute this software. NEITHER THE |
| * GOVERNMENT NOR THE UNIVERSITY MAKES ANY WARRANTY, EXPRESS OR IMPLIED, OR |
| * ASSUMES ANY LIABILITY FOR THE USE OF THIS SOFTWARE. If software is modified |
| * to produce derivative works, such modified software should be clearly |
| * marked, so as not to confuse it with the version available from LANL. |
| * |
| * Additionally, this program and the accompanying materials |
| * are made available under the terms of the Eclipse Public License v1.0 |
| * which accompanies this distribution, and is available at |
| * http://www.eclipse.org/legal/epl-v10.html |
| * |
| * LA-CC 04-115 |
| ******************************************************************************/ |
| |
| #include <sys/types.h> |
| #include <sys/wait.h> |
| #include <sys/errno.h> |
| |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <string.h> |
| #include <signal.h> |
| #include <unistd.h> |
| #include <fcntl.h> |
| |
| #include "list.h" |
| #include "MISession.h" |
| #include "MICommand.h" |
| #include "MIError.h" |
| #include "MIOOBRecord.h" |
| #include "MIValue.h" |
| #include "MIResult.h" |
| |
| static List * MISessionList = NULL; |
| static struct timeval MISessionDefaultSelectTimeout = {0, 1000}; |
| static int MISessionDebug = 0; |
| |
| static void DoOOBAsyncCallbacks(MISession *sess, List *oobs); |
| static void DoOOBStreamCallbacks(MISession *sess, List *oobs); |
| static void HandleChild(int sig); |
| static int WriteCommand(int fd, char *cmd); |
| static char *ReadResponse(int fd); |
| |
| MISession * |
| MISessionNew(void) |
| { |
| MISession * sess = (MISession *)malloc(sizeof(MISession)); |
| |
| sess->in_fd = -1; |
| sess->out_fd = -1; |
| sess->pid = -1; |
| sess->exited = 1; |
| sess->exit_status = 0; |
| sess->command = NULL; |
| sess->send_queue = NewList(); |
| sess->gdb_path = strdup("gdb"); |
| sess->event_callback = NULL; |
| sess->cmd_callback = NULL; |
| sess->exec_callback = NULL; |
| sess->status_callback = NULL; |
| sess->notify_callback = NULL; |
| sess->console_callback = NULL; |
| sess->log_callback = NULL; |
| sess->target_callback = NULL; |
| sess->select_timeout = MISessionDefaultSelectTimeout; |
| |
| if (MISessionList == NULL) |
| MISessionList = NewList(); |
| AddToList(MISessionList, (void *)sess); |
| |
| return sess; |
| } |
| |
| void |
| MISessionFree(MISession *sess) |
| { |
| RemoveFromList(MISessionList, (void *)sess); |
| free(sess); |
| } |
| |
| static void |
| HandleChild(int sig) |
| { |
| int stat; |
| pid_t pid; |
| MISession * sess; |
| |
| pid = wait(&stat); |
| |
| if (MISessionList != NULL) { |
| for (SetList(MISessionList); (sess = (MISession *)GetListElement(MISessionList)) != NULL; ) { |
| if (sess->pid == pid) { |
| fprintf(stderr, "gdb pid %d has exited\n", pid); |
| sess->exited = 1; |
| sess->exit_status = stat; |
| } |
| } |
| } |
| } |
| |
| void |
| MISessionSetTimeout(MISession *sess, long sec, long usec) |
| { |
| sess->select_timeout.tv_sec = sec; |
| sess->select_timeout.tv_usec = usec; |
| } |
| |
| void |
| MISessionSetDebug(int debug) |
| { |
| MISessionDebug = debug; |
| // MISessionDebug = 1; |
| } |
| |
| int |
| MISessionStartLocal(MISession *sess, char *prog) |
| { |
| int p1[2]; |
| int p2[2]; |
| |
| if (pipe(p1) < 0 || pipe(p2) < 0) { |
| MISetError(MI_ERROR_SYSTEM, strerror(errno)); |
| return -1; |
| } |
| |
| sess->in_fd = p2[1]; |
| sess->out_fd = p1[0]; |
| sess->exited = 0; |
| |
| signal(SIGCHLD, HandleChild); |
| signal(SIGPIPE, SIG_IGN); |
| |
| switch (sess->pid = fork()) |
| { |
| case 0: |
| dup2(p2[0], 0); |
| dup2(p1[1], 1); |
| close(p1[0]); |
| close(p1[1]); |
| close(p2[0]); |
| close(p2[1]); |
| |
| if (prog == NULL) |
| execlp(sess->gdb_path, "gdb", "-q", "-tty", "/dev/null", "-i", "mi", NULL); |
| else |
| execlp(sess->gdb_path, "gdb", "-q", "-tty", "/dev/null", "-i", "mi", prog, NULL); |
| |
| exit(1); |
| |
| case -1: |
| MISetError(MI_ERROR_SYSTEM, strerror(errno)); |
| return -1; |
| |
| default: |
| break; |
| } |
| |
| close(p1[1]); |
| close(p2[0]); |
| |
| return 0; |
| } |
| |
| void |
| MISessionRegisterEventCallback(MISession *sess, void (*callback)(MIEvent *)) |
| { |
| sess->event_callback = callback; |
| } |
| |
| void |
| MISessionRegisterCommandCallback(MISession *sess, void (*callback)(MIResultRecord *)) |
| { |
| sess->cmd_callback = callback; |
| } |
| |
| void |
| MISessionRegisterExecCallback(MISession *sess, void (*callback)(char *, List *)) |
| { |
| sess->exec_callback = callback; |
| } |
| |
| void |
| MISessionRegisterStatusCallback(MISession *sess, void (*callback)(char *, List *)) |
| { |
| sess->status_callback = callback; |
| } |
| |
| void |
| MISessionRegisterNotifyCallback(MISession *sess, void (*callback)(char *, List *)) |
| { |
| sess->notify_callback = callback; |
| } |
| |
| void |
| MISessionRegisterConsoleCallback(MISession *sess, void (*callback)(char *)) |
| { |
| sess->console_callback = callback; |
| } |
| |
| void |
| MISessionRegisterLogCallback(MISession *sess, void (*callback)(char *)) |
| { |
| sess->log_callback = callback; |
| } |
| |
| void |
| MISessionRegisterTargetCallback(MISession *sess, void (*callback)(char *)) |
| { |
| sess->target_callback = callback; |
| } |
| |
| void |
| MISessionSetGDBPath(MISession *sess, char *path) |
| { |
| if (sess->gdb_path != NULL) |
| free(sess->gdb_path); |
| sess->gdb_path = strdup(path); |
| } |
| |
| int |
| MISessionSendCommand(MISession *sess, MICommand *cmd) |
| { |
| if (sess->pid == -1) { |
| MISetError(MI_ERROR_SESSION, ""); |
| return -1; |
| } |
| |
| AddToList(sess->send_queue, (void *)cmd); |
| |
| return 0; |
| } |
| |
| /* |
| * Send command to GDB. We keep writing |
| * until the whole command has been sent. |
| * |
| * There is a chance this could block. |
| */ |
| static int |
| WriteCommand(int fd, char *cmd) |
| { |
| int n; |
| int len = strlen(cmd); |
| |
| if (MISessionDebug) { |
| printf("MI: SEND %s", cmd); |
| if (cmd[len-1] != '\n') |
| printf("\n"); |
| fflush(stdout); |
| } |
| |
| while (len > 0) { |
| n = write(fd, cmd, len); |
| if (n <= 0) { |
| if (n < 0) { |
| if (errno == EINTR) |
| continue; |
| MISetError(MI_ERROR_SYSTEM, strerror(errno)); |
| } |
| return -1; |
| } |
| |
| cmd += n; |
| len -= n; |
| } |
| |
| return 0; |
| } |
| |
| /* |
| * Read BUFSIZ chunks of data until we have read |
| * everything available. |
| * |
| * Everything has been read if: |
| * 1) the read returns less than BUFSIZ |
| * 2) the read would block (errno == EAGAIN) |
| * |
| * The assumption is that O_NONBLOCK has been set |
| * of the file descriptor. |
| * |
| * The buffer is static, so we just reuse it for each |
| * call and increase the size if necessary. |
| */ |
| #define MI_BUFSIZ 1024 //BUFSIZ |
| static char * |
| ReadResponse(int fd) |
| { |
| int n; |
| int len = 0; |
| char * p; |
| static int res_buf_len = MI_BUFSIZ; |
| static char * res_buf = NULL; |
| |
| //if (res_buf != NULL) { |
| //free(res_buf); |
| //} |
| |
| if (res_buf == NULL) |
| res_buf = (char *)malloc(MI_BUFSIZ); |
| |
| p = res_buf; |
| for (;;) { |
| n = read(fd, p, MI_BUFSIZ); |
| if (n <= 0) { |
| if (n < 0) { |
| if (errno == EAGAIN) |
| break; |
| if (errno == EINTR) |
| continue; |
| MISetError(MI_ERROR_SYSTEM, strerror(errno)); |
| } |
| return NULL; |
| } |
| |
| if (n < MI_BUFSIZ) |
| break; |
| |
| len += MI_BUFSIZ; |
| |
| if (len == res_buf_len) { |
| res_buf_len += MI_BUFSIZ; |
| res_buf = (char *)realloc(res_buf, res_buf_len); |
| } |
| |
| p = &res_buf[len]; |
| } |
| |
| if (n > 0) |
| p[n] = '\0'; |
| |
| if (MISessionDebug) { |
| printf("MI: RECV %s", res_buf); |
| if (res_buf[strlen(res_buf)-1] != '\n') |
| printf("\n"); |
| fflush(stdout); |
| } |
| |
| return res_buf; |
| } |
| |
| /* |
| * Send first pending command to GDB for each session. |
| * |
| * For each session, read response from GDB and |
| * parse the result. |
| * |
| * Assumes that fds contains file descriptors ready |
| * for writing. |
| */ |
| void |
| MISessionProcessCommandsAndResponses(MISession *sess, fd_set *rfds, fd_set *wfds, MIOutput *output) |
| { |
| char * str; |
| |
| if (sess->pid == -1) |
| return; |
| |
| if (sess->in_fd != -1 |
| && !EmptyList(sess->send_queue) |
| && sess->command == NULL |
| && (wfds == NULL || FD_ISSET(sess->in_fd, wfds)) |
| ) |
| { |
| sess->command = (MICommand *)RemoveFirst(sess->send_queue); |
| |
| #ifdef __gnu_linux__ |
| /* |
| * NOTE: this hack only works if gdb is started with the '-tty' argument (or |
| * presumably if the 'tty' command is issued.) Without this, the only way to |
| * interrupt a running process seems to be from the command line. |
| */ |
| if (strcmp(sess->command->command, "-exec-interrupt") == 0) { |
| if (MISessionDebug) { |
| printf("MI: sending SIGINT to %d\n", sess->pid); |
| fflush(stdout); |
| } |
| kill(sess->pid, SIGINT); |
| } else if (WriteCommand(sess->in_fd, MICommandToString(sess->command)) < 0) { |
| sess->in_fd = -1; |
| } |
| #else /* __gnu_linux__ */ |
| if (WriteCommand(sess->in_fd, MICommandToString(sess->command)) < 0) { |
| sess->in_fd = -1; |
| } |
| #endif /* __gnu_linux */ |
| } |
| |
| if (sess->out_fd != -1 && FD_ISSET(sess->out_fd, rfds)) { |
| if ((str = ReadResponse(sess->out_fd)) == NULL) { |
| sess->out_fd = -1; |
| return; |
| } |
| |
| MIParse(str, output); |
| |
| /* |
| * The output can consist of: |
| * async oob records that are not necessarily the result of a command |
| * stream oob records that always result from a command |
| * result records from a command |
| * |
| * Async oob records are processed immediately and removed. |
| * |
| * Stream oob records are processed immediately but a retained with the |
| * command in case they are needed for later processing. |
| * |
| * If there are result records, then the output *should* have resulted |
| * from the execution of a command. Mark the command as completed an |
| * invoke its callback. |
| * |
| * The stream oob and result records are freed when the command is freed. |
| */ |
| |
| if (output->oobs != NULL) { |
| #ifdef __gnu_linux__ |
| if (sess->command != NULL && strcmp(sess->command->command, "-exec-interrupt") == 0) { |
| sess->command->completed = 1; |
| } |
| #endif /* __gnu_linux__ */ |
| DoOOBAsyncCallbacks(sess, output->oobs); |
| DoOOBStreamCallbacks(sess, output->oobs); |
| } |
| |
| if (output->rr != NULL && sess->command != NULL) { |
| sess->command->completed = 1; |
| sess->command->output = output; |
| if (sess->command->callback != NULL) |
| sess->command->callback(output->rr, sess->command->cb_data); |
| } else { |
| if (sess->command == NULL) { |
| MIOutputFree(output); |
| } |
| } |
| |
| if (sess->command != NULL && sess->command->completed) |
| sess->command = NULL; |
| } |
| } |
| |
| int |
| MISessionCommandCompleted(MISession *sess) |
| { |
| return sess->command == NULL; |
| } |
| |
| /* |
| * Used to process a result record after a CLI command has been |
| * issued if an MIEvent needs to be generated. |
| * |
| * In MI mode, GDB CLI commands append result information (after |
| * the "done" result class) to the result record. This function can be |
| * passed to MICommandRegisterCallback() in order to automatically generate |
| * an MIEvent when the CLI command completes. |
| * |
| * NOTE: CLI commands DO NOT operate asychronously, so some commands will |
| * block until the command is complete (e.g. "run"). This may cause a user |
| * interface to block. |
| */ |
| void |
| ProcessCLIResultRecord(MIResultRecord *rr, void *data) |
| { |
| MISession *sess = (MISession *)data; |
| MIResult *res; |
| MIValue *val; |
| |
| if (rr->resultClass == MIResultRecordDONE) { |
| for (SetList(rr->results); (res = (MIResult *)GetListElement(rr->results)); ) { |
| if (strcmp(res->variable, "reason") == 0) { |
| val = res->value; |
| if (val->type == MIValueTypeConst) { |
| if (sess->event_callback) { |
| sess->event_callback(MIEventCreateStoppedEvent(val->cstring, rr->results)); |
| } |
| } |
| } |
| } |
| } |
| } |
| |
| /* |
| * Process async callbacks. Removes records from oobs list. |
| */ |
| static void |
| DoOOBAsyncCallbacks(MISession *sess, List *oobs) |
| { |
| MIOOBRecord * oob; |
| MIResult * res; |
| MIValue * val; |
| |
| for (SetList(oobs); (oob = (MIOOBRecord *)GetListElement(oobs)) != NULL; ) { |
| if (oob->type == MIOOBRecordTypeAsync) { |
| switch (oob->sub_type) { |
| case MIOOBRecordExecAsync: |
| if (sess->exec_callback != NULL) |
| sess->exec_callback(oob->class, oob->results); |
| |
| if (strcmp(oob->class, "stopped") == 0) { |
| int seen_reason = 0; |
| for (SetList(oob->results); (res = (MIResult *)GetListElement(oob->results)); ) { |
| if (strcmp(res->variable, "reason") == 0) { |
| seen_reason = 1; |
| val = res->value; |
| if (val->type == MIValueTypeConst) { |
| if (sess->event_callback) { |
| sess->event_callback(MIEventCreateStoppedEvent(val->cstring, oob->results)); |
| } |
| } |
| } |
| } |
| |
| /* |
| * Temporary breakpoints under Linux don't have a "reason". If we receive |
| * a stopped event with no reason, then we created a StoppedEvent anyway |
| */ |
| if (seen_reason == 0) { |
| if (sess->event_callback) { |
| sess->event_callback(MIEventCreateStoppedEvent("temporary-breakpoint-hit", oob->results)); |
| } |
| } |
| } |
| break; |
| |
| case MIOOBRecordStatusAsync: |
| if (sess->status_callback != NULL) |
| sess->status_callback(oob->class, oob->results); |
| break; |
| |
| case MIOOBRecordNotifyAsync: |
| if (sess->notify_callback != NULL) |
| sess->notify_callback(oob->class, oob->results); |
| break; |
| } |
| |
| RemoveFromList(oobs, (void *)oob); |
| MIOOBRecordFree(oob); |
| } |
| } |
| } |
| |
| /* |
| * Process stream callbacks. We leave the records on the oobs list |
| * because they may be needed by later command processing. |
| */ |
| static void |
| DoOOBStreamCallbacks(MISession *sess, List *oobs) |
| { |
| MIOOBRecord * oob; |
| |
| for (SetList(oobs); (oob = (MIOOBRecord *)GetListElement(oobs)) != NULL; ) { |
| if (oob->type == MIOOBRecordTypeStream) { |
| switch (oob->sub_type) { |
| case MIOOBRecordConsoleStream: |
| if (sess->console_callback != NULL) { |
| sess->console_callback(oob->cstring); |
| } |
| break; |
| |
| case MIOOBRecordLogStream: |
| if (sess->log_callback != NULL) |
| sess->log_callback(oob->cstring); |
| break; |
| |
| case MIOOBRecordTargetStream: |
| if (sess->target_callback != NULL) |
| sess->target_callback(oob->cstring); |
| break; |
| } |
| } |
| } |
| } |
| |
| void |
| MISessionGetFds(MISession *sess, int *nfds, fd_set *rfds, fd_set *wfds, fd_set *efds) |
| { |
| int n = 0; |
| |
| if (rfds != NULL) |
| FD_ZERO(rfds); |
| |
| if (wfds != NULL) |
| FD_ZERO(wfds); |
| |
| if (efds != NULL) |
| FD_ZERO(efds); |
| |
| if (sess->pid != -1) { |
| if (wfds != NULL && sess->in_fd != -1) { |
| FD_SET(sess->in_fd, wfds); |
| if (sess->in_fd > n) |
| n = sess->in_fd; |
| } |
| if (rfds != NULL && sess->out_fd != -1) { |
| FD_SET(sess->out_fd, rfds); |
| if (sess->out_fd > n) |
| n = sess->out_fd; |
| } |
| } |
| |
| if (nfds != NULL) |
| *nfds = n + 1; |
| } |
| |
| /* |
| * Default progress command if none supplied. |
| * |
| * Don't select on write fds. This will almost always return immediately, |
| * so the timeout is never used. The end result is a busy wait that |
| * consumes gobs of CPU. |
| * |
| * Also, we make a copy of sess->select_timeout as some select() |
| * functions will modify it. |
| */ |
| int |
| MISessionProgress(MISession *sess, MIOutput *output) |
| { |
| int n; |
| int nfds; |
| fd_set rfds; |
| struct timeval tv = sess->select_timeout; |
| |
| MISessionGetFds(sess, &nfds, &rfds, NULL, NULL); |
| |
| for ( ;; ) { |
| n = select(nfds, &rfds, NULL, NULL, &tv); |
| |
| if (n < 0) { |
| if (errno == EINTR) |
| continue; |
| |
| MISetError(MI_ERROR_SYSTEM, strerror(errno)); |
| return -1; |
| } |
| break; |
| } |
| |
| /* |
| * Return if there is nothing to do |
| */ |
| if (n == 0 && EmptyList(sess->send_queue)) { |
| return 0; |
| } |
| MISessionProcessCommandsAndResponses(sess, &rfds, NULL, output); |
| |
| return n; |
| } |