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

目录

1.ELF可执行文件加载

[1.1 ELF文件分类](#1.1 ELF文件分类)

[1.2 ELF文件格式](#1.2 ELF文件格式)

1.3链接地址

​编辑

2.Bootloader程序切换

[2.1 Bootloader介绍](#2.1 Bootloader介绍)

[2.2 代码实现](#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");
}
相关推荐
森焱森1 小时前
无人机三轴稳定控制(2)____根据目标俯仰角,实现俯仰稳定化控制,计算出升降舵输出
c语言·单片机·算法·架构·无人机
白鱼不小白1 小时前
stm32 USART串口协议与外设(程序)——江协教程踩坑经验分享
stm32·单片机·嵌入式硬件
S,D2 小时前
MCU引脚的漏电流、灌电流、拉电流区别是什么
驱动开发·stm32·单片机·嵌入式硬件·mcu·物联网·硬件工程
芯岭技术5 小时前
PY32F002A单片机 低成本控制器解决方案,提供多种封装
单片机·嵌入式硬件
youmdt5 小时前
Arduino IDE ESP8266连接0.96寸SSD1306 IIC单色屏显示北京时间
单片机·嵌入式硬件
嘿·嘘6 小时前
第七章 STM32内部FLASH读写
stm32·单片机·嵌入式硬件
Meraki.Zhang6 小时前
【STM32实践篇】:I2C驱动编写
stm32·单片机·iic·驱动·i2c
几个几个n8 小时前
STM32-第二节-GPIO输入(按键,传感器)
单片机·嵌入式硬件
linweidong10 小时前
物联网MQTT协议与实践:从零到精通的硬核指南
物联网·mqtt·websocket·嵌入式·iot·tdengine·工业物联网
慕尘12 小时前
Clion配置51单片机开发环境
单片机