C++程序  |  437行  |  10.76 KB

/*
 * This file is part of ltrace.
 * Copyright (C) 2012 Petr Machata, Red Hat Inc.
 *
 * 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 <assert.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ucontext.h>

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

static int allocate_gpr(struct fetch_context *ctx, struct process *proc,
			struct arg_type_info *info, struct value *valuep);

/* Floating point registers have the same width on 32-bit as well as
 * 64-bit PPC, but <ucontext.h> presents a different API depending on
 * whether ltrace is PPC32 or PPC64.
 *
 * This is PPC64 definition.  The PPC32 is simply an array of 33
 * doubles, and doesn't contain the terminating pad.  Both seem
 * compatible enough.  */
struct fpregs_t
{
	double fpregs[32];
	double fpscr;
	unsigned int _pad[2];
};

typedef uint32_t gregs32_t[48];
typedef uint64_t gregs64_t[48];

struct fetch_context {
	arch_addr_t stack_pointer;
	int greg;
	int freg;
	int ret_struct;

	union {
		gregs32_t r32;
		gregs64_t r64;
	} regs;
	struct fpregs_t fpregs;

};

static int
fetch_context_init(struct process *proc, struct fetch_context *context)
{
	context->greg = 3;
	context->freg = 1;

	if (proc->e_machine == EM_PPC)
		context->stack_pointer = proc->stack_pointer + 8;
	else
		context->stack_pointer = proc->stack_pointer + 112;

	/* When ltrace is 64-bit, we might use PTRACE_GETREGS to
	 * obtain 64-bit as well as 32-bit registers.  But if we do it
	 * this way, 32-bit ltrace can obtain 64-bit registers.
	 *
	 * XXX this direction is not supported as of this writing, but
	 * should be eventually.  */
	if (proc->e_machine == EM_PPC64) {
		if (ptrace(PTRACE_GETREGS64, proc->pid, 0,
			   &context->regs.r64) < 0)
			return -1;
	} else {
#ifdef __powerpc64__
		if (ptrace(PTRACE_GETREGS, proc->pid, 0,
			  &context->regs.r64) < 0)
			return -1;
		unsigned i;
		for (i = 0; i < sizeof(context->regs.r64)/8; ++i)
			context->regs.r32[i] = context->regs.r64[i];
#else
		if (ptrace(PTRACE_GETREGS, proc->pid, 0,
			  &context->regs.r32) < 0)
			return -1;
#endif
	}

	if (ptrace(PTRACE_GETFPREGS, proc->pid, 0, &context->fpregs) < 0)
		return -1;

	return 0;
}

struct fetch_context *
arch_fetch_arg_init(enum tof type, struct process *proc,
		    struct arg_type_info *ret_info)
{
	struct fetch_context *context = malloc(sizeof(*context));
	if (context == NULL
	    || fetch_context_init(proc, context) < 0) {
		free(context);
		return NULL;
	}

	/* Aggregates or unions of any length, and character strings
	 * of length longer than 8 bytes, will be returned in a
	 * storage buffer allocated by the caller. The caller will
	 * pass the address of this buffer as a hidden first argument
	 * in r3, causing the first explicit argument to be passed in
	 * r4.  */
	context->ret_struct = ret_info->type == ARGTYPE_STRUCT;
	if (context->ret_struct)
		context->greg++;

	return context;
}

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

static int
allocate_stack_slot(struct fetch_context *ctx, 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;

	size_t a = type_alignof(proc, info);
	size_t off = 0;
	if (proc->e_machine == EM_PPC && a < 4)
		a = 4;
	else if (proc->e_machine == EM_PPC64 && a < 8)
		a = 8;

	/* XXX Remove the two double casts when arch_addr_t
	 * becomes integral type.  */
	uintptr_t tmp = align((uint64_t)(uintptr_t)ctx->stack_pointer, a);
	ctx->stack_pointer = (arch_addr_t)tmp;

	if (valuep != NULL)
		value_in_inferior(valuep, ctx->stack_pointer + off);
	ctx->stack_pointer += sz;

	return 0;
}

