基于单片机的ELF可执行文件加载以及Bootloader程序实现程序切换

news2024/9/20 8:06:21

目录

1.ELF可执行文件加载

1.1 ELF文件分类

1.2 ELF文件格式

1.3链接地址

​编辑 

2.Bootloader程序切换

2.1 Bootloader介绍

2.2 代码实现


1.ELF可执行文件加载

ELF(Executable and Linkable Format)文件是一种标准的文件格式,用于可执行文件、目标代码和共享库。它广泛用于各种操作系统,如 Linux、UNIX、以及其他支持 ELF 格式的系统。

1.1 ELF文件分类

根据 ELF 文件的用途,文件头中的 e_type 字段可以区分出几种不同的类型:

ET_REL:可重定位文件(Relocatable file),通常是编译后的中间文件,后续可以被链接成可执行文件或共享库。

ET_EXEC:可执行文件(Executable file),包含可以直接运行的程序。

ET_DYN:共享库(Shared object file),可以动态链接到其他可执行文件或库中。

ET_CORE:核心转储文件(Core dump file),记录了程序崩溃时的内存快照。

1.2 ELF文件格式

1.ELF Header(文件头):包含文件的基本信息,如文件类型、目标架构、入口地址、程序头和节头表的偏移量等;

typedef struct{
    unisgned char       e_ident[EI_NIDENT]; /* 魔数和其他信息 */
    Elf64_Half          e_type;             /* 目标文件类型*/
    Elf64_Half          e_machine;          /* 目标文件所属的体系结构 */
    Elf64_Half          e_version;          /* 目标文件版本 */
    ELf64_Half          e_entry;            /* 程序入口虚拟地址 */
    Elf64_Half          e_phoff;            /* 程序头表在文件内的字节偏移量 */
    Elf64_Half          e_shoff;            /* 节头表在文件内的字节偏移量 */
    Elf64_Half          e_flags;            /* 与处理器相关的标志 */
    Elf64_Half          e_ehsize;           /* 指明elf头的字节大小 */
    Elf64_Half          e_phetsize;         /* 程序头表中每个条目的字节大小*/
    Elf64_Half          e_phunm;            /* 程序头表中条目的数量(段的数量) */
    Elf64_Half          e_shentsize;        /* 节头表中每个条目的字节大小 */
    Elf64_Half          e_shnum;            /* 节头表中条目的数量(节的个数)*/
    Elf64_Half          e_shstrndx;         /* 指明字符串表在节头表的索引 */
} Elf64_Ehdr;

2.Program Header Table(程序头表):描述了如何将文件中的各个段加载到内存中。每个条目(Program Header)对应一个段(Segment),定义了该段的类型、文件中的偏移、加载地址、大小等信息;

3.Section Header Table(节头表):描述了文件中的各个节(Section),如代码段、数据段、符号表等。每个条目(Section Header)包含该节的名称、类型、大小、在文件中的位置等信息;

4.Sections(节):文件中的实际数据部分,如代码、数据、符号表、调试信息等。常见的节包括:

.text:代码段,包含可执行的机器代码。

.data:数据段,包含初始化的全局变量。

.bss:未初始化数据段,包含未初始化的全局变量。

.rodata:只读数据段,包含只读的全局数据,如字符串常量。

.symtab.strtab:符号表和字符串表,用于调试和链接。

通过readelf -h file.elf可以查看elf文件的一些具体内容

1.3链接地址

 

在程序编译的时候,每个目标文件都是由源代码编译得到,最终多个目标文件链接生成一个最终的可执行文件,而链接地址就是指示链接器,各个目标文件的在可执行程序中的位置。比如,一个可执行程序a.out由a.o、b.o、c.o组成,那么最终的a.out中谁在前、谁在中间、谁在结尾,都可以通过制定链接地址来决定。 链接地址是静态的,程序在进行编译时指定;链接地址是给编译器使用的,用来计算代码中相关地址偏移;用于在编译过程中解决符号引用问题。

链接地址的作用包括:

1.符号解析:在编译和链接过程中,编译器和链接器将每个符号(如变量、函数名等)解析为实际的内存地址。这些地址在链接阶段被分配,确保每个符号在程序运行时都能正确引用。

2.生成可执行文件:链接器使用链接地址来生成可执行文件。在这个过程中,它将多个目标文件(object files)和库文件组合成一个完整的可执行文件,并确定每个部分在内存中的位置。

