summaryrefslogtreecommitdiffstats
path: root/src/sysdeps/arm/stack.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/sysdeps/arm/stack.c')
-rw-r--r--src/sysdeps/arm/stack.c269
1 files changed, 269 insertions, 0 deletions
diff --git a/src/sysdeps/arm/stack.c b/src/sysdeps/arm/stack.c
new file mode 100644
index 0000000..d1388e0
--- /dev/null
+++ b/src/sysdeps/arm/stack.c
@@ -0,0 +1,269 @@
+/*
+ ARM EABI argument extraction
+
+ Copyright (C) 2009 Akos Pasztory <akos.pasztory@gmail.com>
+
+ This file is part of the latrace.
+
+ The latrace 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 3 of the License, or
+ (at your option) any later version.
+
+ The latrace 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 the latrace (file COPYING). If not, see
+ <http://www.gnu.org/licenses/>.
+*/
+
+
+#include "config.h"
+
+static inline unsigned long align_up(unsigned long v, unsigned long a)
+{
+ return (v + a-1) & ~(a-1);
+}
+
+/*
+ * The .arch field is split the following way:
+ * -- valid :1
+ * -- size :15
+ * -- offset :16
+ */
+
+static inline void arch_set(struct lt_arg *arg,
+ unsigned int size, unsigned int offs)
+{
+ arg->arch = (void *)((size << 16) | offs | 0x80000000);
+}
+
+static inline int arch_valid(struct lt_arg *arg)
+{
+ return (unsigned int)arg->arch & 0x80000000;
+}
+
+static inline unsigned int arch_get_size(struct lt_arg *arg)
+{
+ return ((unsigned int)arg->arch & ~0x80000000) >> 16;
+}
+
+static inline unsigned int arch_get_offs(struct lt_arg *arg)
+{
+ return (unsigned int)arg->arch & 0xffff;
+}
+
+/* Processes a struct entirely in memory. */
+static int process_struct_mem(struct lt_config_shared *cfg, struct lt_arg *arg,
+ void *pval, struct lt_args_data *data)
+{
+ int i;
+ unsigned long npval;
+ struct lt_arg *a;
+
+ lt_args_cb_struct(cfg, LT_ARGS_STRUCT_ITSELF, arg, 0, data, 0);
+ npval = (unsigned long)pval;
+ i = 0;
+ lt_list_for_each_entry(a, arg->args_head, args_list) {
+ lt_args_cb_struct(cfg, LT_ARGS_STRUCT_ARG, a,
+ (void *)(npval + arch_get_offs(a)),
+ data, i+1 == arg->mmbcnt);
+ ++i;
+ }
+ return 0;
+}
+
+/* Returns a bytearray slice from OFFS with SIZE length, treating the
+ * registers and the stack consecutively. Return value is static,
+ * only valid until the next get() call. */
+static void *get(unsigned long offs, int size, La_regs *regs)
+{
+ /* NB: size of buf is fixed. But if we only copy POD types, hopefully
+ * it won't become a problem. */
+ static __thread char buf[64];
+ int i;
+
+ for (i = 0; i < size; ++i) {
+ if (offs+i < 16)
+ buf[i] = ((char *)regs->lr_reg)[offs+i];
+ else
+ buf[i] = *(char *)(regs->lr_sp + (offs+i-16));
+ }
+ return buf;
+}
+
+/* Calculate member sizes and offsets of a structure. Returns the
+ * size of the entire structure. */
+static unsigned int calcstruct(struct lt_arg *arg)
+{
+ unsigned int maxl, moffs;
+ struct lt_arg *a;
+
+ /* XXX: this is incorrect for struct-in-struct... */
+ maxl = moffs = 0;
+ lt_list_for_each_entry(a, arg->args_head, args_list) {
+ /* We need to know the strictest aligned member to
+ * determine the size of the entire struct. */
+ if (a->type_len > maxl)
+ maxl = a->type_len;
+ /* Align members naturally. */
+ moffs = align_up(moffs, a->type_len);
+ arch_set(a, a->type_len, moffs);
+ moffs += a->type_len;
+ }
+ return align_up(moffs, maxl);
+}
+
+/* Calculates all offsets and `real' (padded) sizes of arguments
+ * (including struct members). Stores them in ->arch. */
+static void calcofs(struct lt_args_sym *asym)
+{
+ int i;
+ unsigned int offs;
+ struct lt_arg *arg;
+
+ /* If function returns a struct by value larger than a word,
+ * it is stored in callee-allocated memory and r0 contains the
+ * address. */
+ arg = asym->args[LT_ARGS_RET];
+ offs = 0;
+ if (arg->dtype == LT_ARGS_DTYPE_STRUCT) {
+ if (!arg->pointer)
+ offs = 4;
+ arch_set(arg, calcstruct(arg), 0);
+ }
+ for (i = 1; i < asym->argcnt; ++i) {
+ unsigned int asize;
+
+ arg = asym->args[i];
+ /* Struct sizes/offsets should be calculated at all times. */
+ if (arg->dtype == LT_ARGS_DTYPE_STRUCT)
+ calcstruct(arg);
+ if (arg->dtype != LT_ARGS_DTYPE_STRUCT || arg->pointer) {
+ /* Non-composite type. */
+ /* Size is 4 if arg is smaller than a word. */
+ asize = arg->type_len;
+ if (asize < 4)
+ asize = 4;
+ /* Types are aligned to their sizes. */
+ offs = align_up(offs, asize);
+ arch_set(arg, asize, offs);
+ } else {
+ /* Composite type. */
+ asize = calcstruct(arg);
+ arch_set(arg, asize, offs);
+ }
+ offs += asize;
+ /* If we are allocating from registers (4*4 bytes),
+ * each arg must start in a new one. */
+ if (offs < 16)
+ offs = align_up(offs, 4);
+ }
+}
+
+/*
+ * -- args which fit are in r0..r3, rest is on the stack
+ * -- if return type is struct, r0 contains the address for it
+ * -- dword sized args must be aligned at even registers
+ * -- one argument may be split between registers and stack
+ * -- on stack, 8byte values are aligned % 8 == 0
+ */
+int lt_stack_process(struct lt_config_shared *cfg, struct lt_args_sym *asym,
+ La_regs *regs, struct lt_args_data *data)
+{
+ int i;
+
+ for (i = 1; i < asym->argcnt; ++i) {
+ struct lt_arg *arg;
+ void *p;
+ unsigned int o, lastp;
+
+ lastp = i+1 == asym->argcnt;
+ arg = asym->args[i];
+ if (!arch_valid(arg))
+ calcofs(asym);
+ /* POD */
+ if (arg->dtype != LT_ARGS_DTYPE_STRUCT) {
+ p = get(arch_get_offs(arg), arch_get_size(arg), regs);
+ lt_args_cb_arg(cfg, arg, p, data, lastp, 1);
+ continue;
+ }
+ /* struct pointer */
+ if (arg->pointer) {
+ p = get(arch_get_offs(arg), sizeof(void *), regs);
+ p = *(void **)p;
+ lt_args_cb_arg(cfg, arg, p, data, lastp, 1);
+ if (cfg->args_detailed)
+ process_struct_mem(cfg, arg, p, data);
+ continue;
+ }
+ /* struct by value */
+ o = arch_get_offs(arg);
+ /* If part of the struct is in registers this outputs 'REG',
+ * otherwise, the stack address is printed. */
+ lt_args_cb_arg(cfg, arg,
+ (void *)(o < 16 ? 0 : regs->lr_sp + o-16),
+ data, lastp, 1);
+ if (cfg->args_detailed) {
+ int j;
+ struct lt_arg *m;
+
+ j = 0;
+ lt_args_cb_struct(cfg, LT_ARGS_STRUCT_ITSELF, arg,
+ 0, data, 0);
+ lt_list_for_each_entry(m, arg->args_head, args_list) {
+ unsigned int o;
+
+ o = arch_get_offs(arg) + arch_get_offs(m);
+ p = get(o, arch_get_size(m), regs);
+ lt_args_cb_struct(cfg, LT_ARGS_STRUCT_ARG, m,
+ p, data, j+1 == arg->mmbcnt);
+ ++j;
+ }
+ }
+ }
+ return 0;
+}
+
+/*
+ * -- 32bit return values in r0
+ * -- 64bit in {r0, r1}
+ * -- composite < 4bytes in r0
+ * -- composite > 4bytes in memory (address is passed as the first parameter
+ * of the call)
+ */
+int lt_stack_process_ret(struct lt_config_shared *cfg,
+ struct lt_args_sym *asym, La_retval *regs,
+ struct lt_args_data *data)
+{
+ struct lt_arg *arg;
+ void *pval;
+
+ /* I think this works for both 32 and 64 bit values (r0 and r1
+ * are consecutive in lrv_reg[]). */
+ pval = &(regs->lrv_reg[0]);
+ arg = asym->args[LT_ARGS_RET];
+ lt_args_cb_arg(cfg, arg, pval, data, 1, 0);
+ if (cfg->args_detailed && arg->dtype == LT_ARGS_DTYPE_STRUCT) {
+ unsigned int asize;
+ /* AAPCS says: a composite type less than 4 bytes is
+ * returned in r0. Larger types have memory allocated
+ * by caller and its address passed as the (hidden)
+ * 1st argument.
+ *
+ * NB: Though I, personally, haven't found it in the
+ * AAPCS, it's both logical (and apparently gcc does
+ * so) to return this address in r0. */
+ asize = arch_get_size(arg);
+ if (arg->pointer || asize > 4)
+ pval = (void *)regs->lrv_reg[0];
+ else if (asize > 4)
+ pval = &regs->lrv_reg[0];
+ process_struct_mem(cfg, arg, pval, data);
+ }
+ return 0;
+}
+/* End of stack.c */