_main

我斑愿称你为最强(STM32启动文件分析)

1)奥利给,肝了:

Stack_Size		EQU     0x400

                AREA    STACK, NOINIT, READWRITE, ALIGN=3
Stack_Mem       SPACE   Stack_Size
__initial_sp
                                                  
; <h> Heap Configuration
;   <o>  Heap Size (in Bytes) <0x0-0xFFFFFFFF:8>
; </h>

Heap_Size      EQU     0x200

                AREA    HEAP, NOINIT, READWRITE, ALIGN=3
__heap_base
Heap_Mem        SPACE   Heap_Size
__heap_limit

                PRESERVE8
                THUMB

重头开始看起:汇编程序和其他程序一样,并不是一上来就是代码段,可能会有一些定义,声明之类的东西。

EQU是一个伪指令(伪指令是没有对应的机器码的),就是说明Stack_Size等于0x400,就是通知编译器,这个符号的值为0x400

这里补充一点伪指令的定义:伪指令没有对应的机器代码,并不像指令语句那样由CPU来执行,是由汇编器在汇编源程序时执行的。

主要是完成变量定义,存储器分配,指示段定义,段分配,段结束等。

AREA代表的是TheAREAdirective instructs the assembler to assemble a new code or data section.

AREA STACK, NOINIT, READWRITE, ALIGN=3

AREA就是告诉汇编器汇编一个代码段或数据段。后面从参数用于告诉汇编器要汇编段的信息。

STACK说明段的名字,就是栈;NOINIT不初始化,READWRITE可读可写,ALIGN说明是8(2^3)字节对齐。

这里主要说明一下字节对齐的概念,8字节对齐的意思就是说该段所在的起始地址的低三位为0。

SPACE 也是个伪指令,前面AREA说明要汇编一个栈段,那么后面要说明该栈段的大小。SPACE告诉汇编器栈段的大小为Stack_Size0x400,Stack_Mem是什么意思呢,他是一个可选项(就是可写,可不写)他与指令中的标号是一个意思,代表的是汇编地址;__initial_sp代表的是栈的结束地址,就是栈顶地址。

如果想看一下__initial_sp到底是什么东西呢,涉及到一个.map文件,里面有所有符号的定义

要想讲清楚.map文件中的内容,涉及到 的还是挺多的,要了解可执行目标文件的格式、编译等内容,还是有点篇幅,这里暂时不讲,不理解的话没关系,暂时只需要清楚上面的内容即可。

可以发现__initial_sp的值就是0x20000410,这里正好对应着程序在运行时SRAM区的栈顶地址。这里面需要了解的就是程序在存储时是存储在flash中,运行时会将RW数据区(有初值的全局变量)移到SRAM中,并添加ZI数据区。(ZI数据区包含有初始化为0 的全局变量(类似于bss段),以及堆,以及栈段),如何看各个段的大小呢?

在使用fromelf工具查看.axf文件(可执行文件)时,就可以看到段的大小,可以发现RW数据区与ZI数据区的bss段所占的空间为16字节(0x10),所以前面STACK的值为0x20000010

在启动文件中明明有堆空间的创建(程序紧接着栈后面),而实际的却没有堆空间,这是怎么回事呢?这一点与armlink中的编译器优化相关。编译器优化看样子是很高深的概念,其实就是在不影响正常程序功能的情况下,让程序更好,就是说可能会让程序占据更小的空间,比如这里的堆,因为没有使用动态内存分配,所以有和没有是一样的,为了减少SRAM空间的消耗,就不给他分配空间。这对资源少的嵌入式设备来说,是有一定帮助的。


接下来就是汇编一个名叫RESET的只读数据区AREA RESET, DATA, READONLY

主要说明就是EXPORT

TheEXPORTdirective declares a symbol that can be used by the linker to resolve symbol references in separate object and library files.GLOBALis a synonym forEXPORT.

EXPORT指令就是声明一个标号,什么样的标号,被连接器使用来解析符号在不同目标与库文件的引用。

