blob: ed383361aca983994e6042bcf06ce774ac93717f [file] [log] [blame]
/*
* 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);
}