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?
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.
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?
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.
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:
Thumb:
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:
Thumb:
The value for the CPSR
register read by the JTAG probe is the one you would expect, i.e. has the T
bit 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.