From ed59dfc80aa6fc48a0894c8e46cee675f38ac949 Mon Sep 17 00:00:00 2001 From: Nicolas Pitre Date: Thu, 3 Dec 2009 17:27:13 -0500 Subject: [PATCH] basic ARM semihosting support Semihosting enables code running on an ARM target to use the I/O facilities on the host computer. The target application must be linked against a library that forwards operation requests by using the SVC instruction that is trapped at the Supervisor Call vector by the debugger. The "hosted" library version provided with CodeSourcery's Sourcery G++ Lite for ARM EABI is one example. This is currently available for ARM9 processors, but any ARM variant should be able to support this with little additional work. Tested using binaries compiled with Sourcery G++ Lite 2009q1-161 and ARM RVCT 3.0. [dbrownell@users.sourceforge.net: doc tweaks, NEWS] Signed-off-by: Nicolas Pitre Signed-off-by: David Brownell --- NEWS | 1 + doc/openocd.texi | 11 + src/target/Makefile.am | 2 + src/target/arm7_9_common.c | 44 ++++ src/target/arm_semihosting.c | 442 +++++++++++++++++++++++++++++++++++ src/target/arm_semihosting.h | 28 +++ 6 files changed, 528 insertions(+) create mode 100644 src/target/arm_semihosting.c create mode 100644 src/target/arm_semihosting.h diff --git a/NEWS b/NEWS index 1af13311..38ae4da6 100644 --- a/NEWS +++ b/NEWS @@ -17,6 +17,7 @@ Target Layer: - register names use "sp" not "r13" - add top-level "mcr" and "mrc" commands, replacing various core-specific operations + - basic semihosting support ARM11 - Preliminary ETM and ETB hookup - accelerated "flash erase_check" diff --git a/doc/openocd.texi b/doc/openocd.texi index 7e232117..4d13f073 100644 --- a/doc/openocd.texi +++ b/doc/openocd.texi @@ -5674,6 +5674,17 @@ cables (FT2232), but might be unsafe if used with targets running at very low speeds, like the 32kHz startup clock of an AT91RM9200. @end deffn +@deffn Command {arm7_9 semihosting} [@option{enable}|@option{disable}] +Display status of semihosting, after optionally changing that status. + +Semihosting allows for code executing on an ARM target to use the +I/O facilities on the host computer i.e. the system where OpenOCD +is running. The target application must be linked against a library +implementing the ARM semihosting convention that forwards operation +requests by using a special SVC instruction that is trapped at the +Supervisor Call vector by OpenOCD. +@end deffn + @subsection ARM720T specific commands @cindex ARM720T diff --git a/src/target/Makefile.am b/src/target/Makefile.am index 7631beaa..bd7bf7ae 100644 --- a/src/target/Makefile.am +++ b/src/target/Makefile.am @@ -78,6 +78,7 @@ ARM_DEBUG_SRC = \ arm_jtag.c \ arm_disassembler.c \ arm_simulator.c \ + arm_semihosting.c \ arm_adi_v5.c \ embeddedice.c \ trace.c \ @@ -101,6 +102,7 @@ noinst_HEADERS = \ arm_adi_v5.h \ arm_disassembler.h \ arm_simulator.h \ + arm_semihosting.h \ arm7_9_common.h \ arm7tdmi.h \ arm720t.h \ diff --git a/src/target/arm7_9_common.c b/src/target/arm7_9_common.c index 255a85f5..7318b5f3 100644 --- a/src/target/arm7_9_common.c +++ b/src/target/arm7_9_common.c @@ -36,6 +36,7 @@ #include "etm.h" #include #include "arm_simulator.h" +#include "arm_semihosting.h" #include "algorithm.h" #include "register.h" @@ -915,6 +916,9 @@ int arm7_9_poll(struct target *target) } } + if (arm_semihosting(target, &retval) != 0) + return retval; + if ((retval = target_call_event_callbacks(target, TARGET_EVENT_HALTED)) != ERROR_OK) { return retval; @@ -2814,6 +2818,39 @@ COMMAND_HANDLER(handle_arm7_9_dcc_downloads_command) return ERROR_OK; } +COMMAND_HANDLER(handle_arm7_9_semihosting_command) +{ + struct target *target = get_current_target(CMD_CTX); + struct arm7_9_common *arm7_9 = target_to_arm7_9(target); + + if (!is_arm7_9(arm7_9)) + { + command_print(CMD_CTX, "current target isn't an ARM7/ARM9 target"); + return ERROR_TARGET_INVALID; + } + + if (CMD_ARGC > 0) + { + COMMAND_PARSE_ENABLE(CMD_ARGV[0], semihosting_active); + + /* TODO: support other methods if vector catch is unavailable */ + if (arm7_9->has_vector_catch) { + struct reg *vector_catch = &arm7_9->eice_cache->reg_list[EICE_VEC_CATCH]; + if (!vector_catch->valid) + embeddedice_read_reg(vector_catch); + buf_set_u32(vector_catch->value, 2, 1, semihosting_active); + embeddedice_store_reg(vector_catch); + } else if (semihosting_active) { + command_print(CMD_CTX, "vector catch unavailable"); + semihosting_active = 0; + } + } + + command_print(CMD_CTX, "semihosting is %s", (semihosting_active) ? "enabled" : "disabled"); + + return ERROR_OK; +} + int arm7_9_init_arch_info(struct target *target, struct arm7_9_common *arm7_9) { int retval = ERROR_OK; @@ -2867,6 +2904,13 @@ static const struct command_registration arm7_9_any_command_handlers[] = { .usage = "", .help = "use DCC downloads for larger memory writes", }, + { + "semihosting", + .handler = &handle_arm7_9_semihosting_command, + .mode = COMMAND_EXEC, + .usage = "", + .help = "activate support for semihosting operations", + }, COMMAND_REGISTRATION_DONE }; const struct command_registration arm7_9_command_handlers[] = { diff --git a/src/target/arm_semihosting.c b/src/target/arm_semihosting.c new file mode 100644 index 00000000..5e0a2bed --- /dev/null +++ b/src/target/arm_semihosting.c @@ -0,0 +1,442 @@ +/*************************************************************************** + * Copyright (C) 2009 by Marvell Technology Group Ltd. * + * Written by Nicolas Pitre * + * * + * 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, write to the * + * Free Software Foundation, Inc., * + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * + ***************************************************************************/ + +/** + * @file + * Hold ARM semihosting support. + * + * Semihosting enables code running on an ARM target to use the I/O + * facilities on the host computer. The target application must be linked + * against a library that forwards operation requests by using the SVC + * instruction trapped at the Supervisor Call vector by the debugger. + * Details can be found in chapter 8 of DUI0203I_rvct_developer_guide.pdf + * from ARM Ltd. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "armv4_5.h" +#include "register.h" +#include "arm_semihosting.h" +#include +#include + +/* TODO: this needs to be per target */ +int semihosting_active; +int semihosting_errno; + +static int do_semihosting(struct target *target) +{ + struct arm *armv4_5 = target_to_armv4_5(target); + uint32_t r0 = buf_get_u32(armv4_5->core_cache->reg_list[0].value, 0, 32); + uint32_t r1 = buf_get_u32(armv4_5->core_cache->reg_list[1].value, 0, 32); + uint32_t lr = buf_get_u32(ARMV4_5_CORE_REG_MODE(armv4_5->core_cache, ARMV4_5_MODE_SVC, 14).value, 0, 32); + uint32_t spsr = buf_get_u32(armv4_5->spsr->value, 0, 32);; + uint8_t params[16]; + int retval, result; + + /* + * TODO: lots of security issues are not considered yet, such as: + * - no validation on target provided file descriptors + * - no safety checks on opened/deleted/renamed file paths + * Beware the target app you use this support with. + */ + switch (r0) { + case 0x01: /* SYS_OPEN */ + retval = target_read_memory(target, r1, 4, 3, params); + if (retval != ERROR_OK) + return retval; + else { + uint32_t a = target_buffer_get_u32(target, params+0); + uint32_t m = target_buffer_get_u32(target, params+4); + uint32_t l = target_buffer_get_u32(target, params+8); + if (l <= 255 && m <= 11) { + uint8_t fn[256]; + int mode; + retval = target_read_memory(target, a, 1, l, fn); + if (retval != ERROR_OK) + return retval; + fn[l] = 0; + if (m & 0x2) + mode = O_RDWR; + else if (m & 0xc) + mode = O_WRONLY; + else + mode = O_RDONLY; + if (m >= 8) + mode |= O_CREAT|O_APPEND; + else if (m >= 4) + mode |= O_CREAT|O_TRUNC; + if (strcmp((char *)fn, ":tt") == 0) { + if ((mode & 3) == 0) + result = dup(0); + else + result = dup(1); + } else + result = open((char *)fn, mode); + semihosting_errno = errno; + } else { + result = -1; + semihosting_errno = EINVAL; + } + } + break; + + case 0x02: /* SYS_CLOSE */ + retval = target_read_memory(target, r1, 4, 1, params); + if (retval != ERROR_OK) + return retval; + else { + int fd = target_buffer_get_u32(target, params+0); + result = close(fd); + semihosting_errno = errno; + } + break; + + case 0x03: /* SYS_WRITEC */ + { + unsigned char c; + retval = target_read_memory(target, r1, 1, 1, &c); + if (retval != ERROR_OK) + return retval; + putchar(c); + result = 0; + } + break; + + case 0x04: /* SYS_WRITE0 */ + do { + unsigned char c; + retval = target_read_memory(target, r1, 1, 1, &c); + if (retval != ERROR_OK) + return retval; + if (!c) + break; + putchar(c); + } while (1); + result = 0; + break; + + case 0x05: /* SYS_WRITE */ + retval = target_read_memory(target, r1, 4, 3, params); + if (retval != ERROR_OK) + return retval; + else { + int fd = target_buffer_get_u32(target, params+0); + uint32_t a = target_buffer_get_u32(target, params+4); + size_t l = target_buffer_get_u32(target, params+8); + uint8_t *buf = malloc(l); + if (!buf) { + result = -1; + semihosting_errno = ENOMEM; + } else { + retval = target_read_buffer(target, a, l, buf); + if (retval != ERROR_OK) { + free(buf); + return retval; + } + result = write(fd, buf, l); + semihosting_errno = errno; + if (result >= 0) + result = l - result; + free(buf); + } + } + break; + + case 0x06: /* SYS_READ */ + retval = target_read_memory(target, r1, 4, 3, params); + if (retval != ERROR_OK) + return retval; + else { + int fd = target_buffer_get_u32(target, params+0); + uint32_t a = target_buffer_get_u32(target, params+4); + ssize_t l = target_buffer_get_u32(target, params+8); + uint8_t *buf = malloc(l); + if (!buf) { + result = -1; + semihosting_errno = ENOMEM; + } else { + result = read(fd, buf, l); + semihosting_errno = errno; + if (result > 0) { + retval = target_write_buffer(target, a, result, buf); + if (retval != ERROR_OK) { + free(buf); + return retval; + } + result = l - result; + } + free(buf); + } + } + break; + + case 0x07: /* SYS_READC */ + result = getchar(); + break; + + case 0x08: /* SYS_ISERROR */ + retval = target_read_memory(target, r1, 4, 1, params); + if (retval != ERROR_OK) + return retval; + result = (target_buffer_get_u32(target, params+0) != 0); + break; + + case 0x09: /* SYS_ISTTY */ + retval = target_read_memory(target, r1, 4, 1, params); + if (retval != ERROR_OK) + return retval; + result = isatty(target_buffer_get_u32(target, params+0)); + break; + + case 0x0a: /* SYS_SEEK */ + retval = target_read_memory(target, r1, 4, 2, params); + if (retval != ERROR_OK) + return retval; + else { + int fd = target_buffer_get_u32(target, params+0); + off_t pos = target_buffer_get_u32(target, params+4); + result = lseek(fd, pos, SEEK_SET); + semihosting_errno = errno; + if (result == pos) + result = 0; + } + break; + + case 0x0c: /* SYS_FLEN */ + retval = target_read_memory(target, r1, 4, 1, params); + if (retval != ERROR_OK) + return retval; + else { + int fd = target_buffer_get_u32(target, params+0); + off_t cur = lseek(fd, 0, SEEK_CUR); + if (cur == (off_t)-1) { + semihosting_errno = errno; + result = -1; + break; + } + result = lseek(fd, 0, SEEK_END); + semihosting_errno = errno; + if (lseek(fd, cur, SEEK_SET) == (off_t)-1) { + semihosting_errno = errno; + result = -1; + } + } + break; + + case 0x0e: /* SYS_REMOVE */ + retval = target_read_memory(target, r1, 4, 2, params); + if (retval != ERROR_OK) + return retval; + else { + uint32_t a = target_buffer_get_u32(target, params+0); + uint32_t l = target_buffer_get_u32(target, params+4); + if (l <= 255) { + uint8_t fn[256]; + retval = target_read_memory(target, a, 1, l, fn); + if (retval != ERROR_OK) + return retval; + fn[l] = 0; + result = remove((char *)fn); + semihosting_errno = errno; + } else { + result = -1; + semihosting_errno = EINVAL; + } + } + break; + + case 0x0f: /* SYS_RENAME */ + retval = target_read_memory(target, r1, 4, 4, params); + if (retval != ERROR_OK) + return retval; + else { + uint32_t a1 = target_buffer_get_u32(target, params+0); + uint32_t l1 = target_buffer_get_u32(target, params+4); + uint32_t a2 = target_buffer_get_u32(target, params+8); + uint32_t l2 = target_buffer_get_u32(target, params+12); + if (l1 <= 255 && l2 <= 255) { + uint8_t fn1[256], fn2[256]; + retval = target_read_memory(target, a1, 1, l1, fn1); + if (retval != ERROR_OK) + return retval; + retval = target_read_memory(target, a2, 1, l2, fn2); + if (retval != ERROR_OK) + return retval; + fn1[l1] = 0; + fn2[l2] = 0; + result = rename((char *)fn1, (char *)fn2); + semihosting_errno = errno; + } else { + result = -1; + semihosting_errno = EINVAL; + } + } + break; + + case 0x11: /* SYS_TIME */ + result = time(NULL); + break; + + case 0x13: /* SYS_ERRNO */ + result = semihosting_errno; + break; + + case 0x15: /* SYS_GET_CMDLINE */ + retval = target_read_memory(target, r1, 4, 2, params); + if (retval != ERROR_OK) + return retval; + else { + uint32_t a = target_buffer_get_u32(target, params+0); + uint32_t l = target_buffer_get_u32(target, params+4); + char *arg = "foobar"; + uint32_t s = strlen(arg) + 1; + if (l < s) + result = -1; + else { + retval = target_write_buffer(target, a, s, (void*)arg); + if (retval != ERROR_OK) + return retval; + result = 0; + } + } + break; + + case 0x16: /* SYS_HEAPINFO */ + retval = target_read_memory(target, r1, 4, 1, params); + if (retval != ERROR_OK) + return retval; + else { + uint32_t a = target_buffer_get_u32(target, params+0); + /* tell the remote we have no idea */ + memset(params, 0, 4*4); + retval = target_write_memory(target, a, 4, 4, params); + if (retval != ERROR_OK) + return retval; + result = 0; + } + break; + + case 0x18: /* angel_SWIreason_ReportException */ + switch (r1) { + case 0x20026: /* ADP_Stopped_ApplicationExit */ + fprintf(stderr, "semihosting: *** application exited ***\n"); + break; + case 0x20000: /* ADP_Stopped_BranchThroughZero */ + case 0x20001: /* ADP_Stopped_UndefinedInstr */ + case 0x20002: /* ADP_Stopped_SoftwareInterrupt */ + case 0x20003: /* ADP_Stopped_PrefetchAbort */ + case 0x20004: /* ADP_Stopped_DataAbort */ + case 0x20005: /* ADP_Stopped_AddressException */ + case 0x20006: /* ADP_Stopped_IRQ */ + case 0x20007: /* ADP_Stopped_FIQ */ + case 0x20020: /* ADP_Stopped_BreakPoint */ + case 0x20021: /* ADP_Stopped_WatchPoint */ + case 0x20022: /* ADP_Stopped_StepComplete */ + case 0x20023: /* ADP_Stopped_RunTimeErrorUnknown */ + case 0x20024: /* ADP_Stopped_InternalError */ + case 0x20025: /* ADP_Stopped_UserInterruption */ + case 0x20027: /* ADP_Stopped_StackOverflow */ + case 0x20028: /* ADP_Stopped_DivisionByZero */ + case 0x20029: /* ADP_Stopped_OSSpecific */ + default: + fprintf(stderr, "semihosting: exception %#x\n", r1); + } + return target_call_event_callbacks(target, TARGET_EVENT_HALTED); + + case 0x0d: /* SYS_TMPNAM */ + case 0x10: /* SYS_CLOCK */ + case 0x12: /* SYS_SYSTEM */ + case 0x17: /* angel_SWIreason_EnterSVC */ + case 0x30: /* SYS_ELAPSED */ + case 0x31: /* SYS_TICKFREQ */ + default: + fprintf(stderr, "semihosting: unsupported call %#x\n", r0); + result = -1; + semihosting_errno = ENOTSUP; + } + + /* resume execution to the original mode */ + buf_set_u32(armv4_5->core_cache->reg_list[0].value, 0, 32, result); + armv4_5->core_cache->reg_list[0].dirty = 1; + buf_set_u32(armv4_5->core_cache->reg_list[15].value, 0, 32, lr); + armv4_5->core_cache->reg_list[15].dirty = 1; + buf_set_u32(armv4_5->core_cache->reg_list[ARMV4_5_CPSR].value, 0, 32, spsr); + armv4_5->core_cache->reg_list[ARMV4_5_CPSR].dirty = 1; + armv4_5->core_mode = spsr & 0x1f; + if (spsr & 0x20) + armv4_5->core_state = ARMV4_5_STATE_THUMB; + return target_resume(target, 1, 0, 0, 0); +} + +/** + * Checks for and processes an ARM semihosting request. This is meant + * to be called when the target is stopped due to a debug mode entry. + * If the value 0 is returned then there was nothing to process. A non-zero + * return value signifies that a request was processed and the target resumed, + * or an error was encountered, in which case the caller must return + * immediately. + * + * @param target Pointer to the ARM target to process + * @param retval Pointer to a location where the return code will be stored + * @return non-zero value if a request was processed or an error encountered + */ +int arm_semihosting(struct target *target, int *retval) +{ + struct arm *armv4_5 = target_to_armv4_5(target); + uint32_t lr, spsr; + + if (!semihosting_active || + armv4_5->core_mode != ARMV4_5_MODE_SVC || + buf_get_u32(armv4_5->core_cache->reg_list[15].value, 0, 32) != 0x08) + return 0; + + lr = buf_get_u32(ARMV4_5_CORE_REG_MODE(armv4_5->core_cache, ARMV4_5_MODE_SVC, 14).value, 0, 32); + spsr = buf_get_u32(armv4_5->spsr->value, 0, 32); + + /* check instruction that triggered this trap */ + if (spsr & (1 << 5)) { + /* was in Thumb mode */ + uint8_t insn_buf[2]; + uint16_t insn; + *retval = target_read_memory(target, lr-2, 2, 1, insn_buf); + if (*retval != ERROR_OK) + return 1; + insn = target_buffer_get_u16(target, insn_buf); + if (insn != 0xDFAB) + return 0; + } else { + /* was in ARM mode */ + uint8_t insn_buf[4]; + uint32_t insn; + *retval = target_read_memory(target, lr-4, 4, 1, insn_buf); + if (*retval != ERROR_OK) + return 1; + insn = target_buffer_get_u32(target, insn_buf); + if (insn != 0xEF123456) + return 0; + } + + *retval = do_semihosting(target); + return 1; +} diff --git a/src/target/arm_semihosting.h b/src/target/arm_semihosting.h new file mode 100644 index 00000000..6b9ac566 --- /dev/null +++ b/src/target/arm_semihosting.h @@ -0,0 +1,28 @@ +/*************************************************************************** + * Copyright (C) 2009 by Marvell Technology Group Ltd. * + * Written by Nicolas Pitre * + * * + * 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, write to the * + * Free Software Foundation, Inc., * + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * + ***************************************************************************/ + +#ifndef ARM_SEMIHOSTING_H +#define ARM_SEMIHOSTING_H + +extern int semihosting_active; + +int arm_semihosting(struct target *target, int *retval); + +#endif -- 2.39.5