lef文件的深入研究

2023-11-09 16:20
文章标签 深入研究 lef

本文主要是介绍lef文件的深入研究,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

    先来解释一下名词,ELF的英文全称是Executable and Linkable Format。可执行和可链接的文件。

    和elf文件对应的是bin文件,bin文件是直接加载到内存中执行的文件,用uboot直接把bin文件拷贝到bin文件的运行地址,(注意,一定要拷贝到运行地址)这时使用go命令就能够执行刚才拷贝的bin文件。

elf文件需要用加载器进行加载,由于elf文件已经包含了程序的加载地址,因此可以把elf文件复制到内存中的非bin文件加载地址。(这里所说的bin文件是加载器解析elf完成以后的bin文件内容)。

了解elf文件的结构可以使用readelf命令 ,先用readelf -h 来看一下elf头信息。

jiefang@jiefang-virtual-machine:/home/yhl/worktest$ readelf -h a.out  
ELF Header:Magic:   7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00 Class:                             ELF64Data:                              2's complement, little endianVersion:                           1 (current)OS/ABI:                            UNIX - System VABI Version:                       0Type:                              EXEC (Executable file)Machine:                           Advanced Micro Devices X86-64Version:                           0x1Entry point address:               0x400670Start of program headers:          64 (bytes into file)Start of section headers:          7032 (bytes into file)Flags:                             0x0Size of this header:               64 (bytes)Size of program headers:           56 (bytes)Number of program headers:         9Size of section headers:           64 (bytes)Number of section headers:         31Section header string table index: 28
jiefang@jiefang-virtual-machine:/home/yhl/worktest$ 

可以看到elf文件包含的头信息,这里注意一下几点信息,

第一是幻数的第2,3,4个,他们是'E' ,'L','F'三个字母,在解析的时候可以通过他们来判断文件的类型是否正确。

第二个是Entry point address,这个就是解析出来的bin文件需要存放的地址。

接下来我们通过C代码自己来解析一下elf文件的头信息,在/usr/include/目录下找到elf.h文件。

typedef struct
{unsigned char e_ident[EI_NIDENT];     /* Magic number and other info */Elf32_Half    e_type;                 /* Object file type */Elf32_Half    e_machine;              /* Architecture */Elf32_Word    e_version;              /* Object file version */Elf32_Addr    e_entry;                /* Entry point virtual address */Elf32_Off     e_phoff;                /* Program header table file offset */Elf32_Off     e_shoff;                /* Section header table file offset */Elf32_Word    e_flags;                /* Processor-specific flags */Elf32_Half    e_ehsize;               /* ELF header size in bytes */Elf32_Half    e_phentsize;            /* Program header table entry size */Elf32_Half    e_phnum;                /* Program header table entry count */Elf32_Half    e_shentsize;            /* Section header table entry size */Elf32_Half    e_shnum;                /* Section header table entry count */Elf32_Half    e_shstrndx;             /* Section header string table index */
} Elf32_Ehdr;

可以找到这个结构,注意,在结构体的上方有这么一句话,

/* The ELF file header.  This appears at the start of every ELF file.  */

这说明,我们可以把elf文件定位到0位置,然后用该结构体去对齐就好。下面是部分实现代码:

        Elf32_Ehdr *elf_head;   //elf 头文件  大小为52个字节Elf32_Phdr *prg_head;   //程序头int fd = open("./bootrom",O_RDWR);if(fd<0){printf("open file error\n");}//开始解析elf头elf_head = (Elf32_Ehdr *)malloc(sizeof(Elf32_Ehdr));read(fd,elf_head,(sizeof(Elf32_Ehdr)));if(elf_head->e_ident[0]==0x7f){printf("this is a elf file\n");}else{printf("this isn't a elf file\n");goto elf_head_err;}printf("p_shnum = %d\n",elf_head->e_phnum);printf("p_shoff = %d\n",elf_head->e_phoff);printf("e_phentsize = %d\n",elf_head->e_phentsize);

这里只解析了程序头的一些信息,为下面elf转bin文件做好铺垫。

这里先来看一张图,来说明elf文件的结构:

 

链接视图是以节(section)为单位,执行视图是以段(segment)为单位。链接视图就是在链接时用到的视图,而执行视图则是在执行时用到的视图。上图左侧的视角是从链接来看的,右侧的视角是执行来看的。

我们的目的是C语言实现elf文件的加载,因此这里不关注左侧的。首先找到程序头的开始位置,在efl文件头中已经有,我们只需要在头文件中找到程序头对应的结构体,然后对应一下就可以了。

typedef struct
{Elf32_Word    p_type;                 /* Segment type */Elf32_Off     p_offset;               /* Segment file offset */Elf32_Addr    p_vaddr;                /* Segment virtual address */Elf32_Addr    p_paddr;                /* Segment physical address */Elf32_Word    p_filesz;               /* Segment size in file */Elf32_Word    p_memsz;                /* Segment size in memory */Elf32_Word    p_flags;                /* Segment flags */Elf32_Word    p_align;                /* Segment alignment */
} Elf32_Phdr;

 下面的实现代码来源于uboot源代码中的do_bootvx()函数:

