0
PUSH {R0, PC} ; A1477E: This register combination results in UNPREDICTABLE behaviour

Why does it cause unpredictable behaviour? Why is LR allowed in register list but not PC? I think implementing recursive functions will be much easier using LR in stack, but when we can still write recursive functions using PC why is it not included in valid list of registers?

artless noise
  • 21,212
  • 6
  • 68
  • 105
  • 1
    The manual I found says it's deprecated not that it's unpredictable. – Jester Mar 16 '21 at 14:36
  • 1
    What ARM architecture version are you referring to? And what tool produced the quoted (A1477E) warning? – tum_ Mar 16 '21 at 14:45
  • @tum_ I got this error while executing on keil u5. And using ARM CM3, device LPC1768. – Vighnesh Nayak Mar 16 '21 at 14:45
  • 3
    During instruction execution, the `pc` is constantly moving, so capturing the pc in certain ways can be problematic depending on the internal architecture of the processor, being pipelined, or out of order. This is not the case with other registers. `bl` has to capture the `pc` (into the `lr`) in a very precise and meaningful way so it needs as much hardware as necessary whether pipelined or not. But when the `pc` is a named register in some instruction that also works with the other registers, that's where it can become problematic for an implementation. – Erik Eidt Mar 16 '21 at 14:46
  • 3
    And since it is not really all that useful to push the pc, the solution by processor designers sometimes to document that a newer version of the processor behaves differently in this respect, or to obsolete/deprecate some feature. In other words, it was probably a mistake to have allowed that form in the first place, but that wasn't realized until later generations of the processor. – Erik Eidt Mar 16 '21 at 14:52
  • 2
    But of course you are still allowed to use pc-relative addressing which has the same problems, and is defined to work in a given way. – Jester Mar 16 '21 at 14:56
  • 1
    As far as I can see from the ARM ARM for ARMv7-M architecture (Cortex-M3), the comment for PUSH instruction says: "The SP and PC cannot be in the list." (page A7-322). Nothing is said about either Unpredictable or Undefined behaviour - such instruction simply can't be encoded for ARMv7-M. – tum_ Mar 16 '21 at 15:03
  • 1
    [ARMv7-M Architecture Reference Manual](https://developer.arm.com/documentation/ddi0403/ed) - Click Download. – tum_ Mar 16 '21 at 15:11
  • That's for thumb mode though. In ARM mode it can be encoded but is deprecated. – Jester Mar 16 '21 at 15:14
  • 2
    @Jester Never worked with Cortex-M3 myself but, as far as I can read in the above reference, Thumb is _the only_ mode for that architecture: "ARMv7-M The microcontroller profile for systems supporting only the Thumb instruction set, and where overall size and deterministic operation for an implementation are more important than absolute performance." – tum_ Mar 16 '21 at 15:19
  • 1
    That is true but I don't see that mentioned in the question. It's only tagged ARM. Also the error message does not say it's invalid. – Jester Mar 16 '21 at 17:39
  • 2
    @Jester Agreed, the question lacks important details. ARM CM3 is mentioned in the OP's answer to my first comment. As to the error message, I find the message confusing too. It's either the tool's fault (Keil u5) or the OP is assembling for the wrong platform(?) – tum_ Mar 16 '21 at 22:18
  • If R15 is specified in the value stored is IMPLEMENTATION DEFINED . – old_timer Mar 17 '21 at 02:29
  • are you sure you have specified the right target to the assembler tool? you might be targetting an older arm instruction set – old_timer Mar 17 '21 at 02:30
  • you can copy it to a pushable register then push that register on the stack. and as noted for the CM3 you cannot push the pc anyway (neither thumb nor thumb2), only on ARM instructions – old_timer Mar 17 '21 at 02:32
  • if you assemble then disassemble without this register or instruction do you see an arm instruction? thumb? thumb2? put something above 7 in there like r8 or r10 or something like that (r8 to r12) – old_timer Mar 17 '21 at 02:35
  • Understand that IMPLEMENTATION DEFINED. And UNPREDICTABLE RESULT, are or at least were used to detect stolen IP. If your UNPREDICTABLE RESULT exactly matched ARMs predictable UNPREDICTABLE RESULT, you might have a lawyer or dozen come visit you. Granted if you made a clone you are going to have a lawyer come visit anyway...unless you called them first. They dont sell you mask sets anymore they sell you source code. And the tools are a zillion times better than 20 years ago so IMPLEMENTATION DEFINED now means the chip vendor defines it not a specific arm core. – old_timer Mar 17 '21 at 02:41

2 Answers2

4

Try these instructions

PUSH {R0, R7}
PUSH {R0, R8}

and disassemble, do you get this:

0:  e92d0081    push    {r0, r7}
4:  e92d0101    push    {r0, r8}

or this:

0:  b481        push    {r0, r7}
2:  e92d 0101   stmdb   sp!, {r0, r8}

or an error that the register list is in valid (r8 is not valid)?

If you get the first set, then you are building for ARM not thumb and the warning/error message makes more sense (for r15). If you are building for thumb it should say invalid (for r15 and/or r8).

Early ARM docs (for ARM) stated

If R15 is specified in the value stored is IMPLEMENTATION DEFINED

Now they say:

The SP and PC can be in the list in ARM instructions, but not in Thumb instructions. However:

• ARM deprecates the use of ARM instructions that include the PC in the list

Reading more (which you should have read yourself by now)

str pc, [sp, #-4]!

0:  e52df004    push    {pc}        ; (str pc, [sp, #-4]!)

Also is shown in the armv7-a as deprecated and the older as implementation defined with some additional comments from back then:

Reading the program counter

When an instruction reads R15 without breaking any of the restrictions on its use, the value read is the address of the instruction plus 8 bytes. As ARM instructions are always word-aligned, bits[1:0] of the resulting value are always zero. (In T variants of the architecture, this behavior changes during Thumb state execution - see Chapter A6 The Thumb Instruction Set for details.) This way of reading the PC is primarily used for quick, position-independent addressing of nearby instructions and data, including position-independent branching within a program.

An exception to the above rule occurs when an STR or STM instruction stores R15. Such instructions can store either the address of the instruction plus 8 bytes, like other instructions that read R15, or the instruction's own address plus 12 bytes. Whether the offset of 8 or the offset of 12 is used is IMPLEMENTATION DEFINED . An implementation must use the same offset for all STR and STM instructions that store R15. It cannot use 8 for some of them and 12 for others.

Because of this exception, it is usually best to avoid the use of STR and STM instructions that store R15. If this is difficult, use a suitable instruction sequence in the program to ascertain which offset the implementation uses.

You should have noticed in the ARMv7-M that it also uses a 16 bit register mask, but the r15 and r13 locations are marked as (0).

Now if you go in and enter the machine code 0xe92d8101 (thumb2) you will see at least the gnu disassembler show

0: e92d 8101 stmdb sp!, {r0, r8, pc}

But I would have to dig out a cortex-m3 or few to see how they behave.

Keil may simply have taken the ARM warning that existed before and applied it to the thumb encoding (or recycled a warning rather than creating a new one), there is a location in the mask for the program counter, you just should not use it and/or should not make assumptions about its use. instead push some stuff, use one of the things you pushed to get a copy of the pc at that point then push that, that will be predictable and whatever value you think you are serving by pushing the pc, you can have.

Peter Cordes
  • 328,167
  • 45
  • 605
  • 847
old_timer
  • 69,149
  • 8
  • 89
  • 168
  • note that push is at times a pseudo instruction for stm, although for thumb it is its own instruction (saving space to not have to specify r13). – old_timer Mar 17 '21 at 03:10
3

The way you call a subroutine in ARM (at least ARMv7) is with a BL or BLX instruction. The PUSH doesn't happen until the subroutine starts executing, and by then the address in the PC is pointing one or two instructions past the push, not past the branch and link. By the time you get to the PUSH the PC no longer has information useful for returning from the subroutine.

The link register is loaded by the processor hardware as part of executing the branch and link, and at that point the processor does know the correct return address.

Now, when returning from a subroutine we basically want to get the value in the link register back into the program counter. There are several ways to do this. As you note, you can PUSH {LR} and POP {PC} but pushing the LR is unnecessary unless your subroutine itself calls a subroutine. Simple functions can just use BX LR. Some other instructions allow you to use the PC as the destination, but some of those are deprecated. An interesting example is the special return-from-exception for ARMv7-A: SUBS PC, LR, #4.

Elliot Alderson
  • 638
  • 4
  • 8