Blob Blame History Raw
/*
 * This file is part of ltrace.
 * Copyright (C) 2011,2012,2013 Petr Machata
 *
 * 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, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA
 */

#include "config.h"

#include <sys/types.h>
#include <assert.h>
#include <gelf.h>
#include <stddef.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>

#include "backend.h"
#include "expr.h"
#include "fetch.h"
#include "proc.h"
#include "ptrace.h"
#include "type.h"
#include "value.h"

enum arg_class {
	CLASS_INTEGER,
	CLASS_SSE,
	CLASS_NO,
	CLASS_MEMORY,
	CLASS_X87,
};

enum reg_pool {
	POOL_FUNCALL,
	POOL_SYSCALL,
	/* A common pool for system call and function call return is
	 * enough, the ABI is similar enough.  */
	POOL_RETVAL,
};

struct fetch_context
{
	struct user_regs_struct iregs;
	struct user_fpregs_struct fpregs;

	arch_addr_t stack_pointer;
	size_t ireg;	/* Used-up integer registers.  */
	size_t freg;	/* Used-up floating registers.  */
	int machine;

	union {
		struct {
			/* Storage classes for return type.  We need
			 * to compute them anyway, so let's keep them
			 * around.  */
			enum arg_class ret_classes[2];
			ssize_t num_ret_classes;
		} x86_64;
		struct {
			struct value retval;
		} ix86;
	} u;
};

#ifndef __x86_64__
__attribute__((noreturn)) static void
i386_unreachable(void)
{
	abort();
}
#endif

static int
contains_unaligned_fields(struct arg_type_info *info)
{
	/* XXX currently we don't support structure alignment.  */
	return 0;
}

static int
has_nontrivial_ctor_dtor(struct arg_type_info *info)
{
	/* XXX another unsupported aspect of type info.  We might call
	 * these types "class" instead of "struct" in the config
	 * file.  */
	return 0;
}

static void
copy_int_register(struct fetch_context *context,
		  struct value *valuep, unsigned long val, size_t offset)
{
	if (valuep != NULL) {
		unsigned char *buf = value_get_raw_data(valuep);
		memcpy(buf + offset, &val, sizeof(val));
	}
	context->ireg++;
}

static void
copy_sse_register(struct fetch_context *context, struct value *valuep,
		  int half, size_t sz, size_t offset)
{
#ifdef __x86_64__
	union {
		uint32_t sse[4];
		long halves[2];
	} u;
	size_t off = 4 * context->freg++;
	memcpy(u.sse, context->fpregs.xmm_space + off, sizeof(u.sse));

	if (valuep != NULL) {
		unsigned char *buf = value_get_raw_data(valuep);
		memcpy(buf + offset, u.halves + half, sz);
	}
#else
	i386_unreachable();
#endif
}

static void
allocate_stack_slot(struct fetch_context *context,
		    struct value *valuep, size_t sz, size_t offset,
		    size_t archw)
{
	assert(valuep != NULL);
	size_t a = type_alignof(valuep->inferior, valuep->type);
	if (a < archw)
		a = archw;
	context->stack_pointer
		= (void *)align((unsigned long)context->stack_pointer, a);

	value_in_inferior(valuep, context->stack_pointer);
	context->stack_pointer += sz;
}

static enum arg_class
allocate_x87(struct fetch_context *context, struct value *valuep,
	     size_t sz, size_t offset, enum reg_pool pool, size_t archw)
{
	/* Both i386 and x86_64 ABI only ever really use x87 registers
	 * to return values.  Otherwise, the parameter is treated as
	 * if it were CLASS_MEMORY.  On x86_64 x87 registers are only
	 * used for returning long double values, which we currently
	 * don't support.  */

	if (pool != POOL_RETVAL) {
		allocate_stack_slot(context, valuep, sz, offset, archw);
		return CLASS_MEMORY;

	}

	/* If the class is X87, the value is returned on the X87 stack
	 * in %st0 as 80-bit x87 number.
	 *
	 * If the class is X87UP, the value is returned together with
	 * the previous X87 value in %st0.
	 *
	 * If the class is COMPLEX_X87, the real part of the value is
	 * returned in %st0 and the imaginary part in %st1.  */

	if (valuep != NULL) {
		union {
			long double ld;
			double d;
			float f;
			char buf[0];
		} u;

		/* The x87 floating point value is in long double
		 * format, so we need to convert in to the right type.
		 * Alternatively we might just leave it as is and
		 * smuggle the long double type into the value (via
		 * value_set_type), but for that we first need to
		 * support long double in the first place.  */

#ifdef __x86_64__
		unsigned int *reg;
#else
		long int *reg;
#endif
		reg = &context->fpregs.st_space[0];
		memcpy(&u.ld, reg, sizeof(u));
		if (valuep->type->type == ARGTYPE_FLOAT)
			u.f = (float)u.ld;
		else if (valuep->type->type == ARGTYPE_DOUBLE)
			u.d = (double)u.ld;
		else
			assert(!"Unexpected floating type!"), abort();

		unsigned char *buf = value_get_raw_data(valuep);
		memcpy(buf + offset, u.buf, sz);
	}
	return CLASS_X87;
}

