| /* 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; |
| } |