它位于Code Composer Studio IDE上,用于调试基于ARM Cortex-a8芯片的操作系统。
我注意到在程序进入主循环中的任何功能之前,链接寄存器包含一个地址,该地址指向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
这是一个实际示例,说明在程序进入函数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
在程序的某些地方,我发现程序计数器的值等于链接寄存器。
虽然我知道
程序计数器包含要由处理器提取的下一条指令的地址。
但是,仍然使我感到困惑,因为这两个寄存器的用途完全不同。
从代码中摘录一个示例。在调试模式下观察它时,我在组装级别上一步一步地完成了任务,但仍然无法解决。
它是如何发生的?在什么情况下?
程序:
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]
答案 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显示了第一个函数调用的状态。