ftmemsim-valgrind/coregrind/m_syswrap/syswrap-amd64-freebsd.c

1162 lines
38 KiB
C

/*--------------------------------------------------------------------*/
/*--- Platform-specific syscalls stuff. syswrap-amd64-freebsd.c ---*/
/*--------------------------------------------------------------------*/
/*
This file is part of Valgrind, a dynamic binary instrumentation
framework.
Copyright (C) 2000-2005 Nicholas Nethercote
njn@valgrind.org
Copyright (C) 2018-2021 Paul Floyd
pjfloyd@wanadoo.fr
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/>.
The GNU General Public License is contained in the file COPYING.
*/
#if defined(VGP_amd64_freebsd)
#include "pub_core_basics.h"
#include "pub_core_vki.h"
#include "pub_core_vkiscnums.h"
#include "pub_core_threadstate.h"
#include "pub_core_aspacemgr.h"
#include "pub_core_debuglog.h"
#include "pub_core_libcbase.h"
#include "pub_core_libcassert.h"
#include "pub_core_libcprint.h"
#include "pub_core_libcproc.h"
#include "pub_core_libcsignal.h"
#include "pub_core_machine.h"
#include "pub_core_options.h"
#include "pub_core_scheduler.h"
#include "pub_core_sigframe.h"
#include "pub_core_signals.h"
#include "pub_core_syscall.h"
#include "pub_core_syswrap.h"
#include "pub_core_tooliface.h"
#include "pub_core_stacks.h" // VG_(register_stack)
#include "priv_types_n_macros.h"
#include "priv_syswrap-generic.h" /* for decls of generic wrappers */
#include "priv_syswrap-freebsd.h" /* for decls of freebsd-ish wrappers */
#include "priv_syswrap-main.h"
/* ---------------------------------------------------------------------
clone() handling
------------------------------------------------------------------ */
/* Call f(arg1), but first switch stacks, using 'stack' as the new
stack, and use 'retaddr' as f's return-to address. Also, clear all
the integer registers before entering f. */
__attribute__((noreturn))
void ML_(call_on_new_stack_0_1) ( Addr stack,
Addr retaddr,
void (*f)(Word),
Word arg1 );
// %rdi == stack
// %rsi == retaddr
// %rdx == f
// %rcx == arg1
__asm__(
".text\n"
".globl vgModuleLocal_call_on_new_stack_0_1\n"
"vgModuleLocal_call_on_new_stack_0_1:\n"
" movq %rdi, %rsp\n" // set stack
" pushq %rsi\n" // retaddr to stack
" pushq %rdx\n" // f to stack
" pushq %rcx\n" // arg1 to stack
" movq $0, %rax\n" // zero all GP regs
" movq $0, %rbx\n"
" movq $0, %rcx\n"
" movq $0, %rdx\n"
" movq $0, %rsi\n"
" movq $0, %rdi\n"
" movq $0, %rbp\n"
" movq $0, %r8\n"
" movq $0, %r9\n"
" movq $0, %r10\n"
" movq $0, %r11\n"
" movq $0, %r12\n"
" movq $0, %r13\n"
" movq $0, %r14\n"
" movq $0, %r15\n"
" popq %rdi\n" // arg1 to correct arg reg
" ret\n" // jump to f
" ud2\n" // should never get here
".previous\n"
);
/* ---------------------------------------------------------------------
More thread stuff
------------------------------------------------------------------ */
void VG_(cleanup_thread) ( ThreadArchState *arch )
{
}
/* ---------------------------------------------------------------------
PRE/POST wrappers for amd64/FreeBSD-specific syscalls
------------------------------------------------------------------ */
#define PRE(name) DEFN_PRE_TEMPLATE(freebsd, name)
#define POST(name) DEFN_POST_TEMPLATE(freebsd, name)
// SYS_sysarch 165
// int sysarch(int number, void *args);
PRE(sys_sysarch)
{
ThreadState *tst;
void **p;
PRINT("sys_sysarch ( %" FMT_REGWORD "u, %#" FMT_REGWORD "x )", ARG1, ARG2);
PRE_REG_READ2(int, "sysarch", int, number, void *, args);
switch (ARG1) {
case VKI_AMD64_SET_FSBASE:
PRINT("sys_amd64_set_fsbase ( %#lx )", ARG2);
if (ML_(safe_to_deref)((void**)ARG2, sizeof(void*))) {
/* On FreeBSD, the syscall loads the %gs selector for us, so do it now. */
tst = VG_(get_ThreadState)(tid);
p = (void**)ARG2;
tst->arch.vex.guest_FS_CONST = (UWord)*p;
/* "do" the syscall ourselves; the kernel never sees it */
SET_STATUS_Success2((ULong)*p, tst->arch.vex.guest_RDX );
} else {
// ????
SET_STATUS_Failure( VKI_EINVAL );
}
break;
case VKI_AMD64_GET_FSBASE:
PRINT("sys_amd64_get_fsbase ( %#lx )", ARG2);
PRE_MEM_WRITE( "amd64_get_fsbase(basep)", ARG2, sizeof(void *) );
if (ML_(safe_to_deref)((void**)ARG2, sizeof(void*))) {
/* "do" the syscall ourselves; the kernel never sees it */
tst = VG_(get_ThreadState)(tid);
SET_STATUS_Success2( tst->arch.vex.guest_FS_CONST, tst->arch.vex.guest_RDX );
} else {
SET_STATUS_Failure( VKI_EINVAL );
}
break;
case VKI_AMD64_GET_XFPUSTATE:
PRINT("sys_amd64_get_xfpustate ( %#lx )", ARG2);
PRE_MEM_WRITE( "amd64_get_xfpustate(basep)", ARG2, sizeof(void *) );
// @todo PJF need a test for this
// I think that it will fail in the POST if ARG2 is not a valid pointer
/* "do" the syscall ourselves; the kernel never sees it */
tst = VG_(get_ThreadState)(tid);
SET_STATUS_Success2( tst->arch.vex.guest_FPTAG[0], tst->arch.vex.guest_FPTAG[0] );
break;
default:
VG_(message) (Vg_UserMsg, "unhandled sysarch cmd %lu", ARG1);
VG_(unimplemented) ("unhandled sysarch cmd");
break;
}
}
POST(sys_sysarch)
{
switch (ARG1) {
case VKI_AMD64_SET_FSBASE:
break;
case VKI_AMD64_GET_FSBASE:
case VKI_AMD64_GET_XFPUSTATE:
POST_MEM_WRITE( ARG2, sizeof(void *) );
break;
default:
break;
}
}
// freebsd6_pread 173
#if (FREEBSD_VERS <= FREEBSD_10)
PRE(sys_freebsd6_pread)
{
*flags |= SfMayBlock;
PRINT("sys_freebsd6_pread ( %" FMT_REGWORD "u, %#" FMT_REGWORD "x, %lu, %" FMT_REGWORD "u, %" FMT_REGWORD "u )", ARG1, ARG2, ARG3, ARG4, ARG5);
PRE_REG_READ5(ssize_t, "read",
unsigned int, fd, char *, buf, vki_size_t, count,
int, pad, unsigned long, off);
if (!ML_(fd_allowed)(ARG1, "freebsd6_pread", tid, False))
SET_STATUS_Failure( VKI_EBADF );
else
PRE_MEM_WRITE( "freebsd6_pread(buf)", ARG2, ARG3 );
}
POST(sys_freebsd6_pread)
{
vg_assert(SUCCESS);
POST_MEM_WRITE( ARG2, RES );
}
#endif
// freebsd6_pwrite 174
#if (FREEBSD_VERS <= FREEBSD_10)
PRE(sys_freebsd6_pwrite)
{
Bool ok;
*flags |= SfMayBlock;
PRINT("sys_freebsd6_pwrite ( %" FMT_REGWORD "d, %#" FMT_REGWORD "x, %" FMT_REGWORD "u, %" FMT_REGWORD "u, %" FMT_REGWORD "u )", SARG1, ARG2, ARG3, ARG4, ARG5);
PRE_REG_READ5(ssize_t, "write",
unsigned int, fd, const char *, buf, vki_size_t, count,
int, pad, unsigned long, off);
/* check to see if it is allowed. If not, try for an exemption from
--sim-hints=enable-outer (used for self hosting). */
ok = ML_(fd_allowed)(ARG1, "freebsd6_pwrite", tid, False);
if (!ok && ARG1 == 2/*stderr*/
&& SimHintiS(SimHint_enable_outer, VG_(clo_sim_hints)))
ok = True;
if (!ok)
SET_STATUS_Failure( VKI_EBADF );
else
PRE_MEM_READ( "freebsd6_pwrite(buf)", ARG2, ARG3 );
}
#endif
// SYS_freebsd6_mmap 197
#if (FREEBSD_VERS <= FREEBSD_10)
/* This is here because on x86 the off_t is passed in 2 regs. Don't ask about pad. */
/* caddr_t mmap(caddr_t addr, size_t len, int prot, int flags, int fd, int pad, off_t pos); */
/* ARG1 ARG2 ARG3 ARG4 ARG5 ARG6 ARG7 */
PRE(sys_freebsd6_mmap)
{
SysRes r;
PRINT("sys_mmap ( %#" FMT_REGWORD "x, %" FMT_REGWORD "u, %" FMT_REGWORD "u, %" FMT_REGWORD "u, %" FMT_REGWORD "u, pad%" FMT_REGWORD "u, 0x%" FMT_REGWORD "x)",
ARG1, (UWord)ARG2, ARG3, ARG4, ARG5, ARG6, ARG7 );
PRE_REG_READ7(long, "mmap",
char *, addr, unsigned long, len, int, prot, int, flags,
int, fd, int, pad, unsigned long, pos);
r = ML_(generic_PRE_sys_mmap)( tid, ARG1, ARG2, ARG3, ARG4, ARG5, ARG7 );
SET_STATUS_from_SysRes(r);
}
#endif
// freebsd6_lseek 199
#if (FREEBSD_VERS <= FREEBSD_10)
PRE(sys_freebsd6_lseek)
{
PRINT("sys_freebsd6_lseek ( %" FMT_REGWORD "u, 0x%" FMT_REGWORD "x, %#" FMT_REGWORD "x, %" FMT_REGWORD "u )", ARG1,ARG2,ARG3,ARG4);
PRE_REG_READ4(long, "lseek",
unsigned int, fd, int, pad, unsigned long, offset,
unsigned int, whence);
}
#endif
// freebsd6_truncate 200
#if (FREEBSD_VERS <= FREEBSD_10)
PRE(sys_freebsd6_truncate)
{
*flags |= SfMayBlock;
PRINT("sys_truncate ( %#" FMT_REGWORD "x(%s), %" FMT_REGWORD "u )", ARG1,(char *)ARG1,ARG3);
PRE_REG_READ3(long, "truncate",
const char *, path, int, pad, unsigned int, length);
PRE_MEM_RASCIIZ( "truncate(path)", ARG1 );
}
#endif
// freebsd6_ftruncate 201
#if (FREEBSD_VERS <= FREEBSD_10)
PRE(sys_freebsd6_ftruncate)
{
*flags |= SfMayBlock;
PRINT("sys_ftruncate ( %" FMT_REGWORD "u, %" FMT_REGWORD "u )", ARG1,ARG3);
PRE_REG_READ3(long, "ftruncate", unsigned int, fd, int, pad,
unsigned int, length);
}
#endif
// SYS_clock_getcpuclockid2 247
// no manpage for this, from syscalls.master
// int clock_getcpuclockid2(id_t id, int which, _Out_ clockid_t *clock_id);
PRE(sys_clock_getcpuclockid2)
{
PRINT("sys_clock_getcpuclockid2( %" FMT_REGWORD "d, %" FMT_REGWORD "d, %#" FMT_REGWORD "x )",
SARG1,SARG2,ARG3);
PRE_REG_READ3(int, "clock_getcpuclockid2",
id_t, id, int, len, clockid_t *, clock_id);
PRE_MEM_WRITE("clock_getcpuclockid2(clock_id)", ARG3, sizeof(vki_clockid_t));
}
// SYS_rfork 251
// pid_t rfork(int flags);
PRE(sys_rfork)
{
PRINT("sys_rfork ( %#" FMT_REGWORD "x )", ARG1 );
PRE_REG_READ1(pid_t, "rfork", int, flags);
VG_(message)(Vg_UserMsg, "warning: rfork() not implemented\n");
if ((UInt)ARG1 == VKI_RFSPAWN) {
// posix_spawn uses RFSPAWN and it will fall back to vfork
// if it sees EINVAL
SET_STATUS_Failure(VKI_EINVAL);
} else {
SET_STATUS_Failure(VKI_ENOSYS);
}
}
// SYS_preadv 289
// ssize_t preadv(int fd, const struct iovec *iov, int iovcnt, off_t offset);
PRE(sys_preadv)
{
Int i;
struct vki_iovec * vec;
char buf[sizeof("preadv(iov[])") + 11];
*flags |= SfMayBlock;
PRINT("sys_preadv ( %" FMT_REGWORD "d, %#" FMT_REGWORD "x, %"
FMT_REGWORD "d, %" FMT_REGWORD "d )", SARG1, ARG2, SARG3, SARG4);
PRE_REG_READ4(ssize_t, "preadv",
int, fd, const struct iovec *, iov,
int, iovcnt, vki_off_t, offset);
if (!ML_(fd_allowed)(ARG1, "preadv", tid, False)) {
SET_STATUS_Failure( VKI_EBADF );
} else {
if ((Int)ARG3 > 0) {
PRE_MEM_READ( "preadv(iov)", ARG2, ARG3 * sizeof(struct vki_iovec) );
}
if (ML_(safe_to_deref)((struct vki_iovec *)ARG2, ARG3 * sizeof(struct vki_iovec))) {
vec = (struct vki_iovec *)(Addr)ARG2;
for (i = 0; i < (Int)ARG3; i++) {
VG_(sprintf)(buf, "preadv(iov[%d])", i);
PRE_MEM_WRITE(buf, (Addr)vec[i].iov_base, vec[i].iov_len);
}
}
}
}
POST(sys_preadv)
{
vg_assert(SUCCESS);
if (RES > 0) {
Int i;
struct vki_iovec * vec = (struct vki_iovec *)(Addr)ARG2;
Int remains = RES;
/* RES holds the number of bytes read. */
for (i = 0; i < (Int)ARG3; i++) {
Int nReadThisBuf = vec[i].iov_len;
if (nReadThisBuf > remains) {
nReadThisBuf = remains;
}
POST_MEM_WRITE( (Addr)vec[i].iov_base, nReadThisBuf );
remains -= nReadThisBuf;
if (remains < 0) {
VG_(core_panic)("preadv: remains < 0");
}
}
}
}
// SYS_pwritev 290
// ssize_t pwritev(int fd, const struct iovec *iov, int iovcnt, off_t offset);
PRE(sys_pwritev)
{
Int i;
struct vki_iovec * vec;
char buf[sizeof("pwritev(iov[])") + 11];
*flags |= SfMayBlock;
PRINT("sys_pwritev ( %" FMT_REGWORD "d, %#" FMT_REGWORD "x, %"
FMT_REGWORD "d, %" FMT_REGWORD "d )", SARG1, ARG2, SARG3, SARG4);
PRE_REG_READ4(ssize_t, "pwritev",
int, fd, const struct iovec *, iov,
int, iovcnt,
vki_off_t, offset);
if (!ML_(fd_allowed)(ARG1, "pwritev", tid, False)) {
SET_STATUS_Failure( VKI_EBADF );
} else {
if ((Int)ARG3 >= 0) {
PRE_MEM_READ( "pwritev(vector)", ARG2, ARG3 * sizeof(struct vki_iovec) );
}
if (ML_(safe_to_deref)((struct vki_iovec *)ARG2, ARG3 * sizeof(struct vki_iovec))) {
vec = (struct vki_iovec *)(Addr)ARG2;
for (i = 0; i < (Int)ARG3; i++) {
VG_(sprintf)(buf, "pwritev(iov[%d])", i);
PRE_MEM_READ(buf, (Addr)vec[i].iov_base, vec[i].iov_len );
}
}
}
}
// SYS_sendfile 393
// int sendfile(int fd, int s, off_t offset, size_t nbytes,
// struct sf_hdtr *hdtr, off_t *sbytes, int flags);
PRE(sys_sendfile)
{
*flags |= SfMayBlock;
PRINT("sys_sendfile ( %" FMT_REGWORD "d, %" FMT_REGWORD "d, %lu, %" FMT_REGWORD "u, %#" FMT_REGWORD "x, %#" FMT_REGWORD "x, %" FMT_REGWORD "d )",
SARG1,SARG2,ARG3,ARG4,ARG5,ARG6,SARG7);
PRE_REG_READ7(int, "sendfile",
int, fd, int, s, vki_off_t, offset, size_t, nbytes,
void *, hdtr, vki_off_t *, sbytes, int, flags);
if (ARG5 != 0) {
PRE_MEM_READ("sendfile(hdtr)", ARG5, sizeof(struct vki_sf_hdtr));
}
if (ARG6 != 0) {
PRE_MEM_WRITE( "sendfile(sbytes)", ARG6, sizeof(vki_off_t) );
}
}
POST(sys_sendfile)
{
if (ARG6 != 0 ) {
POST_MEM_WRITE( ARG6, sizeof( vki_off_t ) );
}
}
// SYS_sigreturn 417
// int sigreturn(const ucontext_t *scp);
PRE(sys_sigreturn)
{
PRINT("sys_sigreturn ( %#" FMT_REGWORD "x )", ARG1);
PRE_REG_READ1(int, "sigreturn",
struct vki_ucontext *, scp);
PRE_MEM_READ( "sigreturn(scp)", ARG1, sizeof(struct vki_ucontext) );
PRE_MEM_WRITE( "sigreturn(scp)", ARG1, sizeof(struct vki_ucontext) );
}
static void restore_mcontext(ThreadState *tst, struct vki_mcontext *sc)
{
tst->arch.vex.guest_RAX = sc->rax;
tst->arch.vex.guest_RCX = sc->rcx;
tst->arch.vex.guest_RDX = sc->rdx;
tst->arch.vex.guest_RBX = sc->rbx;
tst->arch.vex.guest_RBP = sc->rbp;
tst->arch.vex.guest_RSP = sc->rsp;
tst->arch.vex.guest_RSI = sc->rsi;
tst->arch.vex.guest_RDI = sc->rdi;
tst->arch.vex.guest_R8 = sc->r8;
tst->arch.vex.guest_R9 = sc->r9;
tst->arch.vex.guest_R10 = sc->r10;
tst->arch.vex.guest_R11 = sc->r11;
tst->arch.vex.guest_R12 = sc->r12;
tst->arch.vex.guest_R13 = sc->r13;
tst->arch.vex.guest_R14 = sc->r14;
tst->arch.vex.guest_R15 = sc->r15;
tst->arch.vex.guest_RIP = sc->rip;
/*
* XXX: missing support for other flags.
*/
if (sc->rflags & 0x0001)
LibVEX_GuestAMD64_put_rflag_c(1, &tst->arch.vex);
else
LibVEX_GuestAMD64_put_rflag_c(0, &tst->arch.vex);
}
static void fill_mcontext(ThreadState *tst, struct vki_mcontext *sc)
{
sc->rax = tst->arch.vex.guest_RAX;
sc->rcx = tst->arch.vex.guest_RCX;
sc->rdx = tst->arch.vex.guest_RDX;
sc->rbx = tst->arch.vex.guest_RBX;
sc->rbp = tst->arch.vex.guest_RBP;
sc->rsp = tst->arch.vex.guest_RSP;
sc->rsi = tst->arch.vex.guest_RSI;
sc->rdi = tst->arch.vex.guest_RDI;
sc->r8 = tst->arch.vex.guest_R8;
sc->r9 = tst->arch.vex.guest_R9;
sc->r10 = tst->arch.vex.guest_R10;
sc->r11 = tst->arch.vex.guest_R11;
sc->r12 = tst->arch.vex.guest_R12;
sc->r13 = tst->arch.vex.guest_R13;
sc->r14 = tst->arch.vex.guest_R14;
sc->r15 = tst->arch.vex.guest_R15;
sc->rip = tst->arch.vex.guest_RIP;
/*
Not supported by VEX.
sc->cs = tst->arch.vex.guest_CS;
sc->ss = tst->arch.vex.guest_SS;
sc->ds = tst->arch.vex.guest_DS;
sc->es = tst->arch.vex.guest_ES;
sc->fs = tst->arch.vex.guest_FS;
sc->gs = tst->arch.vex.guest_GS;
*/
sc->rflags = LibVEX_GuestAMD64_get_rflags(&tst->arch.vex);
/*
not yet.
VG_(memcpy)(&sc->fpstate, fpstate, sizeof(*fpstate));
*/
sc->fpformat = VKI_FPFMT_NODEV;
sc->ownedfp = VKI_FPOWNED_NONE;
sc->len = sizeof(*sc);
VG_(memset)(sc->spare2, 0, sizeof(sc->spare2));
}
// SYS_getcontext 421
// int getcontext(ucontext_t *ucp);
PRE(sys_getcontext)
{
ThreadState* tst;
struct vki_ucontext *uc;
PRINT("sys_getcontext ( %#" FMT_REGWORD "x )", ARG1);
PRE_REG_READ1(int, "getcontext",
struct vki_ucontext *, ucp);
PRE_MEM_WRITE( "getcontext(ucp)", ARG1, sizeof(struct vki_ucontext) );
uc = (struct vki_ucontext *)ARG1;
if (!ML_(safe_to_deref)(uc, sizeof(struct vki_ucontext))) {
SET_STATUS_Failure(VKI_EFAULT);
return;
}
tst = VG_(get_ThreadState)(tid);
fill_mcontext(tst, &uc->uc_mcontext);
uc->uc_mcontext.rax = 0;
uc->uc_mcontext.rdx = 0;
uc->uc_mcontext.rflags &= ~0x0001; /* PSL_C */
uc->uc_sigmask = tst->sig_mask;
VG_(memset)(uc->__spare__, 0, sizeof(uc->__spare__));
SET_STATUS_Success(0);
}
// SYS_setcontext 422
// int setcontext(const ucontext_t *ucp);
PRE(sys_setcontext)
{
ThreadState* tst;
struct vki_ucontext *uc;
PRINT("sys_setcontext ( %#" FMT_REGWORD "x )", ARG1);
PRE_REG_READ1(long, "setcontext",
struct vki_ucontext *, ucp);
PRE_MEM_READ( "setcontext(ucp)", ARG1, sizeof(struct vki_ucontext) );
PRE_MEM_WRITE( "setcontext(ucp)", ARG1, sizeof(struct vki_ucontext) );
vg_assert(VG_(is_valid_tid)(tid));
vg_assert(tid >= 1 && tid < VG_N_THREADS);
vg_assert(VG_(is_running_thread)(tid));
tst = VG_(get_ThreadState)(tid);
uc = (struct vki_ucontext *)ARG1;
if (!ML_(safe_to_deref)(uc, sizeof(struct vki_ucontext)) || uc->uc_mcontext.len != sizeof(uc->uc_mcontext)) {
SET_STATUS_Failure(VKI_EFAULT);
return;
}
restore_mcontext(tst, &uc->uc_mcontext);
tst->sig_mask = uc->uc_sigmask;
tst->tmp_sig_mask = uc->uc_sigmask;
/* Tell the driver not to update the guest state with the "result",
and set a bogus result to keep it happy. */
*flags |= SfNoWriteResult;
SET_STATUS_Success(0);
/* Check to see if some any signals arose as a result of this. */
*flags |= SfPollAfter;
}
// SYS_swapcontext 423
// int swapcontext(ucontext_t *oucp, const ucontext_t *ucp);
PRE(sys_swapcontext)
{
struct vki_ucontext *ucp;
struct vki_ucontext *oucp;
ThreadState* tst;
PRINT("sys_swapcontext ( %#" FMT_REGWORD "x, %#" FMT_REGWORD "x )", ARG1, ARG2);
PRE_REG_READ2(long, "swapcontext",
struct vki_ucontext *, oucp, struct vki_ucontext *, ucp);
PRE_MEM_READ( "swapcontext(ucp)", ARG2, sizeof(struct vki_ucontext) );
PRE_MEM_WRITE( "swapcontext(oucp)", ARG1, sizeof(struct vki_ucontext) );
oucp = (struct vki_ucontext *)ARG1;
ucp = (struct vki_ucontext *)ARG2;
if (!ML_(safe_to_deref)(oucp, sizeof(struct vki_ucontext)) ||
!ML_(safe_to_deref)(ucp, sizeof(struct vki_ucontext)) ||
ucp->uc_mcontext.len != sizeof(ucp->uc_mcontext)) {
SET_STATUS_Failure(VKI_EINVAL);
return;
}
tst = VG_(get_ThreadState)(tid);
/*
* Save the context.
*/
fill_mcontext(tst, &oucp->uc_mcontext);
oucp->uc_mcontext.rax = 0;
oucp->uc_mcontext.rdx = 0;
oucp->uc_mcontext.rflags &= ~0x0001; /* PSL_C */
oucp->uc_sigmask = tst->sig_mask;
VG_(memset)(oucp->__spare__, 0, sizeof(oucp->__spare__));
/*
* Switch to new one.
*/
restore_mcontext(tst, &ucp->uc_mcontext);
tst->sig_mask = ucp->uc_sigmask;
tst->tmp_sig_mask = ucp->uc_sigmask;
/* Tell the driver not to update the guest state with the "result",
and set a bogus result to keep it happy. */
*flags |= SfNoWriteResult;
SET_STATUS_Success(0);
/* Check to see if some any signals arose as a result of this. */
*flags |= SfPollAfter;
}
// SYS_thr_new 455
// int thr_new(struct thr_param *param, int param_size);
PRE(sys_thr_new)
{
static const Bool debug = False;
ThreadId ctid = VG_(alloc_ThreadState)();
ThreadState* ptst = VG_(get_ThreadState)(tid);
ThreadState* ctst = VG_(get_ThreadState)(ctid);
SysRes res;
vki_sigset_t blockall;
vki_sigset_t savedmask;
struct vki_thr_param tp;
Addr stk;
PRINT("thr_new ( %#" FMT_REGWORD "x, %" FMT_REGWORD "u )",ARG1,ARG2);
PRE_REG_READ2(int, "thr_new",
struct thr_param *, param,
int, param_size);
PRE_MEM_READ( "thr_new(param)", ARG1, offsetof(struct vki_thr_param, spare));
if (!ML_(safe_to_deref)( (void*)ARG1, offsetof(struct vki_thr_param, spare))) {
SET_STATUS_Failure( VKI_EFAULT );
return;
}
VG_(memset)(&tp, 0, sizeof(tp));
VG_(memcpy)(&tp, (void *)ARG1, offsetof(struct vki_thr_param, spare));
PRE_MEM_WRITE("thr_new(parent_tidptr)", (Addr)tp.parent_tid, sizeof(long));
PRE_MEM_WRITE("thr_new(child_tidptr)", (Addr)tp.child_tid, sizeof(long));
VG_(sigfillset)(&blockall);
vg_assert(VG_(is_running_thread)(tid));
vg_assert(VG_(is_valid_tid)(ctid));
/* Copy register state
On linux, both parent and child return to the same place, and the code
following the clone syscall works out which is which, so we
don't need to worry about it.
On FreeBSD, thr_new arranges a direct call. We don't actually need any
of this gunk.
The parent gets the child's new tid returned from clone, but the
child gets 0.
If the clone call specifies a NULL rsp for the new thread, then
it actually gets a copy of the parent's rsp.
*/
/* We inherit our parent's guest state. */
ctst->arch.vex = ptst->arch.vex;
ctst->arch.vex_shadow1 = ptst->arch.vex_shadow1;
ctst->arch.vex_shadow2 = ptst->arch.vex_shadow2;
/* Make thr_new appear to have returned Success(0) in the
child. */
ctst->arch.vex.guest_RAX = 0;
ctst->arch.vex.guest_RDX = 0;
LibVEX_GuestAMD64_put_rflag_c(0, &ctst->arch.vex);
ctst->os_state.parent = tid;
/* inherit signal mask */
ctst->sig_mask = ptst->sig_mask;
ctst->tmp_sig_mask = ptst->sig_mask;
/* Linux has to guess, we don't */
ctst->client_stack_highest_byte = (Addr)tp.stack_base + tp.stack_size;
ctst->client_stack_szB = tp.stack_size;
ctst->os_state.stk_id = VG_(register_stack)((Addr)tp.stack_base, (Addr)tp.stack_base + tp.stack_size);
/* Assume the thr_new will succeed, and tell any tool that wants to
know that this thread has come into existence. If the thr_new
fails, we'll send out a ll_exit notification for it at the out:
label below, to clean up. */
VG_TRACK ( pre_thread_ll_create, tid, ctid );
if (debug) {
VG_(printf)("clone child has SETTLS: tls at %#lx\n", (Addr)tp.tls_base);
}
ctst->arch.vex.guest_FS_CONST = (UWord)tp.tls_base;
tp.tls_base = 0; /* Don't have the kernel do it too */
/* start the thread with everything blocked */
VG_(sigprocmask)(VKI_SIG_SETMASK, &blockall, &savedmask);
/* Set the client state for scheduler to run libthr's trampoline */
ctst->arch.vex.guest_RDI = (Addr)tp.arg;
/* XXX: align on 16-byte boundary? */
ctst->arch.vex.guest_RSP = (Addr)tp.stack_base + tp.stack_size - 8;
ctst->arch.vex.guest_RIP = (Addr)tp.start_func;
/* But this is for thr_new() to run valgrind's trampoline */
tp.start_func = (void *)ML_(start_thread_NORETURN);
tp.arg = &VG_(threads)[ctid];
/* And valgrind's trampoline on its own stack */
stk = ML_(allocstack)(ctid);
if (stk == (Addr)NULL) {
res = VG_(mk_SysRes_Error)( VKI_ENOMEM );
goto fail;
}
tp.stack_base = (void *)ctst->os_state.valgrind_stack_base;
tp.stack_size = (Addr)stk - (Addr)tp.stack_base;
/* Create the new thread */
res = VG_(do_syscall2)(__NR_thr_new, (UWord)&tp, sizeof(tp));
VG_(sigprocmask)(VKI_SIG_SETMASK, &savedmask, NULL);
fail:
if (sr_isError(res)) {
/* thr_new failed */
VG_(cleanup_thread)(&ctst->arch);
ctst->status = VgTs_Empty;
/* oops. Better tell the tool the thread exited in a hurry :-) */
VG_TRACK( pre_thread_ll_exit, ctid );
} else {
POST_MEM_WRITE((Addr)tp.parent_tid, sizeof(long));
POST_MEM_WRITE((Addr)tp.child_tid, sizeof(long));
/* Thread creation was successful; let the child have the chance
to run */
*flags |= SfYieldAfter;
}
/* "Complete" the syscall so that the wrapper doesn't call the kernel again. */
SET_STATUS_from_SysRes(res);
}
// SYS_pread 475
// ssize_t pread(int fd, void *buf, size_t nbytes, off_t offset);
PRE(sys_pread)
{
*flags |= SfMayBlock;
PRINT("sys_pread ( %" FMT_REGWORD "u, %#" FMT_REGWORD "x, %" FMT_REGWORD "u, %" FMT_REGWORD "u )", ARG1, ARG2, ARG3, ARG4);
PRE_REG_READ4(ssize_t, "pread",
unsigned int, fd, char *, buf, vki_size_t, count,
unsigned long, off);
if (!ML_(fd_allowed)(ARG1, "read", tid, False)) {
SET_STATUS_Failure( VKI_EBADF );
} else {
PRE_MEM_WRITE( "pread(buf)", ARG2, ARG3 );
}
}
POST(sys_pread)
{
vg_assert(SUCCESS);
POST_MEM_WRITE( ARG2, RES );
}
// SYS_pwrite 476
// ssize_t pwrite(int fd, const void *buf, size_t nbytes, off_t offset);
PRE(sys_pwrite)
{
Bool ok;
*flags |= SfMayBlock;
PRINT("sys_pwrite ( %" FMT_REGWORD "u, %#" FMT_REGWORD "x, %" FMT_REGWORD "u, %" FMT_REGWORD "u )", ARG1, ARG2, ARG3, ARG4);
PRE_REG_READ4(ssize_t, "pwrite",
int, fd, const char *, buf, vki_size_t, nbytes,
vki_off_t, offset);
/* check to see if it is allowed. If not, try for an exemption from
--sim-hints=enable-outer (used for self hosting). */
ok = ML_(fd_allowed)(ARG1, "pwrite", tid, False);
if (!ok && ARG1 == 2/*stderr*/
&& SimHintiS(SimHint_enable_outer, VG_(clo_sim_hints)))
ok = True;
if (!ok) {
SET_STATUS_Failure( VKI_EBADF );
} else {
PRE_MEM_READ( "pwrite(buf)", ARG2, ARG3 );
}
}
// SYS_mmap 477
/* FreeBSD-7 introduces a "regular" version of mmap etc. */
// void * mmap(void *addr, size_t len, int prot, int flags, int fd, off_t offset);
PRE(sys_mmap)
{
SysRes r;
PRINT("sys_mmap ( %#" FMT_REGWORD "x, %" FMT_REGWORD "u, %" FMT_REGWORD "u, %" FMT_REGWORD "u, %" FMT_REGWORD "u, 0x%" FMT_REGWORD "x)",
ARG1, (UWord)ARG2, ARG3, ARG4, ARG5, ARG6 );
PRE_REG_READ6(void *, "mmap",
void *, addr, size_t, len, int, prot, int, flags,
int, fd, off_t, offset);
r = ML_(generic_PRE_sys_mmap)( tid, ARG1, ARG2, ARG3, ARG4, ARG5, ARG6 );
SET_STATUS_from_SysRes(r);
}
// SYS_lseek 478
// off_t lseek(int fildes, off_t offset, int whence);
PRE(sys_lseek)
{
PRINT("sys_lseek ( %" FMT_REGWORD "u, 0x%" FMT_REGWORD "x, %" FMT_REGWORD "u )", ARG1,ARG2,ARG3);
PRE_REG_READ3(long, "lseek",
unsigned int, fd, unsigned long, offset,
unsigned int, whence);
}
// SYS_truncate 479
// int truncate(const char *path, off_t length);
PRE(sys_truncate)
{
*flags |= SfMayBlock;
PRINT("sys_truncate ( %#" FMT_REGWORD "x(%s), %" FMT_REGWORD "u )", ARG1,(char *)ARG1,ARG2);
PRE_REG_READ2(long, "truncate",
const char *, path, unsigned long, length);
PRE_MEM_RASCIIZ( "truncate(path)", ARG1 );
}
// SYS_ftruncate 480
// int ftruncate(int fd, off_t length);
PRE(sys_ftruncate)
{
*flags |= SfMayBlock;
PRINT("sys_ftruncate ( %" FMT_REGWORD "u, %" FMT_REGWORD "u )", ARG1,ARG2);
PRE_REG_READ2(long, "ftruncate", unsigned int, fd,
unsigned long, length);
}
// SYS_cpuset_setid 485
// int cpuset_setid(cpuwhich_t which, id_t id, cpusetid_t setid);
PRE(sys_cpuset_setid)
{
PRINT("sys_cpuset_setid ( %" FMT_REGWORD "d, %" FMT_REGWORD "d, %#" FMT_REGWORD "x )",
SARG1, SARG2, ARG3);
PRE_REG_READ3(int, "cpuset_setid", vki_cpuwhich_t, which, vki_id_t, id,
vki_cpusetid_t *,setid);
}
// SYS_cpuset_getid 486
// int cpuset_getid(cpulevel_t level, cpuwhich_t which, id_t id,
// cpusetid_t *setid);
PRE(sys_cpuset_getid)
{
PRINT("sys_cpuset_getid ( %" FMT_REGWORD "d, %" FMT_REGWORD "d, %" FMT_REGWORD "d, %#" FMT_REGWORD "x )",
SARG1, SARG2, SARG3, ARG4);
PRE_REG_READ4(int, "cpuset_getid", vki_cpulevel_t, level,
vki_cpuwhich_t, which, vki_id_t, id,
vki_cpusetid_t, setid);
PRE_MEM_WRITE("cpuset_getid(setid)", ARG4, sizeof(vki_cpusetid_t));
}
POST(sys_cpuset_getid)
{
POST_MEM_WRITE(ARG4, sizeof(vki_cpusetid_t));
}
// SYS_cpuset_getaffinity 487
// int cpuset_getaffinity(cpulevel_t level, cpuwhich_t which, id_t id,
// size_t setsize, cpuset_t *mask);
PRE(sys_cpuset_getaffinity)
{
PRINT("sys_cpuset_getaffinity ( %" FMT_REGWORD "u, %" FMT_REGWORD "u, %" FMT_REGWORD "d, %" FMT_REGWORD "u, %#" FMT_REGWORD "x )",
ARG1, ARG2, SARG3, ARG4, ARG5);
PRE_REG_READ5(int, "cpuset_getaffinity",
vki_cpulevel_t, level, vki_cpuwhich_t, which, vki_id_t, id,
size_t, setsize, void *, mask);
PRE_MEM_WRITE("cpuset_getaffinity", ARG5, ARG4);
}
POST(sys_cpuset_getaffinity)
{
vg_assert(SUCCESS);
if (RES == 0)
POST_MEM_WRITE( ARG5, ARG4 );
}
// SYS_cpuset_setaffinity 488
// int cpuset_setaffinity(cpulevel_t level, cpuwhich_t which, id_t id,
// size_t setsize, const cpuset_t *mask);
PRE(sys_cpuset_setaffinity)
{
PRINT("sys_cpuset_setaffinity ( %" FMT_REGWORD "u, %" FMT_REGWORD "u, %" FMT_REGWORD "d, %" FMT_REGWORD "u, %#" FMT_REGWORD "x )",
ARG1, ARG2, SARG3, ARG4, ARG5);
PRE_REG_READ5(int, "cpuset_setaffinity",
vki_cpulevel_t, level, vki_cpuwhich_t, which, vki_id_t, id,
size_t, setsize, void *, mask);
PRE_MEM_READ("cpuset_setaffinity", ARG5, ARG4);
}
// SYS_posix_fallocate 530
// int posix_fallocate(int fd, off_t offset, off_t len);
PRE(sys_posix_fallocate)
{
PRINT("sys_posix_fallocate ( %" FMT_REGWORD "d, %" FMT_REGWORD "u, %" FMT_REGWORD "u )",
SARG1, ARG2, ARG3);
PRE_REG_READ3(long, "posix_fallocate",
int, fd, vki_off_t, offset,
vki_off_t, len);
}
// SYS_posix_fadvise 531
// int posix_fadvise(int fd, off_t offset, off_t len, int advice);
PRE(sys_posix_fadvise)
{
PRINT("sys_posix_fadvise ( %" FMT_REGWORD "d, %" FMT_REGWORD "u, %" FMT_REGWORD "u, %" FMT_REGWORD "d )",
SARG1, ARG2, ARG3, SARG4);
PRE_REG_READ4(long, "posix_fadvise",
int, fd, off_t, offset,
off_t, len,
int, advice);
// @todo PJF advice can be 0 to 5 inclusive
}
// SYS_wait6 532
// pid_t wait6(idtype_t idtype, id_t id, int *status, int options,
// struct __wrusage *wrusage, siginfo_t *infop);
PRE(sys_wait6)
{
PRINT("sys_wait6 ( %" FMT_REGWORD "d, %" FMT_REGWORD "d, %#" FMT_REGWORD "x, %" FMT_REGWORD "d, %#" FMT_REGWORD "x, %#" FMT_REGWORD "x )",
SARG1, SARG2, ARG3, SARG4, ARG5, ARG6);
PRE_REG_READ6(pid_t, "wait6", vki_idtype_t, idtype, vki_id_t, id, int *, status, int, options,
struct vki___wrusage *, wrusage, vki_siginfo_t *,infop);
PRE_MEM_WRITE("wait6(status)", ARG3, sizeof(int));
if (ARG5) {
PRE_MEM_WRITE("wait6(wrusage)", ARG5, sizeof(struct vki___wrusage));
}
if (ARG6) {
PRE_MEM_WRITE("wait6(infop)", ARG6, sizeof(vki_siginfo_t));
}
}
POST(sys_wait6)
{
POST_MEM_WRITE(ARG3, sizeof(int));
if (ARG5) {
POST_MEM_WRITE(ARG5, sizeof(struct vki___wrusage));
}
if (ARG6) {
POST_MEM_WRITE(ARG5, sizeof(vki_siginfo_t));
}
}
// the man page is inconsistent for the last argument
// See https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=247386
// will stick to 'arg' for simplicity
// SYS_procctl 544
// int procctl(idtype_t idtype, id_t id, int cmd, void *arg);
PRE(sys_procctl)
{
PRINT("sys_procctl ( %" FMT_REGWORD "d, %" FMT_REGWORD "d, %" FMT_REGWORD"d, %#" FMT_REGWORD "x )",
SARG1, SARG2, SARG3, ARG4);
PRE_REG_READ4(int, "procctl", vki_idtype_t, idtype, vki_id_t, id, int, cmd, void *, arg);
switch (ARG3) {
case VKI_PROC_ASLR_CTL:
case VKI_PROC_SPROTECT:
case VKI_PROC_TRACE_CTL:
case VKI_PROC_TRAPCAP_CTL:
case VKI_PROC_PDEATHSIG_CTL:
case VKI_PROC_STACKGAP_CTL:
case VKI_PROC_NO_NEW_PRIVS_CTL:
case VKI_PROC_WXMAP_CTL:
PRE_MEM_READ("procctl(arg)", ARG4, sizeof(int));
break;
case VKI_PROC_REAP_STATUS:
PRE_MEM_READ("procctl(arg)", ARG4, sizeof(struct vki_procctl_reaper_status));
break;
case VKI_PROC_REAP_GETPIDS:
PRE_MEM_READ("procctl(arg)", ARG4, sizeof(struct vki_procctl_reaper_pids));
break;
case VKI_PROC_REAP_KILL:
/* The first three fields are reads
* int rk_sig;
* u_int rk_flags;
* pid_t rk_subtree;
*
* The last two fields are writes
* u_int rk_killed;
* pid_t rk_fpid;
*
* There is also a pad field
*/
PRE_MEM_READ("procctl(arg)", ARG4, sizeof(int) + sizeof(u_int) + sizeof(vki_pid_t));
PRE_MEM_WRITE("procctl(arg)", ARG4+offsetof(struct vki_procctl_reaper_kill, rk_killed), sizeof(u_int) + sizeof(vki_pid_t));
break;
case VKI_PROC_ASLR_STATUS:
case VKI_PROC_PDEATHSIG_STATUS:
case VKI_PROC_STACKGAP_STATUS:
case VKI_PROC_TRAPCAP_STATUS:
case VKI_PROC_TRACE_STATUS:
case VKI_PROC_NO_NEW_PRIVS_STATUS:
case VKI_PROC_WXMAP_STATUS:
PRE_MEM_WRITE("procctl(arg)", ARG4, sizeof(int));
case VKI_PROC_REAP_ACQUIRE:
case VKI_PROC_REAP_RELEASE:
default:
break;
}
}
POST(sys_procctl)
{
switch (ARG3) {
case VKI_PROC_REAP_KILL:
POST_MEM_WRITE(ARG4+offsetof(struct vki_procctl_reaper_kill, rk_killed), sizeof(u_int) + sizeof(vki_pid_t));
break;
case VKI_PROC_ASLR_STATUS:
case VKI_PROC_PDEATHSIG_STATUS:
case VKI_PROC_STACKGAP_STATUS:
case VKI_PROC_TRAPCAP_STATUS:
case VKI_PROC_TRACE_STATUS:
case VKI_PROC_NO_NEW_PRIVS_STATUS:
case VKI_PROC_WXMAP_STATUS:
POST_MEM_WRITE(ARG4, sizeof(int));
default:
break;
}
}
// SYS_mknodat 559
// int mknodat(int fd, const char *path, mode_t mode, dev_t dev);
PRE(sys_mknodat)
{
PRINT("sys_mknodat ( %" FMT_REGWORD "u, %#" FMT_REGWORD "x(%s), 0x%" FMT_REGWORD "x, 0x%" FMT_REGWORD "x )", ARG1,ARG2,(char*)ARG2,ARG3,ARG4 );
PRE_REG_READ4(long, "mknodat",
int, fd, const char *, path, vki_mode_t, mode, vki_dev_t, dev);
PRE_MEM_RASCIIZ( "mknodat(pathname)", ARG2 );
}
#if (FREEBSD_VERS >= FREEBSD_12)
// SYS_cpuset_getdomain 561
// int cpuset_getdomain(cpulevel_t level, cpuwhich_t which, id_t id,
// size_t setsize, domainset_t *mask, int *policy);
PRE(sys_cpuset_getdomain)
{
PRINT("sys_cpuset_getdomain ( %" FMT_REGWORD "d, %" FMT_REGWORD "d, %" FMT_REGWORD "d, %" FMT_REGWORD "u, %#" FMT_REGWORD "x, %#" FMT_REGWORD "x )",
SARG1, SARG2, SARG3, ARG4, ARG5, ARG6);
PRE_REG_READ6(int, "cpuset_getdomain",
cpulevel_t, level, cpuwhich_t, which, id_t, id,
size_t, setsize, vki_domainset_t *, mask, int *, policy);
// man page says that setsize (ARG4) "is usually provided by calling sizeof(mask)"
PRE_MEM_WRITE( "cpuset_getdomain(mask)", ARG5, ARG4 );
PRE_MEM_WRITE( "cpuset_getdomain(policy)", ARG6, sizeof(int) );
}
POST(sys_cpuset_getdomain)
{
POST_MEM_WRITE(ARG5, ARG4 );
POST_MEM_WRITE(ARG6, sizeof(int) );
}
// SYS_cpuset_setdomain 562
// int cuset_setdomain(cpulevel_t level, cpuwhich_t which, id_t id,
// size_t setsize, const domainset_t *mask, int policy);
PRE(sys_cpuset_setdomain)
{
PRINT("sys_cpuget_getdomain ( %" FMT_REGWORD "d, %" FMT_REGWORD "d, %" FMT_REGWORD "d, %" FMT_REGWORD "u, %#" FMT_REGWORD "x, %" FMT_REGWORD "d )",
SARG1, SARG2, SARG3, ARG4, ARG5, SARG6);
PRE_REG_READ6(int, "cpuset_getdomain",
cpulevel_t, level, cpuwhich_t, which, id_t, id,
size_t, setsize, vki_domainset_t *, mask, int, policy);
// man page says that setsize (ARG4) "is usually provided by calling sizeof(mask)"
PRE_MEM_READ( "cpuset_getdomain(mask)", ARG5, ARG4 );
}
#endif
PRE(sys_fake_sigreturn)
{
ThreadState* tst;
struct vki_ucontext *uc;
ULong rflags;
PRINT("sys_sigreturn ( %#" FMT_REGWORD "x )", ARG1);
PRE_REG_READ1(long, "sigreturn",
struct vki_ucontext *, scp);
PRE_MEM_READ( "sigreturn(scp)", ARG1, sizeof(struct vki_ucontext) );
PRE_MEM_WRITE( "sigreturn(scp)", ARG1, sizeof(struct vki_ucontext) );
vg_assert(VG_(is_valid_tid)(tid));
vg_assert(tid >= 1 && tid < VG_N_THREADS);
vg_assert(VG_(is_running_thread)(tid));
/* Adjust esp to point to start of frame; skip back up over handler
ret addr */
tst = VG_(get_ThreadState)(tid);
tst->arch.vex.guest_RSP -= sizeof(Addr);
uc = (struct vki_ucontext *)ARG1;
if (uc == NULL || uc->uc_mcontext.len != sizeof(uc->uc_mcontext)) {
SET_STATUS_Failure(VKI_EINVAL);
return;
}
/* This is only so that the EIP is (might be) useful to report if
something goes wrong in the sigreturn */
ML_(fixup_guest_state_to_restart_syscall)(&tst->arch);
VG_(sigframe_destroy)(tid);
/* For unclear reasons, it appears we need the syscall to return
without changing %RAX. Since %RAX is the return value, and can
denote either success or failure, we must set up so that the
driver logic copies it back unchanged. Also, note %RAX is of
the guest registers written by VG_(sigframe_destroy). */
rflags = LibVEX_GuestAMD64_get_rflags(&tst->arch.vex);
SET_STATUS_from_SysRes( VG_(mk_SysRes_amd64_freebsd)( tst->arch.vex.guest_RAX,
tst->arch.vex.guest_RDX, (rflags & 1U) != 0U ? True : False) );
/*
* Signal handler might have changed the signal mask. Respect that.
*/
tst->sig_mask = uc->uc_sigmask;
tst->tmp_sig_mask = uc->uc_sigmask;
/* Tell the driver not to update the guest state with the "result",
and set a bogus result to keep it happy. */
*flags |= SfNoWriteResult;
SET_STATUS_Success(0);
/* Check to see if some any signals arose as a result of this. */
*flags |= SfPollAfter;
}
#undef PRE
#undef POST
#endif /* defined(VGP_amd64_freebsd) */
/*--------------------------------------------------------------------*/
/*--- end ---*/
/*--------------------------------------------------------------------*/