ipdbg: fix double free of virtual-ir data
[openocd.git] / src / target / semihosting_common.c
index ffed7350edd5794612b0b6ed9ec1de4909af57a3..f7acc6092de688b683bc97aaab6b40781c6a735e 100644 (file)
@@ -1,3 +1,5 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+
 /***************************************************************************
  *   Copyright (C) 2018 by Liviu Ionescu                                   *
  *   <ilg@livius.net>                                                      *
  *                                                                         *
  *   Copyright (C) 2016 by Square, Inc.                                    *
  *   Steven Stallion <stallion@squareup.com>                               *
- *                                                                         *
- *   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, see <http://www.gnu.org/licenses/>. *
  ***************************************************************************/
 
 /**
 
 #include <helper/binarybuffer.h>
 #include <helper/log.h>
+#include <server/gdb_server.h>
 #include <sys/stat.h>
 
-static const int open_modeflags[12] = {
+/**
+ * It is not possible to use O_... flags defined in sys/stat.h because they
+ * are not guaranteed to match the values defined by the GDB Remote Protocol.
+ * See https://sourceware.org/gdb/onlinedocs/gdb/Open-Flags.html#Open-Flags
+ */
+enum {
+       TARGET_O_RDONLY = 0x000,
+       TARGET_O_WRONLY = 0x001,
+       TARGET_O_RDWR   = 0x002,
+       TARGET_O_APPEND = 0x008,
+       TARGET_O_CREAT  = 0x200,
+       TARGET_O_TRUNC  = 0x400,
+       /* O_EXCL=0x800 is not required in this implementation. */
+};
+
+/* GDB remote protocol does not differentiate between text and binary open modes. */
+static const int open_gdb_modeflags[12] = {
+       TARGET_O_RDONLY,
+       TARGET_O_RDONLY,
+       TARGET_O_RDWR,
+       TARGET_O_RDWR,
+       TARGET_O_WRONLY | TARGET_O_CREAT | TARGET_O_TRUNC,
+       TARGET_O_WRONLY | TARGET_O_CREAT | TARGET_O_TRUNC,
+       TARGET_O_RDWR   | TARGET_O_CREAT | TARGET_O_TRUNC,
+       TARGET_O_RDWR   | TARGET_O_CREAT | TARGET_O_TRUNC,
+       TARGET_O_WRONLY | TARGET_O_CREAT | TARGET_O_APPEND,
+       TARGET_O_WRONLY | TARGET_O_CREAT | TARGET_O_APPEND,
+       TARGET_O_RDWR   | TARGET_O_CREAT | TARGET_O_APPEND,
+       TARGET_O_RDWR   | TARGET_O_CREAT | TARGET_O_APPEND
+};
+
+static const int open_host_modeflags[12] = {
        O_RDONLY,
        O_RDONLY | O_BINARY,
        O_RDWR,
-       O_RDWR | O_BINARY,
+       O_RDWR   | O_BINARY,
        O_WRONLY | O_CREAT | O_TRUNC,
-       O_WRONLY | O_CREAT | O_TRUNC | O_BINARY,
-       O_RDWR | O_CREAT | O_TRUNC,
-       O_RDWR | O_CREAT | O_TRUNC | O_BINARY,
+       O_WRONLY | O_CREAT | O_TRUNC  | O_BINARY,
+       O_RDWR   | O_CREAT | O_TRUNC,
+       O_RDWR   | O_CREAT | O_TRUNC  | O_BINARY,
        O_WRONLY | O_CREAT | O_APPEND,
        O_WRONLY | O_CREAT | O_APPEND | O_BINARY,
-       O_RDWR | O_CREAT | O_APPEND,
-       O_RDWR | O_CREAT | O_APPEND | O_BINARY
+       O_RDWR   | O_CREAT | O_APPEND,
+       O_RDWR   | O_CREAT | O_APPEND | O_BINARY
 };
 
 static int semihosting_common_fileio_info(struct target *target,
@@ -72,19 +93,6 @@ static int semihosting_common_fileio_info(struct target *target,
 static int semihosting_common_fileio_end(struct target *target, int result,
        int fileio_errno, bool ctrl_c);
 
-static int semihosting_read_fields(struct target *target, size_t number,
-       uint8_t *fields);
-static int semihosting_write_fields(struct target *target, size_t number,
-       uint8_t *fields);
-static uint64_t semihosting_get_field(struct target *target, size_t index,
-       uint8_t *fields);
-static void semihosting_set_field(struct target *target, uint64_t value,
-       size_t index,
-       uint8_t *fields);
-
-/* Attempts to include gdb_server.h failed. */
-extern int gdb_actual_connections;
-
 /**
  * Initialize common semihosting support.
  *
@@ -99,7 +107,7 @@ int semihosting_common_init(struct target *target, void *setup,
        LOG_DEBUG(" ");
 
        target->fileio_info = malloc(sizeof(*target->fileio_info));
-       if (target->fileio_info == NULL) {
+       if (!target->fileio_info) {
                LOG_ERROR("out of memory");
                return ERROR_FAIL;
        }
@@ -107,12 +115,17 @@ int semihosting_common_init(struct target *target, void *setup,
 
        struct semihosting *semihosting;
        semihosting = malloc(sizeof(*target->semihosting));
-       if (semihosting == NULL) {
+       if (!semihosting) {
                LOG_ERROR("out of memory");
                return ERROR_FAIL;
        }
 
        semihosting->is_active = false;
+       semihosting->redirect_cfg = SEMIHOSTING_REDIRECT_CFG_NONE;
+       semihosting->tcp_connection = NULL;
+       semihosting->stdin_fd = -1;
+       semihosting->stdout_fd = -1;
+       semihosting->stderr_fd = -1;
        semihosting->is_fileio = false;
        semihosting->hit_fileio = false;
        semihosting->is_resumable = false;
@@ -123,12 +136,14 @@ int semihosting_common_init(struct target *target, void *setup,
        semihosting->result = -1;
        semihosting->sys_errno = -1;
        semihosting->cmdline = NULL;
+       semihosting->basedir = NULL;
 
        /* If possible, update it in setup(). */
        semihosting->setup_time = clock();
 
        semihosting->setup = setup;
        semihosting->post_result = post_result;
+       semihosting->user_command_extension = NULL;
 
        target->semihosting = semihosting;
 
@@ -138,6 +153,211 @@ int semihosting_common_init(struct target *target, void *setup,
        return ERROR_OK;
 }
 
+struct semihosting_tcp_service {
+       struct semihosting *semihosting;
+       char *name;
+       int error;
+};
+
+static bool semihosting_is_redirected(struct semihosting *semihosting, int fd)
+{
+       if (semihosting->redirect_cfg == SEMIHOSTING_REDIRECT_CFG_NONE)
+               return false;
+
+       bool is_read_op = false;
+
+       switch (semihosting->op) {
+       /* check debug semihosting operations: READC, WRITEC and WRITE0 */
+       case SEMIHOSTING_SYS_READC:
+               is_read_op = true;
+               /* fall through */
+       case SEMIHOSTING_SYS_WRITEC:
+       case SEMIHOSTING_SYS_WRITE0:
+               /* debug operations are redirected when CFG is either DEBUG or ALL */
+               if (semihosting->redirect_cfg == SEMIHOSTING_REDIRECT_CFG_STDIO)
+                       return false;
+               break;
+
+       /* check stdio semihosting operations: READ and WRITE */
+       case SEMIHOSTING_SYS_READ:
+               is_read_op = true;
+               /* fall through */
+       case SEMIHOSTING_SYS_WRITE:
+               /* stdio operations are redirected when CFG is either STDIO or ALL */
+               if (semihosting->redirect_cfg == SEMIHOSTING_REDIRECT_CFG_DEBUG)
+                       return false;
+               break;
+
+       default:
+               return false;
+       }
+
+       if (is_read_op)
+               return fd == semihosting->stdin_fd;
+
+       /* write operation */
+       return fd == semihosting->stdout_fd || fd == semihosting->stderr_fd;
+}
+
+static ssize_t semihosting_redirect_write(struct semihosting *semihosting, void *buf, int size)
+{
+       if (!semihosting->tcp_connection) {
+               LOG_ERROR("No connected TCP client for semihosting");
+               semihosting->sys_errno = EBADF; /* Bad file number */
+               return -1;
+       }
+
+       struct semihosting_tcp_service *service = semihosting->tcp_connection->service->priv;
+
+       int retval = connection_write(semihosting->tcp_connection, buf, size);
+
+       if (retval < 0)
+               log_socket_error(service->name);
+
+       return retval;
+}
+
+static ssize_t semihosting_write(struct semihosting *semihosting, int fd, void *buf, int size)
+{
+       if (semihosting_is_redirected(semihosting, fd))
+               return semihosting_redirect_write(semihosting, buf, size);
+
+       /* default write */
+       int result = write(fd, buf, size);
+       if (result == -1)
+               semihosting->sys_errno = errno;
+       return result;
+}
+
+static ssize_t semihosting_redirect_read(struct semihosting *semihosting, void *buf, int size)
+{
+       if (!semihosting->tcp_connection) {
+               LOG_ERROR("No connected TCP client for semihosting");
+               semihosting->sys_errno = EBADF; /* Bad file number */
+               return -1;
+       }
+
+       struct semihosting_tcp_service *service = semihosting->tcp_connection->service->priv;
+
+       service->error = ERROR_OK;
+       semihosting->tcp_connection->input_pending = true;
+
+       int retval = connection_read(semihosting->tcp_connection, buf, size);
+
+       if (retval <= 0)
+               service->error = ERROR_SERVER_REMOTE_CLOSED;
+
+       if (retval < 0)
+               log_socket_error(service->name);
+
+       semihosting->tcp_connection->input_pending = false;
+
+       return retval;
+}
+
+static inline int semihosting_putchar(struct semihosting *semihosting, int fd, int c)
+{
+       if (semihosting_is_redirected(semihosting, fd))
+               return semihosting_redirect_write(semihosting, &c, 1);
+
+       /* default putchar */
+       return putchar(c);
+}
+
+static inline ssize_t semihosting_read(struct semihosting *semihosting, int fd, void *buf, int size)
+{
+       if (semihosting_is_redirected(semihosting, fd))
+               return semihosting_redirect_read(semihosting, buf, size);
+
+       /* default read */
+       ssize_t result = read(fd, buf, size);
+       if (result == -1)
+               semihosting->sys_errno = errno;
+
+       return result;
+}
+
+static inline int semihosting_getchar(struct semihosting *semihosting, int fd)
+{
+       if (semihosting_is_redirected(semihosting, fd)) {
+               unsigned char c;
+
+               if (semihosting_redirect_read(semihosting, &c, 1) > 0)
+                       return c;
+
+               return EOF;
+       }
+
+       /* default getchar */
+       return getchar();
+}
+
+/**
+ * User operation parameter string storage buffer. Contains valid data when the
+ * TARGET_EVENT_SEMIHOSTING_USER_CMD_xxxxx event callbacks are running.
+ */
+static char *semihosting_user_op_params;
+
+const char *semihosting_opcode_to_str(const uint64_t opcode)
+{
+       switch (opcode) {
+               case SEMIHOSTING_SYS_CLOSE:
+                       return "CLOSE";
+               case SEMIHOSTING_SYS_CLOCK:
+                       return "CLOCK";
+               case SEMIHOSTING_SYS_ELAPSED:
+                       return "ELAPSED";
+               case SEMIHOSTING_SYS_ERRNO:
+                       return "ERRNO";
+               case SEMIHOSTING_SYS_EXIT:
+                       return "EXIT";
+               case SEMIHOSTING_SYS_EXIT_EXTENDED:
+                       return "EXIT_EXTENDED";
+               case SEMIHOSTING_SYS_FLEN:
+                       return "FLEN";
+               case SEMIHOSTING_SYS_GET_CMDLINE:
+                       return "GET_CMDLINE";
+               case SEMIHOSTING_SYS_HEAPINFO:
+                       return "HEAPINFO";
+               case SEMIHOSTING_SYS_ISERROR:
+                       return "ISERROR";
+               case SEMIHOSTING_SYS_ISTTY:
+                       return "ISTTY";
+               case SEMIHOSTING_SYS_OPEN:
+                       return "OPEN";
+               case SEMIHOSTING_SYS_READ:
+                       return "READ";
+               case SEMIHOSTING_SYS_READC:
+                       return "READC";
+               case SEMIHOSTING_SYS_REMOVE:
+                       return "REMOVE";
+               case SEMIHOSTING_SYS_RENAME:
+                       return "RENAME";
+               case SEMIHOSTING_SYS_SEEK:
+                       return "SEEK";
+               case SEMIHOSTING_SYS_SYSTEM:
+                       return "SYSTEM";
+               case SEMIHOSTING_SYS_TICKFREQ:
+                       return "TICKFREQ";
+               case SEMIHOSTING_SYS_TIME:
+                       return "TIME";
+               case SEMIHOSTING_SYS_TMPNAM:
+                       return "TMPNAM";
+               case SEMIHOSTING_SYS_WRITE:
+                       return "WRITE";
+               case SEMIHOSTING_SYS_WRITEC:
+                       return "WRITEC";
+               case SEMIHOSTING_SYS_WRITE0:
+                       return "WRITE0";
+               case SEMIHOSTING_USER_CMD_0X100 ... SEMIHOSTING_USER_CMD_0X1FF:
+                       return "USER_CMD";
+               case SEMIHOSTING_ARM_RESERVED_START ... SEMIHOSTING_ARM_RESERVED_END:
+                       return "ARM_RESERVED_CMD";
+               default:
+                       return "<unknown>";
+       }
+}
+
 /**
  * Portable implementation of ARM semihosting calls.
  * Performs the currently pending semihosting operation
@@ -167,8 +387,9 @@ int semihosting_common(struct target *target)
        /* Enough space to hold 4 long words. */
        uint8_t fields[4*8];
 
-       LOG_DEBUG("op=0x%x, param=0x%" PRIx64, (int)semihosting->op,
-               semihosting->param);
+       LOG_DEBUG("op=0x%x (%s), param=0x%" PRIx64, semihosting->op,
+                         semihosting_opcode_to_str(semihosting->op),
+                         semihosting->param);
 
        switch (semihosting->op) {
 
@@ -232,12 +453,7 @@ int semihosting_common(struct target *target)
                                                        (fd == 0) ? "stdin" :
                                                        (fd == 1) ? "stdout" : "stderr");
                                        /* Just pretend success */
-                                       if (semihosting->is_fileio) {
-                                               semihosting->result = 0;
-                                       } else {
-                                               semihosting->result = 0;
-                                               semihosting->sys_errno = 0;
-                                       }
+                                       semihosting->result = 0;
                                        break;
                                }
                                /* Close the descriptor */
@@ -247,8 +463,9 @@ int semihosting_common(struct target *target)
                                        fileio_info->param_1 = fd;
                                } else {
                                        semihosting->result = close(fd);
-                                       semihosting->sys_errno = errno;
-                                       LOG_DEBUG("close(%d)=%d", fd, (int)semihosting->result);
+                                       if (semihosting->result == -1)
+                                               semihosting->sys_errno = errno;
+                                       LOG_DEBUG("close(%d)=%" PRId64, fd, semihosting->result);
                                }
                        }
                        break;