static uint64_t
read_gpr(struct fetch_context *ctx, struct process *proc, int reg_num)
{
	if (proc->e_machine == EM_PPC)
		return ctx->regs.r32[reg_num];
	else
		return ctx->regs.r64[reg_num];
}

/* The support for little endian PowerPC is in upstream Linux and BFD,
 * and Unix-like Solaris, which we might well support at some point,
 * runs PowerPC in little endian as well.  This code moves SZ-sized
 * value to the beginning of W-sized BUF regardless of
 * endian.  */
static void
align_small_int(unsigned char *buf, size_t w, size_t sz)
{
	assert(w == 4 || w == 8);
	union {
		uint64_t i64;
		uint32_t i32;
		uint16_t i16;
		uint8_t i8;
		char buf[0];
	} u;
	memcpy(u.buf, buf, w);
	if (w == 4)
		u.i64 = u.i32;

	switch (sz) {
	case 1:
		u.i8 = u.i64;
		break;
	case 2:
		u.i16 = u.i64;
		break;
	case 4:
		u.i32 = u.i64;
	case 8:
		break;
	}

	memcpy(buf, u.buf, sz);
}

static int
allocate_gpr(struct fetch_context *ctx, struct process *proc,
	     struct arg_type_info *info, struct value *valuep)
{
	if (ctx->greg > 10)
		return allocate_stack_slot(ctx, proc, info, valuep);

	int reg_num = ctx->greg++;
	if (valuep == NULL)
		return 0;

	size_t sz = type_sizeof(proc, info);
	if (sz == (size_t)-1)
		return -1;
	assert(sz == 1 || sz == 2 || sz == 4 || sz == 8);
	if (value_reserve(valuep, sz) == NULL)
		return -1;

	union {
		uint64_t i64;
		unsigned char buf[0];
	} u;

	u.i64 = read_gpr(ctx, proc, reg_num);
	if (proc->e_machine == EM_PPC)
		align_small_int(u.buf, 8, sz);
	memcpy(value_get_raw_data(valuep), u.buf, sz);
	return 0;
}

static int
allocate_float(struct fetch_context *ctx, struct process *proc,
	       struct arg_type_info *info, struct value *valuep)
{
	int pool = proc->e_machine == EM_PPC64 ? 13 : 8;
	if (ctx->freg <= pool) {
		union {
			double d;
			float f;
			char buf[0];
		} u = { .d = ctx->fpregs.fpregs[ctx->freg] };

		ctx->freg++;
		if (proc->e_machine == EM_PPC64)
			allocate_gpr(ctx, proc, info, NULL);

		size_t sz = sizeof(double);
		if (info->type == ARGTYPE_FLOAT) {
			sz = sizeof(float);
			u.f = (float)u.d;
		}

		if (value_reserve(valuep, sz) == NULL)
			return -1;

		memcpy(value_get_raw_data(valuep), u.buf, sz);
		return 0;
	}
	return allocate_stack_slot(ctx, proc, info, valuep);
}

static int
allocate_argument(struct fetch_context *ctx, struct process *proc,
		  struct arg_type_info *info, struct value *valuep)
{
	/* Floating point types and void are handled specially.  */
	switch (info->type) {
	case ARGTYPE_VOID:
		value_set_word(valuep, 0);
		return 0;

	case ARGTYPE_FLOAT:
	case ARGTYPE_DOUBLE:
		return allocate_float(ctx, proc, info, valuep);

	case ARGTYPE_STRUCT:
		if (proc->e_machine == EM_PPC) {
			if (value_pass_by_reference(valuep) < 0)
				return -1;
		} else {
			/* PPC64: Fixed size aggregates and unions passed by
			 * value are mapped to as many doublewords of the
			 * parameter save area as the value uses in memory.
			 * [...] The first eight doublewords mapped to the
			 * parameter save area correspond to the registers r3
			 * through r10.  */
		}
		/* fall through */
	case ARGTYPE_CHAR:
	case ARGTYPE_SHORT:
	case ARGTYPE_USHORT:
	case ARGTYPE_INT:
	case ARGTYPE_UINT:
	case ARGTYPE_LONG:
	case ARGTYPE_ULONG:
	case ARGTYPE_POINTER:
		break;

	case ARGTYPE_ARRAY:
		/* Arrays decay into pointers.  XXX Fortran?  */
	default:
		assert(info->type != info->type);
		abort();
	}