static enum arg_class
allocate_integer(struct fetch_context *context, struct value *valuep,
		 size_t sz, size_t offset, enum reg_pool pool)
{
#define HANDLE(NUM, WHICH)						\
	case NUM:							\
		copy_int_register(context, valuep,			\
				  context->iregs.WHICH, offset);	\
		return CLASS_INTEGER

	switch (pool) {
	case POOL_FUNCALL:
#ifdef __x86_64__
		switch (context->ireg) {
			HANDLE(0, rdi);
			HANDLE(1, rsi);
			HANDLE(2, rdx);
			HANDLE(3, rcx);
			HANDLE(4, r8);
			HANDLE(5, r9);
		default:
			allocate_stack_slot(context, valuep, sz, offset, 8);
			return CLASS_MEMORY;
		}
#else
		i386_unreachable();
#endif

	case POOL_SYSCALL:
#ifdef __x86_64__
		if (context->machine == EM_X86_64) {
			switch (context->ireg) {
				HANDLE(0, rdi);
				HANDLE(1, rsi);
				HANDLE(2, rdx);
				HANDLE(3, r10);
				HANDLE(4, r8);
				HANDLE(5, r9);
			default:
				assert(!"More than six syscall arguments???");
				abort();
			}
		}
#endif
		if (context->machine == EM_386) {

#ifdef __x86_64__
# define HANDLE32(NUM, WHICH) HANDLE(NUM, r##WHICH)
#else
# define HANDLE32(NUM, WHICH) HANDLE(NUM, e##WHICH)
#endif

			switch (context->ireg) {
				HANDLE32(0, bx);
				HANDLE32(1, cx);
				HANDLE32(2, dx);
				HANDLE32(3, si);
				HANDLE32(4, di);
				HANDLE32(5, bp);
			default:
				assert(!"More than six syscall arguments???");
				abort();
			}
#undef HANDLE32
		}

	case POOL_RETVAL:
		switch (context->ireg) {
#ifdef __x86_64__
			HANDLE(0, rax);
			HANDLE(1, rdx);
#else
			HANDLE(0, eax);
#endif
		default:
			assert(!"Too many return value classes.");
			abort();
		}
	}

	abort();

#undef HANDLE
}

static enum arg_class
allocate_sse(struct fetch_context *context, struct value *valuep,
	     size_t sz, size_t offset, enum reg_pool pool)
{
	size_t num_regs = 0;
	switch (pool) {
	case POOL_FUNCALL:
		num_regs = 8;
	case POOL_SYSCALL:
		break;
	case POOL_RETVAL:
		num_regs = 2;
	}

	if (context->freg >= num_regs) {
		/* We shouldn't see overflow for RETVAL or SYSCALL
		 * pool.  */
		assert(pool == POOL_FUNCALL);
		allocate_stack_slot(context, valuep, sz, offset, 8);
		return CLASS_MEMORY;
	} else {
		copy_sse_register(context, valuep, 0, sz, offset);
		return CLASS_SSE;
	}
}

/* This allocates registers or stack space for another argument of the
 * class CLS.  */