@@ -335,7 +552,7 @@ int semihosting_common(struct target *target)
                                        int code = semihosting_get_field(target, 1, fields);
 
                                        if (type == ADP_STOPPED_APPLICATION_EXIT) {
-                                               if (!gdb_actual_connections)
+                                               if (!gdb_get_actual_connections())
                                                        exit(code);
                                                else {
                                                        fprintf(stderr,
@@ -350,7 +567,7 @@ int semihosting_common(struct target *target)
                                }
                        } else {
                                if (semihosting->param == ADP_STOPPED_APPLICATION_EXIT) {
-                                       if (!gdb_actual_connections)
+                                       if (!gdb_get_actual_connections())
                                                exit(0);
                                        else {
                                                fprintf(stderr,
@@ -359,14 +576,14 @@ int semihosting_common(struct target *target)
                                } else if (semihosting->param == ADP_STOPPED_RUN_TIME_ERROR) {
                                        /* Chosen more or less arbitrarily to have a nicer message,
                                         * otherwise all other return the same exit code 1. */
-                                       if (!gdb_actual_connections)
+                                       if (!gdb_get_actual_connections())
                                                exit(1);
                                        else {
                                                fprintf(stderr,
                                                        "semihosting: *** application exited with error ***\n");
                                        }
                                } else {
-                                       if (!gdb_actual_connections)
+                                       if (!gdb_get_actual_connections())
                                                exit(1);
                                        else {
                                                fprintf(stderr,
@@ -426,7 +643,7 @@ int semihosting_common(struct target *target)
                                int code = semihosting_get_field(target, 1, fields);
 
                                if (type == ADP_STOPPED_APPLICATION_EXIT) {
-                                       if (!gdb_actual_connections)
+                                       if (!gdb_get_actual_connections())
                                                exit(code);
                                        else {
                                                fprintf(stderr,
@@ -473,10 +690,10 @@ int semihosting_common(struct target *target)
                                semihosting->result = fstat(fd, &buf);
                                if (semihosting->result == -1) {
                                        semihosting->sys_errno = errno;
-                                       LOG_DEBUG("fstat(%d)=%d", fd, (int)semihosting->result);
+                                       LOG_DEBUG("fstat(%d)=%" PRId64, fd, semihosting->result);
                                        break;
                                }
-                               LOG_DEBUG("fstat(%d)=%d", fd, (int)semihosting->result);
+                               LOG_DEBUG("fstat(%d)=%" PRId64, fd, semihosting->result);
                                semihosting->result = buf.st_size;
                        }
                        break;
@@ -516,7 +733,7 @@ int semihosting_common(struct target *target)
                                uint64_t addr = semihosting_get_field(target, 0, fields);
                                size_t size = semihosting_get_field(target, 1, fields);
 
-                               char *arg = semihosting->cmdline != NULL ?
+                               char *arg = semihosting->cmdline ?
                                        semihosting->cmdline : "";
                                uint32_t len = strlen(arg) + 1;
                                if (len > size)
@@ -533,8 +750,7 @@ int semihosting_common(struct target *target)
                                        if (retval != ERROR_OK)
                                                return retval;
                                }
-                               LOG_DEBUG("SYS_GET_CMDLINE=[%s],%d", arg,
-                                       (int)semihosting->result);
+                               LOG_DEBUG("SYS_GET_CMDLINE=[%s], %" PRId64, arg, semihosting->result);
                        }
                        break;
 
@@ -624,9 +840,11 @@ int semihosting_common(struct target *target)
                                if (retval != ERROR_OK)
                                        return retval;
                                int fd = semihosting_get_field(target, 0, fields);
-                               semihosting->result = isatty(fd);
-                               semihosting->sys_errno = errno;
-                               LOG_DEBUG("isatty(%d)=%d", fd, (int)semihosting->result);
+                               // isatty() on Windows may return any non-zero value if fd is a terminal
+                               semihosting->result = isatty(fd) ? 1 : 0;
+                               if (semihosting->result == 0)
+                                       semihosting->sys_errno = errno;
+                               LOG_DEBUG("isatty(%d)=%" PRId64, fd, semihosting->result);
                        }
                        break;
 
@@ -693,17 +911,23 @@ int semihosting_common(struct target *target)
                                        semihosting->sys_errno = EINVAL;
                                        break;
                                }
-                               uint8_t *fn = malloc(len+1);
+                               size_t basedir_len = semihosting->basedir ? strlen(semihosting->basedir) : 0;
+                               uint8_t *fn = malloc(basedir_len + len + 2);
                                if (!fn) {
                                        semihosting->result = -1;
                                        semihosting->sys_errno = ENOMEM;
                                } else {
-                                       retval = target_read_memory(target, addr, 1, len, fn);
+                                       if (basedir_len > 0) {
+                                               strcpy((char *)fn, semihosting->basedir);
+                                               if (fn[basedir_len - 1] != '/')
+                                                       fn[basedir_len++] = '/';
+                                       }
+                                       retval = target_read_memory(target, addr, 1, len, fn + basedir_len);
                                        if (retval != ERROR_OK) {
                                                free(fn);
                                                return retval;
                                        }
-                                       fn[len] = 0;
+                                       fn[basedir_len + len] = 0;
                                        /* TODO: implement the :semihosting-features special file.
                                         * */
                                        if (semihosting->is_fileio) {
@@ -711,20 +935,22 @@ int semihosting_common(struct target *target)
                                                        semihosting->result = -1;
                                                        semihosting->sys_errno = EINVAL;
                                                } else if (strcmp((char *)fn, ":tt") == 0) {
-                                                       if (mode == 0)
+                                                       if (mode == 0) {
                                                                semihosting->result = 0;
-                                                       else if (mode == 4)
+                                                       } else if (mode == 4) {
                                                                semihosting->result = 1;
-                                                       else if (mode == 8)
+                                                       } else if (mode == 8) {
                                                                semihosting->result = 2;
-                                                       else
+                                                       } else {
                                                                semihosting->result = -1;
+                                                               semihosting->sys_errno = EINVAL;
+                                                       }
                                                } else {
                                                        semihosting->hit_fileio = true;
                                                        fileio_info->identifier = "open";
                                                        fileio_info->param_1 = addr;
                                                        fileio_info->param_2 = len;
-                                                       fileio_info->param_3 = open_modeflags[mode];
+                                                       fileio_info->param_3 = open_gdb_modeflags[mode];
                                                        fileio_info->param_4 = 0644;
                                                }
                                        } else {
@@ -733,35 +959,33 @@ int semihosting_common(struct target *target)
                                                         * - 0-3 ("r") for stdin,
                                                         * - 4-7 ("w") for stdout,
                                                         * - 8-11 ("a") for stderr */
+                                                       int fd;
                                                        if (mode < 4) {
-                                                               semihosting->result = dup(
-                                                                               STDIN_FILENO);
-                                                               semihosting->sys_errno = errno;
-                                                               LOG_DEBUG("dup(STDIN)=%d",
-                                                                       (int)semihosting->result);
+                                                               fd = dup(STDIN_FILENO);
+                                                               semihosting->stdin_fd = fd;
+                                                               LOG_DEBUG("dup(STDIN)=%d", fd);
                                                        } else if (mode < 8) {
-                                                               semihosting->result = dup(
-                                                                               STDOUT_FILENO);
-                                                               semihosting->sys_errno = errno;
-                                                               LOG_DEBUG("dup(STDOUT)=%d",
-                                                                       (int)semihosting->result);
+                                                               fd = dup(STDOUT_FILENO);
+                                                               semihosting->stdout_fd = fd;
+                                                               LOG_DEBUG("dup(STDOUT)=%d", fd);
                                                        } else {
-                                                               semihosting->result = dup(
-                                                                               STDERR_FILENO);
-                                                               semihosting->sys_errno = errno;
-                                                               LOG_DEBUG("dup(STDERR)=%d",
-                                                                       (int)semihosting->result);
+                                                               fd = dup(STDERR_FILENO);
+                                                               semihosting->stderr_fd = fd;
+                                                               LOG_DEBUG("dup(STDERR)=%d", fd);
                                                        }
+                                                       semihosting->result = fd;
+                                                       if (fd == -1)
+                                                               semihosting->sys_errno = errno;
                                                } else {
                                                        /* cygwin requires the permission setting
                                                         * otherwise it will fail to reopen a previously
                                                         * written file */
                                                        semihosting->result = open((char *)fn,
-                                                                       open_modeflags[mode],
+                                                                       open_host_modeflags[mode],
                                                                        0644);
-                                                       semihosting->sys_errno = errno;
-                                                       LOG_DEBUG("open('%s')=%d", fn,
-                                                               (int)semihosting->result);
+                                                       if (semihosting->result == -1)
+                                                               semihosting->sys_errno = errno;
+                                                       LOG_DEBUG("open('%s')=%" PRId64, fn, semihosting->result);
                                                }
                                        }
                                        free(fn);
@@ -823,13 +1047,12 @@ int semihosting_common(struct target *target)
                                                semihosting->result = -1;
                                                semihosting->sys_errno = ENOMEM;
                                        } else {
-                                               semihosting->result = read(fd, buf, len);
-                                               semihosting->sys_errno = errno;
-                                               LOG_DEBUG("read(%d, 0x%" PRIx64 ", %zu)=%d",
+                                               semihosting->result = semihosting_read(semihosting, fd, buf, len);
+                                               LOG_DEBUG("read(%d, 0x%" PRIx64 ", %zu)=%" PRId64,
                                                        fd,
                                                        addr,
                                                        len,
-                                                       (int)semihosting->result);
+                                                       semihosting->result);
                                                if (semihosting->result >= 0) {
                                                        retval = target_write_buffer(target, addr,
                                                                        semihosting->result,
@@ -864,8 +1087,8 @@ int semihosting_common(struct target *target)
                                LOG_ERROR("SYS_READC not supported by semihosting fileio");
                                return ERROR_FAIL;
                        }
-                       semihosting->result = getchar();
-                       LOG_DEBUG("getchar()=%d", (int)semihosting->result);
+                       semihosting->result = semihosting_getchar(semihosting, semihosting->stdin_fd);
+                       LOG_DEBUG("getchar()=%" PRId64, semihosting->result);
                        break;
 
                case SEMIHOSTING_SYS_REMOVE:    /* 0x0E */
@@ -910,9 +1133,9 @@ int semihosting_common(struct target *target)
                                                }
                                                fn[len] = 0;
                                                semihosting->result = remove((char *)fn);
-                                               semihosting->sys_errno = errno;
-                                               LOG_DEBUG("remove('%s')=%d", fn,
-                                                       (int)semihosting->result);
+                                               if (semihosting->result == -1)
+                                                       semihosting->sys_errno = errno;
+                                               LOG_DEBUG("remove('%s')=%" PRId64, fn, semihosting->result);
 
                                                free(fn);
                                        }
@@ -980,10 +1203,10 @@ int semihosting_common(struct target *target)
                                                fn2[len2] = 0;
                                                semihosting->result = rename((char *)fn1,
                                                                (char *)fn2);
-                                               semihosting->sys_errno = errno;
-                                               LOG_DEBUG("rename('%s', '%s')=%d", fn1, fn2,
-                                                       (int)semihosting->result);
-
+                                               // rename() on Windows returns nonzero on error
+                                               if (semihosting->result != 0)
+                                                       semihosting->sys_errno = errno;
+                                               LOG_DEBUG("rename('%s', '%s')=%" PRId64 " %d", fn1, fn2, semihosting->result, errno);
                                                free(fn1);
                                                free(fn2);
                                        }
@@ -1027,9 +1250,9 @@ int semihosting_common(struct target *target)
                                        fileio_info->param_3 = SEEK_SET;
                                } else {
                                        semihosting->result = lseek(fd, pos, SEEK_SET);
-                                       semihosting->sys_errno = errno;
-                                       LOG_DEBUG("lseek(%d, %d)=%d", fd, (int)pos,
-                                               (int)semihosting->result);
+                                       if (semihosting->result == -1)
+                                               semihosting->sys_errno = errno;
+                                       LOG_DEBUG("lseek(%d, %d)=%" PRId64, fd, (int)pos, semihosting->result);
                                        if (semihosting->result == pos)
                                                semihosting->result = 0;
                                }
@@ -1088,9 +1311,7 @@ int semihosting_common(struct target *target)
                                                        cmd[len] = 0;
                                                        semihosting->result = system(
                                                                        (const char *)cmd);
-                                                       LOG_DEBUG("system('%s')=%d",
-                                                               cmd,
-                                                               (int)semihosting->result);
+                                                       LOG_DEBUG("system('%s')=%" PRId64, cmd, semihosting->result);
                                                }
 
                                                free(cmd);
