前言

在非Linux的嵌入式开发中,自己手写Bootloader是很正常的事。因为可以定制自己想要的功能。比如定制自己的Bootloader通信接口(UART、I2C、SPI),通信协议,甚至更高级的固件备份回退等功能。但是使用ESP8266就不一样了,整个芯片的程序是怎么跑起来的都一知半解(所以我写了这篇文章:ESP8266架构探索-运行的起始);官方提供了Bootloader和完整的接口,但是是闭源的;官方Bootloader虽然有做固件备份,但是没有固件回滚,等等这些问题。所以这时候rboot出现了。我们有很多原因不能从无到有写一个自己的Bootloader,但是我们可以借鉴,知道rboot怎么运作后,就能够通过修改,裁剪,做出自己想要的Bootloader。所以这篇文章不会花大力气去分析rboot的特性是怎么实现的,着重于怎么写一个ESP8266上最基础的Bootloader。

工程目录

.

├── appcode

│   ├── rboot-api.c

│   ├── rboot-api.h

│   └── rboot-bigflash.c

├── build

│   ├── rboot.elf

│   ├── rboot-hex2a.h

│   ├── rboot.o

│   ├── rboot-stage2a.elf

│   └── rboot-stage2a.o

├── eagle.app.v6.ld

├── eagle.rom.addr.v6.ld

├── firmware

│   └── rboot.bin

├── license.txt

├── Makefile

├── rboot.c

├── rboot.h

├── rboot-private.h

├── rboot-stage2a.c

├── rboot-stage2a.ld

├── readme-api.txt

├── readme.md

├── testload1.c

└── testload2.c

其中,rboot.c rboot-stage2a.c就是我们想要的代码。

运行流程

rboot.c的ENTRY是call_user_start,我们看看运行流程:(c语言版和汇编版功能一样,看c语言代码便于理解)

void call_user_start(void) {

uint32_t addr;

stage2a *loader;

addr = find_image();

if (addr != 0) {

loader = (stage2a*)entry_addr; //rboot-stage2a.c中的call_user_start

loader(addr);

}

}

从配置参数中找到应用固件的地址,然后调用entry_addr处的函数执行。

entry_addr在rboot-hex2a.h中定义:

const uint32_t entry_addr = 0x4010fcb4; //被rboot.c中的call_user_start里调用

const uint32_t _text_addr = 0x4010fc00;

const uint32_t _text_len = 192;

const uint8_t _text_data[] = {

0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,

0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,

0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,

0x00, 0x10, 0x00, 0x00, 0x1c, 0x4b, 0x00, 0x40, 0x12, 0xc1, 0xc0, 0xc9, 0xe1, 0x8b, 0x31, 0xcd,

0x02, 0x0c, 0x84, 0xe9, 0xc1, 0xf9, 0xb1, 0x09, 0xf1, 0xd9, 0xd1, 0xc2, 0xcc, 0x08, 0x01, 0xf9,

0xff, 0xc0, 0x00, 0x00, 0xf8, 0x31, 0xe2, 0x01, 0x09, 0x86, 0x10, 0x00, 0x2d, 0x0c, 0x3d, 0x01,

0x0c, 0x84, 0x01, 0xf4, 0xff, 0xc0, 0x00, 0x00, 0x8b, 0xcc, 0x78, 0x01, 0xd8, 0x11, 0x46, 0x09,

0x00, 0x21, 0xef, 0xff, 0x5d, 0x0d, 0xd7, 0xb2, 0x02, 0x20, 0x52, 0x20, 0x2d, 0x0c, 0x3d, 0x07,

0x4d, 0x05, 0x59, 0x51, 0x79, 0x41, 0x01, 0xeb, 0xff, 0xc0, 0x00, 0x00, 0x58, 0x51, 0x78, 0x41,

0x5a, 0xcc, 0x5a, 0x77, 0x50, 0xdd, 0xc0, 0x56, 0x6d, 0xfd, 0x0b, 0x6e, 0x60, 0xe0, 0x74, 0x56,

0x9e, 0xfb, 0x08, 0xf1, 0x2d, 0x0f, 0xc8, 0xe1, 0xd8, 0xd1, 0xe8, 0xc1, 0xf8, 0xb1, 0x12, 0xc1,

0x40, 0x0d, 0xf0, 0x00, 0xfd, 0x00, 0x05, 0xf8, 0xff, 0x0d, 0x0f, 0xa0, 0x02, 0x00, 0x0d, 0xf0,

};

