LV04-02-GPIO-05-根据dis文件分析代码运行

本文主要是根据反汇编出来的dis文件,分析代码的运行的相关笔记,若笔记中有错误或者不合适的地方,欢迎批评指正😃。

点击查看使用工具及版本
Windows版本 windows11
Ubuntu版本 Ubuntu16.04的64位版本
VMware® Workstation 16 Pro 16.2.3 build-19376536
终端软件 MobaXterm(Professional Edition v23.0 Build 5042 (license))
Linux开发板 正点原子 i.MX6ULL Linux 阿尔法开发板
uboot NXP官方提供的uboot,NXP提供的版本为uboot-imx-rel_imx_4.1.15_2.1.0_ga(使用的uboot版本为U-Boot 2016.03)
linux内核 linux-4.15(NXP官方提供)
Win32DiskImager Win32DiskImager v1.0
点击查看本文参考资料
分类 网址 说明
官方网站 https://www.arm.com/ ARM官方网站,在这里我们可以找到Cotex-Mx以及ARMVx的一些文档
https://www.nxp.com.cn/ NXP官方网站
https://www.nxpic.org.cn/NXP 官方社区
https://u-boot.readthedocs.io/en/latest/u-boot官网
https://www.kernel.org/linux内核官网
点击查看相关文件下载
分类 网址 说明
NXP https://github.com/nxp-imx NXP imx开发资源GitHub组织,里边会有u-boot和linux内核的仓库
https://elixir.bootlin.com/linux/latest/source 在线阅读linux kernel源码
nxp-imx/linux-imx/releases/tag/rel_imx_4.1.15_2.1.0_ga NXP linux内核仓库tags中的rel_imx_4.1.15_2.1.0_ga
nxp-imx/uboot-imx/releases/tag/rel_imx_4.1.15_2.1.0_ga NXP u-boot仓库tags中的rel_imx_4.1.15_2.1.0_ga
I.MX6ULL i.MX 6ULL Applications Processors for Industrial Products I.MX6ULL 芯片手册(datasheet,可以在线查看)
i.MX 6ULL Applications ProcessorReference Manual I.MX6ULL 参考手册(下载后才能查看,需要登录NXP官网)

我们分析的时候,以韦东山教程中的文件为例进行分析,我们前边的Makefile文件中已经可以生成反汇编文件,我们可以直接查看。

一、文件准备

1. start.s

点击查看详情
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
.text
.global _start
_start:
ldr sp,=0x80200000
bl clean_bss
bl main

halt:
b halt

clean_bss:
/* 清除BSS段 */
ldr r1, =__bss_start
ldr r2, =__bss_end
mov r3, #0
clean:
str r3, [r1]
add r1, r1, #4
cmp r1, r2
bne clean

mov pc, lr

2. 链接文件

点击查看 imx6ull.lds
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
SECTIONS {
. = 0x80100000;

. = ALIGN(4);
.text :
{
*(.text)
}

. = ALIGN(4);
.rodata : { *(.rodata) }

. = ALIGN(4);
.data : { *(.data) }

. = ALIGN(4);
__bss_start = .;
.bss : { *(.bss) *(.COMMON) }
__bss_end = .;
}

3. C程序文件

3.1 led.c

点击查看 led.c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
#include "led.h"

static volatile unsigned int *CCM_CCGR1 ;
static volatile unsigned int *IOMUXC_SNVS_SW_MUX_CTL_PAD_SNVS_TAMPER3;
static volatile unsigned int *GPIO5_GDIR ;
static volatile unsigned int *GPIO5_DR ;

