1
votes

I read a couple of articles including question here in SO Why does the ARM PC register point to the instruction after the next one to be executed?, that pc register value is actually current executing instruction address plus 2 instructions ahead, so in ARM state it's +8 byte (2*32bits).

My question is that, for thumb state, there could be 16bits or 32bits instructions, does it mean that the fetching pc address could be an offset of +4 bytes OR +8 bytes for 16/32bits instructions respectively?

For example:

279ae6: f8df 9338   ldr.w   r9, [pc, #824] --> pc value= 279aea or 279aee
279aea: f44f 7380   mov.w   r3, #256
279aee: 48cd        ldr r0, [pc, #820]

I did more test with following code:

1598:   467b        mov r3, pc
159a:   f8bf 4000   ldrh.w  r4, [pc]    ; 159c
159e:   46f9        mov r9, pc
15a0:   f83f 5001   ldrh.w  r5, [pc, #-1]   ; 15a3
15a4:   f83f 6002   ldrh.w  r6, [pc, #-2]   ; 15a6
15a8:   f83f 7003   ldrh.w  r7, [pc, #-3]   ; 15a9
15ac:   f83f 8004   ldrh.w  r8, [pc, #-4]   ; 15ac
15b0:   f04f 0908   mov.w   r9, #8
15b4:   f8d9 0008   ldr.w   r0, [r9, #8]    ; Trigger crash to check registers

Upon crash, registers are:

I/DEBUG   ( 2632): signal 11 (SIGSEGV), code 1 (SEGV_MAPERR), fault addr 0x10
I/DEBUG   ( 2632):     r0 b8ef4fc0  r1 aca6bb6c  r2 00000000  r3 aca7c59c
I/DEBUG   ( 2632):     r4 00004000  r5 00003f50  r6 00006002  r7 000003f8
I/DEBUG   ( 2632):     r8 0000f83f  r9 00000008  sl 00000000  fp aca6bbc0
I/DEBUG   ( 2632):     ip aca7c591  sp aca6bb40  lr acab722d  pc aca7c5b4  cpsr 60070030

The addresses shown in above code comments(159c/15a3/15a6/15a9/15ac) are generated by objdump, I checked these positions' contents with registers', seem all right.

for 16 bit instrucion:

1598:   467b        mov r3, pc  ;    // r3 = 1598 + 4 = 159c, correct for +4 theory

While for 32bit thumb instruction:

159a:   f8bf 4000   ldrh.w  r4, [pc]    ; // ld addr = 159a + 2 = 159c, where the content is 4000(hw), exactly r4 shows
                                        ; // Inconsistent with +4 theory

By this, for 32 bit instruction, pc read = pc executing +2. Am I missing anything?? Now I'm really confused about pc offset.

BTW, this is armv7a platform using thumb2.

Thank you guys.

1

1 Answers

5
votes

The PC offset is always 4 bytes in Thumb state. The reason being that it's two instruction fetches ahead, and a Thumb instruction fetch is (conceptually) always a halfword - hence why 32-bit encodings still have the funny byte order of two little-endian halfwords, rather than one little-endian word.

The one "32-bit" encoding in the original Thumb instruction set, bl, had the operation of each halfword defined separately as "prefix" and "suffix" instructions, the neat trick being that whilst executing the first part, the return address is taken directly from the PC, since at that stage it's pointing to the instruction after the second part. By the time Thumb-2 technology came along and made 32-bit encodings a formal thing (including bl retroactively), the PC offset had borne no relation to the actual microarchitecture for several generations*, so changing its defined behaviour to be variable dependent on the instruction stream would have had virtually no benefit and introduced massive compatibility problems.

To further complicate matters, when the PC is used as a base register for addressing operations (i.e. adr/ldr/str/etc.) it is always the word-aligned value that is used, even in Thumb state. So, whilst executing a load instruction at 0x159a, the PC register will read as 0x159e, but the base address of ldr...[pc] is Align(0x159e, 4), i.e. 0x159c. Since PC-relative addressing is normally written by specifying a label rather than calculating offsets manually, this detail can be easy to miss.

* In terms of ARM's own designs, ARM7 was the last microarchitecture based around the original 3-stage pipeline.