【问题标题】:How do I branch/jump to an arbitrary address in Clang?如何在 Clang 中分支/跳转到任意地址?
【发布时间】:2019-05-09 08:01:50
【问题描述】:

我将 Keil µVision 用于一个嵌入式项目,该项目会跳回引导加载程序进行更新。 Keil 之前使用 ARMCC 作为编译器,下面的代码运行良好。

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 在其较新版本中切换到 Clang,我正在尝试移植代码。现在,当调用 runBootloader() 时,该代码会导致重置。

生成的程序集当然是不同的。所以我从清单中提取了 ARMCC 生成的程序集,并将其编写为内联汇编程序。

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}");
}

单步执行,寄存器中的值似乎和以前一样变化。但是 ARMCC 版本会在 Clang 版本重置时跳转到 BLX 上的引导加载程序。从向量表中拉取的地址在两者中是相同的。

我看到有人提到 Clang 不允许像 comment to this answer 这样的事情。但是必须有一种方法可以跳回引导加载程序。我错过了什么?我需要启用链接器中的某些设置以允许此行为吗?


更新

一些 cmets 引导我寻找更好的复位方法,这使我找到了 this article,其中讨论了通过写入应用程序中断和复位控制寄存器 (AIRCR) 的 SYSTEMRESETREQ 位来复位处理器。这让我在 CMSIS 头文件中找到了这个函数。我继承了代码,我不知道为什么它没有被使用。

/**
  \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();
  }
}

但是,应用程序仍然会在不运行引导加载程序的情况下重新启动。

更多信息

引导加载程序和应用程序是独立的项目。该应用程序运行带有 CMSIS 层的 Keil RTX OS。我的想法是应用程序在自己的地址空间中运行并且不知道引导加载程序。因此,当它重置时,它只会在自己的地址空间内重置,并且引导加载程序永远不会运行。

我试图通过将它添加到我的分散文件中来通知应用程序有关引导加载程序的信息。

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

但结果是一样的。

为了澄清,我真的在尝试重置处理器。我正在尝试跳转到引导加载程序,同时向它传达这不是正常引导而是更新。


工具链版本

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

生成的程序集

C 代码:

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

叮当声:

    .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

【问题讨论】:

  • 您确定它实际上从向量表中读取了正确的地址吗?您应该对指针访问进行 volatile 限定。另外,你怎么知道它重置了?复位引脚是否切换? ARM上的地址4是复位向量,你刚刚告诉程序跳转到复位向量(这真的很难看)。另外,问题可能出在向量表中,而不是在这段代码中。
  • @Lundin 在 Clang 中逐步执行 ARMCC 版本和内联程序集,0x06C5(我的引导加载程序的地址)被加载到 r4 并移动到 r5。 ARMCC 运行分支,引导加载程序加载并开始更新。 Clang 版本重新启动,如显示屏所示,它将在重置处理程序中遇到断点。我还没有尝试将我们的引导加载程序移植到 Clang,所以向量表应该保持不变。 volatile 限定符不会改变行为。尽管看起来很丑,但似乎大多数使用 gcc 编译器的人如何从应用程序返回引导加载程序。你有什么建议?
  • 返回引导加载程序的最自然方法是芯片复位 - 将禁止序列写入看门狗或类似设备。这样所有寄存器也都恢复到默认值。您的引导加载程序是否与您发布的代码相关联,还是只是从其他地方获取的一些原始 asm(例如来自 ARMCC 反汇编)?在后者的情况下,我会怀疑调用约定。也许如果你能窥视一下重置的原因,你会得到更多的线索(非法地址?非法操作码?等等)
  • 支持@Lundin 的评论。使用软复位重新启动 CPU。您使用的方法非常可疑。它可能在以前的版本中可以工作,但它有很多问题,如启用中断、硬件外围设备处于意外状态等。

标签: c gcc arm embedded clang


【解决方案1】:

什么版本的clang以及它是如何使用的?

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}

两者都应该正常运行,因为它们使用的是 blx 而不是 bl。

如果你想模拟重置但你需要做这样的事情

.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

并在未编译的 C 程序集中进行。

【讨论】:

  • 您的标题与您的问题 BTW 完全不同。当在 C 中针对 cortex-m 完成时,这个问题通常会导致失败(如果并非总是如此)..
  • 跳过地址 4 不会模拟重置(除此之外,向量表可以位于不同的地址,具体取决于设备和之前的配置)。
  • @toohonestforthissite 非常正确。我指的是不使用编译到引导加载程序中的堆栈指针,距离真正的复位还有很长的路要走。应该用不同的措辞...
  • 同样,如果它是一个函数,那么为什么它有一个向量表。选择一个,bx 并使用堆栈指针,或者如果内核允许,最好将其设为真正的重置(并非所有内核都允许您移动 VTOR,有些允许您在 ram 中重新映射,您可以将目标表复制到最少 sp 并重置),或者让它成为一个函数,不要打扰向量表。
  • 查看我对编译器及其版本号的反汇编的编辑。两者都生成BLX。 ARMCC 正确运行引导加载程序,Clang 重新启动应用程序。我还尝试了您提供的重置组件。我使用了内联汇编,但检查了列表是否正确。它还会重新启动应用程序。引导加载程序是一个单独的项目,并且做了很多事情。它允许通过 CANBus 进行现场重新编程并进行 CRC 校验,并可以通过显示任何不匹配来通知用户。该应用程序有许多变体,引导加载程序允许它们都在通用硬件上运行。
猜你喜欢
  • 2011-10-20
  • 1970-01-01
  • 2015-05-11
  • 1970-01-01
  • 2023-02-11
  • 1970-01-01
  • 1970-01-01
  • 2010-10-20
相关资源
最近更新 更多