Search code examples
gccarm

How to write a function to read ARM CPSR in either ARM or THUMB mode?


I am working on an ARMv7 (Cortex-A7) system, and I want to read CPSR from C file in either ARM mode or THUMB mode.

Firstly, I used the embedded ASSEMBLY instruction in C function as follows,

 __asm__ volatile("mrs %0, CPSR\n" : "=r"(regval));

When I compiled the C file with -mthumb and ran the code with GDB, it showed that the regval is 0x60000010 which is NOT the 0x60000030 shown by GDB!

So how to write a function to read CPSR in either ARM or THUMB mode?

Updated with compiling option

a) Build the code with following command line to specify the THUMB mode.
arm-linux-gnueabi-gcc -g2 backtrace.c -mcpu=cortex-a7 -static -mthumb -o tbacktrace Run tbacktrace with qemu and GDB, I got different value as,

(gdb) p/x regval
$7 = 0x60000010
(gdb) p/x $cpsr
$8 = 0x60000030

The question is why my mrs %0, CPSR\n showd CPSR is ARM mode, instead of THUMB mode which the code is built.

b) When build the code with command line (not specify -mcpu=cortex-a7),

arm-linux-gnueabi-gcc -g2 backtrace.c -mthumb -o tbacktrace

there reported the following error.

$ arm-linux-gnueabi-gcc -g2 backtrace.c -mthumb -o tbacktrace
/tmp/ccOg2tlo.s: Assembler messages:
/tmp/ccOg2tlo.s:2256: Error: selected processor does not support `mrs r3,CPSR' in Thumb mode
/tmp/ccOg2tlo.s:2398: Error: selected processor does not support `mrs r3,CPSR' in Thumb mode

c) Build the code without -mcpu or -mthumb, the code can be built and ran well. So I think there should be some other ways to get right CPSR in both ARM and THUMB modes.

Updated with more assembly codes.

arm-linux-gnueabi-objdump -M force-thumb -d a.elf shows following,

4000014c:       0ff0            lsrs    r0, r6, #31
4000014e:       e92d 0f30       stmdb   sp!, {r4, r5, r8, r9, sl, fp}
40000152:       ee30 3407       cdp     4, 3, cr3, cr0, cr7, {0}
40000156:       e210            b.n     4000057a <__aeabi_f2d+0x16>
40000158:       3ba3            subs    r3, #163        ; 0xa3
4000015a:       e1a0            b.n     4000049e <__adddf3+0x1f6>
4000015c:       001b            movs    r3, r3
4000015e:       0a00            lsrs    r0, r0, #8
40000160:       a000            add     r0, pc, #0      ; (adr r0, 40000164 <B_Loop1>)
40000162:       e3a0            b.n     400008a6 <__udivmoddi4+0x19a>

......
400002a8 <__adddf3>:
400002a8:       b530            push    {r4, r5, lr}
400002aa:       ea4f 0441       mov.w   r4, r1, lsl #1
400002ae:       ea4f 0543       mov.w   r5, r3, lsl #1
400002b2:       ea94 0f05       teq     r4, r5
400002b6:       bf08            it      eq
400002b8:       ea90 0f02       teqeq   r0, r2
400002bc:       bf1f            itttt   ne

Here is a part of code of the project, which is built with -mthumb -mcpu=cortex-a7.
As Nate and Frant mentioned, I think the code is running in THUMB mode, and checking Tbit of CPSR to detect ARM or THUMB mode is un-necessary, is it correct?

A way to detect THUMB or ARM mode

After reading Nate's and Frant's comments, I had an idea to detect which mode the CPU is not by reading Tbit of CPSR. The idea is by reading PC register two times, and check the difference. If it is 2 (length of THUMB instruction), CPU is running in THUMB mode, if it is 4 (length of ARM instruction), CPU is in ARM mode.
The code is as follows,

register uint32_t pc1, pc2;
asm volatile("mov %0, pc\n mov %1, pc" : "=r"(pc1), "=r"(pc2));

I built the code with and without -mthumb, with -Os, the code seems to be able to detect the THUMB or ARM mode.


