diff mbox

Userspace ARM BE8 support

Message ID 1330530051-20404-1-git-send-email-paul@codesourcery.com
State New
Headers show

Commit Message

Paul Brook Feb. 29, 2012, 3:40 p.m. UTC
Add support for ARM BE8 userspace binaries.
i.e. big-endian data and little-endian code.
In principle LE8 mode is also possible, but AFAIK has never actually
been implemented/used.

System emulation doesn't have any useable big-endian board models,
but should in principle work once you fix that.
Dynamic iendianness switching requires messing with data accesses,
preferably with TCG cooperation, and is orthogonal to BE8 support.

Signed-off-by: Paul Brook <paul@codesourcery.com>
---
 disas.c                |   11 +++++++++--
 linux-user/elfload.c   |    1 +
 linux-user/main.c      |   12 ++++++++++++
 linux-user/qemu.h      |    1 +
 target-arm/cpu.h       |   10 +++++++++-
 target-arm/helper.c    |   12 ++++++++++++
 target-arm/translate.c |   14 +++++++++++++-
 7 files changed, 57 insertions(+), 4 deletions(-)

Comments

Andreas Färber March 1, 2012, 8:06 a.m. UTC | #1
Am 29.02.2012 16:40, schrieb Paul Brook:
> Add support for ARM BE8 userspace binaries.
> i.e. big-endian data and little-endian code.
> In principle LE8 mode is also possible, but AFAIK has never actually
> been implemented/used.
> 
> System emulation doesn't have any useable big-endian board models,
> but should in principle work once you fix that.
> Dynamic iendianness switching requires messing with data accesses,
> preferably with TCG cooperation, and is orthogonal to BE8 support.
> 
> Signed-off-by: Paul Brook <paul@codesourcery.com>

Patch looks good in general. There's some braces missing though for ifs:
It's considered mandatory for new code, and we usually fix up old code
that we touch.

Andreas
Peter Maydell March 5, 2012, 7:04 p.m. UTC | #2
On 29 February 2012 15:40, Paul Brook <paul@codesourcery.com> wrote:
> Add support for ARM BE8 userspace binaries.
> i.e. big-endian data and little-endian code.
> In principle LE8 mode is also possible, but AFAIK has never actually
> been implemented/used.

There seems to have been an LE8 flag in the ARM ELF spec at
one point but it doesn't exist in the current version of the
spec. I'm not entirely sure what it was originally intended to
mean: the ARM ARM only talks about BE-8, BE-32 and LE.
(You probably know better than me here since you were around at
the time...)

There is a theoretical configuration involving an R profile core
with SCTLR.IE=1 SCTLR.EE=1 but CPSR.E=0, which would be an OS
running fully big-endian, user process flipped to read data little
endian but code still big-endian. I can't find anything in the
ARM ARM that rules that out but it would be pretty weird.

> @@ -3648,6 +3656,10 @@ int main(int argc, char **argv, char **envp)
>         for(i = 0; i < 16; i++) {
>             env->regs[i] = regs->uregs[i];
>         }
> +        /* Enable BE8.  */
> +        if ((info->elf_flags >> 24) >= 4 && (info->elf_flags & 0x800000)) {
> +            env->bswap_code = 1;
> +        }
>     }