static enum arg_class
allocate_class(enum arg_class cls, struct fetch_context *context,
	       struct value *valuep, size_t sz, size_t offset, enum reg_pool pool)
{
	switch (cls) {
	case CLASS_MEMORY:
		allocate_stack_slot(context, valuep, sz, offset, 8);
	case CLASS_NO:
		return cls;

	case CLASS_INTEGER:
		return allocate_integer(context, valuep, sz, offset, pool);

	case CLASS_SSE:
		return allocate_sse(context, valuep, sz, offset, pool);

	case CLASS_X87:
		return allocate_x87(context, valuep, sz, offset, pool, 8);
	}
	abort();
}

static ssize_t
classify(struct process *proc, struct fetch_context *context,
	 struct arg_type_info *info, enum arg_class classes[],
	 size_t sz, size_t eightbytes);

/* This classifies one eightbyte part of an array or struct.  */
static ssize_t
classify_eightbyte(struct process *proc, struct fetch_context *context,
		   struct arg_type_info *info,
		   enum arg_class *classp, size_t start, size_t end,
		   struct arg_type_info *(*getter)(struct arg_type_info *,
						   size_t))
{
	size_t i;
	enum arg_class cls = CLASS_NO;
	for (i = start; i < end; ++i) {
		enum arg_class cls2;
		struct arg_type_info *info2 = getter(info, i);
		size_t sz = type_sizeof(proc, info2);
		if (sz == (size_t)-1)
			return -1;
		if (classify(proc, context, info2, &cls2, sz, 1) < 0)
			return -1;

		if (cls == CLASS_NO)
			cls = cls2;
		else if (cls2 == CLASS_NO || cls == cls2)
			;
		else if (cls == CLASS_MEMORY || cls2 == CLASS_MEMORY)
			cls = CLASS_MEMORY;
		else if (cls == CLASS_INTEGER || cls2 == CLASS_INTEGER)
			cls = CLASS_INTEGER;
		else
			cls = CLASS_SSE;
	}

	*classp = cls;
	return 1;
}

/* This classifies small arrays and structs.  */
static ssize_t
classify_eightbytes(struct process *proc, struct fetch_context *context,
		    struct arg_type_info *info,
		    enum arg_class classes[], size_t elements,
		    size_t eightbytes,
		    struct arg_type_info *(*getter)(struct arg_type_info *,
						    size_t))
{
	if (eightbytes > 1) {
		/* Where the second eightbyte starts.  Number of the
		 * first element in the structure that belongs to the
		 * second eightbyte.  */
		size_t start_2nd = 0;
		size_t i;
		for (i = 0; i < elements; ++i)
			if (type_offsetof(proc, info, i) >= 8) {
				start_2nd = i;
				break;
			}

		enum arg_class cls1, cls2;
		if (classify_eightbyte(proc, context, info, &cls1,
				       0, start_2nd, getter) < 0
		    || classify_eightbyte(proc, context, info, &cls2,
					  start_2nd, elements, getter) < 0)
			return -1;

		if (cls1 == CLASS_MEMORY || cls2 == CLASS_MEMORY) {
			classes[0] = CLASS_MEMORY;
			return 1;
		}

		classes[0] = cls1;
		classes[1] = cls2;
		return 2;
	}

	return classify_eightbyte(proc, context, info, classes,
				  0, elements, getter);
}

static struct arg_type_info *
get_array_field(struct arg_type_info *info, size_t emt)
{
	return info->u.array_info.elt_type;
}

static int
flatten_structure(struct arg_type_info *flattened, struct arg_type_info *info)
{
	size_t i;
	for (i = 0; i < type_struct_size(info); ++i) {
		struct arg_type_info *field = type_struct_get(info, i);
		assert(field != NULL);
		switch (field->type) {
		case ARGTYPE_STRUCT:
			if (flatten_structure(flattened, field) < 0)
				return -1;
			break;

		default:
			if (type_struct_add(flattened, field, 0) < 0)
				return -1;
		}
	}
	return 0;
}