void led_init(void)
{
unsigned int val;

CCM_CCGR1 = (volatile unsigned int *)(0x20C406C);
IOMUXC_SNVS_SW_MUX_CTL_PAD_SNVS_TAMPER3 = (volatile unsigned int *)(0x2290014);
GPIO5_GDIR = (volatile unsigned int *)(0x020AC000 + 0x4);
GPIO5_DR = (volatile unsigned int *)(0x020AC000);

/* GPIO5_IO03 */
/* a. 使能GPIO5
* set CCM to enable GPIO5
* CCM_CCGR1[CG15] 0x20C406C
* bit[31:30] = 0b11
*/
*CCM_CCGR1 |= (3<<30);

/* b. 设置GPIO5_IO03用于GPIO
* set IOMUXC_SNVS_SW_MUX_CTL_PAD_SNVS_TAMPER3
* to configure GPIO5_IO03 as GPIO
* IOMUXC_SNVS_SW_MUX_CTL_PAD_SNVS_TAMPER3 0x2290014
* bit[3:0] = 0b0101 alt5
*/
val = *IOMUXC_SNVS_SW_MUX_CTL_PAD_SNVS_TAMPER3;
val &= ~(0xf);
val |= (5);
*IOMUXC_SNVS_SW_MUX_CTL_PAD_SNVS_TAMPER3 = val;


/* c. 设置GPIO5_IO03作为output引脚
* set GPIO5_GDIR to configure GPIO5_IO03 as output
* GPIO5_GDIR 0x020AC000 + 0x4
* bit[3] = 0b1
*/
*GPIO5_GDIR |= (1<<3);

}

void led_ctl(int on)
{
if (on) /* on: output 0*/
{
/* d. 设置GPIO5_DR输出低电平
* set GPIO5_DR to configure GPIO5_IO03 output 0
* GPIO5_DR 0x020AC000 + 0
* bit[3] = 0b0
*/
*GPIO5_DR &= ~(1<<3);
}
else /* off: output 1*/
{
/* e. 设置GPIO5_IO3输出高电平
* set GPIO5_DR to configure GPIO5_IO03 output 1
* GPIO5_DR 0x020AC000 + 0
* bit[3] = 0b1
*/
*GPIO5_DR |= (1<<3);
}
}

3.2 led.h

点击查看 led.h
1
2
3
4
5
6
7
#ifndef   __LED_H__
#define __LED_H__

void led_init(void);
void led_ctl(int on);

#endif

3.3 main.c

点击查看 main.c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#include "led.h"

void delay(volatile unsigned int d)
{
while(d--);
}


int main()
{
led_init();

while(1)
{
led_ctl(1);
delay(1000000);
led_ctl(0);
delay(1000000);
}

return 0;
}

4. 工具准备

还有一个生成DCD数据的工具,需要去u-boot的源码中拷贝,所需文件如下:

image-20230723091404100

5. makefile

点击查看makefile文件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
PREFIX=arm-linux-gnueabihf-
CC=$(PREFIX)gcc
LD=$(PREFIX)ld
AR=$(PREFIX)ar
OBJCOPY=$(PREFIX)objcopy
OBJDUMP=$(PREFIX)objdump

led.img : start.S led.c main.c
$(CC) -nostdlib -g -c -o start.o start.S
$(CC) -nostdlib -g -c -o led.o led.c
$(CC) -nostdlib -g -c -o main.o main.c

$(LD) -T imx6ull.lds -g start.o led.o main.o -o led.elf

$(OBJCOPY) -O binary -S led.elf led.bin
$(OBJDUMP) -D -m arm led.elf > led.dis
./tools/mkimage -n ./tools/imximage.cfg.cfgtmp -T imximage -e 0x80100000 -d led.bin led.imx
dd if=/dev/zero of=1k.bin bs=1024 count=1
cat 1k.bin led.imx > led.img

clean:
rm -f led.dis led.bin led.elf led.imx led.img *.o

二、上电后在做啥?

如下图, imx6ull 芯片一上电后,会先执行 bootRom 程序,此程序是芯片出厂时已经固定的程序,除了芯片原厂,我们是无法修改的。

image-20230722195231658

(1)bootRom 会把 EMMC 或 TF 卡的前 4K 数据读入到芯片内部 RAM 运行。

(2)bootRom 根据 DCD 进行初始化 DDR。

(3)bootRom 根据 IVT,从 EMMC 或 TF 卡中将 led.bin 读到 DDR 的0x80100000 地址

