4

I'm using Keil µVision for an embedded project that jumps back to the bootloader for updating. Keil previously used ARMCC as a compiler and the following code worked fine.

void run_bootloader(void)
{
    uint32_t runBootloaderAddress;

    // Read the entry point from bootloader's vector table
    runBootloaderAddress = *(uint32_t*)(0x00000004);
    void (*runBootloader)(void) = (void (*) (void))runBootloaderAddress;
    runBootloader();
}

Keil is switching to Clang in their newer versions and I'm trying ot port the code over. That code now causes a reset when runBootloader() is called.

The generated assembly is, of course, different. So I pulled the ARMCC generated assembly from the listing and wrote it as inline assembler.

void run_bootloader(void)
{
    __asm("PUSH     {r4-r6,lr}");

    __asm("MOVS     r0,#0");
    __asm("LDR      r4,[r0,#4]");
    __asm("MOV      r5,r4");
    __asm("BLX      r5");

    __asm("POP      {r4-r6,pc}");
}

Stepping through, the values in the registers seem to change the same as before. But the ARMCC version jumps to the bootloader on the BLX while the Clang version resets. The address pulled from the vector table is the same in both.

I've seen mentions that Clang does not allow this sort of thing like the comment to this answer. But there has to be a way to jump back to the bootloader. What am I missing? Is there some setting in the linker I need to enable to allow for this behavior?


Update

Some comments lead me to search out a better way to reset which lead me to this article which discusses resetting the processor by writing to the SYSTEMRESETREQ bit of the Application Interrupt and Reset Control Register (AIRCR). Which lead me to find this function in a CMSIS header file. I inherited the code, I don't know why this wasn't being used.

/**
  \brief   System Reset
  \details Initiates a system reset request to reset the MCU.
 */
__STATIC_INLINE void __NVIC_SystemReset(void)
{
  __DSB();                                                          /* Ensure all outstanding memory accesses included
                                                                       buffered write are completed before reset */
  SCB->AIRCR  = (uint32_t)((0x5FAUL << SCB_AIRCR_VECTKEY_Pos)    |
                           (SCB->AIRCR & SCB_AIRCR_PRIGROUP_Msk) |
                            SCB_AIRCR_SYSRESETREQ_Msk    );         /* Keep priority group unchanged */
  __DSB();                                                          /* Ensure completion of memory access */

  for(;;)                                                           /* wait until reset */
  {
    __NOP();
  }
}

However, the application still reboots without running the bootloader.

More information

The bootloader and the application are separate projects. The application is running Keil RTX OS with a CMSIS layer. My thought is the application is running in its own address space and does not know about the bootloader. So when it resets, it only resets within its own address space and the bootloader is never run.

I attempted to inform the application about the bootloader by adding this to my scatter file.

LR_IROM0 0x00  0x00012000   {
  ER_IROM0 0x0 0x00012000   {
   .ANY (+RO)
  }
}

But the results are the same.

To clarify, I'm really trying to reset the processor. I'm trying to jump to the bootloader while communicating to it that it is not a normal boot but rather an update.


Toolchain Versions

PS C:\Keil_v5\ARM\ARMCC\bin> .\armcc.exe --version_number
5060750

PS C:\Keil_v5\ARM\ARMCLANG\bin> .\armclang.exe --version
Product: MDK Plus 5.25 (Flex)
Component: ARM Compiler 6.9
Tool: armclang [5ced1d00]

Target: unspecified-arm-none-unspecified

Generated Assembly

C Code:

void run_bootloader(void)
{
    volatile uint32_t runBootloaderAddress;

    // Read the entry point from bootloader's vector table
    runBootloaderAddress = *(uint32_t*)(0x00000004);
    void (*runBootloader)(void) = (void (*) (void))runBootloaderAddress;
    runBootloader();
}

ARMCC:

                          AREA ||i.run_bootloader||, CODE, READONLY, ALIGN=1

                  run_bootloader PROC