@@ -1167,13 +1388,12 @@ int semihosting_common(struct target *target)
                                                        free(buf);
                                                        return retval;
                                                }
-                                               semihosting->result = write(fd, buf, len);
-                                               semihosting->sys_errno = errno;
-                                               LOG_DEBUG("write(%d, 0x%" PRIx64 ", %zu)=%d",
+                                               semihosting->result = semihosting_write(semihosting, fd, buf, len);
+                                               LOG_DEBUG("write(%d, 0x%" PRIx64 ", %zu)=%" PRId64,
                                                        fd,
                                                        addr,
                                                        len,
-                                                       (int)semihosting->result);
+                                                       semihosting->result);
                                                if (semihosting->result >= 0) {
                                                        /* The number of bytes that are NOT written.
                                                         * */
@@ -1212,7 +1432,7 @@ int semihosting_common(struct target *target)
                                retval = target_read_memory(target, addr, 1, 1, &c);
                                if (retval != ERROR_OK)
                                        return retval;
-                               putchar(c);
+                               semihosting_putchar(semihosting, semihosting->stdout_fd, c);
                                semihosting->result = 0;
                        }
                        break;
@@ -1256,12 +1476,80 @@ int semihosting_common(struct target *target)
                                                return retval;
                                        if (!c)
                                                break;