/** A very simple ELF loader, assumes the image is valid, returns the* entry point address.** The loader firstly reads the EFI class to see if it's a 64-bit image.* If yes, call the ELF64 loader. Otherwise continue with the ELF32 loader.*/
static unsigned long load_elf_image_phdr(unsigned long addr)
{Elf32_Ehdr *ehdr; /* Elf header structure pointer */Elf32_Phdr *phdr; /* Program header structure pointer */int i;ehdr = (Elf32_Ehdr *)addr;if (ehdr->e_ident[EI_CLASS] == ELFCLASS64)return load_elf64_image_phdr(addr);phdr = (Elf32_Phdr *)(addr + ehdr->e_phoff);/* Load each program header */for (i = 0; i < ehdr->e_phnum; ++i) {void *dst = (void *)(uintptr_t)phdr->p_paddr;void *src = (void *)addr + phdr->p_offset;debug("Loading phdr %i to 0x%p (%i bytes)\n",i, dst, phdr->p_filesz);if (phdr->p_filesz)memcpy(dst, src, phdr->p_filesz);if (phdr->p_filesz != phdr->p_memsz)memset(dst + phdr->p_filesz, 0x00,phdr->p_memsz - phdr->p_filesz);flush_cache((unsigned long)dst, phdr->p_filesz);++phdr;}return ehdr->e_entry;
}

在for循环中,需要循环e_phnum次,这个参数只elf头中说明段的个数的结构成员。也就是说需要把每个段从自己的偏移地址拷贝到物理地址中去。两个if是在判断段中的内容,具体现在还说不清,以后补充,留下一个?。

以上内容就实现了一个elf文件加载器,但是只能在物理内存上面运行,虚拟内存不能给固定的地址写数据,以后在探索一下。

暂时留下两个问题

1、段中数据内容,.text .data .bbs 是如何在内存中分布的。链接脚本??

2、虚拟内存怎么给固定的地址上些数据??类似于裸机程序 int addr = 0x12345678; *(int *)addr=123;

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

这篇关于lef文件的深入研究的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/377214

相关文章

深入研究java.lang.Runtime类

http://lavasoft.blog.51cto.com/62575/15565/

绝大多数程序员没有这么深入研究过System.out.println()!

点击上方“朱小厮的博客”,选择“设为星标” 回复”1024“获取独家整理的学习资料 来源:http://1t.click/k87 什么是System.out.println() System.out.println是一个Java语句,一般情况下是将传递的参数,打印到控制台。 System:是 java.lang包中的一个final类。根据javadoc,“java.lang.System该

C语言信号编程深入研究

信号是操作系统向进程发送的一种软件中断,用于通知进程发生了某种特殊情况或异常事件。本篇文章将详细介绍如何使用C语言处理信号,包括基本的信号处理、自定义信号处理函数以及一些高级主题。 1. 引言 信号处理是操作系统与进程之间的一种通信机制。在C语言中,信号处理通常涉及捕获特定信号并在程序中执行相应的处理动作。本指南旨在提供一个全面的框架,帮助读者深入了解信号处理的基本原理和实践技巧。 2.

OC类的深入研究

1、类在创建对象之前也需要加载进空间,也需要占据内存。 2、类只会被加载一次。类也是一个对象,但它的类型是Class类型,就是说类本身也是一个对象,是个Class类型的对象,简称类对象。例如新建一个Person类的对象Person *p=[[Person alloc] init];那么p就是Person类型,而Person是个Class类型的类对象。但平时所说的类就是类对象。 3、由以上得知

深入研究websocket直播中signature这个参数怎么来的,模拟自己生成一个

上一节课我们已经找到了生成signature这个字段的代码位置,就是这个B函数,嗯......听起来好像有点奇怪,但是它确实叫B啊,笑死。不管了,看一下里面的逻辑是啥。 注意e参数的内容是: {"app_name": "douyin_web","version_code": "180800","webcast_sdk_version": "1.0.14-beta.0","update_ve

Spring深入研究文章搜集

感觉这几篇文章写的真好: https://www.cnblogs.com/hello-shf/p/10992377.html https://www.cnblogs.com/hello-shf/p/11006750.html   通过对Spring的剖析,解说的很到位。学习到了

【智能制造1005】智能制造试点企业名单及工具变量数据,助力深入研究!

今天给大家分享的是国内顶级期刊金融研究2022年发表的论文《智能制造赋能企业创新了吗?——基于中国智能制造试点项目的准自然实验》使用到的重要数据集——智能制造试点企业名单以及该政策对应的工具变量数据。该论文以中国智能制造示范项目的推广为准自然实验,研究智能制造对企业创新行为的影响效应及其机制,研究发现:智能制造的推广显著提高了企业创新水平,并且主要是通过信息渠道、人力资本渠道以及资金渠道三条路径提

android系统深入研究

android启动流程分析 android应用启动流程分析

【Docker学习】docker start深入研究

docker start也是很简单的命令。但因为有了几个选项,又变得复杂,而且... 命令: docker container start 描述: 启动一个或多个已停止的容器。 用法: docker container start [OPTIONS] CONTAINER [CONTAINER...] 别名: docker start(docker的一

labview中循环停止事件的深入研究

1.错误用法 第一次值事件运行的时候空白按钮给的F值,第二次值事件运行的时候空白按钮给的T值,这时循环才真正结束。 2.正确用法之一 赋值和值改变事件从同时进行变成按顺序执行。 3.正确用法之二 值事件发生以后超时事件将T值赋值给结束条件,也是从同时进行变成了顺序执行。