-3

thank you for accessing my question. I'm now using STM32F439ZI microcontroller to see how pc works.

Please look at the assembly code below.

int p1, *p2;
p2 = (int *)0x30000008;
__asm volatile("LDR %0, [%1]": "=r"(p1):"r"(p2));

The list file of this code is:

    int p1, *p2;
    p2 = (int *)0x30000008;
 80001f2:   4b03        ldr r3, [pc, #12]   ; (8000200 <main+0x14>)
 80001f4:   607b        str r3, [r7, #4]
    __asm volatile("LDR %0, [%1]": "=r"(p1):"r"(p2));
 80001f6:   687b        ldr r3, [r7, #4]
 80001f8:   681b        ldr r3, [r3, #0]
 80001fa:   603b        str r3, [r7, #0]

    for(;;);
 80001fc:   e7fe        b.n 80001fc <main+0x10>
 80001fe:   bf00        nop
 8000200:   30000008    .word   0x30000008

I found that in arm cortex-m4 architecture, pc always stores the address of current instruction, not next like x86 or two instructions(because arm cortex-m4 adopts thumb instruction set, and so according to the question, in thumb instruction set, pc always points to the current instruction + 4bytes, which is equivalent to 2 instructions in thumb instruction set) ahead as described here.

Why does the ARM PC register point to the instruction after the next one to be executed?

However, my observation is the pc points to the exact current instruction, not plus 4 bytes.

Is it really true that the pc points to the current instruction + 4 bytes? It is opposed to my observation.

Also, as described in the list file, it seems that the immediate value "0x30000008" is stored at 0x8000200. However, at 0x80001f2, it loads the value of pc + 12 bytes to r3, and if you do simple arithmetic, it is not 0x8000200, but 0x8000fe. According to this, it is reasonable to think that pc is 0x80001f4, when the current instruction is 0x80001f2, because 0x80001f4 + 12 = 0x8000200.

Here I'm faced with some contradictory possibilities that the pc stores the address of the current instruction, or next, or two instructions ahead.

Which one is actually true? My observation is indeed the current instruction though.

And is the memory mapping always like this? I mean the compiler always decides a kind of "base address" (In this example, it is the address pc stores at 0x80001f2), and allocates each instructions based on that base address? The constants (I hard-coded 0x30000008 so of course it is a constant) are always stored in somewhere abound the bottom? Finally when the compiler tells to you where the a specific constant is, it always does in a relative way like the address is base + 12 bytes, instead of telling the absolute address directly?

It would be great if you could give me some insight. Below is the actual disassembly that the microcontroller executes

mamo
  • 1
  • 2
  • I’m voting to close this question because it's about hardware implementations of the PC,which of course vary by device. – TomServo Aug 07 '21 at 02:43
  • Varies by hardware. For example my most recent CPU design has two program counters, that point to current and current +1 for decoding all instructions (1 or 2 word) on cycle 0, gets incremented on cycle 1, new value available on cycle 2. Harvard architecture with 2 word PC prefetch. Fast. Bottom line unless you're scoping the address bus somehow or designed it yourself, just read the datasheet. – TomServo Aug 07 '21 at 02:47
  • @TomServo Thank you for pointing out. Just for clearing your misunderstanding, let me say it's not appropriate to say H/W because it sounds like it depends on the chip vendor. It's more about architecture, which is common in all the microcontrollers which are cortex-m. And I just want to know the mechanism of the assembly, so cannot find in the data sheet can you? As you probably know, device vendors provide data sheets, not ARM. And ARM is the one who produces the architecture and the processor core. Please tell me if each data sheet mentions details of PC or assembly that explains this. – mamo Aug 07 '21 at 07:56

1 Answers1

0

It is much easier to do/see things in real assembly not inline assembly.

.thumb
ldr r0,hello
nop
nop
nop
hello: .word 0x12345678

Disassembly of section .text:

00000000 <hello-0x8>:
   0:   4801        ldr r0, [pc, #4]    ; (8 <hello>)
   2:   46c0        nop         ; (mov r8, r8)
   4:   46c0        nop         ; (mov r8, r8)
   6:   46c0        nop         ; (mov r8, r8)

00000008 <hello>:
   8:   12345678    .word   0x12345678

8-(0+4) = 4
4/4 = 1
0x4801

If the pc+4 were not in there then

8-(0+0) = 8
8/4 = 2
0x4802

The instruction would be 0x4802 not 0x4801.

It is in no way using the address of the instruction for pc-relative addressing as the above and your code has also demonstrated.

 80001f2:   4b03        ldr r3, [pc, #12]   ; (8000200 <main+0x14>)
...
 8000200:   30000008    .word   0x30000008

0x80001f2+4+12 = 0x8000202

I forgot that there is only a pc relative ldr (not ldrb, not ldrh). And depending on which arm reference manual. They have not necessarily gotten better with time, often you want to read more than one when diving into an instruction.

(0x80001f2+4+12)&0xFFFFFFFC = 0x8000200

Movs, looks like you are using a debugger instead of a disassembler. debuggers create as many problems as they solve. Although disassemblers struggle too, especially with data. It is very easy to break some disassemblers in particular gnu (super easy with x86 to get bogus output).

.thumb
.inst.w 0x30000008
.word 0x30000008


Disassembly of section .text:

00000000 <.text>:
   0:   3000        adds    r0, #0
   2:   0008        movs    r0, r1
   4:   30000008    .word   0x30000008

Use better tools or different tools or a mix of tools.

Assume all disassembly is wrong until analyzed. Barely trust your debugger, it will hurt you if you don't.

halfer
  • 19,824
  • 17
  • 99
  • 186
old_timer
  • 69,149
  • 8
  • 89
  • 168
  • Thank you very much for your answer. What do you mean by the last words "this is an ldr so 0x8000200"? – mamo Aug 06 '21 at 13:43
  • it is an aligned 32 bit load so from what I understand this is a nuance of thumb since the pc can be on a halfword. basically the 202 becomes a 200 (lower two bits ignored). otherwise doing an pc relative ldr from thumb would suck you could only do it on aligned addresses – old_timer Aug 06 '21 at 13:59
  • "pc can be on a halfword". I understood. "202 becomes a 200". I don't understand. So CortexM is 32 bit architecture, so when it reads the instruction, it should be 32 bit right? But the thumb instruction is 16 bit. So maybe it reads two instructions at a time? And you said 202 becomes 200, but as can be seen in list file, each two byte aligns. So if 80001f2 becomes 80001f0, and 80001f6 becomes 80001f4, where does the original instruction stored in 80001f2 and 80001f6 go then? – mamo Aug 07 '21 at 08:07
  • I also attached the actual assembly code the microcontroller executes, not a list file. It is strange because according to the list file, the address 0x30000008 is stored at 0x8000200, but according to the disassembly, at 0x8000200 another instruction is there. So where did the address 0x30000008 go? – mamo Aug 07 '21 at 08:12
  • the address 0x30000008 is the value/content at the location 0x8000200 that goes into a register and a register based indirect addressing happens. – old_timer Aug 07 '21 at 12:53
  • So what is the movs instruction at 0x8000200 then? – mamo Aug 07 '21 at 15:38
  • gdb's disassembler is not the same as binutils, they can/will give different results. It also depends on the source of the data you are disassembling... – old_timer Aug 07 '21 at 17:19