UseEXPORTto give code in other files access to symbols in the current file.

根据上面这句话的意思就是说,在当前文件中,对其他文件的的 符号使用标号,就可以访问该标号。

三个标号的具体值是多少呢?

(这里有我自己想无意中思考的问题,就是在任何代码文件中,并没有指定这些标号的值,到底这些标号是如何被赋予这些特定的值的呢?后面学习了程序的可执行文件,推测大概是这样的,当编译器在生成可执行文件映像的过程中,会将映像与程序的存储的物理地址联系起来,这个在物理地址编译之前是给了的,最后根据映像在物理地址的位置,上面这些标号的值就建立起来了。这里只是记录个人的想法,可略过,继续下面的内容)

这里很奇怪,这里向量表的结束地址为0x08000130,但是在cortex-M3编程手册中的(PM0056)中向量表的结束地址会大一点,如下图所示:

暂时不是特别清楚,为什么会这样,但是并不影响阅读与理解,先放这里。

DCD命令主要说明的就是分配内存空间,那这时会不会有人像我一样,前面不是有一个叫SPACE的吗?不也是分配内存空间,两者有什么区别呢?

DCD开辟的空间支持4字节对齐,而且可以初始化,就是给开辟的空间赋值DCD __initial_sp.

SPACE开辟的空间是不支持字节对齐,如果想让字节对齐,后面必须跟一条语句。下面的英文是官方的解释。而且用这条语句开辟的空间是初始化为0的。这就是与DCD指令的区别

Use theALIGNdirective to align any code following aSPACEorFILLdirective

整个向量表除了第一项存放的栈顶地址,其余各项存放的是函数名。**函数名就是该函数(代码块)所在内存的起始地址。**以其中一项为例说明:

这个Reset_Handler的值就为0x08000145,本质上就是地址。


接下来就是代码段了AREA |.text|, CODE, READONLY

; Reset handler
Reset_Handler   PROC
                EXPORT  Reset_Handler             [WEAK]
                IMPORT  __main
                IMPORT  SystemInit
                LDR     R0, =SystemInit
                BLX     R0               
                LDR     R0, =__main
                BX      R0
                ENDP

上面就是复位的异常处理函数,当STM32上电时,PC指针(R15)被赋予的初值为复位向量的值,也就是复位异常处理函数。堆栈指针MSP被赋值为向量表的第一项(刚上电时是特权级),也就__initial_sp。然后程序就来执行这里的代码。

PEOCENDP是固定用法,代表的是过程的开始与结束。有点汇编基础的比较好理解。

这里EXPORT后面加了一个[WEAK],代表什么意思呢?官方的解释是这样的。

如果能在其他源文件中找到一个相同的符号,那么那个在其他源文件的符号的优先级将高于这个。也就是说你如果自定义了复位函数,那么系统会执行你自定义的复位函数。

IMPORT说明是什么含义呢?说明后面的标号不是本文件定义的,引用其他文件定义的标号。连接器链接时会帮你找到它真正的位置。

这里先埋下一个伏笔,就是说通过反汇编代码去深入的学习__main函数。因为直接跳到__main函数会发现没有定义。记住非常关键点就是0x8000131。正好在向量表的下面。

SystemInit就是正常的c语言函数。

LDR指令,官方解释:Load addresses to a register using LDR Rd, =label,就是加载地址到寄存器中。简单。

BLX指令, 官方解释:

总结就是,保存下条指令的地址到LR中,跳转到分支去执行,还会切换处理器的状态,但是Cortex-M3只有Thumb态,所以这里并不会切换。所谓Thumb态,还有一种ARM态指的是处理器识别不同 的指令集,像ARM9之类的处理器,存在两种状态的。

BX指令与BLX指令类似(BLBLX类似),就是不保存返回地址。

