[007] [ARM-Cortex-M3/4] C与汇编深入分析

ARM
Cortex-M3/4
子程序(函数)
调用规则
C函数的反汇
编代码分析
Flash中烧录的内
容与启动流程
Flash中烧录的内容
启动流程
纯汇编点灯

1 子程序(函数)调用规则

[ARM-Cortex-M3/4] ATPCS子程序调用规则规定了函数调用过程中参数的传递、局部变量的保存、数据栈的使用等规则。

寄存器 别名 特殊名称 使用规则
r15 PC 程序计数器
r14 LR 连接寄存器
r13 SP 数据栈指针
r12 IP 子程序间调用的暂存寄存器scratch register.
r11 v8 FP ARM状态局部变量寄存器8 / 栈帧指针
r10 v7 SL ARM状态局部变量寄存器7 在支持数据栈检查的ATPCS中为数据栈限制指针
r9 v6 SB ARM状态局部变量寄存器6 在支持RWPI的ATPCS中为静态基址寄存器
r8 v5 ARM状态局部变量寄存器5
r7 v4 WR 局部变量寄存器4 Thumb状态工作寄存器
r6 v3 局部变量寄存器3
r5 v2 局部变量寄存器2
r4 v1 局部变量寄存器1
r3 a4 参数/结果/暂存寄存器scratch register 4
r2 a3 参数/结果/暂存寄存器scratch register 3
r1 a2 参数/结果/暂存寄存器scratch register 2
r0 a1 参数/结果/暂存寄存器scratch register 1
  • r0~r3传递参数,被调用的子程序在返回前无需恢复寄存器r0~r3的内容。
  • r4~r11保存局部变量,若被调程序使用了其中的某些寄存器,则在进入子程序时必须保存它们的值,在返回前必须恢复这些寄存器的值。在Thumb程序中,只能使用r4~r7。
  • r12~r15为特殊寄存器。

2 C函数的反汇编代码分析

keil软件会对名为main的函数添加很多额外的汇编代码(与链接脚本有关),为了简化分析,将main修改为mymain

  • start.s
				PRESERVE8
                THUMB

; Vector Table Mapped to Address 0 at Reset
                AREA    RESET, DATA, READONLY
				EXPORT  __Vectors
					
__Vectors       DCD     0                  
                DCD     Reset_Handler              ; Reset Handler

				AREA    |.text|, CODE, READONLY

; Reset handler
Reset_Handler   PROC
				EXPORT  Reset_Handler             [WEAK]
                IMPORT  mymain			; 修改为mymian

				LDR SP, =(0x20000000+0x10000)
				BL mymain

                ENDP 
                
                END

[007] [ARM-Cortex-M3/4] C与汇编深入分析_第1张图片

▲ C函数与反汇编代码

程序烧录到flash中的即为机器码

在启动文件使用BL指令跳转到mymain函数:

  • 使能GPIOB