对比entry_addr和_text_addr关系,再根据_text_addr和_text_data的名字关系(手动狗头),知道entry_addr的代码就在_text_data中。rboot-hex2a.h由rboot-stage2a.c生成的,call_user_start在rboot-stage2a.ld中定义为ENTRY:

/* Default entry point: */

ENTRY(call_user_start)

rboot-stage2a.c的代码:

void call_user_start(uint32_t readpos) {

usercode* user;

user = load_rom(readpos);

user();

}

从flash中读取代码,然后执行。

所以整个rboot流程就是:从配置参数中找到应用固件的地址,然后调用rboot-stage2a中的函数从flash中读取代码,然后执行。

这样说下来,流程是通了,但是过程一点都经不起推敲啊。那么下面来认真分析rboot-stage2a.c的代码怎么关联到rboot.c中的。

编译分析

我们先看Makefile。

all: $(RBOOT_BUILD_BASE) $(RBOOT_FW_BASE) $(RBOOT_FW_BASE)/rboot.bin

$(RBOOT_BUILD_BASE)/rboot.o: rboot.c rboot-private.h rboot.h $(RBOOT_BUILD_BASE)/rboot-hex2a.h

@echo "CC $

$(Q) $(CC) $(CFLAGS) -I$(RBOOT_BUILD_BASE) -c $< -o $@

$(RBOOT_BUILD_BASE)/rboot-hex2a.h: $(RBOOT_BUILD_BASE)/rboot-stage2a.elf

@echo "E2 $@"

$(Q) $(ESPTOOL2) -quiet -header $< $@ .text

$(RBOOT_BUILD_BASE)/rboot-stage2a.elf: $(RBOOT_BUILD_BASE)/rboot-stage2a.o

@echo "LD $@"

$(Q) $(LD) -Trboot-stage2a.ld $(LDFLAGS) -Wl,--start-group $^ -Wl,--end-group -o $@

顺序有所调整。但最终rboot-stage2a.c变成了rboot-hex2a.h包含在rboot.c中。

这里有两个关键的地方:

rboot-stage2a.elf通过ESPTOOL2变成了rboot-hex2a.h

rboot-stage2a.c最终变成了rboot-hex2a.h

编译过程中用到了esptool2,先把esptool2下载下来:https://github.com/raburton/esptool2

直奔主题,esptool2.c文件中,使用-header参数就是把elf文件变成.h头文件

// load elf file

elf = LoadElf(elffile);

if (!elf) {

goto end_function;

}

// open output file

outfile = fopen(imagefile, "wb");

if(outfile == NULL) {

error("Error: Failed to open output file '%s' for writing.\r\n", imagefile);

goto end_function;

}

// add entry point

fprintf(outfile, "const uint32_t entry_addr = 0x%08x;\r\n", elf->header.e_entry);

// add sections

for (i = 0; i < numsec; i++) {

// get elf section header

sect = GetElfSection(elf, sections[i]);

if(!sect) {

error("Error: Section '%s' not found in elf file.\r\n", sections[i]);

goto end_function;

}

// simple name fix name

strncpy(name, sect->name, 31);

len = strlen(name);

for (j = 0; j < len; j++) {

if (name[j] == '.') name[j] = '_';

}

// add address, length and start the data block

debug("Adding section '%s', addr: 0x%08x, size: %d.\r\n", sections[i], sect->address, sect->size);

fprintf(outfile, "\r\nconst uint32_t %s_addr = 0x%08x;\r\nconst uint32_t %s_len = %d;\r\nconst uint8_t %s_data[] = {",

name, sect->address, name, sect->size, name);

// get elf section binary data

bindata = GetElfSectionData(elf, sect);

if (!bindata) {

goto end_function;

}

// add the data and finish off the block

for (j = 0; j < sect->size; j++) {

if (j % 16 == 0) fprintf(outfile, "\r\n 0x%02x,", bindata[j]);

else fprintf(outfile, " 0x%02x,", bindata[j]);

}

fprintf(outfile, "\r\n};\r\n");

free(bindata);

bindata = 0;

}

