| /* |
| * This file is part of ltrace. |
| * Copyright (C) 2014 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 <stdlib.h> |
| #include <string.h> |
| #include <stdint.h> |
| |
| #include "fetch.h" |
| #include "proc.h" |
| #include "type.h" |
| #include "value.h" |
| |
| int aarch64_read_gregs(struct process *proc, struct user_pt_regs *regs); |
| int aarch64_read_fregs(struct process *proc, struct user_fpsimd_state *regs); |
| |
| |
| struct fetch_context |
| { |
| struct user_pt_regs gregs; |
| struct user_fpsimd_state fpregs; |
| arch_addr_t nsaa; |
| unsigned ngrn; |
| unsigned nsrn; |
| arch_addr_t x8; |
| }; |
| |
| static int |
| context_init(struct fetch_context *context, struct process *proc) |
| { |
| if (aarch64_read_gregs(proc, &context->gregs) < 0 |
| || aarch64_read_fregs(proc, &context->fpregs) < 0) |
| return -1; |
| |
| context->ngrn = 0; |
| context->nsrn = 0; |
| /* XXX double cast */ |
| context->nsaa = (arch_addr_t) (uintptr_t) context->gregs.sp; |
| context->x8 = 0; |
| |
| return 0; |
| } |
| |
| 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 void |
| fetch_next_gpr(struct fetch_context *context, unsigned char *buf) |
| { |
| uint64_t u = context->gregs.regs[context->ngrn++]; |
| memcpy(buf, &u, 8); |
| } |
| |
| static int |
| fetch_gpr(struct fetch_context *context, struct value *value, size_t sz) |
| { |
| if (sz < 8) |
| sz = 8; |
| |
| unsigned char *buf = value_reserve(value, sz); |
| if (buf == NULL) |
| return -1; |
| |
| size_t i; |
| for (i = 0; i < sz; i += 8) |
| fetch_next_gpr(context, buf + i); |
| |
| return 0; |
| } |
| |
| static void |
| fetch_next_sse(struct fetch_context *context, unsigned char *buf, size_t sz) |
| { |
| __int128 u = context->fpregs.vregs[context->nsrn++]; |
| memcpy(buf, &u, sz); |
| } |
| |
| static int |
| fetch_sse(struct fetch_context *context, struct value *value, size_t sz) |
| { |
| unsigned char *buf = value_reserve(value, sz); |
| if (buf == NULL) |
| return -1; |
| |
| fetch_next_sse(context, buf, sz); |
| return 0; |
| } |
| |
| static int |
| fetch_hfa(struct fetch_context *context, |
| struct value *value, struct arg_type_info *hfa_t, size_t count) |
| { |
| size_t sz = type_sizeof(value->inferior, hfa_t); |
| unsigned char *buf = value_reserve(value, sz * count); |
| if (buf == NULL) |
| return -1; |
| |
| size_t i; |
| for (i = 0; i < count; ++i) { |
| fetch_next_sse(context, buf, sz); |
| buf += sz; |
| } |
| return 0; |
| } |
| |
| static int |
| fetch_stack(struct fetch_context *context, struct value *value, |
| size_t align, size_t sz) |
| { |
| if (align < 8) |
| align = 8; |
| size_t amount = ((sz + align - 1) / align) * align; |
| |
| /* XXX double casts */ |
| uintptr_t sp = (uintptr_t) context->nsaa; |
| sp = ((sp + align - 1) / align) * align; |
| |
| value_in_inferior(value, (arch_addr_t) sp); |
| |
| sp += amount; |
| context->nsaa = (arch_addr_t) sp; |
| |
| return 0; |
| } |
| |
| enum convert_method { |
| CVT_ERR = -1, |
| CVT_NOP = 0, |
| CVT_BYREF, |
| }; |
| |
| enum fetch_method { |
| FETCH_NOP, |
| FETCH_STACK, |
| FETCH_GPR, |
| FETCH_SSE, |
| FETCH_HFA, |
| }; |
| |
| struct fetch_script { |
| enum convert_method c; |
| enum fetch_method f; |
| size_t sz; |
| struct arg_type_info *hfa_t; |
| size_t count; |
| }; |
| |
| static struct fetch_script |
| pass_arg(struct fetch_context const *context, |
| struct process *proc, struct arg_type_info *info) |
| { |
| enum fetch_method cvt = CVT_NOP; |
| |
| size_t sz = type_sizeof(proc, info); |
| if (sz == (size_t) -1) |
| return (struct fetch_script) { CVT_ERR, FETCH_NOP, sz }; |
| |
| switch (info->type) { |
| case ARGTYPE_VOID: |
| return (struct fetch_script) { cvt, FETCH_NOP, sz }; |
| |
| case ARGTYPE_STRUCT: |
| case ARGTYPE_ARRAY:; |
| size_t count; |
| struct arg_type_info *hfa_t = type_get_hfa_type(info, &count); |
| if (hfa_t != NULL && count <= 4) { |
| if (context->nsrn + count <= 8) |
| return (struct fetch_script) |
| { cvt, FETCH_HFA, sz, hfa_t, count }; |
| return (struct fetch_script) |
| { cvt, FETCH_STACK, sz, hfa_t, count }; |
| } |
| |
| if (sz <= 16) { |
| size_t count = sz / 8; |
| if (context->ngrn + count <= 8) |
| return (struct fetch_script) |
| { cvt, FETCH_GPR, sz }; |
| } |
| |
| cvt = CVT_BYREF; |
| sz = 8; |
| /* Fall through. */ |
| |
| case ARGTYPE_POINTER: |
| case ARGTYPE_INT: |
| case ARGTYPE_UINT: |
| case ARGTYPE_LONG: |
| case ARGTYPE_ULONG: |
| case ARGTYPE_CHAR: |
| case ARGTYPE_SHORT: |
| case ARGTYPE_USHORT: |
| if (context->ngrn < 8 && sz <= 8) |
| return (struct fetch_script) { cvt, FETCH_GPR, sz }; |
| /* We don't support types wider than 8 bytes as of |
| * now. */ |
| assert(sz <= 8); |
| |
| return (struct fetch_script) { cvt, FETCH_STACK, sz }; |
| |
| case ARGTYPE_FLOAT: |
| case ARGTYPE_DOUBLE: |
| if (context->nsrn < 8) { |
| /* ltrace doesn't support float128. */ |
| assert(sz <= 8); |
| return (struct fetch_script) { cvt, FETCH_SSE, sz }; |
| } |
| |
| return (struct fetch_script) { cvt, FETCH_STACK, sz }; |
| } |
| |
| assert(! "Failed to allocate argument."); |
| abort(); |
| } |
| |
| static int |
| convert_arg(struct value *value, struct fetch_script how) |
| { |
| switch (how.c) { |
| case CVT_NOP: |
| return 0; |
| case CVT_BYREF: |
| return value_pass_by_reference(value); |
| case CVT_ERR: |
| return -1; |
| } |
| |
| assert(! "Don't know how to convert argument."); |
| abort(); |
| } |
| |
| static int |
| fetch_arg(struct fetch_context *context, |
| struct process *proc, struct arg_type_info *info, |
| struct value *value, struct fetch_script how) |
| { |
| if (convert_arg(value, how) < 0) |
| return -1; |
| |
| switch (how.f) { |
| case FETCH_NOP: |
| return 0; |
| |
| case FETCH_STACK: |
| if (how.hfa_t != NULL && how.count != 0 && how.count <= 8) |
| context->nsrn = 8; |
| return fetch_stack(context, value, |
| type_alignof(proc, info), how.sz); |
| |
| case FETCH_GPR: |
| return fetch_gpr(context, value, how.sz); |
| |
| case FETCH_SSE: |
| return fetch_sse(context, value, how.sz); |
| |
| case FETCH_HFA: |
| return fetch_hfa(context, value, how.hfa_t, how.count); |
| } |
| |
| assert(! "Don't know how to fetch argument."); |
| abort(); |
| } |
| |
| 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 || context_init(context, proc) < 0) { |
| fail: |
| free(context); |
| return NULL; |
| } |
| |
| /* There's a provision in ARMv8 parameter passing convention |
| * for returning types that, if passed as first argument to a |
| * function, would be passed on stack. For those types, x8 |
| * contains an address where the return argument should be |
| * placed. The callee doesn't need to preserve the value of |
| * x8, so we need to fetch it now. |
| * |
| * To my knowledge, there are currently no types where this |
| * holds, but the code is here, utterly untested. */ |
| |
| struct fetch_script how = pass_arg(context, proc, ret_info); |
| if (how.c == CVT_ERR) |
| goto fail; |
| if (how.c == CVT_NOP && how.f == FETCH_STACK) { |
| /* XXX double cast. */ |
| context->x8 = (arch_addr_t) (uintptr_t) context->gregs.regs[8]; |
| /* See the comment above about the assert. */ |
| assert(! "Unexpected: first argument passed on stack."); |
| abort(); |
| } |
| |
| return context; |
| } |
| |
| int |
| arch_fetch_arg_next(struct fetch_context *context, enum tof type, |
| struct process *proc, struct arg_type_info *info, |
| struct value *value) |
| { |
| return fetch_arg(context, proc, info, value, |
| pass_arg(context, proc, info)); |
| } |
| |
| int |
| arch_fetch_retval(struct fetch_context *context, enum tof type, |
| struct process *proc, struct arg_type_info *info, |
| struct value *value) |
| { |
| if (context->x8 != 0) { |
| value_in_inferior(value, context->x8); |
| return 0; |
| } |
| |
| if (context_init(context, proc) < 0) |
| return -1; |
| |
| return fetch_arg(context, proc, info, value, |
| pass_arg(context, proc, info)); |
| } |
| |
| void |
| arch_fetch_arg_done(struct fetch_context *context) |
| { |
| if (context != NULL) |
| free(context); |
| } |
| |
| size_t |
| arch_type_sizeof(struct process *proc, struct arg_type_info *arg) |
| { |
| return (size_t) -2; |
| } |
| |
| size_t |
| arch_type_alignof(struct process *proc, struct arg_type_info *arg) |
| { |
| return (size_t) -2; |
| } |