目录
[1.1 ELF文件分类](#1.1 ELF文件分类)
[1.2 ELF文件格式](#1.2 ELF文件格式)
[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");
}