static ssize_t
classify(struct process *proc, struct fetch_context *context,
	 struct arg_type_info *info, enum arg_class classes[],
	 size_t sz, size_t eightbytes)
{
	switch (info->type) {
		struct arg_type_info flattened;
	case ARGTYPE_VOID:
		return 0;

	case ARGTYPE_CHAR:
	case ARGTYPE_SHORT:
	case ARGTYPE_USHORT:
	case ARGTYPE_INT:
	case ARGTYPE_UINT:
	case ARGTYPE_LONG:
	case ARGTYPE_ULONG:

	case ARGTYPE_POINTER:
		/* and LONGLONG */
		/* CLASS_INTEGER */
		classes[0] = CLASS_INTEGER;
		return 1;

	case ARGTYPE_FLOAT:
	case ARGTYPE_DOUBLE:
		/* and DECIMAL, and _m64 */
		classes[0] = CLASS_SSE;
		return 1;

	case ARGTYPE_ARRAY:
		/* N.B. this cannot be top-level array, those decay to
		 * pointers.  Therefore, it must be inside structure
		 * that's at most 2 eightbytes long.  */

		/* Structures with flexible array members can't be
		 * passed by value.  */
		assert(expr_is_compile_constant(info->u.array_info.length));

		long l;
		if (expr_eval_constant(info->u.array_info.length, &l) < 0)
			return -1;

		return classify_eightbytes(proc, context, info, classes,
					   (size_t)l, eightbytes,
					   get_array_field);

	case ARGTYPE_STRUCT:
		/* N.B. "big" structs are dealt with in the caller.
		 *
		 * First, we need to flatten the structure.  In
		 * struct(float,struct(float,float)), first two floats
		 * both belong to the same eightbyte.  */
		type_init_struct(&flattened);

		ssize_t ret;
		if (flatten_structure(&flattened, info) < 0) {
			ret = -1;
			goto done;
		}
		ret = classify_eightbytes(proc, context, &flattened,
					  classes,
					  type_struct_size(&flattened),
					  eightbytes, type_struct_get);
	done:
		type_destroy(&flattened);
		return ret;

	default:
		/* Unsupported type.  */
		assert(info->type != info->type);
		abort();
	}
	abort();
}

static ssize_t
pass_by_reference(struct value *valuep, enum arg_class classes[])
{
	if (valuep != NULL && value_pass_by_reference(valuep) < 0)
		return -1;
	classes[0] = CLASS_INTEGER;
	return 1;
}

static ssize_t
classify_argument(struct process *proc, struct fetch_context *context,
		  struct arg_type_info *info, struct value *valuep,
		  enum arg_class classes[], size_t *sizep)
{
	size_t sz = type_sizeof(proc, info);
	if (sz == (size_t)-1)
		return -1;
	*sizep = sz;

	size_t eightbytes = (sz + 7) / 8;  /* Round up.  */

	/* Arrays decay into pointers.  */
	assert(info->type != ARGTYPE_ARRAY);

	if (info->type == ARGTYPE_STRUCT) {
		if (eightbytes > 2 || contains_unaligned_fields(info)) {
			classes[0] = CLASS_MEMORY;
			return 1;
		}

		if (has_nontrivial_ctor_dtor(info))
			return pass_by_reference(valuep, classes);
	}

	return classify(proc, context, info, classes, sz, eightbytes);
}

static int
fetch_register_banks(struct process *proc, struct fetch_context *context,
		     int floating)
{
	if (ptrace(PTRACE_GETREGS, proc->pid, 0, &context->iregs) < 0)
		return -1;
	context->ireg = 0;

	if (floating) {
		if (ptrace(PTRACE_GETFPREGS, proc->pid,
			   0, &context->fpregs) < 0)
			return -1;
		context->freg = 0;
	} else {
		context->freg = -1;
	}

	return 0;
}

static int
arch_fetch_arg_next_32(struct fetch_context *context, enum tof type,
		       struct process *proc, struct arg_type_info *info,
		       struct value *valuep)
{
	size_t sz = type_sizeof(proc, info);
	if (sz == (size_t)-1)
		return -1;
	if (value_reserve(valuep, sz) == NULL)
		return -1;

	if (type == LT_TOF_SYSCALL || type == LT_TOF_SYSCALLR) {
		int cls = allocate_integer(context, valuep,
					   sz, 0, POOL_SYSCALL);
		assert(cls == CLASS_INTEGER);
		return 0;
	}

	allocate_stack_slot(context, valuep, sz, 0, 4);

	return 0;
}

static int
arch_fetch_retval_32(struct fetch_context *context, enum tof type,
		     struct process *proc, struct arg_type_info *info,
		     struct value *valuep)
{
	if (fetch_register_banks(proc, context, type == LT_TOF_FUNCTIONR) < 0)
		return -1;

