381c42a186
These are broken here and in the bsd-user fork. They won't be fixed as FreeBSD has dropped support for sparc. If people wish to support this in other BSDs, you're better off starting over than starting from these files. Signed-off-by: Warner Losh <imp@bsdimp.com> Reviewed-by: Richard Henderson <richard.henderson@linaro.org>
567 lines
16 KiB
C
567 lines
16 KiB
C
/*
|
|
* BSD syscalls
|
|
*
|
|
* Copyright (c) 2003 - 2008 Fabrice Bellard
|
|
*
|
|
* 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 "qemu/osdep.h"
|
|
#include "qemu/cutils.h"
|
|
#include "qemu/path.h"
|
|
#include <sys/syscall.h>
|
|
#include <sys/param.h>
|
|
#include <sys/sysctl.h>
|
|
#include <utime.h>
|
|
|
|
#include "qemu.h"
|
|
#include "qemu-common.h"
|
|
#include "user/syscall-trace.h"
|
|
|
|
//#define DEBUG
|
|
|
|
static abi_ulong target_brk;
|
|
static abi_ulong target_original_brk;
|
|
|
|
static inline abi_long get_errno(abi_long ret)
|
|
{
|
|
if (ret == -1)
|
|
/* XXX need to translate host -> target errnos here */
|
|
return -(errno);
|
|
else
|
|
return ret;
|
|
}
|
|
|
|
#define target_to_host_bitmask(x, tbl) (x)
|
|
|
|
static inline int is_error(abi_long ret)
|
|
{
|
|
return (abi_ulong)ret >= (abi_ulong)(-4096);
|
|
}
|
|
|
|
void target_set_brk(abi_ulong new_brk)
|
|
{
|
|
target_original_brk = target_brk = HOST_PAGE_ALIGN(new_brk);
|
|
}
|
|
|
|
/* do_obreak() must return target errnos. */
|
|
static abi_long do_obreak(abi_ulong new_brk)
|
|
{
|
|
abi_ulong brk_page;
|
|
abi_long mapped_addr;
|
|
int new_alloc_size;
|
|
|
|
if (!new_brk)
|
|
return 0;
|
|
if (new_brk < target_original_brk)
|
|
return -TARGET_EINVAL;
|
|
|
|
brk_page = HOST_PAGE_ALIGN(target_brk);
|
|
|
|
/* If the new brk is less than this, set it and we're done... */
|
|
if (new_brk < brk_page) {
|
|
target_brk = new_brk;
|
|
return 0;
|
|
}
|
|
|
|
/* We need to allocate more memory after the brk... */
|
|
new_alloc_size = HOST_PAGE_ALIGN(new_brk - brk_page + 1);
|
|
mapped_addr = get_errno(target_mmap(brk_page, new_alloc_size,
|
|
PROT_READ|PROT_WRITE,
|
|
MAP_ANON|MAP_FIXED|MAP_PRIVATE, -1, 0));
|
|
|
|
if (!is_error(mapped_addr))
|
|
target_brk = new_brk;
|
|
else
|
|
return mapped_addr;
|
|
|
|
return 0;
|
|
}
|
|
|
|
#if defined(TARGET_I386)
|
|
static abi_long do_freebsd_sysarch(CPUX86State *env, int op, abi_ulong parms)
|
|
{
|
|
abi_long ret = 0;
|
|
abi_ulong val;
|
|
int idx;
|
|
|
|
switch (op) {
|
|
#ifdef TARGET_ABI32
|
|
case TARGET_FREEBSD_I386_SET_GSBASE:
|
|
case TARGET_FREEBSD_I386_SET_FSBASE:
|
|
if (op == TARGET_FREEBSD_I386_SET_GSBASE)
|
|
#else
|
|
case TARGET_FREEBSD_AMD64_SET_GSBASE:
|
|
case TARGET_FREEBSD_AMD64_SET_FSBASE:
|
|
if (op == TARGET_FREEBSD_AMD64_SET_GSBASE)
|
|
#endif
|
|
idx = R_GS;
|
|
else
|
|
idx = R_FS;
|
|
if (get_user(val, parms, abi_ulong))
|
|
return -TARGET_EFAULT;
|
|
cpu_x86_load_seg(env, idx, 0);
|
|
env->segs[idx].base = val;
|
|
break;
|
|
#ifdef TARGET_ABI32
|
|
case TARGET_FREEBSD_I386_GET_GSBASE:
|
|
case TARGET_FREEBSD_I386_GET_FSBASE:
|
|
if (op == TARGET_FREEBSD_I386_GET_GSBASE)
|
|
#else
|
|
case TARGET_FREEBSD_AMD64_GET_GSBASE:
|
|
case TARGET_FREEBSD_AMD64_GET_FSBASE:
|
|
if (op == TARGET_FREEBSD_AMD64_GET_GSBASE)
|
|
#endif
|
|
idx = R_GS;
|
|
else
|
|
idx = R_FS;
|
|
val = env->segs[idx].base;
|
|
if (put_user(val, parms, abi_ulong))
|
|
return -TARGET_EFAULT;
|
|
break;
|
|
/* XXX handle the others... */
|
|
default:
|
|
ret = -TARGET_EINVAL;
|
|
break;
|
|
}
|
|
return ret;
|
|
}
|
|
#endif
|
|
|
|
#ifdef __FreeBSD__
|
|
/*
|
|
* XXX this uses the undocumented oidfmt interface to find the kind of
|
|
* a requested sysctl, see /sys/kern/kern_sysctl.c:sysctl_sysctl_oidfmt()
|
|
* (this is mostly copied from src/sbin/sysctl/sysctl.c)
|
|
*/
|
|
static int
|
|
oidfmt(int *oid, int len, char *fmt, uint32_t *kind)
|
|
{
|
|
int qoid[CTL_MAXNAME+2];
|
|
uint8_t buf[BUFSIZ];
|
|
int i;
|
|
size_t j;
|
|
|
|
qoid[0] = 0;
|
|
qoid[1] = 4;
|
|
memcpy(qoid + 2, oid, len * sizeof(int));
|
|
|
|
j = sizeof(buf);
|
|
i = sysctl(qoid, len + 2, buf, &j, 0, 0);
|
|
if (i)
|
|
return i;
|
|
|
|
if (kind)
|
|
*kind = *(uint32_t *)buf;
|
|
|
|
if (fmt)
|
|
strcpy(fmt, (char *)(buf + sizeof(uint32_t)));
|
|
return (0);
|
|
}
|
|
|
|
/*
|
|
* try and convert sysctl return data for the target.
|
|
* XXX doesn't handle CTLTYPE_OPAQUE and CTLTYPE_STRUCT.
|
|
*/
|
|
static int sysctl_oldcvt(void *holdp, size_t holdlen, uint32_t kind)
|
|
{
|
|
switch (kind & CTLTYPE) {
|
|
case CTLTYPE_INT:
|
|
case CTLTYPE_UINT:
|
|
*(uint32_t *)holdp = tswap32(*(uint32_t *)holdp);
|
|
break;
|
|
#ifdef TARGET_ABI32
|
|
case CTLTYPE_LONG:
|
|
case CTLTYPE_ULONG:
|
|
*(uint32_t *)holdp = tswap32(*(long *)holdp);
|
|
break;
|
|
#else
|
|
case CTLTYPE_LONG:
|
|
*(uint64_t *)holdp = tswap64(*(long *)holdp);
|
|
break;
|
|
case CTLTYPE_ULONG:
|
|
*(uint64_t *)holdp = tswap64(*(unsigned long *)holdp);
|
|
break;
|
|
#endif
|
|
#ifdef CTLTYPE_U64
|
|
case CTLTYPE_S64:
|
|
case CTLTYPE_U64:
|
|
#else
|
|
case CTLTYPE_QUAD:
|
|
#endif
|
|
*(uint64_t *)holdp = tswap64(*(uint64_t *)holdp);
|
|
break;
|
|
case CTLTYPE_STRING:
|
|
break;
|
|
default:
|
|
/* XXX unhandled */
|
|
return -1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/* XXX this needs to be emulated on non-FreeBSD hosts... */
|
|
static abi_long do_freebsd_sysctl(abi_ulong namep, int32_t namelen, abi_ulong oldp,
|
|
abi_ulong oldlenp, abi_ulong newp, abi_ulong newlen)
|
|
{
|
|
abi_long ret;
|
|
void *hnamep, *holdp, *hnewp = NULL;
|
|
size_t holdlen;
|
|
abi_ulong oldlen = 0;
|
|
int32_t *snamep = g_malloc(sizeof(int32_t) * namelen), *p, *q, i;
|
|
uint32_t kind = 0;
|
|
|
|
if (oldlenp)
|
|
get_user_ual(oldlen, oldlenp);
|
|
if (!(hnamep = lock_user(VERIFY_READ, namep, namelen, 1)))
|
|
return -TARGET_EFAULT;
|
|
if (newp && !(hnewp = lock_user(VERIFY_READ, newp, newlen, 1)))
|
|
return -TARGET_EFAULT;
|
|
if (!(holdp = lock_user(VERIFY_WRITE, oldp, oldlen, 0)))
|
|
return -TARGET_EFAULT;
|
|
holdlen = oldlen;
|
|
for (p = hnamep, q = snamep, i = 0; i < namelen; p++, i++)
|
|
*q++ = tswap32(*p);
|
|
oidfmt(snamep, namelen, NULL, &kind);
|
|
/* XXX swap hnewp */
|
|
ret = get_errno(sysctl(snamep, namelen, holdp, &holdlen, hnewp, newlen));
|
|
if (!ret)
|
|
sysctl_oldcvt(holdp, holdlen, kind);
|
|
put_user_ual(holdlen, oldlenp);
|
|
unlock_user(hnamep, namep, 0);
|
|
unlock_user(holdp, oldp, holdlen);
|
|
if (hnewp)
|
|
unlock_user(hnewp, newp, 0);
|
|
g_free(snamep);
|
|
return ret;
|
|
}
|
|
#endif
|
|
|
|
/* FIXME
|
|
* lock_iovec()/unlock_iovec() have a return code of 0 for success where
|
|
* other lock functions have a return code of 0 for failure.
|
|
*/
|
|
static abi_long lock_iovec(int type, struct iovec *vec, abi_ulong target_addr,
|
|
int count, int copy)
|
|
{
|
|
struct target_iovec *target_vec;
|
|
abi_ulong base;
|
|
int i;
|
|
|
|
target_vec = lock_user(VERIFY_READ, target_addr, count * sizeof(struct target_iovec), 1);
|
|
if (!target_vec)
|
|
return -TARGET_EFAULT;
|
|
for (i = 0;i < count; i++) {
|
|
base = tswapl(target_vec[i].iov_base);
|
|
vec[i].iov_len = tswapl(target_vec[i].iov_len);
|
|
if (vec[i].iov_len != 0) {
|
|
vec[i].iov_base = lock_user(type, base, vec[i].iov_len, copy);
|
|
/* Don't check lock_user return value. We must call writev even
|
|
if a element has invalid base address. */
|
|
} else {
|
|
/* zero length pointer is ignored */
|
|
vec[i].iov_base = NULL;
|
|
}
|
|
}
|
|
unlock_user (target_vec, target_addr, 0);
|
|
return 0;
|
|
}
|
|
|
|
static abi_long unlock_iovec(struct iovec *vec, abi_ulong target_addr,
|
|
int count, int copy)
|
|
{
|
|
struct target_iovec *target_vec;
|
|
abi_ulong base;
|
|
int i;
|
|
|
|
target_vec = lock_user(VERIFY_READ, target_addr, count * sizeof(struct target_iovec), 1);
|
|
if (!target_vec)
|
|
return -TARGET_EFAULT;
|
|
for (i = 0;i < count; i++) {
|
|
if (target_vec[i].iov_base) {
|
|
base = tswapl(target_vec[i].iov_base);
|
|
unlock_user(vec[i].iov_base, base, copy ? vec[i].iov_len : 0);
|
|
}
|
|
}
|
|
unlock_user (target_vec, target_addr, 0);
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* do_syscall() should always have a single exit point at the end so
|
|
that actions, such as logging of syscall results, can be performed.
|
|
All errnos that do_syscall() returns must be -TARGET_<errcode>. */
|
|
abi_long do_freebsd_syscall(void *cpu_env, int num, abi_long arg1,
|
|
abi_long arg2, abi_long arg3, abi_long arg4,
|
|
abi_long arg5, abi_long arg6, abi_long arg7,
|
|
abi_long arg8)
|
|
{
|
|
CPUState *cpu = env_cpu(cpu_env);
|
|
abi_long ret;
|
|
void *p;
|
|
|
|
#ifdef DEBUG
|
|
gemu_log("freebsd syscall %d\n", num);
|
|
#endif
|
|
record_syscall_start(cpu, num, arg1, arg2, arg3, arg4, arg5, arg6, 0, 0);
|
|
|
|
if (do_strace)
|
|
print_freebsd_syscall(num, arg1, arg2, arg3, arg4, arg5, arg6);
|
|
|
|
switch (num) {
|
|
case TARGET_FREEBSD_NR_exit:
|
|
#ifdef CONFIG_GPROF
|
|
_mcleanup();
|
|
#endif
|
|
gdb_exit(arg1);
|
|
qemu_plugin_user_exit();
|
|
/* XXX: should free thread stack and CPU env */
|
|
_exit(arg1);
|
|
ret = 0; /* avoid warning */
|
|
break;
|
|
case TARGET_FREEBSD_NR_read:
|
|
if (!(p = lock_user(VERIFY_WRITE, arg2, arg3, 0)))
|
|
goto efault;
|
|
ret = get_errno(read(arg1, p, arg3));
|
|
unlock_user(p, arg2, ret);
|
|
break;
|
|
case TARGET_FREEBSD_NR_write:
|
|
if (!(p = lock_user(VERIFY_READ, arg2, arg3, 1)))
|
|
goto efault;
|
|
ret = get_errno(write(arg1, p, arg3));
|
|
unlock_user(p, arg2, 0);
|
|
break;
|
|
case TARGET_FREEBSD_NR_writev:
|
|
{
|
|
int count = arg3;
|
|
struct iovec *vec;
|
|
|
|
vec = alloca(count * sizeof(struct iovec));
|
|
if (lock_iovec(VERIFY_READ, vec, arg2, count, 1) < 0)
|
|
goto efault;
|
|
ret = get_errno(writev(arg1, vec, count));
|
|
unlock_iovec(vec, arg2, count, 0);
|
|
}
|
|
break;
|
|
case TARGET_FREEBSD_NR_open:
|
|
if (!(p = lock_user_string(arg1)))
|
|
goto efault;
|
|
ret = get_errno(open(path(p),
|
|
target_to_host_bitmask(arg2, fcntl_flags_tbl),
|
|
arg3));
|
|
unlock_user(p, arg1, 0);
|
|
break;
|
|
case TARGET_FREEBSD_NR_mmap:
|
|
ret = get_errno(target_mmap(arg1, arg2, arg3,
|
|
target_to_host_bitmask(arg4, mmap_flags_tbl),
|
|
arg5,
|
|
arg6));
|
|
break;
|
|
case TARGET_FREEBSD_NR_mprotect:
|
|
ret = get_errno(target_mprotect(arg1, arg2, arg3));
|
|
break;
|
|
case TARGET_FREEBSD_NR_break:
|
|
ret = do_obreak(arg1);
|
|
break;
|
|
#ifdef __FreeBSD__
|
|
case TARGET_FREEBSD_NR___sysctl:
|
|
ret = do_freebsd_sysctl(arg1, arg2, arg3, arg4, arg5, arg6);
|
|
break;
|
|
#endif
|
|
case TARGET_FREEBSD_NR_sysarch:
|
|
ret = do_freebsd_sysarch(cpu_env, arg1, arg2);
|
|
break;
|
|
case TARGET_FREEBSD_NR_syscall:
|
|
case TARGET_FREEBSD_NR___syscall:
|
|
ret = do_freebsd_syscall(cpu_env,arg1 & 0xffff,arg2,arg3,arg4,arg5,arg6,arg7,arg8,0);
|
|
break;
|
|
default:
|
|
ret = get_errno(syscall(num, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8));
|
|
break;
|
|
}
|
|
fail:
|
|
#ifdef DEBUG
|
|
gemu_log(" = %ld\n", ret);
|
|
#endif
|
|
if (do_strace)
|
|
print_freebsd_syscall_ret(num, ret);
|
|
|
|
record_syscall_return(cpu, num, ret);
|
|
return ret;
|
|
efault:
|
|
ret = -TARGET_EFAULT;
|
|
goto fail;
|
|
}
|
|
|
|
abi_long do_netbsd_syscall(void *cpu_env, int num, abi_long arg1,
|
|
abi_long arg2, abi_long arg3, abi_long arg4,
|
|
abi_long arg5, abi_long arg6)
|
|
{
|
|
CPUState *cpu = env_cpu(cpu_env);
|
|
abi_long ret;
|
|
void *p;
|
|
|
|
#ifdef DEBUG
|
|
gemu_log("netbsd syscall %d\n", num);
|
|
#endif
|
|
|
|
record_syscall_start(cpu, num, arg1, arg2, arg3, arg4, arg5, arg6, 0, 0);
|
|
|
|
if (do_strace)
|
|
print_netbsd_syscall(num, arg1, arg2, arg3, arg4, arg5, arg6);
|
|
|
|
switch (num) {
|
|
case TARGET_NETBSD_NR_exit:
|
|
#ifdef CONFIG_GPROF
|
|
_mcleanup();
|
|
#endif
|
|
gdb_exit(arg1);
|
|
qemu_plugin_user_exit();
|
|
/* XXX: should free thread stack and CPU env */
|
|
_exit(arg1);
|
|
ret = 0; /* avoid warning */
|
|
break;
|
|
case TARGET_NETBSD_NR_read:
|
|
if (!(p = lock_user(VERIFY_WRITE, arg2, arg3, 0)))
|
|
goto efault;
|
|
ret = get_errno(read(arg1, p, arg3));
|
|
unlock_user(p, arg2, ret);
|
|
break;
|
|
case TARGET_NETBSD_NR_write:
|
|
if (!(p = lock_user(VERIFY_READ, arg2, arg3, 1)))
|
|
goto efault;
|
|
ret = get_errno(write(arg1, p, arg3));
|
|
unlock_user(p, arg2, 0);
|
|
break;
|
|
case TARGET_NETBSD_NR_open:
|
|
if (!(p = lock_user_string(arg1)))
|
|
goto efault;
|
|
ret = get_errno(open(path(p),
|
|
target_to_host_bitmask(arg2, fcntl_flags_tbl),
|
|
arg3));
|
|
unlock_user(p, arg1, 0);
|
|
break;
|
|
case TARGET_NETBSD_NR_mmap:
|
|
ret = get_errno(target_mmap(arg1, arg2, arg3,
|
|
target_to_host_bitmask(arg4, mmap_flags_tbl),
|
|
arg5,
|
|
arg6));
|
|
break;
|
|
case TARGET_NETBSD_NR_mprotect:
|
|
ret = get_errno(target_mprotect(arg1, arg2, arg3));
|
|
break;
|
|
case TARGET_NETBSD_NR_syscall:
|
|
case TARGET_NETBSD_NR___syscall:
|
|
ret = do_netbsd_syscall(cpu_env,arg1 & 0xffff,arg2,arg3,arg4,arg5,arg6,0);
|
|
break;
|
|
default:
|
|
ret = syscall(num, arg1, arg2, arg3, arg4, arg5, arg6);
|
|
break;
|
|
}
|
|
fail:
|
|
#ifdef DEBUG
|
|
gemu_log(" = %ld\n", ret);
|
|
#endif
|
|
if (do_strace)
|
|
print_netbsd_syscall_ret(num, ret);
|
|
|
|
record_syscall_return(cpu, num, ret);
|
|
return ret;
|
|
efault:
|
|
ret = -TARGET_EFAULT;
|
|
goto fail;
|
|
}
|
|
|
|
abi_long do_openbsd_syscall(void *cpu_env, int num, abi_long arg1,
|
|
abi_long arg2, abi_long arg3, abi_long arg4,
|
|
abi_long arg5, abi_long arg6)
|
|
{
|
|
CPUState *cpu = env_cpu(cpu_env);
|
|
abi_long ret;
|
|
void *p;
|
|
|
|
#ifdef DEBUG
|
|
gemu_log("openbsd syscall %d\n", num);
|
|
#endif
|
|
|
|
record_syscall_start(cpu, num, arg1, arg2, arg3, arg4, arg5, arg6, 0, 0);
|
|
|
|
if (do_strace)
|
|
print_openbsd_syscall(num, arg1, arg2, arg3, arg4, arg5, arg6);
|
|
|
|
switch (num) {
|
|
case TARGET_OPENBSD_NR_exit:
|
|
#ifdef CONFIG_GPROF
|
|
_mcleanup();
|
|
#endif
|
|
gdb_exit(arg1);
|
|
qemu_plugin_user_exit();
|
|
/* XXX: should free thread stack and CPU env */
|
|
_exit(arg1);
|
|
ret = 0; /* avoid warning */
|
|
break;
|
|
case TARGET_OPENBSD_NR_read:
|
|
if (!(p = lock_user(VERIFY_WRITE, arg2, arg3, 0)))
|
|
goto efault;
|
|
ret = get_errno(read(arg1, p, arg3));
|
|
unlock_user(p, arg2, ret);
|
|
break;
|
|
case TARGET_OPENBSD_NR_write:
|
|
if (!(p = lock_user(VERIFY_READ, arg2, arg3, 1)))
|
|
goto efault;
|
|
ret = get_errno(write(arg1, p, arg3));
|
|
unlock_user(p, arg2, 0);
|
|
break;
|
|
case TARGET_OPENBSD_NR_open:
|
|
if (!(p = lock_user_string(arg1)))
|
|
goto efault;
|
|
ret = get_errno(open(path(p),
|
|
target_to_host_bitmask(arg2, fcntl_flags_tbl),
|
|
arg3));
|
|
unlock_user(p, arg1, 0);
|
|
break;
|
|
case TARGET_OPENBSD_NR_mmap:
|
|
ret = get_errno(target_mmap(arg1, arg2, arg3,
|
|
target_to_host_bitmask(arg4, mmap_flags_tbl),
|
|
arg5,
|
|
arg6));
|
|
break;
|
|
case TARGET_OPENBSD_NR_mprotect:
|
|
ret = get_errno(target_mprotect(arg1, arg2, arg3));
|
|
break;
|
|
case TARGET_OPENBSD_NR_syscall:
|
|
case TARGET_OPENBSD_NR___syscall:
|
|
ret = do_openbsd_syscall(cpu_env,arg1 & 0xffff,arg2,arg3,arg4,arg5,arg6,0);
|
|
break;
|
|
default:
|
|
ret = syscall(num, arg1, arg2, arg3, arg4, arg5, arg6);
|
|
break;
|
|
}
|
|
fail:
|
|
#ifdef DEBUG
|
|
gemu_log(" = %ld\n", ret);
|
|
#endif
|
|
if (do_strace)
|
|
print_openbsd_syscall_ret(num, ret);
|
|
|
|
record_syscall_return(cpu, num, ret);
|
|
return ret;
|
|
efault:
|
|
ret = -TARGET_EFAULT;
|
|
goto fail;
|
|
}
|
|
|
|
void syscall_init(void)
|
|
{
|
|
}
|