这里有没有发现一个奇怪的地方,就是SystemInit的值是0x08000abd,(来自工程的.map文件)但是反汇编出来的,真正的地址是0x08000abc,差了1,为什么会差了1呢?又验证了其他几个函数名,发现都是相差1,我曾经看过这么一段话,就是在更新PCR15)寄存器时,会将PC指针的LSB置1,来表示Thumb状态,对于高级编程语言(包括C和C++),编译器会自动将跳转目标的LSB置位。正好对应到这里的相差1。

这里SystemInit只有一句话,就是跳转至lr中的地址,实际的C语言是:

void SystemInit (void)
{
   
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
### 回答1: keil __main是Keil软件中的一个C语言程序的入口函数。在C语言中,每一个程序都必须有一个入口函数来启动程序的执行。在Keil软件中,这个入口函数被命名为__main。 当我们在Keil软件中编写C语言程序时,我们需要在程序中定义一个名为__main的函数。当程序被加载到嵌入式系统中运行时,系统就会自动调用这个函数来开始执行程序。 __main函数的作用类似于传统C语言中的main函数,它是程序的入口,是程序开始执行的地方。我们可以在__main函数中编写程序的初始化代码,例如配置系统的硬件资源、初始化变量、设置中断等等。同时,我们也可以在__main函数中编写程序的主要逻辑,例如循环、判断等等。 总之,keil __main是Keil软件中的C语言程序入口函数,它的作用是开始执行程序并初始化系统资源。 ### 回答2: Keil是一款功能强大的集成开发环境(IDE),被广泛用于嵌入式系统开发。__main是Keil IDE中的一个主函数,也是程序的入口点。在C或C++语言中,程序执行时会从main函数开始执行。因此,我们编写的嵌入式程序通常会在main函数中定义并调用其他功能函数,来实现特定的功能。 在Keil IDE中,__main函数会根据我们的项目设置,在适当的时机自动被调用。我们可以在__main函数中做一些初始化操作,例如设置时钟频率、配置外设等,然后开始执行程序的其他部分。在嵌入式系统中,__main函数起到了一个整个程序的框架的作用,它提供了程序的入口和出口,我们需要将程序的关键功能逻辑放置在__main函数之中。 Keil IDE提供了丰富的开发工具和调试功能,可以帮助我们方便地进行软件开发和调试。我们可以在Keil IDE中进行代码编写、编译、调试和下载等操作。编写完程序后,Keil IDE会根据我们的项目设置自动生成可执行文件或固件文件,方便我们将程序烧录到目标硬件上运行。 总而言之,__main是Keil IDE中的主函数,用于定义程序的入口和出口,我们可以在__main函数中进行初始化操作,并编写其他功能函数来实现嵌入式系统的特定功能。Keil IDE为我们提供了一站式的开发和调试工具,方便我们进行软件开发和测试。 ### 回答3: "__main__" 是一个特殊的内置变量,它在Python中的使用场景主要用于判断当前的脚本是否作为主程序执行。 当一个Python文件被直接运行时,也就是作为主程序执行时,该文件中的__name__变量的值被自动设置为"__main__"。这样可以方便地判断一个模块是被导入执行还是直接运行。 为了更清晰地理解这个概念,我们可以举一个例子。假设我们有一个python文件名为"example.py",其中包含以下代码: ``` def hello_world(): print("Hello, World!") print("I am example.py") print("__name__ value:", __name__) if __name__ == "__main__": hello_world() ``` 当我们直接执行这个文件时,如`python example.py`,输出结果为: ``` I am example.py __name__ value: __main__ Hello, World! ``` 可以看到,当该文件被作为主程序执行时,__name__变量的值为"__main__",并且`hello_world()`函数也被正常地执行了。 相反,如果我们在另一个python文件中导入"example.py"并且调用其中的函数时,如: ``` import example example.hello_world() ``` 那么输出结果为: ``` I am example.py __name__ value: example Hello, World! ``` 可以看到,在这种情况下,__name__变量的值为"example",证明该文件是作为模块被导入执行,而不是直接作为主程序执行。 总之,__main__变量的使用使我们可以方便地判断一个模块是被导入执行还是直接运行,并可以在需要时做出相应的处理。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值