| /* Remote target system call support. | |
| Copyright 1997-2021 Free Software Foundation, Inc. | |
| Contributed by Cygnus Solutions. | |
| This file is part of GDB. | |
| 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 3 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/>. */ | |
| /* This interface isn't intended to be specific to any particular kind | |
| of remote (hardware, simulator, whatever). As such, support for it | |
| (e.g. sim/common/callback.c) should *not* live in the simulator source | |
| tree, nor should it live in the gdb source tree. K&R C must be | |
| supported. */ | |
| /* This must come before any other includes. */ | |
| #include "defs.h" | |
| #include "ansidecl.h" | |
| #include "libiberty.h" | |
| #include <stdarg.h> | |
| #include <stdio.h> | |
| #include <stdlib.h> | |
| #include <string.h> | |
| #ifdef HAVE_UNISTD_H | |
| #include <unistd.h> | |
| #endif | |
| #include <errno.h> | |
| #include <fcntl.h> | |
| #include <time.h> | |
| #include <sys/types.h> | |
| #include <sys/stat.h> | |
| #include "sim/callback.h" | |
| #include "targ-vals.h" | |
| #ifndef ENOSYS | |
| #define ENOSYS EINVAL | |
| #endif | |
| #ifndef ENAMETOOLONG | |
| #define ENAMETOOLONG EINVAL | |
| #endif | |
| /* Maximum length of a path name. */ | |
| #ifndef MAX_PATH_LEN | |
| #define MAX_PATH_LEN 1024 | |
| #endif | |
| /* When doing file read/writes, do this many bytes at a time. */ | |
| #define FILE_XFR_SIZE 4096 | |
| /* FIXME: for now, need to consider target word size. */ | |
| #define TWORD long | |
| #define TADDR unsigned long | |
| /* Path to be prepended to syscalls with absolute paths, and to be | |
| chdir:ed at startup, if not empty. */ | |
| char *simulator_sysroot = ""; | |
| /* Utility of cb_syscall to fetch a path name or other string from the target. | |
| The result is 0 for success or a host errno value. */ | |
| int | |
| cb_get_string (host_callback *cb, CB_SYSCALL *sc, char *buf, int buflen, | |
| TADDR addr) | |
| { | |
| char *p, *pend; | |
| for (p = buf, pend = buf + buflen; p < pend; ++p, ++addr) | |
| { | |
| /* No, it isn't expected that this would cause one transaction with | |
| the remote target for each byte. The target could send the | |
| path name along with the syscall request, and cache the file | |
| name somewhere (or otherwise tweak this as desired). */ | |
| unsigned int count = (*sc->read_mem) (cb, sc, addr, p, 1); | |
| if (count != 1) | |
| return EINVAL; | |
| if (*p == 0) | |
| break; | |
| } | |
| if (p == pend) | |
| return ENAMETOOLONG; | |
| return 0; | |
| } | |
| /* Utility of cb_syscall to fetch a path name. | |
| The buffer is malloc'd and the address is stored in BUFP. | |
| The result is that of get_string, but prepended with | |
| simulator_sysroot if the string starts with '/'. | |
| If an error occurs, no buffer is left malloc'd. */ | |
| static int | |
| get_path (host_callback *cb, CB_SYSCALL *sc, TADDR addr, char **bufp) | |
| { | |
| char *buf = xmalloc (MAX_PATH_LEN); | |
| int result; | |
| int sysroot_len = strlen (simulator_sysroot); | |
| result = cb_get_string (cb, sc, buf, MAX_PATH_LEN - sysroot_len, addr); | |
| if (result == 0) | |
| { | |
| /* Prepend absolute paths with simulator_sysroot. Relative paths | |
| are supposed to be relative to a chdir within that path, but at | |
| this point unknown where. */ | |
| if (simulator_sysroot[0] != '\0' && *buf == '/') | |
| { | |
| /* Considering expected rareness of syscalls with absolute | |
| file paths (compared to relative file paths and insn | |
| execution), it does not seem worthwhile to rearrange things | |
| to get rid of the string moves here; we'd need at least an | |
| extra call to check the initial '/' in the path. */ | |
| memmove (buf + sysroot_len, buf, sysroot_len); | |
| memcpy (buf, simulator_sysroot, sysroot_len); | |
| } | |
| *bufp = buf; | |
| } | |
| else | |
| free (buf); | |
| return result; | |
| } | |
| /* Perform a system call on behalf of the target. */ | |
| CB_RC | |
| cb_syscall (host_callback *cb, CB_SYSCALL *sc) | |
| { | |
| TWORD result = 0, errcode = 0; | |
| if (sc->magic != CB_SYSCALL_MAGIC) | |
| abort (); | |
| switch (cb_target_to_host_syscall (cb, sc->func)) | |
| { | |
| #if 0 /* FIXME: wip */ | |
| case CB_SYS_argvlen : | |
| { | |
| /* Compute how much space is required to store the argv,envp | |
| strings so that the program can allocate the space and then | |
| call SYS_argv to fetch the values. */ | |
| int addr_size = cb->addr_size; | |
| int argc,envc,arglen,envlen; | |
| const char **argv = cb->init_argv; | |
| const char **envp = cb->init_envp; | |
| argc = arglen = 0; | |
| if (argv) | |
| { | |
| for ( ; argv[argc]; ++argc) | |
| arglen += strlen (argv[argc]) + 1; | |
| } | |
| envc = envlen = 0; | |
| if (envp) | |
| { | |
| for ( ; envp[envc]; ++envc) | |
| envlen += strlen (envp[envc]) + 1; | |
| } | |
| result = arglen + envlen; | |
| break; | |
| } | |
| case CB_SYS_argv : | |
| { | |
| /* Pointer to target's buffer. */ | |
| TADDR tbuf = sc->arg1; | |
| /* Buffer size. */ | |
| int bufsize = sc->arg2; | |
| /* Q is the target address of where all the strings go. */ | |
| TADDR q; | |
| int word_size = cb->word_size; | |
| int i,argc,envc,len; | |
| const char **argv = cb->init_argv; | |
| const char **envp = cb->init_envp; | |
| argc = 0; | |
| if (argv) | |
| { | |
| for ( ; argv[argc]; ++argc) | |
| { | |
| int len = strlen (argv[argc]); | |
| int written = (*sc->write_mem) (cb, sc, tbuf, argv[argc], len + 1); | |
| if (written != len) | |
| { | |
| result = -1; | |
| errcode = EINVAL; | |
| goto FinishSyscall; | |
| } | |
| tbuf = len + 1; | |
| } | |
| } | |
| if ((*sc->write_mem) (cb, sc, tbuf, "", 1) != 1) | |
| { | |
| result = -1; | |
| errcode = EINVAL; | |
| goto FinishSyscall; | |
| } | |
| tbuf++; | |
| envc = 0; | |
| if (envp) | |
| { | |
| for ( ; envp[envc]; ++envc) | |
| { | |
| int len = strlen (envp[envc]); | |
| int written = (*sc->write_mem) (cb, sc, tbuf, envp[envc], len + 1); | |
| if (written != len) | |
| { | |
| result = -1; | |
| errcode = EINVAL; | |
| goto FinishSyscall; | |
| } | |
| tbuf = len + 1; | |
| } | |
| } | |
| if ((*sc->write_mem) (cb, sc, tbuf, "", 1) != 1) | |
| { | |
| result = -1; | |
| errcode = EINVAL; | |
| goto FinishSyscall; | |
| } | |
| result = argc; | |
| sc->result2 = envc; | |
| break; | |
| } | |
| #endif /* wip */ | |
| case CB_SYS_exit : | |
| /* Caller must catch and handle; see sim_syscall as an example. */ | |
| break; | |
| case CB_SYS_open : | |
| { | |
| char *path; | |
| errcode = get_path (cb, sc, sc->arg1, &path); | |
| if (errcode != 0) | |
| { | |
| result = -1; | |
| goto FinishSyscall; | |
| } | |
| result = (*cb->open) (cb, path, sc->arg2 /*, sc->arg3*/); | |
| free (path); | |
| if (result < 0) | |
| goto ErrorFinish; | |
| } | |
| break; | |
| case CB_SYS_close : | |
| result = (*cb->close) (cb, sc->arg1); | |
| if (result < 0) | |
| goto ErrorFinish; | |
| break; | |
| case CB_SYS_read : | |
| { | |
| /* ??? Perfect handling of error conditions may require only one | |
| call to cb->read. One can't assume all the data is | |
| contiguously stored in host memory so that would require | |
| malloc'ing/free'ing the space. Maybe later. */ | |
| char buf[FILE_XFR_SIZE]; | |
| int fd = sc->arg1; | |
| TADDR addr = sc->arg2; | |
| size_t count = sc->arg3; | |
| size_t bytes_read = 0; | |
| int bytes_written; | |
| while (count > 0) | |
| { | |
| if (cb_is_stdin (cb, fd)) | |
| result = (int) (*cb->read_stdin) (cb, buf, | |
| (count < FILE_XFR_SIZE | |
| ? count : FILE_XFR_SIZE)); | |
| else | |
| result = (int) (*cb->read) (cb, fd, buf, | |
| (count < FILE_XFR_SIZE | |
| ? count : FILE_XFR_SIZE)); | |
| if (result == -1) | |
| goto ErrorFinish; | |
| if (result == 0) /* EOF */ | |
| break; | |
| bytes_written = (*sc->write_mem) (cb, sc, addr, buf, result); | |
| if (bytes_written != result) | |
| { | |
| result = -1; | |
| errcode = EINVAL; | |
| goto FinishSyscall; | |
| } | |
| bytes_read += result; | |
| count -= result; | |
| addr += result; | |
| /* If this is a short read, don't go back for more */ | |
| if (result != FILE_XFR_SIZE) | |
| break; | |
| } | |
| result = bytes_read; | |
| } | |
| break; | |
| case CB_SYS_write : | |
| { | |
| /* ??? Perfect handling of error conditions may require only one | |
| call to cb->write. One can't assume all the data is | |
| contiguously stored in host memory so that would require | |
| malloc'ing/free'ing the space. Maybe later. */ | |
| char buf[FILE_XFR_SIZE]; | |
| int fd = sc->arg1; | |
| TADDR addr = sc->arg2; | |
| size_t count = sc->arg3; | |
| int bytes_read; | |
| size_t bytes_written = 0; | |
| while (count > 0) | |
| { | |
| int bytes_to_read = count < FILE_XFR_SIZE ? count : FILE_XFR_SIZE; | |
| bytes_read = (*sc->read_mem) (cb, sc, addr, buf, bytes_to_read); | |
| if (bytes_read != bytes_to_read) | |
| { | |
| result = -1; | |
| errcode = EINVAL; | |
| goto FinishSyscall; | |
| } | |
| if (cb_is_stdout (cb, fd)) | |
| { | |
| result = (int) (*cb->write_stdout) (cb, buf, bytes_read); | |
| (*cb->flush_stdout) (cb); | |
| } | |
| else if (cb_is_stderr (cb, fd)) | |
| { | |
| result = (int) (*cb->write_stderr) (cb, buf, bytes_read); | |
| (*cb->flush_stderr) (cb); | |
| } | |
| else | |
| result = (int) (*cb->write) (cb, fd, buf, bytes_read); | |
| if (result == -1) | |
| goto ErrorFinish; | |
| bytes_written += result; | |
| count -= result; | |
| addr += result; | |
| } | |
| result = bytes_written; | |
| } | |
| break; | |
| case CB_SYS_lseek : | |
| { | |
| int fd = sc->arg1; | |
| unsigned long offset = sc->arg2; | |
| int whence = sc->arg3; | |
| result = (*cb->lseek) (cb, fd, offset, whence); | |
| if (result < 0) | |
| goto ErrorFinish; | |
| } | |
| break; | |
| case CB_SYS_unlink : | |
| { | |
| char *path; | |
| errcode = get_path (cb, sc, sc->arg1, &path); | |
| if (errcode != 0) | |
| { | |
| result = -1; | |
| goto FinishSyscall; | |
| } | |
| result = (*cb->unlink) (cb, path); | |
| free (path); | |
| if (result < 0) | |
| goto ErrorFinish; | |
| } | |
| break; | |
| case CB_SYS_truncate : | |
| { | |
| char *path; | |
| long len = sc->arg2; | |
| errcode = get_path (cb, sc, sc->arg1, &path); | |
| if (errcode != 0) | |
| { | |
| result = -1; | |
| errcode = EFAULT; | |
| goto FinishSyscall; | |
| } | |
| result = (*cb->truncate) (cb, path, len); | |
| free (path); | |
| if (result < 0) | |
| goto ErrorFinish; | |
| } | |
| break; | |
| case CB_SYS_ftruncate : | |
| { | |
| int fd = sc->arg1; | |
| long len = sc->arg2; | |
| result = (*cb->ftruncate) (cb, fd, len); | |
| if (result < 0) | |
| goto ErrorFinish; | |
| } | |
| break; | |
| case CB_SYS_rename : | |
| { | |
| char *path1, *path2; | |
| errcode = get_path (cb, sc, sc->arg1, &path1); | |
| if (errcode != 0) | |
| { | |
| result = -1; | |
| errcode = EFAULT; | |
| goto FinishSyscall; | |
| } | |
| errcode = get_path (cb, sc, sc->arg2, &path2); | |
| if (errcode != 0) | |
| { | |
| result = -1; | |
| errcode = EFAULT; | |
| free (path1); | |
| goto FinishSyscall; | |
| } | |
| result = (*cb->rename) (cb, path1, path2); | |
| free (path1); | |
| free (path2); | |
| if (result < 0) | |
| goto ErrorFinish; | |
| } | |
| break; | |
| case CB_SYS_stat : | |
| { | |
| char *path,*buf; | |
| int buflen; | |
| struct stat statbuf; | |
| TADDR addr = sc->arg2; | |
| errcode = get_path (cb, sc, sc->arg1, &path); | |
| if (errcode != 0) | |
| { | |
| result = -1; | |
| goto FinishSyscall; | |
| } | |
| result = (*cb->to_stat) (cb, path, &statbuf); | |
| free (path); | |
| if (result < 0) | |
| goto ErrorFinish; | |
| buflen = cb_host_to_target_stat (cb, NULL, NULL); | |
| buf = xmalloc (buflen); | |
| if (cb_host_to_target_stat (cb, &statbuf, buf) != buflen) | |
| { | |
| /* The translation failed. This is due to an internal | |
| host program error, not the target's fault. */ | |
| free (buf); | |
| errcode = ENOSYS; | |
| result = -1; | |
| goto FinishSyscall; | |
| } | |
| if ((*sc->write_mem) (cb, sc, addr, buf, buflen) != buflen) | |
| { | |
| free (buf); | |
| errcode = EINVAL; | |
| result = -1; | |
| goto FinishSyscall; | |
| } | |
| free (buf); | |
| } | |
| break; | |
| case CB_SYS_fstat : | |
| { | |
| char *buf; | |
| int buflen; | |
| struct stat statbuf; | |
| TADDR addr = sc->arg2; | |
| result = (*cb->to_fstat) (cb, sc->arg1, &statbuf); | |
| if (result < 0) | |
| goto ErrorFinish; | |
| buflen = cb_host_to_target_stat (cb, NULL, NULL); | |
| buf = xmalloc (buflen); | |
| if (cb_host_to_target_stat (cb, &statbuf, buf) != buflen) | |
| { | |
| /* The translation failed. This is due to an internal | |
| host program error, not the target's fault. */ | |
| free (buf); | |
| errcode = ENOSYS; | |
| result = -1; | |
| goto FinishSyscall; | |
| } | |
| if ((*sc->write_mem) (cb, sc, addr, buf, buflen) != buflen) | |
| { | |
| free (buf); | |
| errcode = EINVAL; | |
| result = -1; | |
| goto FinishSyscall; | |
| } | |
| free (buf); | |
| } | |
| break; | |
| case CB_SYS_lstat : | |
| { | |
| char *path, *buf; | |
| int buflen; | |
| struct stat statbuf; | |
| TADDR addr = sc->arg2; | |
| errcode = get_path (cb, sc, sc->arg1, &path); | |
| if (errcode != 0) | |
| { | |
| result = -1; | |
| goto FinishSyscall; | |
| } | |
| result = (*cb->to_lstat) (cb, path, &statbuf); | |
| free (path); | |
| if (result < 0) | |
| goto ErrorFinish; | |
| buflen = cb_host_to_target_stat (cb, NULL, NULL); | |
| buf = xmalloc (buflen); | |
| if (cb_host_to_target_stat (cb, &statbuf, buf) != buflen) | |
| { | |
| /* The translation failed. This is due to an internal | |
| host program error, not the target's fault. | |
| Unfortunately, it's hard to test this case, so there's no | |
| test-case for this execution path. */ | |
| free (buf); | |
| errcode = ENOSYS; | |
| result = -1; | |
| goto FinishSyscall; | |
| } | |
| if ((*sc->write_mem) (cb, sc, addr, buf, buflen) != buflen) | |
| { | |
| free (buf); | |
| errcode = EINVAL; | |
| result = -1; | |
| goto FinishSyscall; | |
| } | |
| free (buf); | |
| } | |
| break; | |
| case CB_SYS_pipe : | |
| { | |
| int p[2]; | |
| char *target_p = xcalloc (1, cb->target_sizeof_int * 2); | |
| result = (*cb->pipe) (cb, p); | |
| if (result != 0) | |
| goto ErrorFinish; | |
| cb_store_target_endian (cb, target_p, cb->target_sizeof_int, p[0]); | |
| cb_store_target_endian (cb, target_p + cb->target_sizeof_int, | |
| cb->target_sizeof_int, p[1]); | |
| if ((*sc->write_mem) (cb, sc, sc->arg1, target_p, | |
| cb->target_sizeof_int * 2) | |
| != cb->target_sizeof_int * 2) | |
| { | |
| /* Close the pipe fd:s. */ | |
| (*cb->close) (cb, p[0]); | |
| (*cb->close) (cb, p[1]); | |
| errcode = EFAULT; | |
| result = -1; | |
| } | |
| free (target_p); | |
| } | |
| break; | |
| case CB_SYS_getpid: | |
| /* POSIX says getpid always succeeds. */ | |
| result = (*cb->getpid) (cb); | |
| break; | |
| case CB_SYS_kill: | |
| /* If killing self, leave it to the caller to process so it can send the | |
| signal to the engine. */ | |
| if (sc->arg1 == (*cb->getpid) (cb)) | |
| { | |
| result = -1; | |
| errcode = ENOSYS; | |
| } | |
| else | |
| { | |
| int signum = cb_target_to_host_signal (cb, sc->arg2); | |
| result = (*cb->kill) (cb, sc->arg1, signum); | |
| cb->last_errno = errno; | |
| goto ErrorFinish; | |
| } | |
| break; | |
| case CB_SYS_time : | |
| { | |
| /* FIXME: May wish to change CB_SYS_time to something else. | |
| We might also want gettimeofday or times, but if system calls | |
| can be built on others, we can keep the number we have to support | |
| here down. */ | |
| time_t t = (*cb->time) (cb); | |
| result = t; | |
| /* It is up to target code to process the argument to time(). */ | |
| } | |
| break; | |
| case CB_SYS_chdir : | |
| case CB_SYS_chmod : | |
| case CB_SYS_utime : | |
| /* fall through for now */ | |
| default : | |
| result = -1; | |
| errcode = ENOSYS; | |
| break; | |
| } | |
| FinishSyscall: | |
| sc->result = result; | |
| if (errcode == 0) | |
| sc->errcode = 0; | |
| else | |
| sc->errcode = cb_host_to_target_errno (cb, errcode); | |
| return CB_RC_OK; | |
| ErrorFinish: | |
| sc->result = result; | |
| sc->errcode = (*cb->get_errno) (cb); | |
| return CB_RC_OK; | |
| } |