| /* |
| * This file is part of ltrace. |
| * Copyright (C) 2013 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 <sys/ptrace.h> |
| #include <asm/ptrace.h> |
| #include <assert.h> |
| #include <elf.h> |
| #include <libelf.h> |
| #include <stdint.h> |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <string.h> |
| #include <stdbool.h> |
| |
| #include "backend.h" |
| #include "fetch.h" |
| #include "library.h" |
| #include "proc.h" |
| #include "ptrace.h" |
| #include "regs.h" |
| #include "type.h" |
| #include "value.h" |
| |
| enum { |
| /* How many (double) VFP registers the AAPCS uses for |
| * parameter passing. */ |
| NUM_VFP_REGS = 8, |
| }; |
| |
| struct fetch_context { |
| struct pt_regs regs; |
| |
| struct { |
| union { |
| double d[32]; |
| float s[64]; |
| }; |
| uint32_t fpscr; |
| } fpregs; |
| |
| /* VFP register allocation. ALLOC.S tracks whether the |
| * corresponding FPREGS.S register is taken, ALLOC.D the same |
| * for FPREGS.D. We only track 8 (16) registers, because |
| * that's what the ABI uses for parameter passing. */ |
| union { |
| int16_t d[NUM_VFP_REGS]; |
| int8_t s[NUM_VFP_REGS * 2]; |
| } alloc; |
| |
| unsigned ncrn; |
| arch_addr_t sp; |
| arch_addr_t nsaa; |
| arch_addr_t ret_struct; |
| |
| bool hardfp:1; |
| bool in_varargs:1; |
| }; |
| |
| static int |
| fetch_register_banks(struct process *proc, struct fetch_context *context) |
| { |
| if (ptrace(PTRACE_GETREGS, proc->pid, NULL, &context->regs) == -1) |
| return -1; |
| |
| if (context->hardfp |
| && ptrace(PTRACE_GETVFPREGS, proc->pid, |
| NULL, &context->fpregs) == -1) |
| return -1; |
| |
| context->ncrn = 0; |
| context->nsaa = context->sp = get_stack_pointer(proc); |
| memset(&context->alloc, 0, sizeof(context->alloc)); |
| |
| 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)); |
| |
| { |
| struct process *mainp = proc; |
| while (mainp->libraries == NULL && mainp->parent != NULL) |
| mainp = mainp->parent; |
| context->hardfp = mainp->libraries->arch.hardfp; |
| } |
| |
| if (context == NULL |
| || fetch_register_banks(proc, context) < 0) { |
| free(context); |
| return NULL; |
| } |
| |
| if (ret_info->type == ARGTYPE_STRUCT |
| || ret_info->type == ARGTYPE_ARRAY) { |
| size_t sz = type_sizeof(proc, ret_info); |
| assert(sz != (size_t)-1); |
| if (sz > 4) { |
| /* XXX double cast */ |
| context->ret_struct |
| = (arch_addr_t)context->regs.uregs[0]; |
| context->ncrn++; |
| } |
| } |
| |
| 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; |
| } |
| |
| /* 0 is success, 1 is failure, negative value is an error. */ |
| static int |
| pass_in_vfp(struct fetch_context *ctx, struct process *proc, |
| enum arg_type type, size_t count, struct value *valuep) |
| { |
| assert(type == ARGTYPE_FLOAT || type == ARGTYPE_DOUBLE); |
| unsigned max = type == ARGTYPE_DOUBLE ? NUM_VFP_REGS : 2 * NUM_VFP_REGS; |
| if (count > max) |
| return 1; |
| |
| size_t i; |
| size_t j; |
| for (i = 0; i < max; ++i) { |
| for (j = i; j < i + count; ++j) |
| if ((type == ARGTYPE_DOUBLE && ctx->alloc.d[j] != 0) |
| || (type == ARGTYPE_FLOAT && ctx->alloc.s[j] != 0)) |
| goto next; |
| |
| /* Found COUNT consecutive unallocated registers at I. */ |
| const size_t sz = (type == ARGTYPE_FLOAT ? 4 : 8) * count; |
| unsigned char *data = value_reserve(valuep, sz); |
| if (data == NULL) |
| return -1; |
| |
| for (j = i; j < i + count; ++j) |
| if (type == ARGTYPE_DOUBLE) |
| ctx->alloc.d[j] = -1; |
| else |
| ctx->alloc.s[j] = -1; |
| |
| if (type == ARGTYPE_DOUBLE) |
| memcpy(data, ctx->fpregs.d + i, sz); |
| else |
| memcpy(data, ctx->fpregs.s + i, sz); |
| |
| return 0; |
| |
| next: |
| continue; |
| } |
| return 1; |
| } |
| |
| /* 0 is success, 1 is failure, negative value is an error. */ |
| static int |
| consider_vfp(struct fetch_context *ctx, struct process *proc, |
| struct arg_type_info *info, struct value *valuep) |
| { |
| struct arg_type_info *float_info = NULL; |
| size_t hfa_size = 1; |
| if (info->type == ARGTYPE_FLOAT || info->type == ARGTYPE_DOUBLE) |
| float_info = info; |
| else |
| float_info = type_get_hfa_type(info, &hfa_size); |
| |
| if (float_info != NULL && hfa_size <= 4) |
| return pass_in_vfp(ctx, proc, float_info->type, |
| hfa_size, valuep); |
| return 1; |
| } |
| |
| int |
| arch_fetch_arg_next(struct fetch_context *ctx, enum tof type, |
| struct process *proc, |
| struct arg_type_info *info, struct value *valuep) |
| { |
| const size_t sz = type_sizeof(proc, info); |
| assert(sz != (size_t)-1); |
| |
| if (ctx->hardfp && !ctx->in_varargs) { |
| int rc; |
| if ((rc = consider_vfp(ctx, proc, info, valuep)) != 1) |
| return rc; |
| } |
| |
| /* IHI0042E_aapcs: If the argument requires double-word |
| * alignment (8-byte), the NCRN is rounded up to the next even |
| * register number. */ |
| const size_t al = type_alignof(proc, info); |
| assert(al != (size_t)-1); |
| if (al == 8) |
| ctx->ncrn = ((ctx->ncrn + 1) / 2) * 2; |
| |
| /* If the size in words of the argument is not more than r4 |
| * minus NCRN, the argument is copied into core registers, |
| * starting at the NCRN. */ |
| /* If the NCRN is less than r4 and the NSAA is equal to the |
| * SP, the argument is split between core registers and the |
| * stack. */ |
| |
| const size_t words = (sz + 3) / 4; |
| if (ctx->ncrn < 4 && ctx->nsaa == ctx->sp) { |
| unsigned char *data = value_reserve(valuep, words * 4); |
| if (data == NULL) |
| return -1; |
| size_t i; |
| for (i = 0; i < words && ctx->ncrn < 4; ++i) { |
| memcpy(data, &ctx->regs.uregs[ctx->ncrn++], 4); |
| data += 4; |
| } |
| const size_t rest = (words - i) * 4; |
| if (rest > 0) { |
| umovebytes(proc, ctx->nsaa, data, rest); |
| ctx->nsaa += rest; |
| } |
| return 0; |
| } |
| |
| assert(ctx->ncrn == 4); |
| |
| /* If the argument required double-word alignment (8-byte), |
| * then the NSAA is rounded up to the next double-word |
| * address. */ |
| if (al == 8) |
| /* XXX double cast. */ |
| ctx->nsaa = (arch_addr_t)((((uintptr_t)ctx->nsaa + 7) / 8) * 8); |
| else |
| ctx->nsaa = (arch_addr_t)((((uintptr_t)ctx->nsaa + 3) / 4) * 4); |
| |
| value_in_inferior(valuep, ctx->nsaa); |
| ctx->nsaa += sz; |
| |
| return 0; |
| } |
| |
| int |
| arch_fetch_retval(struct fetch_context *ctx, enum tof type, |
| struct process *proc, struct arg_type_info *info, |
| struct value *valuep) |
| { |
| if (fetch_register_banks(proc, ctx) < 0) |
| return -1; |
| |
| if (ctx->hardfp && !ctx->in_varargs) { |
| int rc; |
| if ((rc = consider_vfp(ctx, proc, info, valuep)) != 1) |
| return rc; |
| } |
| |
| size_t sz = type_sizeof(proc, info); |
| assert(sz != (size_t)-1); |
| |
| switch (info->type) { |
| unsigned char *data; |
| |
| case ARGTYPE_VOID: |
| return 0; |
| |
| case ARGTYPE_FLOAT: |
| case ARGTYPE_DOUBLE: |
| if (ctx->hardfp && !ctx->in_varargs) { |
| unsigned char *data = value_reserve(valuep, sz); |
| if (data == NULL) |
| return -1; |
| memmove(data, &ctx->fpregs, sz); |
| return 0; |
| } |
| goto pass_in_registers; |
| |
| case ARGTYPE_ARRAY: |
| case ARGTYPE_STRUCT: |
| if (sz > 4) { |
| value_in_inferior(valuep, ctx->ret_struct); |
| return 0; |
| } |
| /* 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: |
| pass_in_registers: |
| if ((data = value_reserve(valuep, sz)) == NULL) |
| return -1; |
| memmove(data, ctx->regs.uregs, sz); |
| return 0; |
| } |
| assert(info->type != info->type); |
| abort(); |
| } |
| |
| 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) |
| { |
| if (ppflavor == PARAM_PACK_VARARGS) |
| context->in_varargs = true; |
| return 0; |
| } |
| |
| void |
| arch_fetch_param_pack_end(struct fetch_context *context) |
| { |
| context->in_varargs = false; |
| } |