	struct value *retval = &context->u.ix86.retval;
	if (retval->type != NULL) {
		/* Struct return value was extracted when in fetch
		 * init.  */
		memcpy(valuep, &context->u.ix86.retval, sizeof(*valuep));
		return 0;
	}

	size_t sz = type_sizeof(proc, info);
	if (sz == (size_t)-1)
		return -1;
	if (value_reserve(valuep, sz) == NULL)
		return -1;

	switch (info->type) {
		enum arg_class cls;
	case ARGTYPE_VOID:
		return 0;

	case ARGTYPE_INT:
	case ARGTYPE_UINT:
	case ARGTYPE_LONG:
	case ARGTYPE_ULONG:
	case ARGTYPE_CHAR:
	case ARGTYPE_SHORT:
	case ARGTYPE_USHORT:
	case ARGTYPE_POINTER:
		cls = allocate_integer(context, valuep, sz, 0, POOL_RETVAL);
		assert(cls == CLASS_INTEGER);
		return 0;

	case ARGTYPE_FLOAT:
	case ARGTYPE_DOUBLE:
		cls = allocate_x87(context, valuep, sz, 0, POOL_RETVAL, 4);
		assert(cls == CLASS_X87);
		return 0;

	case ARGTYPE_STRUCT: /* Handled above.  */
	default:
		assert(!"Unexpected i386 retval type!");
		abort();
	}

	abort();
}

static arch_addr_t
fetch_stack_pointer(struct fetch_context *context)
{
	arch_addr_t sp;
#ifdef __x86_64__
	sp = (arch_addr_t)context->iregs.rsp;
#else
	sp = (arch_addr_t)context->iregs.esp;
#endif
	return sp;
}

struct fetch_context *
arch_fetch_arg_init_32(struct fetch_context *context,
		       enum tof type, struct process *proc,
		       struct arg_type_info *ret_info)
{
	context->stack_pointer = fetch_stack_pointer(context) + 4;

	size_t sz = type_sizeof(proc, ret_info);
	if (sz == (size_t)-1)
		return NULL;

	struct value *retval = &context->u.ix86.retval;
	if (ret_info->type == ARGTYPE_STRUCT) {
		value_init(retval, proc, NULL, ret_info, 0);

		enum arg_class dummy[2];
		if (pass_by_reference(retval, dummy) < 0)
			return NULL;
		allocate_stack_slot(context, retval, 4, 0, 4);

	} else {
		value_init_detached(retval, NULL, NULL, 0);
	}

	return context;
}

struct fetch_context *
arch_fetch_arg_init_64(struct fetch_context *ctx, enum tof type,
		       struct process *proc, struct arg_type_info *ret_info)
{
	/* The first stack slot holds a return address.  */
	ctx->stack_pointer = fetch_stack_pointer(ctx) + 8;

	size_t size;
	ctx->u.x86_64.num_ret_classes
		= classify_argument(proc, ctx, ret_info, NULL,
				    ctx->u.x86_64.ret_classes, &size);
	if (ctx->u.x86_64.num_ret_classes == -1)
		return NULL;

	/* If the class is MEMORY, then the first argument is a hidden
	 * pointer to the allocated storage.  */
	if (ctx->u.x86_64.num_ret_classes > 0
	    && ctx->u.x86_64.ret_classes[0] == CLASS_MEMORY) {
		/* MEMORY should be the sole class.  */
		assert(ctx->u.x86_64.num_ret_classes == 1);
		allocate_integer(ctx, NULL, size, 0, POOL_FUNCALL);
	}

	return ctx;
}

struct fetch_context *
arch_fetch_arg_init(enum tof type, struct process *proc,
		    struct arg_type_info *ret_info)
{
	struct fetch_context *ctx = malloc(sizeof(*ctx));
	if (ctx == NULL)
		return NULL;
	ctx->machine = proc->e_machine;

	assert(type != LT_TOF_FUNCTIONR
	       && type != LT_TOF_SYSCALLR);
	if (fetch_register_banks(proc, ctx, type == LT_TOF_FUNCTION) < 0) {
	fail:
		free(ctx);
		return NULL;
	}

	struct fetch_context *ret;
	if (proc->e_machine == EM_386)
		ret = arch_fetch_arg_init_32(ctx, type, proc, ret_info);
	else
		ret = arch_fetch_arg_init_64(ctx, type, proc, ret_info);
	if (ret == NULL)
		goto fail;
	return ret;
}

