+// 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 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.
*
semihosting->setup = setup;
semihosting->post_result = post_result;
+ semihosting->user_command_extension = NULL;
target->semihosting = semihosting;
return semihosting_redirect_write(semihosting, buf, size);
/* default write */
- return write(fd, buf, size);
+ 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)
/* default read */
ssize_t result = read(fd, buf, size);
- semihosting->sys_errno = errno;
+ if (result == -1)
+ semihosting->sys_errno = errno;
return result;
}
*/
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
/* Enough space to hold 4 long words. */
uint8_t fields[4*8];
- LOG_DEBUG("op=0x%x, param=0x%" PRIx64, 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) {
(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 */
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;
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,
}
} else {
if (semihosting->param == ADP_STOPPED_APPLICATION_EXIT) {
- if (!gdb_actual_connections)
+ if (!gdb_get_actual_connections())
exit(0);
else {
fprintf(stderr,
} 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,
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,
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;
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;
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;
semihosting->result = -1;
semihosting->sys_errno = ENOMEM;
} else {
- strncpy((char *)fn, semihosting->basedir, basedir_len);
- if (fn[basedir_len - 1] != '/')
- fn[basedir_len++] = '/';
+ 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);
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";
* - 0-3 ("r") for stdin,
* - 4-7 ("w") for stdout,
* - 8-11 ("a") for stderr */
+ int fd;
if (mode < 4) {
- int fd = dup(STDIN_FILENO);
- semihosting->result = fd;
+ fd = dup(STDIN_FILENO);
semihosting->stdin_fd = fd;
- semihosting->sys_errno = errno;
- LOG_DEBUG("dup(STDIN)=%d",
- (int)semihosting->result);
+ LOG_DEBUG("dup(STDIN)=%d", fd);
} else if (mode < 8) {
- int fd = dup(STDOUT_FILENO);
- semihosting->result = fd;
+ fd = dup(STDOUT_FILENO);
semihosting->stdout_fd = fd;
- semihosting->sys_errno = errno;
- LOG_DEBUG("dup(STDOUT)=%d",
- (int)semihosting->result);
+ LOG_DEBUG("dup(STDOUT)=%d", fd);
} else {
- int fd = dup(STDERR_FILENO);
- semihosting->result = fd;
+ fd = dup(STDERR_FILENO);
semihosting->stderr_fd = fd;
- semihosting->sys_errno = errno;
- LOG_DEBUG("dup(STDERR)=%d",
- (int)semihosting->result);
+ 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
semihosting->result = open((char *)fn,
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);
semihosting->sys_errno = ENOMEM;
} else {
semihosting->result = semihosting_read(semihosting, fd, buf, len);
- LOG_DEBUG("read(%d, 0x%" PRIx64 ", %zu)=%d",
+ 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,
return ERROR_FAIL;
}
semihosting->result = semihosting_getchar(semihosting, semihosting->stdin_fd);
- LOG_DEBUG("getchar()=%d", (int)semihosting->result);
+ LOG_DEBUG("getchar()=%" PRId64, semihosting->result);
break;
case SEMIHOSTING_SYS_REMOVE: /* 0x0E */
}
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);
}
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);
}
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;
}
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);
return retval;
}
semihosting->result = semihosting_write(semihosting, fd, buf, len);
- semihosting->sys_errno = errno;
- LOG_DEBUG("write(%d, 0x%" PRIx64 ", %zu)=%d",
+ 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.
* */
}
break;
- case SEMIHOSTING_USER_CMD_0x100 ... SEMIHOSTING_USER_CMD_0x107:
+ 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).
* Return
* On exit, the RETURN REGISTER contains the return status.
*/
- {
- assert(!semihosting_user_op_params);
+ 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"
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",
- semihosting->op);
+ 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 */
/*
semihosting->hit_fileio = false;
semihosting->result = result;
- semihosting->sys_errno = fileio_errno;
/*
* Some fileio results do not match up with what the semihosting
*/
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 */
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;
/**
* 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;
/**
* 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;
/**
* 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)
{
{
struct target *target = get_current_target(CMD_CTX);
- if (target == NULL) {
+ if (!target) {
LOG_ERROR("No target selected");
return ERROR_FAIL;
}