3.内存管理:链接地址还用于内存管理,尤其是在嵌入式系统中。开发者可以通过设置特定的链接地址来指定代码、数据、堆栈等在内存中的布局,以优化内存使用和提高系统性能。

2.Bootloader程序切换

2.1 Bootloader介绍

1.BootLoader就是单片机启动时候运行的一段小程序,这段程序负责单片机固件的更新,也就是单片机选择性的自己给自己下程序。可以更新,也可以不更新,更新的话,BootLoader更新完程序后,跳转到新程序运行;不更新的话,BootLoader直接跳转到原来的程序去运行。

2.BootLoader更新完程序后并不擦除自己,下次启动后依然先运行BootLoader程序,又可以选择性的更新或者不更新程序,所以BootLoader就是用来管理单片机程序的更新。

3.在实际的单片机工程项目中,如果加入了BootLoader功能,就可以给单片机日后升级程序留出一个接口,方便日后单片机程序更新。当然,这就需要创建两个工程项目,一个为BootLoader工程,一个为APP工程。

2.2 代码实现

#include <stdio.h>
#include "spi_flash.h"
#include "elf.h"
#include "dump.h"
#include "littlefs_port.h"
#include "lfs.h"
/* LittleFS partition start address and size */
#define LFS_PART_OFSET		0x500000
#define LFS_PART_SIZE		0x500000

#define DEF_APPS_DIR		"/"

#define PAGE_SIZE			256
#define MAX_PROGRAMS 		4
#define RXBUF_SIZE  		1

extern lfs_t 					lfs;
extern lfs_file_t 				file;
extern uint8_t            	 	g_xymodem_rxbuf[RXBUF_SIZE];
extern struct ring_buffer  		g_xymodem_rb;

typedef struct
{
    char name[32];
    char file[64];
} Program;

Program programs[MAX_PROGRAMS] = {
    {"program1.elf", "classa.elf"},
    {"cprogram2.elf", "classb.elf"},
    {"program3.elf", "classc.elf"},
    {"Default Program", "SPI_Project.elf"}
};

int get_user_choice();

int parser_bootfile(char *elf, int size);

int boot_elf(char *elf);

int do_load_elf()
{
	int				rv;
	char            elf_fname[32];
	int				choice = -1;

	const struct lfs_config cfg = {
		.read 				= lfs_read,
		.prog 				= lfs_write,
		.erase 				= lfs_SectorErase,
		.sync				= lfs_sync,

		.read_size 			= 256,
		.prog_size 			= 256,
		.block_size 		= 65536,
		.block_count 		= 64,
		.block_cycles 		= 100,
		.cache_size 		= 256,

	};

	printf ("\r\nAU15P Board SPI Flash Bootloader v1.0 Build on %s\r\n", __DATE__);

	/* Displays the menu. */
	choice = get_user_choice();
	switch (choice) {
	        case 1:
	            strncpy(elf_fname, "classa.elf", sizeof(elf_fname));
	            break;
	        case 2:
	            strncpy(elf_fname, "classb.elf", sizeof(elf_fname));
	            break;
	        case 3:
	            strncpy(elf_fname, "classc.elf", sizeof(elf_fname));
	            break;
	        case 4:
	        default:
	            strncpy(elf_fname, "SPI_Project.elf", sizeof(elf_fname));
	            printf("Invalid choice. Loading default program.\n");
	            break;
	    }

	/* Sets the selected program file name. */

/*
	rv = parser_bootfile(elf_fname, sizeof(elf_fname));
	if( rv < 0 )
	{
		printf("ERROR: Parser boot configure file failure,the rv's value is %d\r\n", rv);
		goto cleanup;
	}
*/
BOOT_ELF:
	printf("Boot application %s\r\n", elf_fname);
	boot_elf(elf_fname);

cleanup:
	lfs_unmount(&lfs);



	return 0;
}

int parser_bootfile( char *elf, int size)
{
	lfs_file_t 		file;
	char            buf[64];
	char           *ptr;
	int             i, rv = 0;

	memset(buf, 0, sizeof(buf));
	if( lfs_file_read(&lfs, &file, buf, sizeof(buf)) <= 0 )
	{
		rv = -3;
		goto cleanup;
	}

	/* parser boot application */
	ptr = strstr(buf, "BOOT=");
	if( !ptr )
	{
		rv = -4;
		goto cleanup;
	}
	ptr += strlen("BOOT=");

	/* get rid the end blank char \r\n */
	for(i=0; i<size; i++)
	{
		if( isblank(ptr[i]) )
			break;

		elf[i] = ptr[i];
	}

cleanup:
	lfs_file_close(&lfs, &file);
	return rv;
}