-                                       putchar(c);
+                                       semihosting_putchar(semihosting, semihosting->stdout_fd, c);
                                } while (1);
                                semihosting->result = 0;
                        }
                        break;
 
+               case SEMIHOSTING_USER_CMD_0X100 ... SEMIHOSTING_USER_CMD_0X107:
+                       /**
+                        * This is a user defined operation (while user cmds 0x100-0x1ff
+                        * are possible, only 0x100-0x107 are currently implemented).
+                        *
+                        * Reads the user operation parameters from target, then fires the
+                        * corresponding target event. When the target callbacks returned,
+                        * cleans up the command parameter buffer.
+                        *
+                        * Entry
+                        * On entry, the PARAMETER REGISTER contains a pointer to a
+                        * two-field data block:
+                        * - field 1 Contains a pointer to the bound command parameter
+                        * string
+                        * - field 2 Contains the command parameter string length
+                        *
+                        * Return
+                        * On exit, the RETURN REGISTER contains the return status.
+                        */
+                       if (semihosting->user_command_extension) {
+                               retval = semihosting->user_command_extension(target);
+                               if (retval != ERROR_NOT_IMPLEMENTED)
+                                       break;
+                               /* If custom user command not handled, we are looking for the TCL handler */
+                       }
+
+                       assert(!semihosting_user_op_params);
+                       retval = semihosting_read_fields(target, 2, fields);
+                       if (retval != ERROR_OK) {
+                               LOG_ERROR("Failed to read fields for user defined command"
+                                               " op=0x%x", semihosting->op);
+                               return retval;
+                       }
+
+                       uint64_t addr = semihosting_get_field(target, 0, fields);
+
+                       size_t len = semihosting_get_field(target, 1, fields);
+                       if (len > SEMIHOSTING_MAX_TCL_COMMAND_FIELD_LENGTH) {
+                               LOG_ERROR("The maximum length for user defined command "
+                                               "parameter is %u, received length is %zu (op=0x%x)",
+                                               SEMIHOSTING_MAX_TCL_COMMAND_FIELD_LENGTH,
+                                               len,
+                                               semihosting->op);
+                               return ERROR_FAIL;
+                       }
+
+                       semihosting_user_op_params = malloc(len + 1);
+                       if (!semihosting_user_op_params)
+                               return ERROR_FAIL;
+                       semihosting_user_op_params[len] = 0;
+
+                       retval = target_read_buffer(target, addr, len,
+                                       (uint8_t *)(semihosting_user_op_params));
+                       if (retval != ERROR_OK) {
+                               LOG_ERROR("Failed to read from target, semihosting op=0x%x (%s)",
+                                               semihosting->op,
+                                               semihosting_opcode_to_str(semihosting->op));
+                               free(semihosting_user_op_params);
+                               semihosting_user_op_params = NULL;
+                               return retval;
+                       }
+
+                       target_handle_event(target, semihosting->op);
+                       free(semihosting_user_op_params);
+                       semihosting_user_op_params = NULL;
+                       semihosting->result = 0;
+                       break;
+
                case SEMIHOSTING_SYS_ELAPSED:   /* 0x30 */
                /*
                 * Returns the number of elapsed target ticks since execution
@@ -1388,7 +1676,6 @@ static int semihosting_common_fileio_end(struct target *target, int result,
        semihosting->hit_fileio = false;
 
        semihosting->result = result;
-       semihosting->sys_errno = fileio_errno;
 
        /*
         * Some fileio results do not match up with what the semihosting
@@ -1397,17 +1684,11 @@ static int semihosting_common_fileio_end(struct target *target, int result,
         */
        switch (semihosting->op) {
                case SEMIHOSTING_SYS_WRITE:     /* 0x05 */
+               case SEMIHOSTING_SYS_READ:      /* 0x06 */
                        if (result < 0)
-                               semihosting->result = fileio_info->param_3;
+                               semihosting->result = fileio_info->param_3;  /* Zero bytes read/written. */
                        else
-                               semihosting->result = 0;
-                       break;
-
-               case SEMIHOSTING_SYS_READ:      /* 0x06 */
-                       if (result == (int)fileio_info->param_3)
-                               semihosting->result = 0;
-                       if (result <= 0)
-                               semihosting->result = fileio_info->param_3;
+                               semihosting->result = (int64_t)fileio_info->param_3 - result;
                        break;
 
                case SEMIHOSTING_SYS_SEEK:      /* 0x0a */
@@ -1416,13 +1697,27 @@ static int semihosting_common_fileio_end(struct target *target, int result,
                        break;
        }
 
+       bool fileio_failed = false;
+       if (semihosting->op == SEMIHOSTING_SYS_ISTTY)
+               fileio_failed = (semihosting->result == 0);
+       else if (semihosting->op == SEMIHOSTING_SYS_RENAME)
+               fileio_failed = (semihosting->result != 0);
+       else
+               fileio_failed = (semihosting->result == -1);
+
+       if (fileio_failed)
+               semihosting->sys_errno = fileio_errno;
+
        return semihosting->post_result(target);
 }
 