然后对比rboot-hex2a.h文件,是不是这样转过来的:

const uint32_t entry_addr = 0x4010fcb4;

const uint32_t _text_addr = 0x4010fc00;

const uint32_t _text_len = 192;

const uint8_t _text_data[] = {

0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,

0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,

0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,

0x00, 0x10, 0x00, 0x00, 0x1c, 0x4b, 0x00, 0x40, 0x12, 0xc1, 0xc0, 0xc9, 0xe1, 0x8b, 0x31, 0xcd,

0x02, 0x0c, 0x84, 0xe9, 0xc1, 0xf9, 0xb1, 0x09, 0xf1, 0xd9, 0xd1, 0xc2, 0xcc, 0x08, 0x01, 0xf9,

0xff, 0xc0, 0x00, 0x00, 0xf8, 0x31, 0xe2, 0x01, 0x09, 0x86, 0x10, 0x00, 0x2d, 0x0c, 0x3d, 0x01,

0x0c, 0x84, 0x01, 0xf4, 0xff, 0xc0, 0x00, 0x00, 0x8b, 0xcc, 0x78, 0x01, 0xd8, 0x11, 0x46, 0x09,

0x00, 0x21, 0xef, 0xff, 0x5d, 0x0d, 0xd7, 0xb2, 0x02, 0x20, 0x52, 0x20, 0x2d, 0x0c, 0x3d, 0x07,

0x4d, 0x05, 0x59, 0x51, 0x79, 0x41, 0x01, 0xeb, 0xff, 0xc0, 0x00, 0x00, 0x58, 0x51, 0x78, 0x41,

0x5a, 0xcc, 0x5a, 0x77, 0x50, 0xdd, 0xc0, 0x56, 0x6d, 0xfd, 0x0b, 0x6e, 0x60, 0xe0, 0x74, 0x56,

0x9e, 0xfb, 0x08, 0xf1, 0x2d, 0x0f, 0xc8, 0xe1, 0xd8, 0xd1, 0xe8, 0xc1, 0xf8, 0xb1, 0x12, 0xc1,

0x40, 0x0d, 0xf0, 0x00, 0xfd, 0x00, 0x05, 0xf8, 0xff, 0x0d, 0x0f, 0xa0, 0x02, 0x00, 0x0d, 0xf0,

};

entry_addr是elf文件中的入口地址。

之后则是.text段的地址长度内容。

但这堆16进制数字,我们也不能确定就是代码编译出来的。不怕,我们有办法。在ESP8266编译后,会生成一个eagle.dump文件,我们同样可以把rboot-stage2a.elf里的信息弄出来。

稍微修改Makefile文件

ifndef XTENSA_BINDIR

CC := xtensa-lx106-elf-gcc

LD := xtensa-lx106-elf-gcc

OBJDUMP := xtensa-lx106-elf-objdump #加上

else

CC := $(addprefix $(XTENSA_BINDIR)/,xtensa-lx106-elf-gcc)

LD := $(addprefix $(XTENSA_BINDIR)/,xtensa-lx106-elf-gcc)

OBJDUMP := $(addprefix $(XTENSA_BINDIR)/,xtensa-lx106-elf-objdump) #加上

endif

$(RBOOT_BUILD_BASE)/rboot-hex2a.h: $(RBOOT_BUILD_BASE)/rboot-stage2a.elf

@echo "E2 $@"

$(Q) $(OBJDUMP) -x -s -d $< > $(RBOOT_BUILD_BASE)/rboot-stage2a.dump #加上

$(Q) $(ESPTOOL2) -quiet -header $< $@ .text

重新编译,就能生成rboot-stage2a.dump文件了。

make clean

make

rboot-stage2a.dump:(注意看里面加的注释)

build/rboot-stage2a.elf: file format elf32-xtensa-le

build/rboot-stage2a.elf