int valid_elf_image(void *addr);

void load_elf_image(Elf32_Ehdr *ehdr);

int boot_elf(char *elf)
{

	Elf32_Ehdr	    ehdr; /* Elf header structure pointer */
	int				rv = 0;
	char            fpath[64];

	/* ELF images is in apps/ folder */
	strcpy(fpath, DEF_APPS_DIR);
	strncat(fpath, elf, sizeof(fpath)-strlen(DEF_APPS_DIR));
	printf("Attempting to open file: %s\n", fpath);

	/* open and read boot configure file */
	if( lfs_file_open(&lfs, &file, fpath, LFS_O_RDONLY) < 0)
	{
		printf("ERROR: Open ELF image %s failure\r\n", elf);
		lfs_unmount(&lfs);
		return -2;
	}

	/* Read the ELF image header littlefs rootfs */
	if( lfs_file_read(&lfs, &file, &ehdr, sizeof(ehdr)) <= 0 )
	{
		rv = -3;
		goto cleanup;
	}

    /* check it's valid ELF image or not */
    if ( !valid_elf_image(&ehdr) )
    {
		printf("## %s is not a 32-bit ELF image\r\n", elf);
		goto cleanup;
    }

    /* Load ELF application from SPI Flash to DDR */
    load_elf_image(&ehdr);

    /* Jump to run ELF application */
    printf("the program address 0x%02X\r\n", ehdr.e_entry);
	((void (*)(void))(ehdr.e_entry))();
    return 0;

    /* should never come here */
cleanup:
	lfs_file_close(&lfs, &file);
	return rv;
}

int valid_elf_image(void *addr)
{
    Elf32_Ehdr *ehdr; /* Elf header structure pointer */

    ehdr = (Elf32_Ehdr *)addr;

    if (!IS_ELF(*ehdr) || ehdr->e_type != ET_EXEC)
    {
        return 0;
    }

    return 1;
}

void load_elf_image( Elf32_Ehdr *ehdr)
{
	uint8_t			buf[PAGE_SIZE];
	Elf32_Phdr 	   *phdr; /* Program header structure pointer */
	int				ofset;
	int				i;

	/* Load each program header */
	for(i=0; i<ehdr->e_phnum; ++i)
	{
		ofset = ehdr->e_phoff+i*sizeof(*phdr);
		lfs_file_seek(&lfs, &file , ofset, SEEK_SET);
		lfs_file_read(&lfs, &file, buf, sizeof(*phdr));

		phdr = (Elf32_Phdr *)buf;
		if( phdr->p_type == PT_LOAD )
		{
			//xil_printf("Loading phdr[%d] %d bytes to RAM addr@0x%x\r\n", i, phdr->p_memsz, phdr->p_paddr);

			lfs_file_seek(&lfs, &file , phdr->p_offset, SEEK_SET);
			lfs_file_read(&lfs, &file, (uint8_t *)phdr->p_paddr, phdr->p_memsz);
			printf(".");
		}
	}
	printf("\r\n");
}

int get_user_choice()
{
    int 	choice = -1;
    char 	input[10];
    printf("Please select a program to boot:\n");
    printf("--------------------------------\n");
    for (int i = 0; i < MAX_PROGRAMS; i++)
    {
        printf("%d. %s\n", i + 1, programs[i].name);
    }
    //memset(input, 0, sizeof(input));
    printf("--------------------------------\n");
    printf("Enter the number of the program to load (1-%d): ", MAX_PROGRAMS);
    printf("g_xymodem_rxbuf:%d\r\n", g_xymodem_rxbuf[0]);
    input[0] = g_xymodem_rxbuf[0];
    input[1] = '\0';
    if (sscanf(input, "%d", &choice) == 1)
    {
    	printf("choice:%d\r\n", choice);
        if (choice < 1 || choice > MAX_PROGRAMS)
        {
        	printf("Invalid choice. Loading default program.\n");
            choice = MAX_PROGRAMS;
        }
        }
    else
        {
            printf("Invalid input. Loading default program.\n");
            choice = MAX_PROGRAMS;
        }

        return choice - 1;
    }