If we updated the #defines in elf.h based on a newer linux kernel header
we could say
    if ((info->elf_flags & EF_ARM_EABI_MASK) >= EF_ARM_EABI_VER4
        && (info->elf_flags & EF_ARM_BE8)) {

> --- a/target-arm/cpu.h
> +++ b/target-arm/cpu.h
> @@ -216,6 +216,9 @@ typedef struct CPUARMState {
>         uint32_t cregs[16];
>     } iwmmxt;
>
> +    /* For mixed endian mode.  */
> +    int bswap_code;

If we make this a fixed-width type to start with it will avoid
having to change it if/when we implement system mode dynamic
endianness and need to put it into the vmstate save/load.

>  #if defined(CONFIG_USER_ONLY)
>     /* For usermode syscall translation.  */
>     int eabi;
> @@ -490,6 +493,8 @@ static inline void cpu_clone_regs(CPUState *env, target_ulong newsp)
>  #define ARM_TBFLAG_VFPEN_MASK       (1 << ARM_TBFLAG_VFPEN_SHIFT)
>  #define ARM_TBFLAG_CONDEXEC_SHIFT   8
>  #define ARM_TBFLAG_CONDEXEC_MASK    (0xff << ARM_TBFLAG_CONDEXEC_SHIFT)
> +#define ARM_TBFLAG_BSWAP_CODE_SHIFT 16
> +#define ARM_TBFLAG_BSWAP_CODE_MASK  (1 << ARM_TBFLAG_BSWAP_CODE_SHIFT)
>  /* Bits 31..16 are currently unused. */

We're using bit 16 now so this comment needs updating.

> diff --git a/target-arm/helper.c b/target-arm/helper.c
> index 4929372..34507b1 100644
> --- a/target-arm/helper.c
> +++ b/target-arm/helper.c
> @@ -846,6 +846,9 @@ static void do_interrupt_v7m(CPUARMState *env)
>         if (semihosting_enabled) {
>             int nr;
>             nr = lduw_code(env->regs[15]) & 0xff;
> +            if (env->bswap_code) {
> +                nr = bswap16(nr);
> +            }
>             if (nr == 0xab) {
>                 env->regs[15] += 2;
>                 env->regs[0] = do_arm_semihosting(env);

[and several similar hunks]

It's kind of ugly to have all these places doing a "ld*_code()
and then bswap it". Also it has resulted in bugs as here where
we're doing a mask before the bswap rather than afterwards.
Trying to make the generic qemu ld*_code macros handle wrong-endian
insns for the sake of ARM BE8 seems like overkill though. Maybe
we should just have target-arm/cpu.h macros/inline functions to
do the load-and-maybe-bswap ?

(As you pointed out on IRC we can't actually currently get to any
of these places with bswap_code true since they're all system
mode only code, so they're only latent bugs rather than real ones.)

-- PMM
diff mbox

Patch

diff --git a/disas.c b/disas.c
index 3b1fd97..fcb0b02 100644
--- a/disas.c
+++ b/disas.c
@@ -138,7 +138,7 @@  print_insn_thumb1(bfd_vma pc, disassemble_info *info)
 /* Disassemble this for me please... (debugging). 'flags' has the following
    values:
     i386 - 1 means 16 bit code, 2 means 64 bit code
-    arm  - nonzero means thumb code
+    arm  - bit 0 = thumb, bit 1 = reverse endian
     ppc  - nonzero means little endian
     other targets - unused
  */
@@ -169,10 +169,17 @@  void target_disas(FILE *out, target_ulong code, target_ulong size, int flags)
         disasm_info.mach = bfd_mach_i386_i386;
     print_insn = print_insn_i386;
 #elif defined(TARGET_ARM)
-    if (flags)
+    if (flags & 1)
 	print_insn = print_insn_thumb1;
     else
 	print_insn = print_insn_arm;
+    if (flags & 2) {
+#ifdef TARGET_WORDS_BIGENDIAN
+        disasm_info.endian = BFD_ENDIAN_LITTLE;
+#else
+        disasm_info.endian = BFD_ENDIAN_BIG;
+#endif
+    }
 #elif defined(TARGET_SPARC)
     print_insn = print_insn_sparc;
 #ifdef TARGET_SPARC64
diff --git a/linux-user/elfload.c b/linux-user/elfload.c
index 2fd4a93..00a152d 100644
--- a/linux-user/elfload.c
+++ b/linux-user/elfload.c
@@ -1553,6 +1553,7 @@  static void load_elf_image(const char *image_name, int image_fd,
     info->start_data = -1;
     info->end_data = 0;
     info->brk = 0;
+    info->elf_flags = ehdr->e_flags;
 
     for (i = 0; i < ehdr->e_phnum; i++) {
         struct elf_phdr *eppnt = phdr + i;
diff --git a/linux-user/main.c b/linux-user/main.c
index 14bf5f0..e8b3110 100644
--- a/linux-user/main.c
+++ b/linux-user/main.c
@@ -767,11 +767,15 @@  void cpu_loop(CPUARMState *env)
                     if (env->thumb) {
                         /* FIXME - what to do if get_user() fails? */
                         get_user_u16(insn, env->regs[15]);
+                        if (env->bswap_code)
+                            insn = bswap16(insn);
                         n = insn & 0xff;
                         env->regs[15] += 2;
                     } else {
                         /* FIXME - what to do if get_user() fails? */
                         get_user_u32(insn, env->regs[15]);
+                        if (env->bswap_code)
+                            insn = bswap32(insn);
                         n = (insn & 0xf) | ((insn >> 4) & 0xff0);
                         env->regs[15] += 4;
                     }
@@ -779,10 +783,14 @@  void cpu_loop(CPUARMState *env)
                     if (env->thumb) {
                         /* FIXME - what to do if get_user() fails? */
                         get_user_u16(insn, env->regs[15] - 2);
+                        if (env->bswap_code)
+                            insn = bswap16(insn);
                         n = insn & 0xff;
                     } else {
                         /* FIXME - what to do if get_user() fails? */
                         get_user_u32(insn, env->regs[15] - 4);
+                        if (env->bswap_code)
+                            insn = bswap32(insn);
                         n = insn & 0xffffff;
                     }
                 }
@@ -3648,6 +3656,10 @@  int main(int argc, char **argv, char **envp)
         for(i = 0; i < 16; i++) {
             env->regs[i] = regs->uregs[i];
         }
+        /* Enable BE8.  */
+        if ((info->elf_flags >> 24) >= 4 && (info->elf_flags & 0x800000)) {
+            env->bswap_code = 1;
+        }
     }
 #elif defined(TARGET_UNICORE32)
     {
diff --git a/linux-user/qemu.h b/linux-user/qemu.h
index 308dbc0..58243c6 100644
--- a/linux-user/qemu.h
+++ b/linux-user/qemu.h
@@ -51,6 +51,7 @@  struct image_info {
         abi_ulong       auxv_len;
         abi_ulong       arg_start;
         abi_ulong       arg_end;
+        uint32_t        elf_flags;
 	int		personality;
 #ifdef CONFIG_USE_FDPIC
         abi_ulong       loadmap_addr;
diff --git a/target-arm/cpu.h b/target-arm/cpu.h
index 0d9b39c..f5b50e1 100644
--- a/target-arm/cpu.h
+++ b/target-arm/cpu.h
@@ -216,6 +216,9 @@  typedef struct CPUARMState {
         uint32_t cregs[16];
     } iwmmxt;
 
+    /* For mixed endian mode.  */
+    int bswap_code;
+
 #if defined(CONFIG_USER_ONLY)
     /* For usermode syscall translation.  */
     int eabi;
@@ -490,6 +493,8 @@  static inline void cpu_clone_regs(CPUState *env, target_ulong newsp)
 #define ARM_TBFLAG_VFPEN_MASK       (1 << ARM_TBFLAG_VFPEN_SHIFT)
 #define ARM_TBFLAG_CONDEXEC_SHIFT   8
 #define ARM_TBFLAG_CONDEXEC_MASK    (0xff << ARM_TBFLAG_CONDEXEC_SHIFT)
+#define ARM_TBFLAG_BSWAP_CODE_SHIFT 16
+#define ARM_TBFLAG_BSWAP_CODE_MASK  (1 << ARM_TBFLAG_BSWAP_CODE_SHIFT)
 /* Bits 31..16 are currently unused. */
 
 /* some convenience accessor macros */
@@ -505,6 +510,8 @@  static inline void cpu_clone_regs(CPUState *env, target_ulong newsp)
     (((F) & ARM_TBFLAG_VFPEN_MASK) >> ARM_TBFLAG_VFPEN_SHIFT)
 #define ARM_TBFLAG_CONDEXEC(F) \
     (((F) & ARM_TBFLAG_CONDEXEC_MASK) >> ARM_TBFLAG_CONDEXEC_SHIFT)
+#define ARM_TBFLAG_BSWAP_CODE(F) \
+    (((F) & ARM_TBFLAG_BSWAP_CODE_MASK) >> ARM_TBFLAG_BSWAP_CODE_SHIFT)
 
 static inline void cpu_get_tb_cpu_state(CPUState *env, target_ulong *pc,
                                         target_ulong *cs_base, int *flags)
@@ -515,7 +522,8 @@  static inline void cpu_get_tb_cpu_state(CPUState *env, target_ulong *pc,
     *flags = (env->thumb << ARM_TBFLAG_THUMB_SHIFT)
         | (env->vfp.vec_len << ARM_TBFLAG_VECLEN_SHIFT)
         | (env->vfp.vec_stride << ARM_TBFLAG_VECSTRIDE_SHIFT)
-        | (env->condexec_bits << ARM_TBFLAG_CONDEXEC_SHIFT);
+        | (env->condexec_bits << ARM_TBFLAG_CONDEXEC_SHIFT)
+        | (env->bswap_code << ARM_TBFLAG_BSWAP_CODE_SHIFT);
     if (arm_feature(env, ARM_FEATURE_M)) {
         privmode = !((env->v7m.exception == 0) && (env->v7m.control & 1));
     } else {
diff --git a/target-arm/helper.c b/target-arm/helper.c
index 4929372..34507b1 100644
--- a/target-arm/helper.c
+++ b/target-arm/helper.c
@@ -846,6 +846,9 @@  static void do_interrupt_v7m(CPUARMState *env)
         if (semihosting_enabled) {
             int nr;
             nr = lduw_code(env->regs[15]) & 0xff;
+            if (env->bswap_code) {
+                nr = bswap16(nr);
+            }
             if (nr == 0xab) {
                 env->regs[15] += 2;
                 env->regs[0] = do_arm_semihosting(env);
@@ -917,8 +920,14 @@  void do_interrupt(CPUARMState *env)
             /* Check for semihosting interrupt.  */
             if (env->thumb) {
                 mask = lduw_code(env->regs[15] - 2) & 0xff;
+                if (env->bswap_code) {
+                    mask = bswap16(mask);
+                }
             } else {
                 mask = ldl_code(env->regs[15] - 4) & 0xffffff;
+                if (env->bswap_code) {
+                    mask = bswap32(mask);
+                }
             }
             /* Only intercept calls from privileged modes, to provide some
                semblance of security.  */
@@ -939,6 +948,9 @@  void do_interrupt(CPUARMState *env)
         /* See if this is a semihosting syscall.  */
         if (env->thumb && semihosting_enabled) {
             mask = lduw_code(env->regs[15]) & 0xff;
+            if (env->bswap_code) {
+                mask = bswap16(mask);
+            }
             if (mask == 0xab
                   && (env->uncached_cpsr & CPSR_M) != ARM_CPU_MODE_USR) {
                 env->regs[15] += 2;
diff --git a/target-arm/translate.c b/target-arm/translate.c
index 280bfca..ab374e1 100644
--- a/target-arm/translate.c
+++ b/target-arm/translate.c
@@ -59,6 +59,7 @@  typedef struct DisasContext {
     struct TranslationBlock *tb;
     int singlestep_enabled;
     int thumb;
+    int bswap_code;
 #if !defined(CONFIG_USER_ONLY)
     int user;
 #endif
@@ -6706,6 +6707,9 @@  static void disas_arm_insn(CPUState * env, DisasContext *s)
     TCGv_i64 tmp64;
 
     insn = ldl_code(s->pc);
+    if (s->bswap_code) {
+        insn = bswap32(insn);
+    }
     s->pc += 4;
 
     /* M variants do not implement ARM mode.  */
@@ -8134,6 +8138,9 @@  static int disas_thumb2_insn(CPUState *env, DisasContext *s, uint16_t insn_hw1)
     }
 
     insn = lduw_code(s->pc);
+    if (s->bswap_code) {
+        insn = bswap16(insn);
+    }
     s->pc += 2;
     insn |= (uint32_t)insn_hw1 << 16;
 
@@ -9164,6 +9171,9 @@  static void disas_thumb_insn(CPUState *env, DisasContext *s)
     }
 
     insn = lduw_code(s->pc);
+    if (s->bswap_code) {
+        insn = bswap16(insn);
+    }
     s->pc += 2;
 
     switch (insn >> 12) {
@@ -9855,6 +9865,7 @@  static inline void gen_intermediate_code_internal(CPUState *env,
     dc->singlestep_enabled = env->singlestep_enabled;
     dc->condjmp = 0;
     dc->thumb = ARM_TBFLAG_THUMB(tb->flags);
+    dc->bswap_code = ARM_TBFLAG_BSWAP_CODE(tb->flags);
     dc->condexec_mask = (ARM_TBFLAG_CONDEXEC(tb->flags) & 0xf) << 1;
     dc->condexec_cond = ARM_TBFLAG_CONDEXEC(tb->flags) >> 4;
 #if !defined(CONFIG_USER_ONLY)
@@ -10088,7 +10099,8 @@  done_generating:
     if (qemu_loglevel_mask(CPU_LOG_TB_IN_ASM)) {
         qemu_log("----------------\n");
         qemu_log("IN: %s\n", lookup_symbol(pc_start));
-        log_target_disas(pc_start, dc->pc - pc_start, dc->thumb);
+        log_target_disas(pc_start, dc->pc - pc_start, 
+                         dc->thumb | (dc->bswap_code << 1));
         qemu_log("\n");
     }
 #endif