+/* -------------------------------------------------------------------------
+ * Utility functions. */
+
 /**
  * Read all fields of a command from target to buffer.
  */
-static int semihosting_read_fields(struct target *target, size_t number,
+int semihosting_read_fields(struct target *target, size_t number,
        uint8_t *fields)
 {
        struct semihosting *semihosting = target->semihosting;
@@ -1434,7 +1729,7 @@ static int semihosting_read_fields(struct target *target, size_t number,
 /**
  * Write all fields of a command from buffer to target.
  */
-static int semihosting_write_fields(struct target *target, size_t number,
+int semihosting_write_fields(struct target *target, size_t number,
        uint8_t *fields)
 {
        struct semihosting *semihosting = target->semihosting;
@@ -1446,7 +1741,7 @@ static int semihosting_write_fields(struct target *target, size_t number,
 /**
  * Extract a field from the buffer, considering register size and endianness.
  */
-static uint64_t semihosting_get_field(struct target *target, size_t index,
+uint64_t semihosting_get_field(struct target *target, size_t index,
        uint8_t *fields)
 {
        struct semihosting *semihosting = target->semihosting;
@@ -1459,7 +1754,7 @@ static uint64_t semihosting_get_field(struct target *target, size_t index,
 /**
  * Store a field in the buffer, considering register size and endianness.
  */
-static void semihosting_set_field(struct target *target, uint64_t value,
+void semihosting_set_field(struct target *target, uint64_t value,
        size_t index,
        uint8_t *fields)
 {
@@ -1470,15 +1765,79 @@ static void semihosting_set_field(struct target *target, uint64_t value,
                target_buffer_set_u32(target, fields + (index * 4), value);
 }
 
+/* -------------------------------------------------------------------------
+ * Semihosting redirect over TCP structs and functions */
+
+static int semihosting_service_new_connection_handler(struct connection *connection)
+{
+       struct semihosting_tcp_service *service = connection->service->priv;
+       service->semihosting->tcp_connection = connection;
+
+       return ERROR_OK;
+}
+
+static int semihosting_service_input_handler(struct connection *connection)
+{
+       struct semihosting_tcp_service *service = connection->service->priv;
+
+       if (!connection->input_pending) {
+               /* consume received data, not for semihosting IO */
+               const int buf_len = 100;
+               char buf[buf_len];
+               int bytes_read = connection_read(connection, buf, buf_len);
+
+               if (bytes_read == 0) {
+                       return ERROR_SERVER_REMOTE_CLOSED;
+               } else if (bytes_read == -1) {
+                       LOG_ERROR("error during read: %s", strerror(errno));
+                       return ERROR_SERVER_REMOTE_CLOSED;
+               }
+       } else if (service->error != ERROR_OK) {
+               return ERROR_SERVER_REMOTE_CLOSED;
+       }
+
+       return ERROR_OK;
+}
+
+static int semihosting_service_connection_closed_handler(struct connection *connection)
+{
+       struct semihosting_tcp_service *service = connection->service->priv;
+       if (service) {
+               free(service->name);
+               free(service);
+       }
+
+       return ERROR_OK;
+}
+
+static void semihosting_tcp_close_cnx(struct semihosting *semihosting)
+{
+       if (!semihosting->tcp_connection)
+               return;
+
+       struct service *service = semihosting->tcp_connection->service;
+       remove_service(service->name, service->port);
+       semihosting->tcp_connection = NULL;
+
+}
+
+static const struct service_driver semihosting_service_driver = {
+       .name = "semihosting",
+       .new_connection_during_keep_alive_handler = NULL,
+       .new_connection_handler = semihosting_service_new_connection_handler,
+       .input_handler = semihosting_service_input_handler,
+       .connection_closed_handler = semihosting_service_connection_closed_handler,
+       .keep_client_alive_handler = NULL,
+};
 
 /* -------------------------------------------------------------------------
  * Common semihosting commands handlers. */
 
-static __COMMAND_HANDLER(handle_common_semihosting_command)
+COMMAND_HANDLER(handle_common_semihosting_command)
 {
        struct target *target = get_current_target(CMD_CTX);
 
-       if (target == NULL) {
+       if (!target) {
                LOG_ERROR("No target selected");
                return ERROR_FAIL;
        }
@@ -1515,11 +1874,96 @@ static __COMMAND_HANDLER(handle_common_semihosting_command)
        return ERROR_OK;
 }
 
-static __COMMAND_HANDLER(handle_common_semihosting_fileio_command)
+COMMAND_HANDLER(handle_common_semihosting_redirect_command)
+{
+       struct target *target = get_current_target(CMD_CTX);
+
+       if (!target) {
+               LOG_ERROR("No target selected");
+               return ERROR_FAIL;
+       }
+
+       struct semihosting *semihosting = target->semihosting;
+       if (!semihosting) {
+               command_print(CMD, "semihosting not supported for current target");
+               return ERROR_FAIL;
+       }
+
+       if (!semihosting->is_active) {
+               command_print(CMD, "semihosting not yet enabled for current target");
+               return ERROR_FAIL;
+       }
+
+       enum semihosting_redirect_config cfg;
+       const char *port;
+
+       if (CMD_ARGC < 1)
+               return ERROR_COMMAND_SYNTAX_ERROR;
+
+       if (strcmp(CMD_ARGV[0], "disable") == 0) {
+               cfg = SEMIHOSTING_REDIRECT_CFG_NONE;
+               if (CMD_ARGC > 1)
+                       return ERROR_COMMAND_SYNTAX_ERROR;
+       } else if (strcmp(CMD_ARGV[0], "tcp") == 0) {
+               if (CMD_ARGC < 2 || CMD_ARGC > 3)
+                       return ERROR_COMMAND_SYNTAX_ERROR;
+
+               port = CMD_ARGV[1];
+
+               cfg = SEMIHOSTING_REDIRECT_CFG_ALL;
+               if (CMD_ARGC == 3) {
+                       if (strcmp(CMD_ARGV[2], "debug") == 0)
+                               cfg = SEMIHOSTING_REDIRECT_CFG_DEBUG;
+                       else if (strcmp(CMD_ARGV[2], "stdio") == 0)
+                               cfg = SEMIHOSTING_REDIRECT_CFG_STDIO;
+                       else if (strcmp(CMD_ARGV[2], "all") != 0)
+                               return ERROR_COMMAND_SYNTAX_ERROR;
+               }
+       } else {
+               return ERROR_COMMAND_SYNTAX_ERROR;
+       }
+
+       semihosting_tcp_close_cnx(semihosting);
+       semihosting->redirect_cfg = SEMIHOSTING_REDIRECT_CFG_NONE;
+
+       if (cfg != SEMIHOSTING_REDIRECT_CFG_NONE) {
+               struct semihosting_tcp_service *service =
+                               calloc(1, sizeof(struct semihosting_tcp_service));
+               if (!service) {
+                       LOG_ERROR("Failed to allocate semihosting TCP service.");
+                       return ERROR_FAIL;
+               }
+
+               service->semihosting = semihosting;
+
+               service->name = alloc_printf("%s semihosting service", target_name(target));
+               if (!service->name) {
+                       LOG_ERROR("Out of memory");
+                       free(service);
+                       return ERROR_FAIL;
+               }
+
+               int ret = add_service(&semihosting_service_driver,
+                               port, 1, service);
+
+               if (ret != ERROR_OK) {
+                       LOG_ERROR("failed to initialize %s", service->name);
+                       free(service->name);
+                       free(service);
+                       return ERROR_FAIL;
+               }
+       }
+
+       semihosting->redirect_cfg = cfg;
+
+       return ERROR_OK;
+}
+
+COMMAND_HANDLER(handle_common_semihosting_fileio_command)
 {
        struct target *target = get_current_target(CMD_CTX);
 
-       if (target == NULL) {
+       if (!target) {
                LOG_ERROR("No target selected");
                return ERROR_FAIL;
        }
@@ -1545,12 +1989,12 @@ static __COMMAND_HANDLER(handle_common_semihosting_fileio_command)
        return ERROR_OK;
 }
 
-static __COMMAND_HANDLER(handle_common_semihosting_cmdline)
+COMMAND_HANDLER(handle_common_semihosting_cmdline)
 {
        struct target *target = get_current_target(CMD_CTX);
        unsigned int i;
 
-       if (target == NULL) {
+       if (!target) {
                LOG_ERROR("No target selected");
                return ERROR_FAIL;
        }
@@ -1566,7 +2010,7 @@ static __COMMAND_HANDLER(handle_common_semihosting_cmdline)
 
        for (i = 1; i < CMD_ARGC; i++) {
                char *cmdline = alloc_printf("%s %s", semihosting->cmdline, CMD_ARGV[i]);
-               if (cmdline == NULL)
+               if (!cmdline)
                        break;
                free(semihosting->cmdline);
                semihosting->cmdline = cmdline;
@@ -1578,11 +2022,11 @@ static __COMMAND_HANDLER(handle_common_semihosting_cmdline)
        return ERROR_OK;
 }
 
-static __COMMAND_HANDLER(handle_common_semihosting_resumable_exit_command)
+COMMAND_HANDLER(handle_common_semihosting_resumable_exit_command)
 {
        struct target *target = get_current_target(CMD_CTX);
 
-       if (target == NULL) {
+       if (!target) {
                LOG_ERROR("No target selected");
                return ERROR_FAIL;
        }
@@ -1608,34 +2052,117 @@ static __COMMAND_HANDLER(handle_common_semihosting_resumable_exit_command)
        return ERROR_OK;
 }
 
+COMMAND_HANDLER(handle_common_semihosting_read_user_param_command)
+{
+       struct target *target = get_current_target(CMD_CTX);
+       struct semihosting *semihosting = target->semihosting;
+
+       if (CMD_ARGC)
+               return ERROR_COMMAND_SYNTAX_ERROR;
+
+       if (!semihosting->is_active) {
+               LOG_ERROR("semihosting not yet enabled for current target");
+               return ERROR_FAIL;
+       }
+
+       if (!semihosting_user_op_params) {
+               LOG_ERROR("This command is usable only from a registered user "
+                               "semihosting event callback.");
+               return ERROR_FAIL;
+       }
+
+       command_print_sameline(CMD, "%s", semihosting_user_op_params);
+
+       return ERROR_OK;
+}
+
+COMMAND_HANDLER(handle_common_semihosting_basedir_command)
+{
+       struct target *target = get_current_target(CMD_CTX);
+
+       if (CMD_ARGC > 1)
+               return ERROR_COMMAND_SYNTAX_ERROR;
+
+       if (!target) {
+               LOG_ERROR("No target selected");
+               return ERROR_FAIL;
+       }
+
+       struct semihosting *semihosting = target->semihosting;
+       if (!semihosting) {
+               command_print(CMD, "semihosting not supported for current target");
+               return ERROR_FAIL;
+       }
+
+       if (!semihosting->is_active) {
+               command_print(CMD, "semihosting not yet enabled for current target");
+               return ERROR_FAIL;
+       }
+
+       if (CMD_ARGC > 0) {
+               free(semihosting->basedir);
+               semihosting->basedir = strdup(CMD_ARGV[0]);
+               if (!semihosting->basedir) {
+                       command_print(CMD, "semihosting failed to allocate memory for basedir!");
+                       return ERROR_FAIL;
+               }
+       }
+
+       command_print(CMD, "semihosting base dir: %s",
+               semihosting->basedir ? semihosting->basedir : "");
+
+       return ERROR_OK;
+}
+
 const struct command_registration semihosting_common_handlers[] = {
        {
-               "semihosting",
+               .name = "semihosting",
                .handler = handle_common_semihosting_command,
                .mode = COMMAND_EXEC,
                .usage = "['enable'|'disable']",
                .help = "activate support for semihosting operations",
        },
        {
-               "semihosting_cmdline",
+               .name = "semihosting_redirect",
+               .handler = handle_common_semihosting_redirect_command,
+               .mode = COMMAND_EXEC,
+               .usage = "(disable | tcp <port> ['debug'|'stdio'|'all'])",
+               .help = "redirect semihosting IO",
+       },
+       {
+               .name = "semihosting_cmdline",
                .handler = handle_common_semihosting_cmdline,
                .mode = COMMAND_EXEC,
                .usage = "arguments",
                .help = "command line arguments to be passed to program",
        },
        {
-               "semihosting_fileio",
+               .name = "semihosting_fileio",
                .handler = handle_common_semihosting_fileio_command,
                .mode = COMMAND_EXEC,
                .usage = "['enable'|'disable']",
                .help = "activate support for semihosting fileio operations",
        },
        {
-               "semihosting_resexit",
+               .name = "semihosting_resexit",
                .handler = handle_common_semihosting_resumable_exit_command,
                .mode = COMMAND_EXEC,
                .usage = "['enable'|'disable']",
                .help = "activate support for semihosting resumable exit",
        },
+       {
+               .name = "semihosting_read_user_param",
+               .handler = handle_common_semihosting_read_user_param_command,
+               .mode = COMMAND_EXEC,
+               .usage = "",
+               .help = "read parameters in semihosting-user-cmd-0x10X callbacks",
+       },
+       {
+               .name = "semihosting_basedir",
+               .handler = handle_common_semihosting_basedir_command,
+               .mode = COMMAND_EXEC,
+               .usage = "[dir]",
+               .help = "set the base directory for semihosting I/O operations",
+       },
        COMMAND_REGISTRATION_DONE
 };

Linking to existing account procedure

If you already have an account and want to add another login method you MUST first sign in with your existing account and then change URL to read https://review.openocd.org/login/?link to get to this page again but this time it'll work for linking. Thank you.

SSH host keys fingerprints

1024 SHA256:YKx8b7u5ZWdcbp7/4AeXNaqElP49m6QrwfXaqQGJAOk gerrit-code-review@openocd.zylin.com (DSA)
384 SHA256:jHIbSQa4REvwCFG4cq5LBlBLxmxSqelQPem/EXIrxjk gerrit-code-review@openocd.org (ECDSA)
521 SHA256:UAOPYkU9Fjtcao0Ul/Rrlnj/OsQvt+pgdYSZ4jOYdgs gerrit-code-review@openocd.org (ECDSA)
256 SHA256:A13M5QlnozFOvTllybRZH6vm7iSt0XLxbA48yfc2yfY gerrit-code-review@openocd.org (ECDSA)
256 SHA256:spYMBqEYoAOtK7yZBrcwE8ZpYt6b68Cfh9yEVetvbXg gerrit-code-review@openocd.org (ED25519)
+--[ED25519 256]--+
|=..              |
|+o..   .         |
|*.o   . .        |
|+B . . .         |
|Bo. = o S        |
|Oo.+ + =         |
|oB=.* = . o      |
| =+=.+   + E     |
|. .=o   . o      |
+----[SHA256]-----+
2048 SHA256:0Onrb7/PHjpo6iVZ7xQX2riKN83FJ3KGU0TvI0TaFG4 gerrit-code-review@openocd.zylin.com (RSA)