STM32 实现类似手机运行APP的方法(篇二:ELF文件解析和APP调用)

一、调用APP遇到的问题

在实验中,系统可以直接运行Keil编译出来的bin文件,并且可以正常运行如下代码:

include "main_inc.h"

const char g_txt[]="g_txt from app\r\n"
int my_main(void)
{
	printf(g_txt);
	return;
}

在我重定向printf函数到串口之后,在电脑端的串口助手正常收到了字符串。

但是运行如下代码时却不能打印正确的字符串

include "main_inc.h"

char g_txt[]="g_txt from app\r\n"
int my_main(void)
{
	printf(g_txt);
	return;
}

只是因为去掉了const关键字,打印就不正常了

二、分析原因

程序编译出来般有3个数据段:只读RO、读写RW、零段ZI
在正常情况下,单片机从复位开始会在调用main函数之前调用一次__main函数,这个函数是由编译器生成的,用于把RW段的值从FLASH中搬运到内存中,然后进入main函数之后才能正确使用不加const关键字的全局变量,而我们的APP显然没调用__main函数,这就需要系统在调用APP的时候做这个事情(初始化运行环境)。
那么为什么不在APP中也调用__main函数呢?我试过了,但是出了很多问题以放弃告终,读者有兴趣可以尝试,如果成功了,别忘了告诉我,(●ˇ∀ˇ●)。

三、解决方法

听说linux中的可执行文件是一种叫做elf格式的文件,正好Keil编译出来的axf文件也是elf文件,只不过换了一个名,那这个文件是一定能够运行的。但是为什么不直接运行bin文件?bin文件是一段2进制代码,不区分RO\RW\ZI这些段,系统无法完成”初始化运行环境“这个操作,但是elf格式的文件附带了段信息,可以实现这些功能。

参考这篇文章,了解elf文件格式:

https://blog.csdn.net/muaxi8/article/details/79627859

创建 elf.c 文件编写以下代码:


#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <elf.h> 
#include "ftfile.h"
#include <errno.h>


#define FILE     ft_file_struct
#define fclose   ft_if_fclose
#define fopen    ft_if_fopen
#define fread    ft_if_fread
#define fseek    ft_if_fseek
#define ftell    ft_if_ftell

typedef struct
{
 FILE *file;
 int mem_offset;
 void *exe_addr;
 void *r9;
}app_struct;

/**elf 文件头和section表解析和检查***/
static int check_elf_head(FILE *file )
{
 FILE *file_elf = file;
 int i = 0;
 int MagNum = 0;
 unsigned long  file_size = 0;
 int sechnum = 0;
 uint32_t symsize = 0;
 uint32_t symoff = 0;
 uint32_t nSyms = 0,kk=0;
 Elf32_Ehdr *Elf_header = malloc(sizeof(Elf32_Ehdr));
 
 /*文件大小*/
 fseek(file_elf,0,SEEK_END);
 file_size = ftell(file_elf);
 fseek(file_elf,0,SEEK_SET);
 printf("file total size is:%ld bytes\n",file_size);
 fread(Elf_header,sizeof(Elf32_Ehdr),1,file_elf); 

 /**确认是否为elf格式**/
 if((Elf_header->e_ident[EI_MAG0] == ELFMAG0) && (Elf_header->e_ident[EI_MAG1] == ELFMAG1) \
  && (Elf_header->e_ident[EI_MAG2] == ELFMAG2) && (Elf_header->e_ident[EI_MAG3] == ELFMAG3))
 { 
  printf("\nThis is ELF file!\n");
 }
 else
 {
  printf("\n NOT ELF file!\n");
  free(Elf_header);
  return -1;
 }
 free(Elf_header);
 
 return 0;
}