(4)跳转到 DDR 的 0x80100000 地址执行 。

目前 led.bin 程序已经复制到内存中, CPU 开始从内存 0x80100000 地址开始执行机器码,每一条机器码是 32 位/4 字节,此处的机器码就是 led.bin 中的
机器码,我们可以打开led.bin看一下机器码是不是上边图中画的:

image-20230722195704437

前面介绍过大/小端模式,从这里就可以看出来啦。此处可以看到机器码 e59fd028(指令: ldr sp,=0x80200000)的存储形式:

1
2
3
4
5
   地址   机器码  
00000000 28
00000001 d0
00000002 9f
00000003 e5

可以看到imx6ull 的存储方式是小端模式,其实, ARM 处理器中存储方式一般都是小端模式

三、运行过程分析

这一部分主要是分析led.dis文件。

1. start.s的反汇编

image-20230722201137948

(1)CPU 执行的第一条机器码就是内存地址 0x80100000 存储的 e59fd028 机器码对应的指令是“ ldr sp, [pc, #40] ”,相当于 start.s 文件的 “ ldr sp,=0x80200000 ” 指令。执行完后,寄存器 SP 的值等于 0x80200000。

1
80100000: e59fd028 ldr sp, [pc, #40] ; 80100030 <clean+0x14>

(2)每执行完一条机器码,会自动执行下一个内存地址 0x80100004 存储的eb000001 机器码对应的指令是“ bl 80100010”,相当于 Start.S 文件的“ bl clean_bss”指令。

1
2
3
4
5
6
7
8
80100004:	eb000001 	bl	80100010 <clean_bss>

@......

80100010 <clean_bss>:
80100010: e59f101c ldr r1, [pc, #28] ; 80100034 <clean+0x18>
80100014: e59f201c ldr r2, [pc, #28] ; 80100038 <clean+0x1c>
80100018: e3a03000 mov r3, #0

(3)跳转到内存地址 0x80100010 执行 e59f101c 机器码,对应的指令是 ldr r1, [pc, #28] 。相当于 start.s 文件的“ ldr r1, =__bss_start”指令。

(4)此处 clean_bss 相当于一个函数, CPU 会逐条执行指令,clean为循环体,直到执行“ mov pc, lr”指令后,才返回。

(5)最后返回哪里?返回内存地址0x80100008 处执行 fa000057 机器码,对应的指令是“blx 8010016c”。对应 start.s 文件的“ bl main”指令 。

2. 进入main函数

我们简单看一下main函数的反汇编文件对应情况:

image-20230722202506845

(1)进入 main()函数后,先将寄存器 R7、 LR 入栈,保存现场/上下文,方便main()函数执行完毕后返回,并且将当前栈指向的内存地址赋值给寄存器R7。

(2)调用 led_init()函数,因为没有参数传递,所以直接调用 BL 指令进行跳转,即“bl 8010003c”指令。

(3)调用 led_ctl(1)函数,此处只有一个参数,通过寄存器 R0 进行传递,即“movs r0, #1”指令,然后通过 BL 指令进行跳转,即“bl 801000f8”指令,关于参数传递问题,可以参考前面《01嵌入式开发/01HQ课程体系/LV08-ARM基础/LV08-01-ARM体系-03-ARM汇编基础.md》的笔记。

(4)调用 delay(1000000)函数,此处只有一个参数,通过寄存器 R0 进行传递,然后通过 BL 指令进行跳转。

(5)调用 led_ctl(0)函数,此处只有一个参数,通过寄存器 R0 进行传递,然后通过 BL 指令进行跳转。

(6)调用 delay(1000000)函数,此处只有一个参数,通过寄存器 R0 进行传递,然后通过 BL 指令进行跳转。

(7)while(1)循环体到此已经结束,但是需要循环执行循环体的内容,通过 B指令进行跳转到循环体开头,即“b.n 80100174”指令,执行内存地址0x80100174 处的指令,也就是 led_ctl(1)函数对应的汇编指令“movs r0,#1”。