472 lines
12 KiB
C
472 lines
12 KiB
C
/*
|
|
* This file is part of ltrace.
|
|
* Copyright (C) 2012,2013 Petr Machata, Red Hat Inc.
|
|
* Copyright (C) 2008,2009 Juan Cespedes
|
|
* Copyright (C) 2006 Steve Fink
|
|
* Copyright (C) 2006 Ian Wienand
|
|
*
|
|
* 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 <stdlib.h>
|
|
#include <assert.h>
|
|
#include <sys/rse.h>
|
|
#include <ptrace.h>
|
|
#include <string.h>
|
|
#include <errno.h>
|
|
|
|
#include "backend.h"
|
|
#include "fetch.h"
|
|
#include "type.h"
|
|
#include "proc.h"
|
|
#include "value.h"
|
|
|
|
struct fetch_context {
|
|
arch_addr_t stack_pointer;
|
|
struct pt_all_user_regs regs;
|
|
enum param_pack_flavor ppflavor;
|
|
|
|
/* Return values larger than 256 bits (except HFAs of up to 8
|
|
* elements) are returned in a buffer allocated by the
|
|
* caller. A pointer to the buffer is passed to the called
|
|
* procedure in r8. This register is not guaranteed to be
|
|
* preserved by the called procedure. */
|
|
unsigned long r8;
|
|
|
|
int slot_n;
|
|
int flt;
|
|
};
|
|
|
|
union cfm_t {
|
|
struct {
|
|
unsigned long sof:7;
|
|
unsigned long sol:7;
|
|
unsigned long sor:4;
|
|
unsigned long rrb_gr:7;
|
|
unsigned long rrb_fr:7;
|
|
unsigned long rrb_pr:6;
|
|
} cfm;
|
|
unsigned long value;
|
|
};
|
|
|
|
static int
|
|
fetch_context_init(struct process *proc, struct fetch_context *context)
|
|
{
|
|
context->slot_n = 0;
|
|
context->flt = 8;
|
|
if (ptrace(PTRACE_GETREGS, proc->pid, 0, &context->regs) < 0)
|
|
return -1;
|
|
context->stack_pointer = (void *)(context->regs.gr[12] + 16);
|
|
context->ppflavor = PARAM_PACK_ARGS;
|
|
|
|
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;
|
|
}
|
|
context->r8 = context->regs.gr[8];
|
|
|
|
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;
|
|
}
|
|
|
|
int
|
|
allocate_stack_slot(struct fetch_context *ctx, struct process *proc,
|
|
struct arg_type_info *info, struct value *valuep)
|
|
{
|
|
size_t al = type_alignof(proc, info);
|
|
size_t sz = type_sizeof(proc, info);
|
|
if (al == (size_t)-1 || sz == (size_t)-1)
|
|
return -1;
|
|
|
|
errno = 0;
|
|
long value = ptrace(PTRACE_PEEKDATA, proc->pid, ctx->stack_pointer, 0);
|
|
if (value == -1 && errno != 0)
|
|
return -1;
|
|
ctx->stack_pointer += 8;
|
|
value_set_word(valuep, value);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
allocate_reg(struct fetch_context *ctx, struct process *proc,
|
|
struct arg_type_info *info, struct value *valuep)
|
|
{
|
|
if (ctx->slot_n >= 8)
|
|
return allocate_stack_slot(ctx, proc, info, valuep);
|
|
|
|
int reg_num = ctx->slot_n++;
|
|
if (ctx->slot_n == 8)
|
|
ctx->flt = 16;
|
|
if (valuep == NULL)
|
|
return 0;
|
|
|
|
/* This would normally be brought over from asm/ptrace.h, but
|
|
* when we do, we get namespace conflicts between asm/fpu.h
|
|
* and libunwind. */
|
|
enum { PT_AUR_BSP = 17 };
|
|
|
|
union cfm_t cfm = { .value = ctx->regs.cfm };
|
|
unsigned long *bsp = (unsigned long *)ctx->regs.ar[PT_AUR_BSP];
|
|
unsigned long idx = -cfm.cfm.sof + reg_num;
|
|
unsigned long *ptr = ia64_rse_skip_regs(bsp, idx);
|
|
errno = 0;
|
|
long ret = ptrace(PTRACE_PEEKDATA, proc->pid, ptr, 0);
|
|
if (ret == -1 && errno != 0)
|
|
return -1;
|
|
|
|
value_set_word(valuep, ret);
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
copy_aggregate_part(struct fetch_context *ctx, struct process *proc,
|
|
unsigned char *buf, size_t size)
|
|
{
|
|
size_t slots = (size + 7) / 8;
|
|
struct arg_type_info *long_info = type_get_simple(ARGTYPE_LONG);
|
|
while (slots-- > 0) {
|
|
size_t chunk_sz = size > 8 ? 8 : size;
|
|
size -= 8;
|
|
|
|
struct value tmp;
|
|
value_init(&tmp, proc, NULL, long_info, 0);
|
|
int rc = allocate_reg(ctx, proc, long_info, &tmp);
|
|
if (rc >= 0) {
|
|
memcpy(buf, value_get_data(&tmp, NULL), chunk_sz);
|
|
buf += 8;
|
|
}
|
|
value_destroy(&tmp);
|
|
if (rc < 0)
|
|
return -1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
allocate_arg(struct fetch_context *ctx, struct process *proc,
|
|
struct arg_type_info *info, struct value *valuep)
|
|
{
|
|
size_t sz = type_sizeof(proc, info);
|
|
size_t align = type_alignof(proc, info);
|
|
if (sz == (size_t)-1 || align == (size_t)-1)
|
|
return -1;
|
|
|
|
unsigned char *buf = value_reserve(valuep, sz);
|
|
if (buf == NULL)
|
|
return -1;
|
|
|
|
assert(align == 0 || align == 1 || align == 2 || align == 4
|
|
|| align == 8 || align == 16);
|
|
|
|
/* For aggregates with an external alignment of 16 bytes, the
|
|
* Next Even policy is used. 128-bit integers use the Next
|
|
* Even policy as well. */
|
|
if (align == 16 && ctx->slot_n % 2 != 0)
|
|
allocate_reg(ctx, proc, info, NULL);
|
|
|
|
int rc= copy_aggregate_part(ctx, proc, buf, sz);
|
|
|
|
return rc;
|
|
}
|
|
|
|
/* Stolen from David Mosberger's utrace tool, which he released under
|
|
the GPL
|
|
(http://www.gelato.unsw.edu.au/archives/linux-ia64/0104/1405.html) */
|
|
static inline double
|
|
fpreg_to_double (struct ia64_fpreg *fp) {
|
|
double result;
|
|
asm ("ldf.fill %0=%1" : "=f"(result) : "m"(*fp));
|
|
return result;
|
|
}
|
|
|
|
static int
|
|
allocate_float(struct fetch_context *ctx, struct process *proc,
|
|
struct arg_type_info *info, struct value *valuep,
|
|
int take_slot)
|
|
{
|
|
/* The actual parameter is passed in the next available
|
|
* floating-point parameter register, if one is
|
|
* available. Floating-point parameter registers are allocated
|
|
* as needed from the range f8-f15, starting with f8. */
|
|
/* Any register parameters corresponding to a
|
|
* variable-argument specification are passed in GRs. */
|
|
if (ctx->flt > 15 || ctx->ppflavor == PARAM_PACK_VARARGS)
|
|
/* If all available floating-point parameter registers
|
|
* have been used, the actual parameter is passed in
|
|
* the appropriate general register(s). */
|
|
return allocate_reg(ctx, proc, info, valuep);
|
|
|
|
union {
|
|
double d;
|
|
float f;
|
|
char buf[0];
|
|
} u = { .d = fpreg_to_double(&ctx->regs.fr[ctx->flt++]) };
|
|
if (take_slot)
|
|
allocate_reg(ctx, proc, info, NULL);
|
|
|
|
if (info->type == ARGTYPE_FLOAT)
|
|
u.f = u.d;
|
|
else
|
|
assert(info->type == ARGTYPE_DOUBLE);
|
|
|
|
if (value_reserve(valuep, sizeof(u)) == NULL)
|
|
return -1;
|
|
memmove(value_get_raw_data(valuep), u.buf, sizeof(u));
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
allocate_hfa(struct fetch_context *ctx, struct process *proc,
|
|
struct arg_type_info *info, struct value *valuep,
|
|
enum arg_type hfa_type, size_t hfa_count)
|
|
{
|
|
size_t sz = type_sizeof(proc, info);
|
|
if (sz == (size_t)-1)
|
|
return -1;
|
|
|
|
/* If an actual parameter is known to correspond to an HFA
|
|
* formal parameter, each element is passed in the next
|
|
* available floating-point argument register, until the eight
|
|
* argument registers are exhausted. The remaining elements of
|
|
* the aggregate are passed in output GRs, according to the
|
|
* normal conventions.
|
|
*
|
|
* Because HFAs are mapped to parameter slots as aggregates,
|
|
* single-precision HFAs will be allocated with two
|
|
* floating-point values in each parameter slot, but only one
|
|
* value per register.
|
|
*
|
|
* It is possible for the first of two values in a parameter
|
|
* slot to occupy the last available floating- point parameter
|
|
* register. In this case, the second value is passed in its
|
|
* designated GR, but the half of the GR that would have
|
|
* contained the first value is undefined. */
|
|
|
|
size_t slot_off = 0;
|
|
|
|
unsigned char *buf = value_reserve(valuep, sz);
|
|
if (buf == NULL)
|
|
return -1;
|
|
|
|
struct arg_type_info *hfa_info = type_get_simple(hfa_type);
|
|
size_t hfa_sz = type_sizeof(proc, hfa_info);
|
|
|
|
/* Pass in register the part that we can. */
|
|
while (ctx->flt <= 15 && hfa_count > 0) {
|
|
struct value tmp;
|
|
value_init(&tmp, proc, NULL, hfa_info, 0);
|
|
int rc = allocate_float(ctx, proc, hfa_info, &tmp, 0);
|
|
if (rc >= 0) {
|
|
memcpy(buf, value_get_data(&tmp, NULL), hfa_sz);
|
|
slot_off += hfa_sz;
|
|
buf += hfa_sz;
|
|
hfa_count--;
|
|
|
|
/* Scratch each fully used slot. */
|
|
while (slot_off >= 8) {
|
|
if (allocate_reg(ctx, proc, info, NULL) < 0)
|
|
rc = -1;
|
|
slot_off -= 8;
|
|
}
|
|
}
|
|
value_destroy(&tmp);
|
|
if (rc < 0)
|
|
return -1;
|
|
}
|
|
|
|
/* If we have half-slot opened (the case where odd
|
|
* ARGTYPE_FLOAT member fits into the last floating point
|
|
* register, and the following even member does not), finish
|
|
* it first. */
|
|
struct arg_type_info *long_info = type_get_simple(ARGTYPE_LONG);
|
|
if (slot_off != 0 && hfa_count > 0) {
|
|
struct value tmp;
|
|
value_init(&tmp, proc, NULL, long_info, 0);
|
|
int rc = allocate_reg(ctx, proc, long_info, &tmp);
|
|
if (rc >= 0) {
|
|
unsigned char *data = value_get_data(&tmp, NULL);
|
|
memcpy(buf, data, 8 - slot_off);
|
|
buf += 8 - slot_off;
|
|
hfa_count--;
|
|
}
|
|
value_destroy(&tmp);
|
|
if (rc < 0) {
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
/* The rest is passed in registers and on stack. */
|
|
size_t rest = hfa_count * hfa_sz;
|
|
return copy_aggregate_part(ctx, proc, buf, rest);
|
|
}
|
|
|
|
static int
|
|
allocate_ret(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;
|
|
|
|
/* Homogeneous floating-point aggregates [...] are returned in
|
|
* floating-point registers, provided the array or structure
|
|
* contains no more than eight individual values. The
|
|
* elements of the aggregate are placed in successive
|
|
* floating-point registers, beginning with f8. */
|
|
if (info->type == ARGTYPE_STRUCT || info->type == ARGTYPE_ARRAY) {
|
|
size_t hfa_size;
|
|
struct arg_type_info *hfa_info
|
|
= type_get_hfa_type(info, &hfa_size);
|
|
if (hfa_info != NULL && hfa_size <= 8)
|
|
return allocate_hfa(ctx, proc, info, valuep,
|
|
hfa_info->type, hfa_size);
|
|
}
|
|
|
|
/* Integers and pointers are passed in r8. 128-bit integers
|
|
* are passed in r8 and r9. Aggregates of up to 256 bits [32
|
|
* bytes] are passed in registers r8...r11. */
|
|
if (sz <= 32) {
|
|
unsigned char *buf = value_reserve(valuep, sz);
|
|
if (buf == NULL)
|
|
return -1;
|
|
memcpy(buf, ctx->regs.gr + 8, sz);
|
|
return 0;
|
|
}
|
|
|
|
if (value_pass_by_reference(valuep) < 0)
|
|
return -1;
|
|
value_set_word(valuep, ctx->r8);
|
|
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)
|
|
{
|
|
switch (info->type) {
|
|
struct arg_type_info *hfa_info;
|
|
size_t hfa_size;
|
|
|
|
case ARGTYPE_VOID:
|
|
value_set_word(valuep, 0);
|
|
return 0;
|
|
|
|
case ARGTYPE_FLOAT:
|
|
case ARGTYPE_DOUBLE:
|
|
return allocate_float(ctx, proc, info, valuep, 1);
|
|
|
|
case ARGTYPE_STRUCT:
|
|
hfa_info = type_get_hfa_type(info, &hfa_size);
|
|
if (hfa_info != NULL)
|
|
return allocate_hfa(ctx, proc, info, valuep,
|
|
hfa_info->type, hfa_size);
|
|
/* 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:
|
|
return allocate_arg(ctx, proc, info, valuep);
|
|
|
|
case ARGTYPE_ARRAY:
|
|
/* Arrays decay into pointers. XXX Fortran? */
|
|
default:
|
|
assert(info->type != info->type);
|
|
abort();
|
|
}
|
|
}
|
|
|
|
int
|
|
arch_fetch_retval(struct fetch_context *ctx, enum tof type,
|
|
struct process *proc, struct arg_type_info *info,
|
|
struct value *valuep)
|
|
{
|
|
if (fetch_context_init(proc, ctx) < 0)
|
|
return -1;
|
|
|
|
switch (info->type) {
|
|
case ARGTYPE_VOID:
|
|
case ARGTYPE_FLOAT:
|
|
case ARGTYPE_DOUBLE:
|
|
/* The rules for returning those types are the same as
|
|
* for passing them in arguments. */
|
|
return arch_fetch_arg_next(ctx, type, proc, info, valuep);
|
|
|
|
case ARGTYPE_CHAR:
|
|
case ARGTYPE_SHORT:
|
|
case ARGTYPE_USHORT:
|
|
case ARGTYPE_INT:
|
|
case ARGTYPE_UINT:
|
|
case ARGTYPE_LONG:
|
|
case ARGTYPE_ULONG:
|
|
case ARGTYPE_POINTER:
|
|
case ARGTYPE_STRUCT:
|
|
return allocate_ret(ctx, proc, info, valuep);
|
|
|
|
case ARGTYPE_ARRAY:
|
|
/* Arrays decay into pointers. XXX Fortran? */
|
|
assert(info->type != ARGTYPE_ARRAY);
|
|
abort();
|
|
}
|
|
assert("unhandled type");
|
|
abort();
|
|
return arch_fetch_arg_next(ctx, type, proc, info, valuep);
|
|
}
|
|
|
|
void
|
|
arch_fetch_arg_done(struct fetch_context *context)
|
|
{
|
|
free(context);
|
|
}
|
|
|
|
int
|
|
arch_fetch_param_pack_start(struct fetch_context *context,
|
|
enum param_pack_flavor ppflavor)
|
|
{
|
|
context->ppflavor = ppflavor;
|
|
return 0;
|
|
}
|
|
|
|
void
|
|
arch_fetch_param_pack_end(struct fetch_context *context)
|
|
{
|
|
context->ppflavor = PARAM_PACK_ARGS;
|
|
}
|