基于单片机的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");
}
相关推荐
FreakStudio2 小时前
全网最适合入门的面向对象编程教程:50 Python函数方法与接口-接口和抽象基类
python·嵌入式·面向对象·电子diy
辰哥单片机设计6 小时前
门磁模块详解(防盗感应开关 STM32)
stm32·单片机·嵌入式硬件·传感器
weixin_632077636 小时前
udp聊天室
udp·网络编程·嵌入式·编程·聊天室
yrx0203077 小时前
stm32 IIC总线busy解决方法
stm32·单片机·嵌入式硬件
YHPsophie8 小时前
ATGM331C-5T杭州中科微BDS/GNSS全星座定位授时模块应用领域
经验分享·笔记·单片机·信息与通信·交通物流
Archie_IT9 小时前
【STM32系统】基于STM32设计的SD卡数据读取与上位机显示系统(SDIO接口驱动、雷龙SD卡)——文末资料下载
arm开发·stm32·单片机·嵌入式硬件
辰哥单片机设计9 小时前
1×4矩阵键盘详解(STM32)
stm32·单片机·嵌入式硬件·矩阵·传感器
wmkswd9 小时前
CAN总线-STM32上CAN外设
stm32·单片机·嵌入式硬件
Ruohongxu10 小时前
LAN8720A-CP-TR-ABC QFN-24 以太网收发器芯片
单片机