; pReg = (unsigned int *)(0x40021000 + 0x18);
LDR  	r3,[pc,#52] 	; pc+52=0x8000058, 将该地址的中的内容0x40021018加载到 r3 (r3即为指针pReg)
; *pReg |= (1<<3);
LDR  	r0,[r3,#0]		; r0 = *r3  (*即为解引用, 表示取r3地址中的值)
ORR     r0,r0,#8		; r0 |= (1 << 3)
STR     r0,[r3,#0]		; *r3 = r0 	
  • 设置GPIOB0为输出引脚
; pReg = (unsigned int *)(0x40010C00 + 0x00);
LDR     r3,[pc,#48] 	; 从0x0800005c内存地址读出0x40010c00加载到r3
; *pReg |= (1<<0);
LDR     r0,[r3,#0]		; r0 = *r3
ORR     r0,r0,#1		; r0 |= 1
STR     r0,[r3,#0]		; *r3 = r0
; pReg = (unsigned int *)(0x40010C00 + 0x0C);
LDR     r3,[pc,#36] 	; r3 = 0x40010c00
ADDS    r3,r3,#0xc		; r3 += 0xc, S表示需要修改CPSR状态寄存器
  • while循环
0x08000038:   B        0x8000056 	; 跳转到最后一句0x8000056处
; *pReg |= (1<<0);
0x0800003a:   LDR      r0,[r3,#0]	; r0 = *r3 = *(0x40010c00 + 0xc)
0x0800003c:   ORR      r0,r0,#1		; r0 |= (1 << 0)
0x08000040:   STR      r0,[r3,#0]	; *r3 = r0
; delay(100000);
0x08000042:   LDR      r0,[pc,#28]  ; r0 = *0x08000060 = 0x186a0 = 100000, 根据ATPCS规则r0保存delay函数的入口参数
0x08000044:   BL       delay 		; 跳转到0x8000014处的delay函数↓, 并将下一条指令的地址0x08000048加载到LR
; *pReg &= ~(1<<0);
0x08000048:   LDR      r0,[r3,#0]	; r0 = *r3 = *(0x40010c00 + 0xc)
0x0800004a:   BIC      r0,r0,#1		; r0 &= ~(1 << 0)
0x0800004e:   STR      r0,[r3,#0]	; *r3 = r0
; delay(100000);
0x08000050:   LDR      r0,[pc,#12]  ; r0 = 0x186a0 = 100000
0x08000052:   BL       delay 		; 跳转到0x8000014处的delay函数↓
0x08000056:   B        0x800003a 	; 跳回到第二句0x800003a处

; 以下是为一些常量分配的地址与值, 不属于while部分
0x08000058:    40021018    DCD    1073877016
0x0800005c:    40010c00    DCD    1073810432
0x08000060:    000186a0    DCD    100000
  • delay函数
0x08000014:   NOP      				; 空操作伪指令,用于延时
0x08000016:   SUBS     r1,r0,#0		; r1 = r0 - 0, S表示会更新cpsr
0x08000018:   SUB      r0,r0,#1		; r0 -= 1
0x0800001c:   BNE      0x8000016 	; not equal(Z=0)执行,即若r0不为0, 跳转到0x8000016处继续执行
0x0800001e:   BX       lr			; 调转到 while循环 中BL指令的下一条指令处继续执行

start.s中设置了栈LDR SP, =(0x20000000+0x10000),为体现函数栈的调用,将delay函数变种(加volatile):

int delay(volatile int d)
{
	while(d--);
	return 0x55;
}
delay
    0x08000014:    PUSH     {r0,lr}			; 将1r压入到sp-4, r0压入到sp-8, sp = sp - 8
    0x08000016:    NOP      
    ; 因为加了volatile(从栈中读r0到r1 -> 修改r1-> 将r1再写回栈中r0
    0x08000018:    LDR      r0,[sp,#0]		; r0 = sp, sp存储原r0
    0x0800001a:    SUBS     r1,r0,#1		; r1 = r0 - 1, 并改变cpsr
    0x0800001c:    STR      r1,[sp,#0]		; *sp = r1, 即*r0 = r1
    0x0800001e:    CMP      r0,#0			; 比较r0是否为0
    0x08000020:    BNE      0x8000018 		; 如果r0不为0, 跳转到0x8000018处
    0x08000022:    MOVS     r0,#0x55		; 用r0保存函数返回值0x55
    0x08000024:    POP      {r3,pc}			; r3 = r0, pc = lr 跳转
    0x08000026:    MOVS     r0,r0			; 无用语句->字节对齐

不加volatile

0x08000016:   SUBS     r1,r0,#0		; r1 = r0 - 0, S表示会更新cpsr
0x08000018:   SUB      r0,r0,#1		; r0 -= 1

volatile

0x08000018:   LDR      r0,[sp,#0]	; r0 = sp, sp存储原r0
0x0800001a:   SUBS     r1,r0,#1		; r1 = r0 - 1, 并改变cpsr
0x0800001c:   STR      r1,[sp,#0]	; *sp = r1, sp存储原r0, 即*r0 = r1

3 Flash中烧录的内容与启动流程

3.1 Flash中烧录的内容

[007] [ARM-Cortex-M3/4] C与汇编深入分析_第2张图片

▲ 反汇编文件

反汇编文件的框选部分即为机器码,将其烧录到对应的flash地址上(四字节对齐):

地址 Flash内容
0x08000000 00000000
0x08000004 08000009
0x08000008 f8dfd004
0x0800000c f000f808
0x08000010 20010000
0x08000014 bf001e10
0x08000018 f1a00001
…… ……

3.2 启动流程

分散加载文件中指定了flash起始地址为0x80000000:

LR_IROM1 0x08000000 0x00080000  {    ; load region size_region
  ER_IROM1 0x08000000 0x00080000  {  ; load address = execution address
   *.o (RESET, +First)
   *(InRoot$$Sections)
   .ANY (+RO)
   .ANY (+XO)
  }
  RW_IRAM1 0x20000000 0x00010000  {  ; RW data
   .ANY (+RW +ZI)
  }
}
  • 设置栈:CPU会从0x08000000读取值,用来设置SP指向flash起始地址(示例的启动文件中后面再次设置了SP,让其指向RAM末尾地址)
  • 跳转:CPU从0x08000004得到地址值0x08000009,即PC=0x08000009,PC的LSB为1表示Thumb状态(0为ARM)
    • 对于cortex M3/M4,它只支持Thumb状态,所以0x08000004上的值的LSB总为1
    • 跳到Reset_Handler(0x08000008地址处)复位中断服务例程中执行

4 纯汇编点灯

引脚PA8

				PRESERVE8
                THUMB

; Vector Table Mapped to Address 0 at Reset
                AREA    RESET, DATA, READONLY
				EXPORT  __Vectors
					
__Vectors       DCD     0                  
                DCD     Reset_Handler              ; Reset Handler
				AREA    |.text|, CODE, READONLY

; Reset handler
Reset_Handler   PROC
				EXPORT  Reset_Handler             [WEAK]
					
DEALY_TIME      EQU     500000	; 定义延时时间常量
				; 使能GPIOA时钟, RCC寄存器基地址0x4002 1000, 0x40021000 + 0x18为RCC->RCC_APB2ENR
				LDR	R0, =(0x40021000 + 0x18)
				LDR R1, [R0]
				ORR R1, R1, #(1<<2)
				STR R1, [R0]
				; 配置CRH寄存器, 设置PA8为推挽输出, GPIOA基地址0x4001 0800, 0x40010800 + 0x04为GPIOA->CRH
				LDR	R0, =(0x40010800 + 0x04)
				LDR R1, [R0]
				BIC R1, R1, #0xf	; 先清除
				ORR R1, R1, #0x3	; 推挽输出, 50mhz
				STR R1, [R0]
				; 操作ODR寄存器控制GPIO电平, 0x40010800 + 0x0C为GPIOA->ODR)
				LDR	R0, =(0x40010800 + 0x0C)
LOOP				
				; 拉高
				LDR R1, [R0] 
				ORR R1, R1, #(1<<8)
				STR R1, [R0]
				; 延时
				LDR R2, =DEALY_TIME
				BL DELAY
				
				; 拉低
				LDR R1, [R0] 
				BIC R1, R1, #(1<<8)
				STR R1, [R0]
				; 延时
				LDR R2, =DEALY_TIME
				BL DELAY
				
				B LOOP
                ENDP 
DELAY
				SUBS R2, R2, #1
				NOP		; vb字节对齐
				BNE DELAY
				BX LR	; MOV PC, LR
                END

实验现象:PA8红色LED正常闪烁。

END

你可能感兴趣的:(ARM,arm,c语言,单片机)