void print_bootloader_header() {
    printf("\n");
    printf("*********************************************\n");
    printf("*                                           *\n");
    printf("*       ISK Board SPI Flash Bootloader      *\n");
    printf("*           Version 1.0                     *\n");
    printf("*           Build Date: %s                  *\n", __DATE__);
    printf("*                                           *\n");
    printf("*********************************************\n");
    printf("\n");
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1973079.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

秒懂Linux之编写小程序——进度条

目录 一.前文摘要 二.进度条编写 三全部代码&#xff08;非Linux环境下测试&#xff09; 一.前文摘要 在开始编写之前我们先来学习一些后面会用到的知识点~ 测试结果发现&#xff0c;Sleep无作用&#xff0c;编译完会立刻打印~ 再来看另一个测试~我们同样没有换行&#xff0c…

前后端中的日期格式转换问题

从前端接收到的日期转为想要的格式 JSON日期的反序列化为java对象时 JsonFormat(timezone “GMT8”, pattern “yyyy-MM-dd HH:mm:ss”) 从后端发送的日期转为想要的格式给前端 Java对象中的日期的序列化为JSON时会用到 Date数据序列化为JSON发往前端时&#xff0c;按以下格…

【机器学习西瓜书学习笔记——支持向量机】

机器学习西瓜书学习笔记【第六章】 第六章 支持向量机6.1 间隔与支持向量硬间隔最大化 6.2 对偶问题6.3核函数定义构建核函数应用优劣优势劣势 6.4 软间隔与正则化软间隔正则化 6.5支持向量回归&#xff08;SVR&#xff09;函数间隔和几何间隔SVR的原理SVR数学模型线性硬间隔SV…

The dependencies of some of the beans in the application context form a cycle

你们好&#xff0c;我是金金金。 场景 启动服务时&#xff0c;报错&#xff1a;应用程序上下文中的某些bean的依赖关系形成了一个循环 循环依赖 依赖循环指的是两个或多个类之间相互依赖的情况&#xff0c;即类A依赖类B&#xff0c;同时类B也依赖类A。 这种情况会导致编译器无…

java接口只能定义抽象方法吗?

写在前面 在Java中接口时作为规范来存在的&#xff0c;那么除了抽象方法&#xff0c;接口中还能定义其他方法吗?比如静态方法&#xff1f;本文一起来看下。 1&#xff1a;正文 这并不是一个绝对的是和否的问题&#xff0c;不同的jdk版本表现不同&#xff0c;在<1.7的版本…

用例管理框架

用例管理框架之pytest单元测试框架&#xff08;上&#xff09; 一、pytest用例管理框架&#xff08;单元测试框架&#xff09; 1.分类&#xff1a; python&#xff1a;unittest&#xff0c;pytest 必须非常熟练 2.主要作用&#xff1a; 发现测试用例&#xff1a;从多个py文…

深入理解 C 语言中的联合体

目录 引言 一、 联合体的定义与基本用法 1.联合体的定义 2.基本用法 二、 联合体与结构体的区别 1.结构体 2.联合体 3.对比 ​编辑三、联合体的优势 1. 节省内存 2. 提高效率 3. 代码简洁性 四、联合体的存储细节 1.内存对齐 2.大小计算 五、联合体的高级用法…

Windows内核态开发笔记

文章目录 r3/r0通信x64 HOOK回调监控进程强杀minifilterObRegisterCallbacksWFP后记 r3/r0通信 用户态 #include <Windows.h> #include <stdio.h>#define SENDSTR CTL_CODE(FILE_DEVICE_UNKNOWN, 0x800, METHOD_BUFFERED, FILE_ANY_ACCESS) void main() {HANDLE …

Reader

FileInputStream和FileOutputStream其实还叫字节输入流和字节输出流。关于输入和输出这2个总是有点模糊。 以内存为基准&#xff0c;InputStream是文件流向内存&#xff0c;就是从文件中读取数据&#xff0c;又称为输入流。 OutputStream是从内存中流向文件&#xff0c;就是向…

使用s3cmd 2.x 与 Cyberduck 管理在 DigitalOcean Spaces 对象存储中的数据

有很多用户在使用 DigitalOcean Spaces 对象存储的过程中&#xff0c;还没有找到一个合适的数据管理工具。其实目前有很多开源工具都可以使用&#xff0c;比如 s3cmd、Cyberduck、rclone、transmit5。Cyberduck 提供了直观的图形用户界面&#xff0c;而 s3cmd 2.x 则擅长于批处…

怎么搭建AI带货直播间生成虚拟主播?

随着电商直播带货的热潮不断升温&#xff0c;虚拟主播逐渐崭露头角&#xff0c;成为电商直播领域的新宠&#xff0c;相较于真人主播&#xff0c;虚拟主播具备无档期风险、人设稳定可控、24小时不间断直播等显著优势。 本文将深入探讨如何搭建一个AI带货直播间&#xff0c;并详…

最新小猫咪PHP加密系统源码V1.4_本地API接口_带后台

简介&#xff1a; 最新小猫咪PHP加密系统源码V1.4_完全本地化加密API接口_带后台 小猫咪PHP加密系统历时半年&#xff0c;它再一次迎来更新&#xff0c;更新加密算法&#xff08;这应该是最后一次更新加密算法了&#xff0c;以后主要更新都在框架功能上面了&#xff09;&…

在WordPress上启用reCAPTCHA的指南

随着网络安全问题的日益严重&#xff0c;网站管理员必须采取措施保护自己的网站免受恶意攻击。对于WordPress用户来说&#xff0c;可以通过启用谷歌的reCAPTCHA功能来增强网站的安全性。本文将介绍两种在WordPress上启用reCAPTCHA的方法&#xff1a;使用插件和手动添加代码。 一…

Git基础:使用指南

Git是一个开源的分布式版本控制系统&#xff0c;可以有效、高速地处理从很小到非常大的项目版本管理。分布式相比于集中式的最大区别在于开发者可以提交到本地&#xff0c;每个开发者通过克隆&#xff0c;在本地机器上拷贝一个完整的Git仓库。 一、版本管理 1.1 创建版本库 版…

大模型的架构参数是指定义模型基本结构和组成的各种参数,这些参数对模型的性能、训练效率和泛化能力具有重要影响。以下是对大模型架构参数的详细介绍

大模型架构参数 大模型的架构参数是指定义模型基本结构和组成的各种参数&#xff0c;这些参数对模型的性能、训练效率和泛化能力具有重要影响。以下是对大模型架构参数的详细介绍&#xff1a; 一、基本结构和组成 层数&#xff1a;模型的层数是指模型中全连接网络或特定结构…

vue3直播视频流easy-player

vue3直播视频流easy-player <script src"/easyPlayer/EasyPlayer-element.min.js"></script> easyPlayer文件下载地址 https://download.csdn.net/download/weixin_42120669/89605739 <template><div class"container"><div …

Vue进阶之Vue无代码可视化项目(九)

Vue无代码可视化项目—补充内容 背景介绍、方案设计Canvas Table创建一个新的vue项目普通表格的效果Canvas上手Canvas画表格-画基本表格CanvasTable处理事件系统CanvasTable表格滚动Vue组件封装思想拖拽组件 —smooth-dndDndDemo1.vueDndContainer.jsCanvasTable封装CanvasTabl…

LDR6020 iPad皮套一体式键盘充电方案解析

在移动办公与学习的浪潮中&#xff0c;iPad凭借其强大的性能与便携性&#xff0c;成为了越来越多人的首选设备。然而&#xff0c;随着工作与学习任务的日益复杂&#xff0c;单一的触控操作已难以满足高效、精准的需求。因此&#xff0c;搭配一款优秀的键盘成为了提升iPad使用体…

月木学途开发 3.1搭建CentOS虚拟机

安装CentOS 下载地址 &#xff1a;https://mirrors.aliyun.com/centos-vault/?spma2c6h.13651104.0.0.5f6612b2O7Cy9G 选择7.6.1810——isos——x86_64——CentOS-7-x86_64-DVD-1810.iso 安装 VMWare虚拟机 下载 下载地址&#xff1a;https://www.vmware.com/products/desktop…

分享c语言中一些实用的函数2

目录 一.头文件 1.sqrt()函数 2.sin&#xff0c;cos&#xff0c;tan函数 附加:宏定义π 3.exp函数 4.fabs函数 5.fmax函数 6.floor函数 7.log函数 附加&#xff1a;求一个数是几为数(运用floor函数和log函数) 8.pow函数 二.头文件 1.abs函数 附加: 一.头文件<…