in semihosting/arm-compat-semi.c [814:1290]
target_ulong do_common_semihosting(CPUState *cs)
{
CPUArchState *env = cs->env_ptr;
target_ulong args;
target_ulong arg0, arg1, arg2, arg3;
target_ulong ul_ret;
char * s;
int nr;
uint32_t ret;
uint32_t len;
GuestFD *gf;
int64_t elapsed;
(void) env; /* Used implicitly by arm lock_user macro */
nr = common_semi_arg(cs, 0) & 0xffffffffU;
args = common_semi_arg(cs, 1);
switch (nr) {
case TARGET_SYS_OPEN:
{
int guestfd;
GET_ARG(0);
GET_ARG(1);
GET_ARG(2);
s = lock_user_string(arg0);
if (!s) {
errno = EFAULT;
return set_swi_errno(cs, -1);
}
if (arg1 >= 12) {
unlock_user(s, arg0, 0);
errno = EINVAL;
return set_swi_errno(cs, -1);
}
guestfd = alloc_guestfd();
if (guestfd < 0) {
unlock_user(s, arg0, 0);
errno = EMFILE;
return set_swi_errno(cs, -1);
}
if (strcmp(s, ":tt") == 0) {
int result_fileno;
/*
* We implement SH_EXT_STDOUT_STDERR, so:
* open for read == stdin
* open for write == stdout
* open for append == stderr
*/
if (arg1 < 4) {
result_fileno = STDIN_FILENO;
} else if (arg1 < 8) {
result_fileno = STDOUT_FILENO;
} else {
result_fileno = STDERR_FILENO;
}
associate_guestfd(guestfd, result_fileno);
unlock_user(s, arg0, 0);
return guestfd;
}
if (strcmp(s, ":semihosting-features") == 0) {
unlock_user(s, arg0, 0);
/* We must fail opens for modes other than 0 ('r') or 1 ('rb') */
if (arg1 != 0 && arg1 != 1) {
dealloc_guestfd(guestfd);
errno = EACCES;
return set_swi_errno(cs, -1);
}
init_featurefile_guestfd(guestfd);
return guestfd;
}
if (use_gdb_syscalls()) {
common_semi_open_guestfd = guestfd;
ret = common_semi_gdb_syscall(cs, common_semi_open_cb,
"open,%s,%x,1a4", arg0, (int)arg2 + 1,
gdb_open_modeflags[arg1]);
} else {
ret = set_swi_errno(cs, open(s, open_modeflags[arg1], 0644));
if (ret == (uint32_t)-1) {
dealloc_guestfd(guestfd);
} else {
associate_guestfd(guestfd, ret);
ret = guestfd;
}
}
unlock_user(s, arg0, 0);
return ret;
}
case TARGET_SYS_CLOSE:
GET_ARG(0);
gf = get_guestfd(arg0);
if (!gf) {
errno = EBADF;
return set_swi_errno(cs, -1);
}
ret = guestfd_fns[gf->type].closefn(cs, gf);
dealloc_guestfd(arg0);
return ret;
case TARGET_SYS_WRITEC:
qemu_semihosting_console_outc(cs->env_ptr, args);
return 0xdeadbeef;
case TARGET_SYS_WRITE0:
return qemu_semihosting_console_outs(cs->env_ptr, args);
case TARGET_SYS_WRITE:
GET_ARG(0);
GET_ARG(1);
GET_ARG(2);
len = arg2;
gf = get_guestfd(arg0);
if (!gf) {
errno = EBADF;
return set_swi_errno(cs, -1);
}
return guestfd_fns[gf->type].writefn(cs, gf, arg1, len);
case TARGET_SYS_READ:
GET_ARG(0);
GET_ARG(1);
GET_ARG(2);
len = arg2;
gf = get_guestfd(arg0);
if (!gf) {
errno = EBADF;
return set_swi_errno(cs, -1);
}
return guestfd_fns[gf->type].readfn(cs, gf, arg1, len);
case TARGET_SYS_READC:
return qemu_semihosting_console_inc(cs->env_ptr);
case TARGET_SYS_ISERROR:
GET_ARG(0);
return (target_long) arg0 < 0 ? 1 : 0;
case TARGET_SYS_ISTTY:
GET_ARG(0);
gf = get_guestfd(arg0);
if (!gf) {
errno = EBADF;
return set_swi_errno(cs, -1);
}
return guestfd_fns[gf->type].isattyfn(cs, gf);
case TARGET_SYS_SEEK:
GET_ARG(0);
GET_ARG(1);
gf = get_guestfd(arg0);
if (!gf) {
errno = EBADF;
return set_swi_errno(cs, -1);
}
return guestfd_fns[gf->type].seekfn(cs, gf, arg1);
case TARGET_SYS_FLEN:
GET_ARG(0);
gf = get_guestfd(arg0);
if (!gf) {
errno = EBADF;
return set_swi_errno(cs, -1);
}
return guestfd_fns[gf->type].flenfn(cs, gf);
case TARGET_SYS_TMPNAM:
GET_ARG(0);
GET_ARG(1);
GET_ARG(2);
if (asprintf(&s, "/tmp/qemu-%x%02x", getpid(),
(int) (arg1 & 0xff)) < 0) {
return -1;
}
ul_ret = (target_ulong) -1;
/* Make sure there's enough space in the buffer */
if (strlen(s) < arg2) {
char *output = lock_user(VERIFY_WRITE, arg0, arg2, 0);
strcpy(output, s);
unlock_user(output, arg0, arg2);
ul_ret = 0;
}
free(s);
return ul_ret;
case TARGET_SYS_REMOVE:
GET_ARG(0);
GET_ARG(1);
if (use_gdb_syscalls()) {
ret = common_semi_gdb_syscall(cs, common_semi_cb, "unlink,%s",
arg0, (int)arg1 + 1);
} else {
s = lock_user_string(arg0);
if (!s) {
errno = EFAULT;
return set_swi_errno(cs, -1);
}
ret = set_swi_errno(cs, remove(s));
unlock_user(s, arg0, 0);
}
return ret;
case TARGET_SYS_RENAME:
GET_ARG(0);
GET_ARG(1);
GET_ARG(2);
GET_ARG(3);
if (use_gdb_syscalls()) {
return common_semi_gdb_syscall(cs, common_semi_cb, "rename,%s,%s",
arg0, (int)arg1 + 1, arg2,
(int)arg3 + 1);
} else {
char *s2;
s = lock_user_string(arg0);
s2 = lock_user_string(arg2);
if (!s || !s2) {
errno = EFAULT;
ret = set_swi_errno(cs, -1);
} else {
ret = set_swi_errno(cs, rename(s, s2));
}
if (s2)
unlock_user(s2, arg2, 0);
if (s)
unlock_user(s, arg0, 0);
return ret;
}
case TARGET_SYS_CLOCK:
return clock() / (CLOCKS_PER_SEC / 100);
case TARGET_SYS_TIME:
return set_swi_errno(cs, time(NULL));
case TARGET_SYS_SYSTEM:
GET_ARG(0);
GET_ARG(1);
if (use_gdb_syscalls()) {
return common_semi_gdb_syscall(cs, common_semi_cb, "system,%s",
arg0, (int)arg1 + 1);
} else {
s = lock_user_string(arg0);
if (!s) {
errno = EFAULT;
return set_swi_errno(cs, -1);
}
ret = set_swi_errno(cs, system(s));
unlock_user(s, arg0, 0);
return ret;
}
case TARGET_SYS_ERRNO:
return get_swi_errno(cs);
case TARGET_SYS_GET_CMDLINE:
{
/* Build a command-line from the original argv.
*
* The inputs are:
* * arg0, pointer to a buffer of at least the size
* specified in arg1.
* * arg1, size of the buffer pointed to by arg0 in
* bytes.
*
* The outputs are:
* * arg0, pointer to null-terminated string of the
* command line.
* * arg1, length of the string pointed to by arg0.
*/
char *output_buffer;
size_t input_size;
size_t output_size;
int status = 0;
#if !defined(CONFIG_USER_ONLY)
const char *cmdline;
#else
TaskState *ts = cs->opaque;
#endif
GET_ARG(0);
GET_ARG(1);
input_size = arg1;
/* Compute the size of the output string. */
#if !defined(CONFIG_USER_ONLY)
cmdline = semihosting_get_cmdline();
if (cmdline == NULL) {
cmdline = ""; /* Default to an empty line. */
}
output_size = strlen(cmdline) + 1; /* Count terminating 0. */
#else
unsigned int i;
output_size = ts->info->arg_end - ts->info->arg_start;
if (!output_size) {
/*
* We special-case the "empty command line" case (argc==0).
* Just provide the terminating 0.
*/
output_size = 1;
}
#endif
if (output_size > input_size) {
/* Not enough space to store command-line arguments. */
errno = E2BIG;
return set_swi_errno(cs, -1);
}
/* Adjust the command-line length. */
if (SET_ARG(1, output_size - 1)) {
/* Couldn't write back to argument block */
errno = EFAULT;
return set_swi_errno(cs, -1);
}
/* Lock the buffer on the ARM side. */
output_buffer = lock_user(VERIFY_WRITE, arg0, output_size, 0);
if (!output_buffer) {
errno = EFAULT;
return set_swi_errno(cs, -1);
}
/* Copy the command-line arguments. */
#if !defined(CONFIG_USER_ONLY)
pstrcpy(output_buffer, output_size, cmdline);
#else
if (output_size == 1) {
/* Empty command-line. */
output_buffer[0] = '\0';
goto out;
}
if (copy_from_user(output_buffer, ts->info->arg_start,
output_size)) {
errno = EFAULT;
status = set_swi_errno(cs, -1);
goto out;
}
/* Separate arguments by white spaces. */
for (i = 0; i < output_size - 1; i++) {
if (output_buffer[i] == 0) {
output_buffer[i] = ' ';
}
}
out:
#endif
/* Unlock the buffer on the ARM side. */
unlock_user(output_buffer, arg0, output_size);
return status;
}
case TARGET_SYS_HEAPINFO:
{
target_ulong retvals[4];
target_ulong limit;
int i;
#ifdef CONFIG_USER_ONLY
TaskState *ts = cs->opaque;
#else
target_ulong rambase = common_semi_rambase(cs);
#endif
GET_ARG(0);
#ifdef CONFIG_USER_ONLY
/*
* Some C libraries assume the heap immediately follows .bss, so
* allocate it using sbrk.
*/
if (!ts->heap_limit) {
abi_ulong ret;
ts->heap_base = do_brk(0);
limit = ts->heap_base + COMMON_SEMI_HEAP_SIZE;
/* Try a big heap, and reduce the size if that fails. */
for (;;) {
ret = do_brk(limit);
if (ret >= limit) {
break;
}
limit = (ts->heap_base >> 1) + (limit >> 1);
}
ts->heap_limit = limit;
}
retvals[0] = ts->heap_base;
retvals[1] = ts->heap_limit;
retvals[2] = ts->stack_base;
retvals[3] = 0; /* Stack limit. */
#else
limit = current_machine->ram_size;
/* TODO: Make this use the limit of the loaded application. */
retvals[0] = rambase + limit / 2;
retvals[1] = rambase + limit;
retvals[2] = rambase + limit; /* Stack base */
retvals[3] = rambase; /* Stack limit. */
#endif
for (i = 0; i < ARRAY_SIZE(retvals); i++) {
bool fail;
if (is_64bit_semihosting(env)) {
fail = put_user_u64(retvals[i], arg0 + i * 8);
} else {
fail = put_user_u32(retvals[i], arg0 + i * 4);
}
if (fail) {
/* Couldn't write back to argument block */
errno = EFAULT;
return set_swi_errno(cs, -1);
}
}
return 0;
}
case TARGET_SYS_EXIT:
case TARGET_SYS_EXIT_EXTENDED:
if (common_semi_sys_exit_extended(cs, nr)) {
/*
* The A64 version of SYS_EXIT takes a parameter block,
* so the application-exit type can return a subcode which
* is the exit status code from the application.
* SYS_EXIT_EXTENDED is an a new-in-v2.0 optional function
* which allows A32/T32 guests to also provide a status code.
*/
GET_ARG(0);
GET_ARG(1);
if (arg0 == ADP_Stopped_ApplicationExit) {
ret = arg1;
} else {
ret = 1;
}
} else {
/*
* The A32/T32 version of SYS_EXIT specifies only
* Stopped_ApplicationExit as normal exit, but does not
* allow the guest to specify the exit status code.
* Everything else is considered an error.
*/
ret = (args == ADP_Stopped_ApplicationExit) ? 0 : 1;
}
gdb_exit(ret);
exit(ret);
case TARGET_SYS_ELAPSED:
elapsed = get_clock() - clock_start;
if (sizeof(target_ulong) == 8) {
SET_ARG(0, elapsed);
} else {
SET_ARG(0, (uint32_t) elapsed);
SET_ARG(1, (uint32_t) (elapsed >> 32));
}
return 0;
case TARGET_SYS_TICKFREQ:
/* qemu always uses nsec */
return 1000000000;
case TARGET_SYS_SYNCCACHE:
/*
* Clean the D-cache and invalidate the I-cache for the specified
* virtual address range. This is a nop for us since we don't
* implement caches. This is only present on A64.
*/
#ifdef TARGET_ARM
if (is_a64(cs->env_ptr)) {
return 0;
}
#endif
#ifdef TARGET_RISCV
return 0;
#endif
/* fall through -- invalid for A32/T32 */
default:
fprintf(stderr, "qemu: Unsupported SemiHosting SWI 0x%02x\n", nr);
cpu_dump_state(cs, stderr, 0);
abort();
}
}