程序可以从主程序返回吗?为什么链接寄存器的值与程序计数器的值相同?

时间:2019-02-14 07:38:43

标签: assembly arm embedded microcontroller

它位于Code Composer Studio IDE上,用于调试基于ARM Cortex-a8芯片的操作系统。

截屏1

我注意到在程序进入主循环中的任何功能之前,链接寄存器包含一个地址,该地址指向while(1)的无限循环。

这引起了我的注意,因为

  

链接寄存器是内核在调用子例程时放置返回地址的位置。

尽管启动代码将调用main,但是只要main中没有return语句或操作系统启动,main就无法返回,更不用说一旦程序进入函数,链接寄存器的值就会改变。

那么,为什么需要这个无限循环呢?

程序:

int main(void)
{                     //program stops here. link register: 0x805dcda0

    OS_ERR  err = OS_ERR_NONE;

    IntAINTCInit();   //once program enters this function,
                      //the value of link register is changed
    OS_Init();        //initialize OS
    OSStart(&err);    //start OS

    if (err != OS_ERR_NONE)
    {
        printf("OS fails");
    }
    /*no return statement*/
}

反汇编:

          start_boot():
805dcd94:   E92D4008            push       {r3, lr}
138           CopyVectorTable();
805dcd98:   EB000001            bl         CopyVectorTable
141           main();
805dcd9c:   EB00001D            bl         main
143           while(1);
          $C$L1:
805dcda0:   EAFFFFFE            b          $C$L1

屏幕截图2

这是一个实际示例,说明在程序进入函数IntAINTCInit()之后,链接寄存器的值将更改。

程序:

void IntAINTCInit(void)
{   //program stops here. link register: 0x805dce28

    /*pseudo code*/
    !!Reset the ARM interrupt controller
    !!Wait for the reset to complete 
    !!Enable any interrupt generation by setting priority threshold
    !!Register the default handler for all interrupts
}

反汇编:

44          OS_Init();      //initialize OS
805dce28:   EBFF30EA            bl         CPU_Init

屏幕截图3和4

在程序的某些地方,我发现程序计数器的值等于链接寄存器。

虽然我知道

  

程序计数器包含要由处理器提取的下一条指令的地址。

但是,仍然使我感到困惑,因为这两个寄存器的用途完全不同。

从代码中摘录一个示例。在调试模式下观察它时,我在组装级别上一步一步地完成了任务,但仍然无法解决。

它是如何发生的?在什么情况下?

程序:

DNM_s* p = DNMManager_Retrive(index);
if(NULL != p)
{
    char name_unicode[40] = {0};
    char name_utf8[40] = {0};
    memcpy(&name_unicode[0], DNMManager_GetName(p), 40);    //program stops here.
                                                            //link register  : 0x805afec8
                                                            //program counter: 0x805afec8
    enc_unicode_to_utf8(name_unicode, 20, name_utf8);
    memcpy(&((UI_DNM_RealList_s*)structs)->Name[0], &name_utf8[0], 40);              
}

反汇编:

366                     char name_utf8[40] = {0};
805afeb8:   E3A01000            mov        r1, #0
805afebc:   E28D004C            add        r0, sp, #0x40
805afec0:   E3A02028            mov        r2, #0x28
805afec4:   EB00D1FD            bl         memset
367                     memcpy(&name_unicode[0], DNMManager_GetName(p), 40);
805afec8:   E59D0020            ldr        r0, [sp, #0x14]
805afecc    EB0076E4            bl         DNMManager_GetName
805afed0:   E28DC024            add        r12, sp, #0x18
805afed4:   E1A01000            mov        r1, r0
805afed8:   E3A02028            mov        r2, #0x28
805afedc:   E1A0000C            mov        r0, r12
805afee0:   EB00C2EF            bl         memcpy

程序:

char* DNMManager_GetName(DNM_s* element)
{    //program stops here.
     //link register  : 0x805afed0
     //program counter: 0x805cda64

    return &element->Name[0];
}

反汇编:

          DNMManager_GetName():
805cda64:   E24DD008            sub        sp, sp, #8
805cda68:   E58D0000            str        r0, [sp]

1 个答案:

答案 0 :(得分:2)

由于您只显示了几个屏幕截图而不是完整的代码,所以我只能推测...

截屏1:入口点

除了在Windows或Linux上,微控制器的程序均不应从 main()返回。无处可去。为了防止进一步的损坏, LR 指向一个无限循环。如果您不小心从 main()返回,那就是程序流程的去向。

屏幕截图2:IntAINTCInit()

没问题。

截屏3:PC = LR

LR 包含输入功能时的返回地址。如果该函数调用其他函数,则必须保存 LR (通常在堆栈中)并为其分配一个新值。屏幕截图显示了语句后的状态:

char name_utf8[40] = {0};

如果查看汇编代码,可以看到调用 memset 将40个字节初始化为0。所以确实调用了另一个函数,并且屏幕截图显示了从<返回后的状态em> memset 。每次函数返回时, PC 必须等于 LR ,因为这就是返回的工作方式。所以这里一切都很好。

截屏4:DNMManager_GetName

屏幕截图显示了输入 DNMManager_GetName()时的状态。该函数的调用由以下语句完成:

805AFECC    bl  DNMManager_GetName

调用后(需要返回的位置)的语句为0x805AFED0,恰好是屏幕截图中 LR 中包含的内容。

请注意,C代码中的单行会导致两个函数调用,一个函数调用 DNMManager_GetName(),另一个函数调用 memcpy 。 0x805AFEE4将是第二个函数调用的返回地址。但是屏幕截图4显示了第一个函数调用的状态。