#include #include #include #include #include #include #include #include #include #include #include #include #include #include /* Establish a connection to the calculator. If nostart is set, assume the calculator is ready immediately; otherwise, wait for a message of type gdb:start. The latter can be useful is the calculator is using USB prior to starting a debugging session. */ static struct fxlink_device *setup_calc(libusb_context *context, bool nostart) { struct fxlink_filter filter = { 0 }; filter.intf_fxlink = true; fxlink_filter_clean_libusb(&filter); hlog("calculators"); log_("waiting for calculator to connect...\n"); delay_t delay = delay_infinite(); struct fxlink_device *fdev = fxlink_device_find_wait(context, &filter, &delay); if(!fdev) { elog("unable to open calculator\n"); return NULL; } if(!fxlink_device_claim_fxlink(fdev)) { elog("unable to claim fxlink interface\n"); return NULL; } fxlink_device_start_bulk_IN(fdev); if(nostart) return fdev; hlog("gdb"); log_("waiting for gdb init message...\n"); while(true) { libusb_handle_events(context); struct fxlink_message *msg = fxlink_device_finish_bulk_IN(fdev); if(!msg) continue; hlog("gdb"); if(fxlink_message_is_apptype(msg, "gdb", "start")) { log_("got start message\n"); break; } wlog("dropped a message of type %.16s:%.16s\n", msg->application, msg->type); fxlink_device_start_bulk_IN(fdev); } return fdev; } static int setup_socket(char const *listen_path) { int listen_socket = socket(AF_UNIX, SOCK_STREAM, 0); if(listen_socket < 0) { perror("socket"); return -1; } unlink(listen_path); struct sockaddr_un listen_address = { .sun_family = AF_UNIX, .sun_path = {0}, }; strncpy(listen_address.sun_path, listen_path, sizeof(listen_address.sun_path) - 1); if(bind(listen_socket, (struct sockaddr *)&listen_address, sizeof(listen_address)) < 0) { close(listen_socket); perror("bind"); return -1; } if(listen(listen_socket, 1024) < 0) { close(listen_socket); perror("listen"); return -1; } hlog("socket"); log_("waiting for client on \"%s\"...\n", listen_address.sun_path); return listen_socket; } static int volatile gdb_terminated_flag = 0; static int volatile interrupted_flag = 0; void SIGINT_SIGCHLD_handler(int signal) { if(signal == SIGCHLD) gdb_terminated_flag = 1; else interrupted_flag = 1; } static pid_t fork_gdb(char **user_argv, char const *socket_path) { int n = 0; while(user_argv[n]) n++; char target_command[256]; sprintf(target_command, "target remote %s", socket_path); char **argv = malloc((n + 7) * sizeof *argv); argv[0] = "sh-elf-gdb"; argv[1] = "-q"; argv[2] = "-ex"; argv[3] = "set architecture sh4al-dsp"; argv[4] = "-ex"; argv[5] = target_command; memcpy(argv+6, user_argv, (n+1) * sizeof *argv); struct sigaction action = { .sa_handler = SIGINT_SIGCHLD_handler, }; sigaction(SIGCHLD, &action, NULL); pid_t pid = fork(); /* Child - execvp() only returns if there is an error */ if(pid == 0) { execvp("sh-elf-gdb", argv); perror("execvp"); exit(1); } /* Parent */ if(pid == -1) perror("fork"); return pid; } static int accept_gdb(int listen_socket) { int client_socket = accept(listen_socket, NULL, NULL); if(client_socket < 0) { close(listen_socket); perror("accept"); return -1; } close(listen_socket); return client_socket; } struct options { bool bridge_only; bool log_packets; }; /* Parse options, returns positional arguments to forward to gdb. */ static char **parse_argv(int argc, char **argv, struct options *opts) { int bridge_only = 0; int log_packets = 0; struct option longs[] = { { "bridge-only", no_argument, &bridge_only, 1 }, { "log-packets", no_argument, &log_packets, 1 }, { NULL }, }; getopt_long(argc, argv, "", longs, NULL); opts->bridge_only = (bridge_only != 0); opts->log_packets = (log_packets != 0); return argv + optind + (argv[optind] && !strcmp(argv[optind], "--")); } static void noop_log_handler(int display_fmt, char const *str) { (void)display_fmt; (void)str; } int main(int argc, char **argv) { libusb_context *context = NULL; struct fxlink_device *fdev = NULL; struct fxlink_pollfds fxlink_polled_fds = { 0 }; struct fxlink_device_list device_list = { 0 }; char socket_path[256] = { 0 }; char log_path[256] = { 0 }; FILE *log_fp = NULL; pid_t gdb_pid = -1; int ret = 1; struct options opts; char **gdb_argv = parse_argv(argc, argv, &opts); int err = libusb_init(&context); if(err != 0) { elog_libusb(err, "libusb_init"); goto end; } bool nostart = true; fdev = setup_calc(context, nostart); if(!fdev) goto end; sprintf(socket_path, "/tmp/fxsdk-gdb-bridge-%03d-%03d.socket", fdev->busNumber, fdev->deviceAddress); sprintf(log_path, "/tmp/fxsdk-gdb-bridge-%03d-%03d.txt", fdev->busNumber, fdev->deviceAddress); int listen_socket = setup_socket(socket_path); if(listen_socket < 0) goto end; if(opts.log_packets) { log_fp = fopen(log_path, "a"); if(!log_fp) perror("cannot open packet log file"); else { setvbuf(log_fp, NULL, _IOLBF, 0); hlog("gdb"); log_("writing packets to %s\n", log_path); } } if(!opts.bridge_only) { gdb_pid = fork_gdb(gdb_argv, socket_path); if(gdb_pid == -1) goto end; /* Cut fxlink logs to not mix in with gdb's output */ fxlink_log_set_handler(noop_log_handler); } int client_socket = accept_gdb(listen_socket); if(client_socket < 0) goto end; struct sigaction action = { .sa_handler = opts.bridge_only ? SIGINT_SIGCHLD_handler : SIG_IGN, }; sigaction(SIGINT, &action, NULL); /* Track libusb fds as an indirect way to watch the calculator itself */ struct pollfd client_socket_pollfd = { .fd = client_socket, .events = POLLIN }; fxlink_pollfds_track(&fxlink_polled_fds, context); /* Track devices to find out when our device is removed */ fxlink_device_list_track(&device_list, context); while(!interrupted_flag && !gdb_terminated_flag) { int err = fxlink_multipoll(-1, fxlink_polled_fds.fds, fxlink_polled_fds.count, &client_socket_pollfd, 1, NULL); if (err < 0) { perror("poll"); goto end; } struct timeval zero = {0}; libusb_handle_events_timeout(context, &zero); /* Check if our device is still in the list */ fxlink_device_list_refresh(&device_list); bool still_there = false; for(int i = 0; i < device_list.count; i++) still_there = still_there || device_list.devices[i].dp == fdev->dp; if(!still_there) { hlog("gdb"); log_("device disconnected\n"); send(client_socket, "$W00#b7", 7, 0); break; } struct fxlink_message *msg; while((msg = fxlink_device_finish_bulk_IN(fdev)) != NULL) { if(fxlink_message_is_apptype(msg, "fxlink", "text")) { hlog("stub"); log_("%.*s", msg->size, (char *)msg->data); fxlink_message_free(msg, true); fxlink_device_start_bulk_IN(fdev); continue; } if(!fxlink_message_is_apptype(msg, "gdb", "remote")) { hlog("gdb"); wlog("dropped a message of type %.16s:%.16s\n", msg->application, msg->type); fxlink_message_free(msg, true); fxlink_device_start_bulk_IN(fdev); continue; } if(opts.bridge_only || log_fp) { fprintf(log_fp ? log_fp : stdout, "CAL> %.*s\n", msg->size, (char *)msg->data); } ssize_t send_ret = send(client_socket, msg->data, msg->size, 0); if(send_ret != msg->size) { perror("send"); goto end; } fxlink_message_free(msg, true); fxlink_device_start_bulk_IN(fdev); } /* Don't start an OUT transfer if there's one in progress */ if(fdev->comm->ftransfer_OUT) continue; int bytes_socket; if(ioctl(client_socket, FIONREAD, &bytes_socket) < 0) { perror("ioctl"); goto end; } if(bytes_socket > 0) { char buf[1024]; ssize_t recv_ret = recv(client_socket, buf, sizeof(buf), 0); if(recv_ret < 0) { perror("recv"); goto end; } if(opts.bridge_only || log_fp) { fprintf(log_fp ? log_fp : stdout, "GDB> %.*s\n", (int)recv_ret, buf); } if(!fxlink_device_start_bulk_OUT(fdev, "gdb", "remote", buf, recv_ret, false)) { elog("unable to start bulk OUT transfer\n"); goto end; } } } ret = 0; end: if(gdb_pid != -1) waitpid(gdb_pid, NULL, 0); if(log_fp) fclose(log_fp); if(socket_path[0]) unlink(socket_path); if(device_list.ctx) fxlink_device_list_stop(&device_list); if(fxlink_polled_fds.ctx) fxlink_pollfds_stop(&fxlink_polled_fds); if(fdev) { fxlink_device_interrupt_transfers(fdev); fxlink_device_cleanup(fdev); free(fdev); } if(context) libusb_exit(context); return ret; }