aboutsummaryrefslogtreecommitdiff
path: root/sysdeps/linux-gnu/aarch64/fetch.c
diff options
context:
space:
mode:
Diffstat (limited to 'sysdeps/linux-gnu/aarch64/fetch.c')
-rw-r--r--sysdeps/linux-gnu/aarch64/fetch.c365
1 files changed, 365 insertions, 0 deletions
diff --git a/sysdeps/linux-gnu/aarch64/fetch.c b/sysdeps/linux-gnu/aarch64/fetch.c
new file mode 100644
index 0000000..8779f03
--- /dev/null
+++ b/sysdeps/linux-gnu/aarch64/fetch.c
@@ -0,0 +1,365 @@
+/*
+ * 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 "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;
+}