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.
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.
来源:https://stackoverflow.com/questions/29586536/about-arm-pc-value-in-thumb-16-32bits-mixed-instructions-stream