How do I branch/jump to an arbitrary address in Clang?

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
c
gcc
arm
embedded
clang
asked on Stack Overflow Dec 7, 2018 by embedded.kyle • edited Dec 10, 2018 by embedded.kyle

1 Answer

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.

answered on Stack Overflow Dec 8, 2018 by old_timer

User contributions licensed under CC BY-SA 3.0