struct fetch_context *
arch_fetch_arg_clone(struct process *proc, struct fetch_context *context)
{
	struct fetch_context *ret = malloc(sizeof(*ret));
	if (ret == NULL)
		return NULL;
	return memcpy(ret, context, sizeof(*ret));
}

static int
arch_fetch_pool_arg_next(struct fetch_context *context, enum tof type,
			 struct process *proc, struct arg_type_info *info,
			 struct value *valuep, enum reg_pool pool)
{
	enum arg_class classes[2];
	size_t sz, sz1;
	ssize_t i;
	ssize_t nclasses = classify_argument(proc, context, info, valuep,
					     classes, &sz);
	if (nclasses == -1)
		return -1;
	if (value_reserve(valuep, sz) == NULL)
		return -1;

	/* If there are no registers available for any eightbyte of an
	 * argument, the whole argument is passed on the stack.  If
	 * registers have already been assigned for some eightbytes of
	 * such an argument, the assignments get reverted.  */
	struct fetch_context tmp_context = *context;
	int revert;
	if (nclasses == 1) {
		revert = allocate_class(classes[0], &tmp_context,
					valuep, sz, 0, pool) != classes[0];
	} else {
		revert = 0;
		for (i = 0; i < nclasses; ++i) {
			sz1 = (size_t)(8 * (i + 1)) > sz ? sz - 8 * i : 8;
			if (allocate_class(classes[i], &tmp_context, valuep,
					   sz1, 8 * i, pool) != classes[i])
				revert = 1;
		}
	}

	if (nclasses > 1 && revert)
		allocate_class(CLASS_MEMORY, context, valuep, sz, 0, pool);
	else
		*context = tmp_context; /* Commit.  */

	return 0;
}

int
arch_fetch_fun_retval(struct fetch_context *context, enum tof type,
		      struct process *proc, struct arg_type_info *info,
		      struct value *valuep)
{
	assert(type != LT_TOF_FUNCTION
	       && type != LT_TOF_SYSCALL);
	if (value_reserve(valuep, 8 * context->u.x86_64.num_ret_classes) == NULL
	    || fetch_register_banks(proc, context,
				    type == LT_TOF_FUNCTIONR) < 0)
		return -1;

	if (context->u.x86_64.num_ret_classes == 1
	    && context->u.x86_64.ret_classes[0] == CLASS_MEMORY)
		pass_by_reference(valuep, context->u.x86_64.ret_classes);

	size_t sz = type_sizeof(proc, valuep->type);
	if (sz == (size_t)-1)
		return -1;

	ssize_t i;
	size_t sz1 = context->u.x86_64.num_ret_classes == 1 ? sz : 8;
	for (i = 0; i < context->u.x86_64.num_ret_classes; ++i) {
		enum arg_class cls
			= allocate_class(context->u.x86_64.ret_classes[i],
					 context, valuep, sz1,
					 8 * i, POOL_RETVAL);
		assert(cls == context->u.x86_64.ret_classes[i]);
	}
	return 0;
}

int
arch_fetch_arg_next(struct fetch_context *context, enum tof type,
		    struct process *proc, struct arg_type_info *info,
		    struct value *valuep)
{
	if (proc->e_machine == EM_386)
		return arch_fetch_arg_next_32(context, type, proc,
					      info, valuep);

	switch (type) {
	case LT_TOF_FUNCTION:
	case LT_TOF_FUNCTIONR:
		return arch_fetch_pool_arg_next(context, type, proc,
						info, valuep, POOL_FUNCALL);

	case LT_TOF_SYSCALL:
	case LT_TOF_SYSCALLR:
		return arch_fetch_pool_arg_next(context, type, proc,
						info, valuep, POOL_SYSCALL);
	}

	abort();
}

int
arch_fetch_retval(struct fetch_context *context, enum tof type,
		  struct process *proc, struct arg_type_info *info,
		  struct value *valuep)
{
	if (proc->e_machine == EM_386)
		return arch_fetch_retval_32(context, type, proc, info, valuep);

	return arch_fetch_fun_retval(context, type, proc, info, valuep);
}

void
arch_fetch_arg_done(struct fetch_context *context)
{
	if (context != NULL)
		free(context);
}