881 lines
21 KiB
C
881 lines
21 KiB
C
/*
|
|
* 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);
|
|
}
|