/* 加载elf文件segment 到内存中 */
static int load_elf_2_mem(app_struct * app)
{
 
 if(app==0) return -1;
 
 Elf32_Phdr *phdr_head = NULL;
 Elf32_Ehdr *Elf_header = NULL;
 FILE *file1 = app->file;
 u32 rb;
 
 
 Elf_header = (Elf32_Ehdr *)malloc(sizeof(Elf32_Ehdr));
 memset(Elf_header,0,sizeof(Elf32_Ehdr));
 
 fseek(file1,0,SEEK_SET);
 rb = fread(Elf_header, sizeof(Elf32_Ehdr), 1, file1);
 
 /* 这里 Elf_header->e_phentsize 应该是== sizeof(Elf32_Phdr)*/
 phdr_head=malloc(Elf_header->e_phentsize *Elf_header->e_phnum);
 fseek(file1,Elf_header->e_phoff,SEEK_SET);
 fread(phdr_head,Elf_header->e_phentsize,Elf_header->e_phnum,file1);

 //Elf_header->e_phnum一般等于1
 for(int i=0;i< Elf_header->e_phnum; i++)
 {
  //是程序节
  //Keil编译的时候程序分为RO,RW,ZI,3个节,但是从运行时角度看来这3个节合并为一个段了
  if(phdr_head[i].p_type == PT_LOAD )
  { 
   //内存
   if(app->exe_addr==0) 
   {
    app->exe_addr=malloc(phdr_head[i].p_memsz);
    fseek( file1,phdr_head[i].p_offset,SEEK_SET);
    if((rb = fread(app->exe_addr,1,phdr_head[i].p_filesz,file1)) != phdr_head[i].p_filesz)
    {
     printf("\nfunction:%s,line:%d, read p_vaddr err!\n",__FUNCTION__,__LINE__);
     printf("\nread 返回值%d\n",rb);
     printf("read(file,ProHead->p_vaddr,ProHead->p_filesz) != ProHead->p_filesz %x  p_filesz %d\n",\
      phdr_head[i].p_vaddr,phdr_head[i].p_filesz);
    }
    
    /**多余的空间写0,做BSS段**/
    if((phdr_head[i].p_filesz) < (phdr_head[i].p_memsz))
    {
     memset((uint8_t*)app->exe_addr+phdr_head[i].p_filesz,
       0,
       phdr_head[i].p_memsz - phdr_head[i].p_filesz);
    }
   }
  }
 }
 //这里求得RW区在程序映像中的偏移
 Elf32_Shdr *sher_head=NULL;
 sher_head=malloc(Elf_header->e_shentsize *Elf_header->e_shnum);
 fseek(file1,Elf_header->e_shoff,SEEK_SET);
 fread(sher_head,Elf_header->e_shentsize,Elf_header->e_shnum,file1);
 for(int i=0;i<Elf_header->e_shnum;i++)
 {
  if((sher_head[i].sh_type==SHT_PROGBITS)&&(sher_head[i].sh_flags==(SHF_ALLOC|SHF_EXECINSTR)))
  {
   app->mem_offset=sher_head[i].sh_size;
   break;
  }
 }
 free(sher_head);
 free(Elf_header);
 free(phdr_head);
 return 0;
}

static void app_clear(app_struct *app)
{
 if(app==0) return;
 if(app->exe_addr) free(app->exe_addr);
}

//获取和保存r9寄存器
void *get_r9(void);
void set_r9(void *r9);

//调用这个函数即可运行sd卡中的app文件
int app_run(char *path)
{
 FILE *file_elf = NULL;
 file_elf = fopen(path,"rb");
 int ret=0;
 app_struct app={0};
 if(file_elf == NULL)
 {
  printf("open file err!!%s\n",strerror(errno));
  return -1;
 }
 if(check_elf_head(file_elf)==0)
 {
  app.file=file_elf;
  load_elf_2_mem(&app);  
  
  int (*fun)(void)=(int (*)(void))((int)app.exe_addr|1);
  app.r9=get_r9();
  set_r9((void *)((int)app.exe_addr+app.mem_offset));
  ret=fun();
  set_r9(app.r9);
  app_clear(&app);
 }
 
 fclose(file_elf);
 return ret;
}

其中:elf.h文件是在ubuntu中 /usr/include/elf.h复制出来的,为了编译通过做了一些修改,主要是要使用其中定义的结构体和宏;get_r9 和set_r9是两个汇编函数,如下:

__ASM void *get_r9(void)
{
 mov r0,r9
 bx lr
}

__ASM void set_r9(void *r9)
{
 mov r9 ,r0
 bx lr
}

因为全局变量的地址无关是使用R9寄存器的值加上偏移值来确定的,所以系统在调用APP的时候需要确定R9寄存器的基地址。
系统中只需要调用app_run,即可运行app文件。

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值