;;;1335   }
;;;1336   void run_bootloader(void)
000000  b570              PUSH     {r4-r6,lr}
;;;1337   {
;;;1338   uint32_t runBootloaderAddress;
;;;1339   
;;;1340   // Read the entry point from bootloader's vector table
;;;1341   runBootloaderAddress = *(uint32_t*)(0x00000004);
000002  2000              MOVS     r0,#0
000004  6844              LDR      r4,[r0,#4]
;;;1342   void (*runBootloader)(void) = (void (*) (void))runBootloaderAddress;
000006  4625              MOV      r5,r4
;;;1343   runBootloader();
000008  47a8              BLX      r5
;;;1344   }
00000a  bd70              POP      {r4-r6,pc}
;;;1345   
                          ENDP

Clang:

    .section    .text.run_bootloader,"ax",%progbits
    .hidden run_bootloader          @ -- Begin function run_bootloader
    .globl  run_bootloader
    .p2align    2
    .type   run_bootloader,%function
    .code   16                      @ @run_bootloader
    .thumb_func
run_bootloader:
.Lfunc_begin2:
    .loc    2 1338 0                @ ../_Primary/source/can_tools.c:1338:0
    .fnstart
    .cfi_startproc
@ BB#0:
    .save   {r7, lr}
    push    {r7, lr}
.Lcfi8:
    .cfi_def_cfa_offset 8
.Lcfi9:
    .cfi_offset lr, -4
.Lcfi10:
    .cfi_offset r7, -8
    .pad    #8
    sub sp, #8
.Lcfi11:
    .cfi_def_cfa_offset 16
.Ltmp8:
    .loc    2 1343 28 prologue_end  @ ../_Primary/source/can_tools.c:1343:28
    movs    r0, #4
    ldr r0, [r0]
    .loc    2 1343 26 is_stmt 0     @ ../_Primary/source/can_tools.c:1343:26
    str r0, [sp, #4]
    .loc    2 1344 52 is_stmt 1     @ ../_Primary/source/can_tools.c:1344:52
    ldr r0, [sp, #4]
    .loc    2 1344 12 is_stmt 0     @ ../_Primary/source/can_tools.c:1344:12
    str r0, [sp]
    .loc    2 1345 5 is_stmt 1      @ ../_Primary/source/can_tools.c:1345:5
    ldr r0, [sp]
    blx r0
    .loc    2 1346 1                @ ../_Primary/source/can_tools.c:1346:1
    add sp, #8
    pop {r7, pc}
.Ltmp9:
.Lfunc_end2:
    .size   run_bootloader, .Lfunc_end2-run_bootloader
    .cfi_endproc
    .cantunwind
    .fnend
                                        @ -- End function
embedded.kyle
  • 10,976
  • 5
  • 37
  • 56
  • Are you certain it actually reads the correct address from the vector table? You should volatile-qualify the pointer access. Also, how do you know that it resets? Did the reset pin toggle? Address 4 on ARM is the reset vector, and you just told the program to jump to the reset vector (which is really ugly). Also, maybe the problem is in the vector table and not in this code. – Lundin Dec 07 '18 at 14:44
  • @Lundin Stepping through the ARMCC version & inline assembly in Clang, 0x06C5 (address of my bootloader) gets loaded in to r4 and moved into r5. ARMCC runs the branch, the bootloader loads & starts updating. The Clang version version reboots as seen on the display & it will hit a breakpoint in the reset handler. I haven't tried to port our bootloader to Clang yet so vector table should be unchanged. `volatile` qualifier does not change the behavior. Ugly as it may be it seems to be how most people using gcc compilers get back to the bootloader from the application. What do you suggest? – embedded.kyle Dec 07 '18 at 15:03
  • The most natural way to get back to the bootloader would be a chip reset - write a forbidden sequence to the watchdog or similar. That way all registers too go back to the default. Is your bootloader linked with the code you posted or is it just some raw asm taken from elsewhere (like from ARMCC disassembly)? In case of the latter, I would suspect calling convention. Maybe if you can peek at the reset causes, you'll get more clues (illegal address? illegal opcode? etc) – Lundin Dec 07 '18 at 15:09
  • 1
    Supporting @Lundin's comment. Use a soft-reset to start over the CPU. The approach you used is very suspisious. It might have worked in the previous version, but it has a lot of issues like enabled interrupts, hardware peripherals in unexpected states, etc. – too honest for this site Dec 07 '18 at 15:29
  • did you try to look at [llvm target specific flags](https://clang.llvm.org/docs/ClangCommandLineReference.html) ? – ntshetty Dec 07 '18 at 15:30
  • @ThiruShetty There's a lot of options and I'm not really sure what I'm looking for. I tried `-mlong-calls` but no change. Please see my edit and let me know if you can suggest an option or what to search for. – embedded.kyle Dec 07 '18 at 16:54
  • If you have set any option bits that will enable the watchdog on reset, you must maintain the watchdog in your bootloader. In your original attempt jumping to the bootloader, any watchdog that was enabled would not have been halted either; in fact it was a terrible method because you may have interrupt handlers enabled and peripherals running that may cause problems with the bootloader. – Clifford Dec 07 '18 at 17:06
  • You should read the documentation of your MCU, which includes the reference manuals and the ARM documentation. Of course you need to know the architecture of the software, too before you do such changes to it. Siad that, stack overflow cannot help with that. If you can't do this for whatever reason, consider hiring a consultant. – too honest for this site Dec 07 '18 at 18:22
  • @too honest for this site It's not specific to my MCU though. I've found several answers on SO that are very similar to the original code and that code does in fact work when compiled with a gcc based compiler. The only difference here is Clang. There is something specific to Clang that is different from gcc in how it translates ARM assembler to machine code. More specifically how it jumps to arbitrary addresses outside the defined space for the project. – embedded.kyle Dec 07 '18 at 19:50
  • The appropriate approach very well depends on your hardware and software architecture. Whether the code works or not is pure luck, it invokes undefined behaviour and depends on specifics which might change with a new revision of the toolchain, options, etc. While there are some UB by the standard is defined for a platorm e.g. by the ABI, this approach is typically not. Also read my comments completely, don't pick single parts. I provided some typical things which can go wrong - or at least are bad design practice to rely on. Said that, I'd refuse it from a co-worker. – too honest for this site Dec 07 '18 at 19:56
  • please show the clang/llvm disassembly, you must use a bx or blx you cannot use a bl for example so it depends on what the compiler is generating. Since you cannot control the compiler you should never use code like that as the results are not predictable you should specifically call asm to specifically get the correct instruction or set of instructions. you should probably also set the stack pointer correctly before launching the bootloader as well. If this happened to work in the past it was luck. – old_timer Dec 08 '18 at 03:41
  • are you expecting the bootloader to return?? why does it have a vector table if you expect it to return? – old_timer Dec 08 '18 at 03:43
  • @embedded.kyle the reason asked you to try with C flags.. Clang translate C code to assembly style Intermediate Representation, then LLVM converts IR to Object file. Since LLVM still evolving tool for C. It adopts many GCC plug-in as it is. So some flag will help to retain ur asm code unchanged. try to use `--gcc-toolchain=/path/to/cross-toolchain(ARMCC)` suspect llvm tools are not aligned ur binary properly. – ntshetty Dec 10 '18 at 03:00
  • For what it's worth, the original `run_bootloader()` will probably have undefined behavior because it's not setting up the stack pointer and relying on the one you're currently using (which is almost certainly not the bootloader's allocated stack) – Russ Schultz Dec 10 '18 at 14:28
  • It's possible that using the NVIC self reset doesn't appear go to the bootloader as expected because all of the ARM registers are reset to known values and the bootloader expects one of the registers to be set to a magic value if it's being re-entered in bootloader mode, else it jumps to the application. – Russ Schultz Dec 10 '18 at 14:33
  • @ThiruShetty I suspect you're right about the alignment. See the disassembly above from both compilers. Not sure how to control that though. Still reading up. Pointing Clang to ARMCC produces the following warning: `ArmClang.exe: warning: argument unused during compilation: '--gcc-toolchain=C:\Keil_v5\ARM\ARMCC\bin' [-Wunused-command-line-argument]` – embedded.kyle Dec 10 '18 at 20:25
  • you can find compiler flags for [armclang vs armcc](https://developer.arm.com/docs/100068/0607/migrating-from-armcc-to-armclang/migrating-architecture-and-processor-names-for-command-line-options) – ntshetty Dec 11 '18 at 03:47

1 Answers1

0

what version of clang and how is it being used?

void run_bootloader(void)
{
    uint32_t runBootloaderAddress;

    // Read the entry point from bootloader's vector table
    runBootloaderAddress = *(uint32_t*)(0x00000004);
    void (*runBootloader)(void) = (void (*) (void))runBootloaderAddress;
    runBootloader();
}

gcc 8.2.0

00000000 <run_bootloader>:
   0:   2304        movs    r3, #4
   2:   b510        push    {r4, lr}
   4:   681b        ldr r3, [r3, #0]
   6:   4798        blx r3
   8:   bd10        pop {r4, pc}
   a:   46c0        nop         ; (mov r8, r8)

clang/llvm 3.8.0

00000000 <run_bootloader>:
   0:   b580        push    {r7, lr}
   2:   af00        add r7, sp, #0
   4:   2004        movs    r0, #4
   6:   6800        ldr r0, [r0, #0]
   8:   4780        blx r0
   a:   bd80        pop {r7, pc}

both should function correctly as they are using blx and not bl.

if you want to simulate a reset though you need to do something like this

.thumb
mov r0,#0
ldr r1,[r0,#0]
ldr r2,[r0,#4]
mov sp,r1
bx r2

00000000 <.text>:
   0:   2000        movs    r0, #0
   2:   6801        ldr r1, [r0, #0]
   4:   6842        ldr r2, [r0, #4]
   6:   468d        mov sp, r1
   8:   4710        bx  r2

and do it in assembly not compiled C.

old_timer
  • 69,149
  • 8
  • 89
  • 168
  • Your title is completely different from your question BTW. That question often results in failure when done in C against a cortex-m (if not always).. – old_timer Dec 08 '18 at 04:01
  • Jumping through address 4 does not simulate a reset (let apart, the vector table can be at a different address, depending on the device and previous configuration). – too honest for this site Dec 09 '18 at 09:04
  • @toohonestforthissite very true. I was referring to not using the stack pointer compiled into the bootloader, it is a long long long way from a true reset. should have phrased that differently... – old_timer Dec 09 '18 at 23:39
  • Likewise, if it was meant to be a function then why does it have a vector table. Pick one, bx to it and use the stack pointer or better make it a real reset if the core allows (not all of the cores allow you to move the VTOR, some allow you to remap in ram and you can copy the target table at least sp and reset), or make it a function and dont bother with a vector table. – old_timer Dec 09 '18 at 23:41
  • See my edit for the disassembly with both compilers and their version numbers. Both generate a `BLX`. ARMCC runs the bootloader correctly, Clang restarts the application. I also tried the reset assembly you provided. I used inline assembly but checked that the listing was correct. It also restarts the application. The bootloader is a separate project and does quite a bit. It allows for in-field reprogramming over a CANBus and does CRC checking and can inform the user via the display of any mismatch. The application has many variations and the bootloader allows them all to run on common HW. – embedded.kyle Dec 10 '18 at 20:23
  • what is the value at 0x4? is it a vector and does it point at the bootloader? check the simple stuff too.. – old_timer Dec 10 '18 at 21:30