| /* |
| * 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); |
| } |