architecture: xtensa, flags 0x00000112:

EXEC_P, HAS_SYMS, D_PAGED

start address 0x4010fcb4 #和entry_addr对应

Contents of section .text: #.text段内容和_text_data[]对应

4010fc00 00000000 00000000 00000000 00000000 ................

4010fc10 00000000 00000000 00000000 00000000 ................

4010fc20 00000000 00000000 00000000 00000000 ................

4010fc30 00100000 1c4b0040 12c1c0c9 e18b31cd .....K.@......1.

4010fc40 020c84e9 c1f9b109 f1d9d1c2 cc0801f9 ................

4010fc50 ffc00000 f831e201 09861000 2d0c3d01 .....1......-.=.

4010fc60 0c8401f4 ffc00000 8bcc7801 d8114609 ..........x...F.

4010fc70 0021efff 5d0dd7b2 02205220 2d0c3d07 .!..].... R -.=.

4010fc80 4d055951 794101eb ffc00000 58517841 M.YQyA......XQxA

4010fc90 5acc5a77 50ddc056 6dfd0b6e 60e07456 Z.ZwP..Vm..n`.tV

4010fca0 9efb08f1 2d0fc8e1 d8d1e8c1 f8b112c1 ....-...........

4010fcb0 400df000 fd0005f8 ff0d0fa0 02000df0 @...............

4010fcb4 : #entry_addr执行的代码

4010fcb4: 00fd mov.n a15, a0 #注意这个地址

4010fcb6: fff805 call0 4010fc38

4010fcb9: 0f0d mov.n a0, a15

4010fcbb: 0002a0 jx a2

4010fcbe: f00d ret.n

call_user_start在rboot-stage2a.ld中定义为ENTRY:

EMORY

{

dport0_0_seg : org = 0x3FF00000, len = 0x10

dram0_0_seg : org = 0x3FFE8000, len = 0x14000

iram1_0_seg : org = 0x4010FC00, len = 0x400

irom0_0_seg : org = 0x40240000, len = 0x3C000

}

PHDRS

{

dport0_0_phdr PT_LOAD;

dram0_0_phdr PT_LOAD;

dram0_0_bss_phdr PT_LOAD;

iram1_0_phdr PT_LOAD;

irom0_0_phdr PT_LOAD;

}

/* Default entry point: */

ENTRY(call_user_start)

EXTERN(_DebugExceptionVector)

EXTERN(_DoubleExceptionVector)

EXTERN(_KernelExceptionVector)

EXTERN(_NMIExceptionVector)

EXTERN(_UserExceptionVector)

PROVIDE(_memmap_vecbase_reset = 0x40000000);

到了这里,应该就能弄清楚rboot-stage2a.c是如何编译,并且如果和rboot-hex2a.h对应起来的了。

但为什么要搞那么麻烦?

因为rboot.c还有一处巧妙的地方:

// copy the loader to top of iram

ets_memcpy((void*)_text_addr, _text_data, _text_len);

rboot-stage2a.c通过工具转换成rboot-hex2a.h,就是为了能将这段代码加载到iram中。

小结

这篇文章主要对rboot目录进行了解,大概了解了加载流程,同时根据编译过程将重要的两个程序文件串了起来。rboot的设计还是比较巧妙的。我们下篇文章会对整个加载流程做详细讲解。

esp8266解析php,ESP8266 Bootloader开源代码解析之rboot(一)相关推荐

  1. java常用代码解析_Java设计模式常用原则代码解析

    本篇文章小编给大家分享一下Java设计模式常用原则代码解析,代码介绍的很详细,小编觉得挺不错的,现在分享给大家供大家参考,有需要的小伙伴们可以来看看. 1.单一职责原则每一个类负责一个职责(一个类只有 ...

  2. (一)turtlebot3学习,运行的开源代码解析

    1.文件解析和系统运行思路 turtlebot3要动起来,需要运行一些程序.下面对运行的程序进行解析 (1)启动激光和控制命令发送程序及传感器接收程序 roslaunch turtlebot3_bri ...

  3. java 解析二进制_Java二进制指令代码解析

    常量入栈指令 操作码(助记符) 操作数 描述(栈指操作数栈) aconst_null null值入栈. iconst_m1 -1(int)值入栈. iconst_0 0(int)值入栈. iconst ...

  4. ECCV2020|图像重建(超分辨率,图像恢复,去雨,去雾等)相关论文汇总(附论文链接/代码/解析)

    转载自https://zhuanlan.zhihu.com/p/180551773 原帖地址: ECCV2020|图像重建/底层视觉(超分辨率,图像恢复,去雨,去雾,去模糊,去噪等)相关论文汇总(附论 ...

  5. CornerNet代码解析——损失函数

    CornerNet代码解析--损失函数 文章目录 CornerNet代码解析--损失函数 前言 总体损失 1.Heatmap的损失 2.Embedding的损失 3.Offset的损失 前言 今天要解 ...

  6. Tensorflow 代码解析

    Tensorflow 代码解析(一) Tensorflow 代码解析(二) Tensorflow 代码解析(三) Tensorflow 代码解析(四) Tensorflow 代码解析(五)

  7. ESP8266 学习 十一 ESP8266 JSON解析

    解析JSON格式信息是一个较为繁琐的工作,因此我们将借助解析Arduino – ESP8266平台中解析JSON格式信息的第三方库--ArduionJson库.该库是目前最受好评的解析JSON信息第三 ...

  8. 视觉SLAM开源算法ORB-SLAM3 原理与代码解析

    来源:深蓝学院,文稿整理者:何常鑫,审核&修改:刘国庆 本文总结于上交感知与导航研究所科研助理--刘国庆关于[视觉SLAM开源算法ORB-SLAM3 原理与代码解析]的公开课. ORB-SLA ...

  9. 分析FLV文件分析和解析器的开源代码

    分析一下GitHub上一份FLV文件分析和解析器的开源代码 GitHub源码地址:功能强大的 FLV 文件分析和解析器 :可以将flv文件的视频tag中的h264类型数据和音频tag中的aac类型数据 ...

最新文章

  1. 逻辑回归 + GBDT模型融合实战!
  2. ansi c标准_C/C++的起源与发展故事,我是最牛的软件编程语言,不接受反驳
  3. 博士申请 | 香港科技大学谢知遥教授实验组招收机器学习全奖博士生
  4. MySQL设计索引的原则
  5. win8系统关闭共享服务器,Windows8系统关闭Windows Media Player网络共享服务的方法
  6. ASP.NET 5 入门(1) - 建立和开发ASP.NET 5 项目
  7. 前端学习(2053)vue之电商管理系统电商系统之使用pm2管理
  8. pycharm遇到的小问题
  9. ssm练手小项目_20 个 JavaScript+Html+CSS 练手的小项目
  10. 20211102:数字滤波器按照实现结构的分类及其优缺点总结
  11. C#中lock关键字的用法
  12. 华为视频会议系统常见问题及解决方法
  13. matlab矩阵(一)--如何控制矩阵中小数点的位数
  14. 拖拽文件作为文件输入
  15. 勒索病毒解密工具的汇总
  16. c++项目开发——吃豆子游戏
  17. Python 使用OpenCV计算机视觉(一篇文章从零毕业)【附带OCR文字识别项目、停车场车位智能识别项目】
  18. linux scl软件包下载,Centos7升级gcc版本方法之一使用scl软件集
  19. 西安适合计算机专业的研究所,计算机类专业比较厉害的6所大学,适合于中等偏上的学生报考...
  20. Java练手项目:点菜系统

热门文章

  1. 3月13日云栖精选夜读 | Serverless 风暴来袭,前端工程师如何应对?...
  2. oracle数据库恢复aul_ORACLE恢复神器之ODU/AUL/DUL
  3. aptx android8,支持aptxHD和LDAC!安卓8.0蓝牙音质大爆发
  4. js如何让段落首行缩进2个字符
  5. Mysql性能衡量指标
  6. SeetaFace开源人脸识别引擎
  7. FDD and HDD
  8. 费马小定理+插板法 - Sum(HDU4704)
  9. win10升级nvidia、cuda、cudnn,非常简单
  10. Android 系统原生TTS使用