Solution

  • CPSR.c:

    #include <stdint.h>
    
    int main(int argc, char* argv[]) {
      uint32_t regval;
      asm volatile("mrs %0, CPSR" : "=r"(regval));
      return regval;
    }
    

    If you don't use -mcpu=cortex-a7, your compiler will default to another CPU:

    /opt/arm/10/gcc-arm-none-eabi-10.3-2021.10/bin/arm-none-eabi-gcc -O0 -nostartfiles -nostdlib -Wl,--section-start=.text=0x80800000 -S CPSR.c
    
    cat CPSR.s 
            .cpu arm7tdmi
            .arch armv4t
    
        
    

    The ARM7TDMI-S was introduced in 2001, and, as pointed out by your compiler, does not seem to support mrs r3,CPSR in Thumb mode. Therefore, you must specify -mcpu=cortex-a7:

    /opt/arm/10/gcc-arm-none-eabi-10.3-2021.10/bin/arm-none-eabi-gcc -mcpu=cortex-a7 -O0 -nostartfiles -nostdlib -Wl,--section-start=.text=0x80800000 -S CPSR.c
    
    cat CPSR.s 
           .cpu cortex-a7
           .arch armv7-a
    

    CPU and architecture are now as expected.

    Testing your code on real hardware - a Cortex-A7 running u-boot - in Arm and Thumb mode:

    Arm:

    /opt/arm/10/gcc-arm-none-eabi-10.3-2021.10/bin/arm-none-eabi-gcc -O0 -mcpu=cortex-a7 -marm -nostartfiles -nostdlib -Wl,--section-start=.text=0x80800000 -o CPSR-arm.elf CPSR.c
    /opt/arm/10/gcc-arm-none-eabi-10.3-2021.10/bin/../lib/gcc/arm-none-eabi/10.3.1/../../../../arm-none-eabi/bin/ld: warning: cannot find entry symbol _start; defaulting to 0000000080800000
    /opt/arm/10/gcc-arm-none-eabi-10.3-2021.10/bin/arm-none-eabi-objcopy -O srec CPSR-arm.elf CPSR-arm.srec
    /opt/arm/10/gcc-arm-none-eabi-10.3-2021.10/bin/arm-none-eabi-objdump -j .text -D CPSR-arm.elf
    
    CPSR-arm.elf:     file format elf32-littlearm
    
    
    Disassembly of section .text:
    
    80800000 <main>:
    80800000:       e52db004        push    {fp}            ; (str fp, [sp, #-4]!)
    80800004:       e28db000        add     fp, sp, #0
    80800008:       e24dd01c        sub     sp, sp, #28
    8080000c:       e50b0010        str     r0, [fp, #-16]
    80800010:       e50b1014        str     r1, [fp, #-20]  ; 0xffffffec
    80800014:       e50b2018        str     r2, [fp, #-24]  ; 0xffffffe8
    80800018:       e10f3000        mrs     r3, CPSR
    8080001c:       e50b3008        str     r3, [fp, #-8]
    80800020:       e51b3008        ldr     r3, [fp, #-8]
    80800024:       e1a00003        mov     r0, r3
    80800028:       e28bd000        add     sp, fp, #0
    8080002c:       e49db004        pop     {fp}            ; (ldr fp, [sp], #4)
    80800030:       e12fff1e        bx      lr
    

    I.MX7d running u-boot:

    # loads
    ## Ready for S-Record download ...
    
    ## First Load Addr = 0x80800000
    ## Last  Load Addr = 0x80800033
    ## Total Size      = 0x00000034 = 52 Bytes
    CACHE: Misaligned operation at range [80800000, 80800034]
    ## Start Addr      = 0x80800000
    # go 0x80800000
    ## Starting application at 0x80800000 ...
    ## Application terminated, rc = 0x200000D3
    

    Thumb:

    /opt/arm/10/gcc-arm-none-eabi-10.3-2021.10/bin/arm-none-eabi-gcc -O0 -mcpu=cortex-a7 -mthumb -nostartfiles -nostdlib -Wl,--section-start=.text=0x80800000 -o CPSR-thumb.elf CPSR.c
    /opt/arm/10/gcc-arm-none-eabi-10.3-2021.10/bin/../lib/gcc/arm-none-eabi/10.3.1/../../../../arm-none-eabi/bin/ld: warning: cannot find entry symbol _start; defaulting to 0000000080800000
    /opt/arm/10/gcc-arm-none-eabi-10.3-2021.10/bin/arm-none-eabi-objcopy -O srec CPSR-thumb.elf CPSR-thumb.srec
    /opt/arm/10/gcc-arm-none-eabi-10.3-2021.10/bin/arm-none-eabi-objdump -j .text -D CPSR-thumb.elf
    
    CPSR-thumb.elf:     file format elf32-littlearm
    
    
    Disassembly of section .text:
    
    80800000 <main>:
    80800000:       b480            push    {r7}
    80800002:       b087            sub     sp, #28
    80800004:       af00            add     r7, sp, #0
    80800006:       60f8            str     r0, [r7, #12]
    80800008:       60b9            str     r1, [r7, #8]
    8080000a:       607a            str     r2, [r7, #4]
    8080000c:       f3ef 8300       mrs     r3, CPSR
    80800010:       617b            str     r3, [r7, #20]
    80800012:       697b            ldr     r3, [r7, #20]
    80800014:       4618            mov     r0, r3
    80800016:       371c            adds    r7, #28
    80800018:       46bd            mov     sp, r7
    8080001a:       bc80            pop     {r7}
    8080001c:       4770            bx      lr
    

    I.MX7d running u-boot:

    # loads
    ## Ready for S-Record download ...
    
    ## First Load Addr = 0x80800000
    ## Last  Load Addr = 0x8080001D
    ## Total Size      = 0x0000001E = 30 Bytes
    CACHE: Misaligned operation at range [80800000, 8080001e]
    ## Start Addr      = 0x80800000
    # 
    # go 0x80800001
    ## Starting application at 0x80800001 ...
    ## Application terminated, rc = 0x200000D3
    

    Bottom-line, both versions returned the same value for CPSR, i.e. 0x200000D3.

    To the question

    How to write a function to read ARM CPSR in either ARM or THUMB mode?

    The answer would then be: The way you did. Asking why p/x regval and p/x $cpsr are not returning the same value should be the topic for a different question, may be on the GDB forum.


    Update #1: Nate Eldredge explained why the value read into the register has always the T bit set to zero.

    Testing on a different Cortex-A7 (Allwinner H3), a JLink probe and the Ozone debugger, we can see that even though the value read by the MRS instruction is 0x200000D3, the value of CPSR_USR read by the JTAG probe and Ozone is 0x200001F3 when executing the Thumb version, and 0x200000D3 when executing the Arm version:

    Arm:

    Arm - disassembly

    Arm - general purpose/system registers

    Thumb:

    Thumb - disassembly

    Thumb - general purpose/system registers

    This would I.M.H.O. perfectly validate his explanation.


    Update #2

    Still using the JLink debug probe, but in combination with JLinkGDBServerExe and arm-none-eabi-gdb 12.1 in TUI mode:

    Arm:

    GDB debug window - Arm mode

    R3/CPSR values - Arm mode

    Thumb:

    GDB debug window - Thumb mode

    R3/CPSR values - Thumb mode

    The value for the CPSR register read by the JTAG probe is the one you would expect, i.e. has the Tbit set in Thumb mode.

    You probably would get the same result in Linux using a TRACE32 JTAG probe.

    Not sure this could be useful, but note that some pre-defined symbols differ when building an Arm or Thumb executable:

    /opt/arm/11/arm-gnu-toolchain-11.3.rel1-x86_64-arm-none-eabi/bin/arm-none-eabi-gcc -dM -E -mcpu=cortex-a7 -marm - < /dev/null | grep -i arm
    #define __ARM_SIZEOF_WCHAR_T 4
    #define __ARM_FEATURE_SAT 1
    #define __ARM_ARCH_ISA_ARM 1
    #define __ARMEL__ 1
    #define __ARM_FEATURE_IDIV 1
    #define __ARM_SIZEOF_MINIMAL_ENUM 1
    #define __ARM_FEATURE_LDREX 15
    #define __ARM_PCS 1
    #define __ARM_FEATURE_QBIT 1
    #define __ARM_ARCH_PROFILE 65
    #define __ARM_32BIT_STATE 1
    #define __ARM_FEATURE_CLZ 1
    #define __ARM_ARCH_ISA_THUMB 2
    #define __ARM_ARCH 7
    #define __ARM_FEATURE_UNALIGNED 1
    #define __arm__ 1
    #define __ARM_ARCH_7A__ 1
    #define __ARM_FEATURE_SIMD32 1
    #define __ARM_FEATURE_COPROC 15
    #define __ARM_FEATURE_DSP 1
    #define __ARM_ARCH_EXT_IDIV__ 1
    #define __ARM_EABI__ 1
    
    /opt/arm/11/arm-gnu-toolchain-11.3.rel1-x86_64-arm-none-eabi/bin/arm-none-eabi-gcc -dM -E -mcpu=cortex-a7 -mthumb - < /dev/null | grep -i thumb
    #define __thumb2__ 1
    #define __THUMB_INTERWORK__ 1
    #define __thumb__ 1
    #define __ARM_ARCH_ISA_THUMB 2
    #define __THUMBEL__ 1
    

    You could therefore use #ifdef __arm__ and #ifdef __thumb2__ statements in your code in order to know if you are executing the Arm or the Thumb version.