	unsigned width = proc->e_machine == EM_PPC64 ? 8 : 4;

	/* For other cases (integral types and aggregates), read the
	 * eightbytes comprising the data.  */
	size_t sz = type_sizeof(proc, valuep->type);
	if (sz == (size_t)-1)
		return -1;
	size_t slots = (sz + width - 1) / width;  /* Round up.  */
	unsigned char *buf = value_reserve(valuep, slots * width);
	if (buf == NULL)
		return -1;
	struct arg_type_info *long_info = type_get_simple(ARGTYPE_LONG);

	unsigned char *ptr = buf;
	while (slots-- > 0) {
		struct value val;
		value_init(&val, proc, NULL, long_info, 0);

		/* Floating point registers [...] are used [...] to
		   pass [...] one member aggregates passed by value
		   containing a floating point value[.]  Note that for
		   one member aggregates, "containing" extends to
		   aggregates within aggregates ad infinitum.  */
		int rc;
		struct arg_type_info *fp_info
			= type_get_fp_equivalent(valuep->type);
		if (fp_info != NULL)
			rc = allocate_float(ctx, proc, fp_info, &val);
		else
			rc = allocate_gpr(ctx, proc, long_info, &val);

		if (rc >= 0) {
			memcpy(ptr, value_get_data(&val, NULL), width);
			ptr += width;
		}
		value_destroy(&val);

		/* Bail out if we failed or if we are dealing with
		 * FP-equivalent.  Those don't need the adjustments
		 * made below.  */
		if (rc < 0 || fp_info != NULL)
			return rc;
	}

	/* Small values need post-processing.  */
	if (sz < width) {
		switch (info->type) {
		default:
			abort();

		/* Simple integer types (char, short, int, long, enum)
		 * are mapped to a single doubleword. Values shorter
		 * than a doubleword are sign or zero extended as
		 * necessary.  */
		case ARGTYPE_CHAR:
		case ARGTYPE_SHORT:
		case ARGTYPE_INT:
		case ARGTYPE_USHORT:
		case ARGTYPE_UINT:
			align_small_int(buf, width, sz);
			break;

		/* Single precision floating point values are mapped
		 * to the second word in a single doubleword.
		 *
		 * An aggregate or union smaller than one doubleword
		 * in size is padded so that it appears in the least
		 * significant bits of the doubleword.  */
		case ARGTYPE_FLOAT:
		case ARGTYPE_ARRAY:
		case ARGTYPE_STRUCT:
			memmove(buf, buf + width - sz, sz);
			break;
		}
	}

	return 0;
}

int
arch_fetch_arg_next(struct fetch_context *ctx, enum tof type,
		    struct process *proc,
		    struct arg_type_info *info, struct value *valuep)
{
	return allocate_argument(ctx, proc, info, valuep);
}

int
arch_fetch_retval(struct fetch_context *ctx, enum tof type,
		  struct process *proc, struct arg_type_info *info,
		  struct value *valuep)
{
	if (ctx->ret_struct) {
		assert(info->type == ARGTYPE_STRUCT);

		uint64_t addr = read_gpr(ctx, proc, 3);
		value_init(valuep, proc, NULL, info, 0);

		valuep->where = VAL_LOC_INFERIOR;
		/* XXX Remove the double cast when arch_addr_t
		 * becomes integral type. */
		valuep->u.address = (arch_addr_t)(uintptr_t)addr;
		return 0;
	}

	if (fetch_context_init(proc, ctx) < 0)
		return -1;
	return allocate_argument(ctx, proc, info, valuep);
}

void
arch_fetch_arg_done(struct fetch_context *context)
{
	free(context);
}