基于单片机的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");
}
相关推荐
大聪明-PLUS4 小时前
关于新的 Linux 内核接口 gpio uapi 的说明
linux·嵌入式·arm·smarc
清风6666664 小时前
基于单片机的螺旋藻生长大棚PH智能控制设计
单片机·嵌入式硬件·毕业设计·课程设计
ting_zh5 小时前
微控制器(Micro Controller Unit, MCU)基础整理
单片机·嵌入式硬件
清风6666666 小时前
基于单片机的图书馆智能座位管理平台
数据库·单片机·嵌入式硬件·毕业设计·课程设计
得单片机的运7 小时前
STM32的以太网的搭建
stm32·单片机·嵌入式硬件·物联网·以太网·iot·w5500
酷飞飞8 小时前
RTC和看门狗基于GD32F407VE的天空星的配置
stm32·单片机·嵌入式硬件·mcu
WD137298015579 小时前
WD5030A,24V降5V,15A 大电流,应用于手机、平板、笔记本充电器
stm32·单片机·嵌入式硬件·智能手机·汽车·电脑·51单片机
日更嵌入式的打工仔10 小时前
GPIO 中断通用配置指南
stm32·单片机·嵌入式硬件
平凡灵感码头10 小时前
基于 STM32 的智能门锁系统,系统界面设计
stm32·单片机·嵌入式硬件
Truffle7电子11 小时前
STM32理论 —— 存储、中断
stm32·嵌入式硬件·嵌入式·存储·中断