@devilogic
2016-08-12T06:21:04.000000Z
字数 31273
阅读 4853
devilogic
libelf是REDHAT的一个操作elf文件的开发库。我将安卓NDK4.4.2版本中的库扒出来了。直接make all即可编译。作为我另外一个项目的开发库。代码链接如下:
https://git.coding.net/devilogic/ydog.git
本来想把自己原来写的库来用,但是发现这个的实现比自己写的要好的多。主要好在哪里呢?有几下几点:
1. 考虑到了32/64位的问题,但是对外只需要操作一个elf结构即可。
2. 考虑到了大小端字序的转换。
3. 考虑到了ELF文件版本对其结构的印象,虽然好几十年了这个版本仍然是1,本人希望永远是1。
4. 以多种方式对elf文件进行操作,例如映射内存,直接打开文件,并且带有严格的权限控制。
5. 考虑到了多线程同时操作一个elf结构的问题。是线程安全的。
6. 内存地址页对齐。
7. 不仅支持elf文件,还支持静态库(.a)文件。
8. 当是自己创建一个elf结构时,
9. 考虑到一些自己没有对ELF文档读全的问题。。。-_-!!!
下面是摘自objdump.c中的一个使用实例。当objdump接收到要反汇编的程序文件后,就会调用此函数。
static intprocess_file (const char *fname, bool more_than_one) {/* 以只读权限打开,并且获取文件句柄 */int fd = open (fname, O_RDONLY);if (fd == -1) {error (0, errno, gettext ("cannot open %s"), fname);return 1;}/* 获取elf描述符,使用‘读取映射’的方式* 首先要获取这个结构,以下所有就是对这个结构进行操作*/Elf *elf = elf_begin (fd, ELF_C_READ_MMAP, NULL);if (elf != NULL) {/* 确定是文件类型是否是ELF文件 */if (elf_kind (elf) == ELF_K_ELF) {int result = handle_elf (elf, more_than_one ? "" : NULL,fname, NULL);if (elf_end (elf) != 0)INTERNAL_ERROR (fname);if (close (fd) != 0)error (EXIT_FAILURE, errno, gettext ("while close `%s'"), fname);return result;} else if (elf_kind (elf) == ELF_K_AR) { /* 目标是库文件 */int result = handle_ar (fd, elf, NULL, fname, NULL);if (elf_end (elf) != 0)INTERNAL_ERROR (fname);if (close (fd) != 0)error (EXIT_FAILURE, errno, gettext ("while close `%s'"), fname);return result;}/* 关闭elf结构句柄 */if (elf_end (elf) != 0)INTERNAL_ERROR (fname);}error (0, 0, gettext ("%s: File format not recognized"), fname);return 1;}
接下来看看在handle_elf中的处理。
static inthandle_elf (Elf *elf, /* elf结构指针 */const char *prefix, /* 如果为NULL表明接下来没有文件要处理 */const char *fname, /* 要处理的文件名 */const char *suffix /* 后缀 */) {/* 获取当前的ebl的指针* libebl也是elfunits中一个非常重要的组件库* 也是在我的分析范围之内,它也用到了libelf库* 这里先不用去理会它。*/Ebl *ebl = ebl_openbackend (elf);/* 打印当前的文件信息,使用gelf_getclass获取当前elf文件的位数 */printf ("%s: elf%d-%s\n\n",fname,gelf_getclass (elf) == ELFCLASS32 ? 32 : 64,ebl_backend_name (ebl));/* 创建这个文件完整的文件名称 *//* 忽悠没有的代码... *//* 获取节名称表节索引 */size_t shstrndx;if (elf_getshstrndx (ebl->elf, &shstrndx) < 0)error (EXIT_FAILURE, 0,gettext ("cannot get section header string table index"));int result = 0;if (print_disasm)result = show_disasm (ebl, fullname, shstrndx);if (print_relocs && !print_disasm)result = show_relocs (ebl, fullname, shstrndx);if (print_full_content)result = show_full_content (ebl, fullname, shstrndx);/* 关闭ebl句柄 */ebl_closebackend (ebl);return result;}
接下来看看show_relocs里的工作。
static intshow_relocs (Ebl *ebl, const char *fname, uint32_t shstrndx) {int elfclass = gelf_getclass (ebl->elf);/* 获取elf文件的位数 *//* Elf_Scn是libelf中保存节信息的结构 */Elf_Scn *scn = NULL;/* 遍历节 */while ((scn = elf_nextscn (ebl->elf, scn)) != NULL) {GElf_Shdr shdr_mem;/* 复制节头,libelf有个特点* 但凡带g开头的函数都是复制操作,不对本身映射进行直接操作*/GElf_Shdr *shdr = gelf_getshdr (scn, &shdr_mem);if (shdr == NULL)INTERNAL_ERROR (fname);/* 如果节类型是重定位节 */if (shdr->sh_type == SHT_REL || shdr->sh_type == SHT_RELA) {/* elf_ndxscn用于获取当前节的索引* 这里就是匹配是否是用户指定的节*/if (! section_match (ebl->elf, elf_ndxscn (scn), shdr, shstrndx))continue;GElf_Shdr destshdr_mem;/* 复制一个节头出来 */GElf_Shdr *destshdr =gelf_getshdr (elf_getscn (ebl->elf,shdr->sh_info),&destshdr_mem);/* elf_strptr可以获取当前字符串表的字符串 */printf (gettext ("RELOCATION RECORDS FOR [%s]:\n""%-*s TYPE VALUE\n"),elf_strptr (ebl->elf, shstrndx, destshdr->sh_name),elfclass == ELFCLASS32 ? 8 : 16, gettext ("OFFSET"));/* 获取节数据* elf_getdata是通过节结构获取* 节数据,libelf中将节数据保存到* 一个Elf_Data结构中*/Elf_Data *data = elf_getdata (scn, NULL);if (data == NULL)continue;/* 获取符号表信息 */Elf_Scn *symscn = elf_getscn (ebl->elf, shdr->sh_link);GElf_Shdr symshdr_mem;GElf_Shdr *symshdr = gelf_getshdr (symscn, &symshdr_mem);/* 获取符号表数据 */Elf_Data *symdata = elf_getdata (symscn, NULL);/* 搜索可选的扩展节索引表 */Elf_Data *xndxdata = NULL;Elf_Scn *xndxscn = NULL;while ((xndxscn = elf_nextscn (ebl->elf, xndxscn)) != NULL) {GElf_Shdr xndxshdr_mem;GElf_Shdr *xndxshdr;/* 复制扩展节索引节的节头 */xndxshdr = gelf_getshdr (xndxscn, &xndxshdr_mem);if (xndxshdr != NULL && xndxshdr->sh_type == SHT_SYMTAB_SHNDX&& xndxshdr->sh_link == elf_ndxscn (symscn)) {/* 如果匹配,找到扩展节索引节的数据 */xndxdata = elf_getdata (xndxscn, NULL);break;}}/* end while *//* 按照重定位类型进行输出 */if (shdr->sh_type == SHT_REL)show_relocs_rel (ebl, shdr, data, symdata, xndxdata,symshdr->sh_link, shstrndx);elseshow_relocs_rela (ebl, shdr, data, symdata, xndxdata,symshdr->sh_link, shstrndx);}}fputs_unlocked ("\n\n", stdout);return 0;}
大致就是通过elf_begin打开文件使用elf_end来释放elf结构体内存。期间可以通过提供的函数获取elf文件的信息,如果需要复制其中的值而不是直接获取映射指针,则直接使用gelf开头的函数。如果改动了文件的内容,则使用带update函数进行更新结构,并且最后用elf_update写入到实操文件中。
| 接口名称 | 说明 |
|---|---|
| elf_begin | 用来打开目标文件并进行elf描述符号的映射 |
| elf_clone | 用来对现有的elf文件描述符进行克隆 |
| elf_end | 用来释放分配elf描述符号 |
| elf_memory | 用来从文件的内存映射创建elf文件描述符号 |
| elf_next | 如果是库文件,则返回下一个elf文件描述符号 |
| elf_update | 对elf文件进行修改后,写入对应文件中 |
| elf_kind | 返回文件类型 |
| elf_getbase | 获取基地址 |
| elf_getident | 取回文件标志数据 |
| elf32_getehdr | 获取32位elf文件头 |
| elf64_getehdr | 获取64位elf文件头 |
| elf32_newehdr | 创建32位elf文件夹头 |
| elf64_newehdr | 创建32位elf文件夹头 |
| elf32_getphdr | 获取32位段表头 |
| elf64_getphdr | 获取64位段表头 |
| elf32_newphdr | 创建32位elf段表头 |
| elf64_newphdr | 创建64位elf段表头 |
| elf_getscn | 从elf描述符获取按照节索引获取节接口 |
| elf32_offscn | 从32位elf描述符获取按照节偏移获取节接口 |
| elf64_offscn | 从64位elf描述符获取按照节偏移获取节接口 |
| elf_ndxscn | 通过节结构获取节的索引 |
| elf_nextscn | 通过节结构获取下一个节的结构 |
| elf_newscn | 创建一个新的节表添加到节表队列末尾 |
| elf_scnshndx | 通过给定的符号表获取扩展节索引表的节索引 |
| elf_getshnum | 返回节的数量 |
| elf_getshstrndx | 从elf结构中获取节名表索引 |
| elf32_getshdr | 获取32位的节表头 |
| elf64_getshdr | 获取64位的节表头 |
| elf_flagelf | 设置或者清除elf描述符的标志 |
| elf_flagehdr | 设置或者清除ehdr头描述符的标志 |
| elf_flagphdr | 设置或者清除phdr头描述符的标志 |
| elf_flagscn | 设置或者清除节结构描述符的标志 |
| elf_flagdata | 设置或者清除数据描述符的标志 |
| elf_flagshdr | 设置或者清除节头描述符的标志 |
| elf_getdata | 从节中获取节数据(经过了字节序的转换) |
| elf_rawdata | 从节中获取节数据(没有经过了字节序的转换) |
| elf_newdata | 为节结构创建新的数据 |
| elf_getdata_rawchunk | 按照偏移获取数据,并进行字节序的转换,最后关联到elf当前操作数据 |
| elf_strptr | 从指定的字符串表中通过偏移获取字符串 |
| elf_getarhdr | 获取ar库文件的文件描述符号头 |
| elf_getaroff | 从库文件中返回当前正在操作的elf文件偏移 |
| elf_rand | 选定文件指定的偏移 |
| elf_getarsym | 获取库文件的符号表 |
| elf_cntl | 对elf描述符进行重写的映射操作 |
| elf_rawfile | 返回没有解释的文件内容 |
| elf32_fsize | 返回32位下指定个数个指定类型的队列大小 |
| elf64_fsize | 返回64位下指定个数个指定类型的队列大小 |
| elf32_xlatetom | 在32位下转换数据的字节序结构到内存映射 |
| elf64_xlatetom | 在64位下转换数据的字节序结构到内存映射 |
| elf32_xlatetof | 在32位下转换数据的字节序结构到文件 |
| elf64_xlatetof | 在64位下转换数据的字节序结构到文件 |
| elf_errno | 返回最后一次的错误代码 |
| elf_errmsg | 返回错误代码的描述 |
| elf_version | elf的当前版本 |
| elf_fill | 设置填充字节,在填充数据结构时使用 |
| elf_hash | 计算HASH值 |
| elf_gnu_hash | 使用GNU指定的HASH算法计算hash值 |
| elf32_checksum | 计算32位elf文件的校验和 |
| elf64_checksum | 计算64位elf文件的校验和 |
| gelf_getclass | 获取文件关联的class值 |
| gelf_fsize | 返回count个type大小个长度,type的大小按照指定版本version而定 |
| gelf_getehdr | 获取elf文件头副本 |
| gelf_update_ehdr | 更新elf文件头,将src指定的头信息,写回到elf描述符中 |
| gelf_newehdr | 创建ehdr头如果在elf描述符中不存在的话 |
| gelf_offscn | 通过偏移获取节结构 |
| gelf_getshdr | 通过节结构复制节表头 |
| gelf_update_shdr | 更新节结构中的节头 |
| gelf_getphdr | 按照段索引复制段表 |
| gelf_update_phdr | 更新指定段索引的段表头 |
| gelf_newphdr | 创建按照指定个数新的程序段头队列 |
| gelf_xlatetom | 转换字节序到内存映射 |
| gelf_xlatetof | 转换字节序到文件,其实与gelf_xlatetom一样,没有写入什么文件 |
| gelf_getrel | 通过指定的重定位索引复制重定位项 |
| gelf_getrela | 通过指定的重定位项目索引复制带偏移的重定位项 |
| gelf_update_rel | 通过给定重定位项偏移更新项内容 |
| gelf_update_rela | 通过给定重定位项偏移更新项内容(带偏移的重定位项) |
| gelf_getsym | 通过符号索引复制符号结构 |
| gelf_update_sym | 通过符号索引更新符号项目 |
| gelf_getsymshndx | 返回符号信息与扩展节索引 |
| gelf_update_symshndx | 更新符号项的内容与扩展节索引 |
| gelf_getsyminfo | 通过给定的符号索引复制附加的符号信息 |
| gelf_update_syminfo | 通过给定符号索引更新符号信息 |
| gelf_getdyn | 通过给定的索引从动态表获取信息 |
| gelf_update_dyn | 通过给定索引在动态表中更新信息 |
| gelf_getmove | 通过给定索引获取move结构 |
| gelf_update_move | 通过给定索引更新move结构 |
| gelf_getlib | 通过给定索引从表中获取库结构 |
| gelf_update_lib | 通过给定索引从表中更新库结构 |
| gelf_getversym | 通过给定索引返回符号版本信息 |
| gelf_update_versym | 更新符号版本信息 |
| gelf_getverneed | 通过给定偏移返回符号的版本信息 |
| gelf_update_verneed | 更新符号版本信息 |
| gelf_getvernaux | 通过给定偏移返回附加的符号版本信息 |
| gelf_update_vernaux | 更新附加的符号版本信息 |
| gelf_getverdef | 通过给定偏移获取符号版本定义信息 |
| gelf_getverdaux | 通过给定偏移返回附加符号版本定义信息 |
| gelf_update_verdaux | 更新附加符号版本定义信息 |
| gelf_getauxv | 通过给定索引获取辅助项 |
| gelf_update_auxv | 通过给定索引更新辅助项 |
| gelf_getnote | 通过数据偏移,note的名称偏移与描述数据偏移获取note头。返回下一个note的头的偏移,或者0,对于一个错误偏移或者一个错误的note头 |
| gelf_checksum | 计算ELF不变部分的校验和 |
| 名称 | 文件 | 说明 |
|---|---|---|
| Elf | libelfP.h | 对Elf整体进行封装 |
最重要的结构Elf结构在libelfP.h中进行定义。这套库就是通过围绕这个结构来展开工作的。它也将库文件与ELF文件直接通过一个结构包装起来,无论是库文件还是单独的ELF文件只要操作这个结构即可。随后它还封装了32位与64位的操作。在外部开来此结构是无位数说明的。
struct Elf {Elf_Kind kind; /* 表名当前的elf文件类型,是文件还是库 */Elf_Cmd cmd; /* 创建这个描述符所使用的命令 */unsigned int class; /* 是32位还是64位 */int fildes; /* 当前elf结构的文件描述符号. -1表示无效 */off_t start_offset; /* 如果这个elf是一个库文件,则字段表示子文件的开始偏移 */size_t maximum_size; /* 文件的最大长度 */void *map_address; /* 文件映射地址,NULL表示不支持映射 */int flags; /* 文件标志,标志说明会更详细一些 */Elf *parent; /* 当一个库文件成员被创建,这个指针指向库文件本身 */rwlock_define (,lock);/* 多线程锁 */int ref_count; /* 描述符引用计数 */struct Elf *next; /* 当库文件描述符时使用,表示下一个在库中的elf文件 *//* 这是一个联合体,这很重要,一些技巧性的体现 */union {/* 通用头标记 */struct {int ehdr_flags; /* ELF头的标记(主要是dirty标记,表明是否被修改) */int phdr_flags; /* 程序头标记(主要是dirty|malloc两个) */int shdr_malloced; /* 非零,如果shdr队列是内存动态分配的 *//* 这些指针仅仅是当测试 ==/!= NULL时* 这几个指针的偏移与elf32,elf64中的* ehdr,shdr,phdr相对应。记住这是一个联合体*/void *ehdr;void *shdr;void *phdr;Elf_ScnList *scns_last; /* 节表中最后一个节指针,如果为NULL表示* 还没有从文件中读取。 */Elf_Data_Chunk *rawchunks; /* elf_getdata_rawchunk结构的链表,内存原始数据 */unsigned int scnincr; /* 最后一次分配的节数量 */off64_t sizestr_offset; /* size字符串在库文件中的偏移 */} elf;/* 这里就是联合体的好处,以下定义了具体的32位与64位,但是一些通用性验证* 可以通过以上的字段,底下的就是具体定义与上面定义大同小异。但是再末尾* 又加入了自己特有的一些字段。*//* 32为elf头 */struct {/* 这部分与通用性的一致 */Elf32_Ehdr ehdr_mem; /* 当文件读写方式打开时,保存elf头的区域 */char __e32scnspad[sizeof (Elf64_Ehdr) - sizeof (Elf32_Ehdr)];/* 与64位对齐 */Elf_ScnList scns; /* 真正保存节队列数据的地方 */} elf32;/* 64为elf头 */struct {/* 这里与32位一样,不过是少了__e32scnspad对齐数据 */} elf64;/* 库文件头,其实我对库文件结构并不怎么关心~~~ */struct {int has_index; /* 当文件存在索引时设置,0表示不确定,大于0表示有一个 */Elf_Arsym *ar_sym; /* 通过elf_getarsym返回的符号表 */size_t ar_sym_num; /* 符号表`ar_sym'的符号数量 */char *long_names; /* 指向库文件名表 */size_t long_names_len; /* 名称表的长度 */off_t offset; /* 在这个库中当前我们选取文件的偏移。elf_next()函数将改变这个值到下一个文件。 */Elf_Arhdr elf_ar_hdr; /* 通过'elf_getarhdr'返回的自己定义的库头 */struct ar_hdr ar_hdr; /* 标准的库文件头 */char ar_name[16]; /* 在elf_ar_hdr中以NUL结尾的ar_name */char raw_name[17];struct Elf *children; /* 这个库的所有ELF描述符 */} ar;} state;};
首先看下Elf_Scn。毕竟elf格式的各种不同种类的节拼凑成的。
/* ELF节的描述符 */struct Elf_Scn {/* 我们不得不区分几种不同的情况* 1. 节是用户自己创建的。 在这种情况下没有文件或者内存映射提供数据读取。* 这里我们定义两种不同的子情况:** a) 数据没有被添加 (在调用`elf_newdata`之前)* b) 至少一个数据集是有效的** 2. 这个节是从一个文件/内存映射区域读取而来。我们不得不从一个数据块中读* 读取正确的内容。但是我们不读取数据直到读取是必须的。因此我们也有三种* 情况:** a) 在文件中的节是0长度的(因为一些原因)* b) 文件没有被读取* c) 数据可读并且有效** 我们有不同的数据集,原始与转换后的数据。这需要区分数据仅从文件中读取。* 还是用户添加的数据集合(但是首先当读取从文件或者是用户创建的节)这两者* 都是相同的字节格式。我们不创建字节转换数据在它是必要之前。** 在原始数据的字节格式`data_read`元素标记数据是有效的。** 如果有数据从文件或者内存映射区域或者读取一个数据集合被添加`rawdata_list_read`* 指针为非空并且指向最后填充的数据集。`raw_datalist_rear`是NULL,如果数据集合* 没有被设置。** 这允许区分两种状况(`rawdata_list`与`data_list`两项都被初始化为0)是在没有* 从文件或者内存映射中读取数据并且一个节是0长度并且类型是ELF_T_BYTE。*/Elf_Data_List data_list; /* 数据缓存列表,节的数据保存到这个列表 */Elf_Data_List *data_list_rear; /* 指向数据列表 */Elf_Data_Scn rawdata; /* 在节中不解释的数据 */int data_read; /* 非0值:如果节被用户创建或者数据从文件/内存中读取 */int shndx_index; /* 与符号表节关联节的索引(如果这个节是符号) */size_t index; /* 这个节的索引 */struct Elf *elf; /* 与之关联的ELF指针 *//* 接头指针 */union {Elf32_Shdr *e32; /* 指向32位原始节表结构 */Elf64_Shdr *e64; /* 指向64位原始节表结构 */} shdr;unsigned int shdr_flags; /* 节头是否被修改? */unsigned int flags; /* 在长度上节被修改过 */char *rawdata_base; /* 没有修改的节的原始数据 */char *data_base; /* 经过转换后的节数据 */struct Elf_ScnList *list; /* 指向数据中的节列表元素,可以访问到其他的节 */};
在上面的结构中出现了节数据的结构,这个结构用来将数据封装起来。下面就是节数据结构。
/* 将数据存储结果与所属节对应起来 */typedef struct {Elf_Data d; /* 更通用的数据结构 */Elf_Scn *s; /* 表示它所属哪个节 */} Elf_Data_Scn;
节数据列表结构,在每个节中,还涉及到分区的操作。在下面分析elf_begin时会详细探讨。
/* `Elf_Data`描述列表 */typedef struct Elf_Data_List {Elf_Data_Scn data; /* `data` 必须是此结构的第一个元素 */struct Elf_Data_List *next; /* 下一个节数据指针 */int flags; /* 数据的标志 */} Elf_Data_List;
将data放到第一个字段以及在Elf_Data_Scn中将Elf_Data放到第一个的好处就是如下所示直接做类型转换很方便。
Elf_Data_List *list = xxx;Elf_Data *d = (Elf_Data*)(list);
接下来就是保存每个节的链表结构
/* 节列表 */typedef struct Elf_ScnList {unsigned int cnt; /* 'data'字段使用的数量 */unsigned int max; /* 'data'字段最大的分配数量 */struct Elf_ScnList *next; /* 下一个节列表区块 */struct Elf_Scn data[0]; /* 节表数据 */} Elf_ScnList;
以下就是获取原始数据(不经过字节序转换的数据)的结构。
/* elf_getdata_rawchunk函数返回的结果结构 */typedef struct Elf_Data_Chunk {Elf_Data_Scn data; /* 数据 */union {Elf_Scn dummy_scn; /* 傀儡节 */struct Elf_Data_Chunk *next; /* 下一片数据 */};} Elf_Data_Chunk;
最后看看真正保存数据的结构。这个结构在libelf.h中定义。
/* 描述数据 */typedef struct {void *d_buf; /* 指向真实的数据 */Elf_Type d_type; /* 这段数据的类型 */unsigned int d_version; /* ELF版本 */size_t d_size; /* 数据长度 *//* ANDROID_CHANGE_BEGIN,这是android自己的修改 */#if 0loff_t d_off; /* 节的偏移 */#elseoff_t d_off; /* 节的偏移 */#endif/* ANDROID_CHANGE_END */size_t d_align; /* 节的对齐粒度 */} Elf_Data;
/* 在这个库中将各个类型标记了出来,当获取类型长度时使用 */typedef enum {ELF_T_BYTE, /* unsigned char */ELF_T_ADDR, /* Elf32_Addr, Elf64_Addr, ... */ELF_T_DYN, /* Dynamic section record. */ELF_T_EHDR, /* ELF header. */ELF_T_HALF, /* Elf32_Half, Elf64_Half, ... */ELF_T_OFF, /* Elf32_Off, Elf64_Off, ... */ELF_T_PHDR, /* Program header. */ELF_T_RELA, /* Relocation entry with addend. */ELF_T_REL, /* Relocation entry. */ELF_T_SHDR, /* Section header. */ELF_T_SWORD, /* Elf32_Sword, Elf64_Sword, ... */ELF_T_SYM, /* Symbol record. */ELF_T_WORD, /* Elf32_Word, Elf64_Word, ... */ELF_T_XWORD, /* Elf32_Xword, Elf64_Xword, ... */ELF_T_SXWORD, /* Elf32_Sxword, Elf64_Sxword, ... */ELF_T_VDEF, /* Elf32_Verdef, Elf64_Verdef, ... */ELF_T_VDAUX, /* Elf32_Verdaux, Elf64_Verdaux, ... */ELF_T_VNEED, /* Elf32_Verneed, Elf64_Verneed, ... */ELF_T_VNAUX, /* Elf32_Vernaux, Elf64_Vernaux, ... */ELF_T_NHDR, /* Elf32_Nhdr, Elf64_Nhdr, ... */ELF_T_SYMINFO, /* Elf32_Syminfo, Elf64_Syminfo, ... */ELF_T_MOVE, /* Elf32_Move, Elf64_Move, ... */ELF_T_LIB, /* Elf32_Lib, Elf64_Lib, ... */ELF_T_GNUHASH, /* GNU-style hash section. */ELF_T_AUXV, /* Elf32_auxv_t, Elf64_auxv_t, ... *//* Keep this the last entry. */ELF_T_NUM} Elf_Type;
这些命令多数是针对文件打开的权限操作以及映射方式,ELF_C_CLR与ELF_C_SET是针对所有结构的flag字段设置与清除标志而言的。多数命令会在elf_begin中详细探讨。
/* 命令 */typedef enum {ELF_C_NULL, /* 空... */ELF_C_READ, /* 以读取权限打开 */ELF_C_RDWR, /* 以读写权限打开 */ELF_C_WRITE, /* 以写权限打开 */ELF_C_CLR, /* 清除标志 */ELF_C_SET, /* 写入标志 */ELF_C_FDDONE, /* 标记文件句柄将不会再被使用 */ELF_C_FDREAD, /* 读取并且重设数据,文件句柄将不会再被使用 *//* 下面的是扩展 */ELF_C_READ_MMAP, /* 以读权限映射文件 */ELF_C_RDWR_MMAP, /* 以读写权限映射文件 */ELF_C_WRITE_MMAP, /* 以写权限映射文件 */ELF_C_READ_MMAP_PRIVATE, /* 以读权限映射文件,内存可写,但是不可写回文件 */ELF_C_EMPTY, /* 拷贝基础的文件数据,但是内容为空 *//* 最后一个计数而已 */ELF_C_NUM} Elf_Cmd;
/* ELF结构的标志 */enum {ELF_F_DIRTY = 0x1, /* 一般当改写了原始缓存内存时设置 */#define ELF_F_DIRTY ELF_F_DIRTYELF_F_LAYOUT = 0x4, /* 自己创建组织的elf文件结构时设置 */#define ELF_F_LAYOUT ELF_F_LAYOUTELF_F_PERMISSIVE = 0x8 /* 进行不严格验证 */#define ELF_F_PERMISSIVE ELF_F_PERMISSIVE};
ELF_F_PERMISSIVE只在elf32_updatanull.c中使用到一次。
if (shdr->sh_entsize != 0&& unlikely (shdr->sh_size % shdr->sh_entsize != 0)&& (elf->flags & ELF_F_PERMISSIVE) == 0) {__libelf_seterrno (ELF_E_INVALID_SHENTSIZE);return -1;}
上述代码是验证节项的长度是否是节大小的倍数。如果此标志被设置将忽略这一验证。
其实就是表明当前操作文件的类型而已。
/* 标识文件类型 */typedef enum {ELF_K_NONE, /* 未知 */ELF_K_AR, /* 静态库 */ELF_K_COFF, /* coff文件格式 */ELF_K_ELF, /* elf文件格式 *//* 无意义 */ELF_K_NUM} Elf_Kind;
好吧,这个库是从elf_begin开始的,那就先分析以下这个函数吧!
Elf *elf_begin (int fildes, Elf_Cmd cmd, Elf *ref) {Elf *retval;/* 如果版本没有初始化则报错 */if (unlikely (! __libelf_version_initialized)) {__libelf_seterrno (ELF_E_NO_VERSION);return NULL;}/* 引用指针不为空 */if (ref != NULL)/* 确定描述符不会突然的丢失 */rwlock_rdlock (ref->lock);else if (unlikely (fcntl (fildes, F_GETFL) == -1 && errno == EBADF)) {__libelf_seterrno (ELF_E_INVALID_FILE);return NULL;}/* 锁并且复制elf描述符,稍后分析 */Elf *lock_dup_elf () {/* 如果是库文件 */if (ref->kind == ELF_K_AR) {rwlock_unlock (ref->lock);rwlock_wrlock (ref->lock);}/* 复制elf描述符 */return dup_elf (fildes, cmd, ref);}/* 命令解析 */switch (cmd) {/* 空命令直接退出 */case ELF_C_NULL:retval = NULL;break;case ELF_C_READ_MMAP_PRIVATE:/* 如果我们有一个引用,其打开方式必须与当前的相同 */if (unlikely (ref != NULL && ref->cmd != ELF_C_READ_MMAP_PRIVATE)) {__libelf_seterrno (ELF_E_INVALID_CMD);retval = NULL;break;}/* FALLTHROUGH *//* 以读权限打开 */case ELF_C_READ:case ELF_C_READ_MMAP:/* 如果当前ref不为空,则说明 */if (ref != NULL)/* 读取一个elf结构 */retval = lock_dup_elf ();else/* 创建存在文件的描述符 */retval = read_file (fildes, 0, ~((size_t) 0), cmd, NULL);break;/* 以读写权限打开 */case ELF_C_RDWR:case ELF_C_RDWR_MMAP:/* 如果我们有一个引用的对象,则尝试直接复制出来一个 */if (ref != NULL) {/* 此段话检测原先的映射必须拥有写权限 */if (unlikely (ref->cmd != ELF_C_RDWR&& ref->cmd != ELF_C_RDWR_MMAP&& ref->cmd != ELF_C_WRITE&& ref->cmd != ELF_C_WRITE_MMAP)) {__libelf_seterrno (ELF_E_INVALID_CMD);retval = NULL;} elseretval = lock_dup_elf ();} else /* 创建存在文件的描述符 */retval = read_file (fildes, 0, ~((size_t) 0), cmd, NULL);break;/* 写权限打开 */case ELF_C_WRITE:case ELF_C_WRITE_MMAP:/* 我们忽略引用并且准备一个可写的新文件 */retval = write_file (fildes, cmd);break;default:__libelf_seterrno (ELF_E_INVALID_CMD);retval = NULL;break;}/* 释放锁 */if (ref != NULL)rwlock_unlock (ref->lock);return retval;}
中间内部定义的函数lock_dup_elf只是一个接口函数,做了对库文件的判断就调用了dup_elf函数。
/* 返回一个克隆的存在描述符。这个函数必须被调用使用锁 */static Elf *dup_elf (int fildes, Elf_Cmd cmd, Elf *ref) {struct Elf *result;if (fildes == -1)/* 允许用户传递-1作为新的文件 */fildes = ref->fildes;/* 如果它已经被断开(使用`elf_cntl`)我们不需要测试它 */else if (unlikely (ref->fildes != -1 && fildes != ref->fildes)) {__libelf_seterrno (ELF_E_FD_MISMATCH);return NULL;}/* 模式必须允许读。描述符通过一个与ELF_READ,ELF_C_WRITE与ELF_C_RDWR* 的命令进行创建。*/if (unlikely (ref->cmd != ELF_C_READ&& ref->cmd != ELF_C_READ_MMAP&& ref->cmd != ELF_C_WRITE && ref->cmd != ELF_C_WRITE_MMAP&& ref->cmd != ELF_C_RDWR && ref->cmd != ELF_C_RDWR_MMAP&& ref->cmd != ELF_C_READ_MMAP_PRIVATE)) {__libelf_seterrno (ELF_E_INVALID_OP);return NULL;}/* 现在是区分读正常文件与库文件的时间点。正常的文件增加引用计数 */if (ref->kind != ELF_K_AR) {++ref->ref_count;return ref;}/* 如果是一个库文件。我们必须创建一个库成员描述符的库文件描述符内部指针指向它。* 首先读取下一个成员的头 */if (ref->state.ar.elf_ar_hdr.ar_name == NULL&& __libelf_next_arhdr_wrlock (ref) != 0)return NULL;/* 我们有所有我们需要下一个库成员的信息,现在我们创建它 */result = read_file (fildes, ref->state.ar.offset +sizeof (struct ar_hdr),ref->state.ar.elf_ar_hdr.ar_size, cmd, ref);/* 将新的文件描述符号,链接到库列表 */if (result != NULL) {result->next = ref->state.ar.children;ref->state.ar.children = result;}return result;}
上面的流程也很简单,涉及了两点一点就是如果文件是库文件,一种是elf。如果是elf如果ref存在则直接增加引用次数并返回,如果是库文件则需要调用__libelf_next_arhdr_wrlock获取下一个在库中的elf文件。随后则使用read_file进行读取。
让我们先忽略库文件,首先分析一下read_file函数。
/* 打开文件读取。如果可能我们将尝试使用mmap() 映射文件 */static struct Elf *read_file (int fildes, /* 库文件的句柄 */off_t offset, /* 要读取文件的偏移 */size_t maxsize, /* 库文件最大长度 */Elf_Cmd cmd, /* 读取的使用的命令 */Elf *parent /* 父Elf的指针 */) {void *map_address = NULL;int use_mmap = (cmd == ELF_C_READ_MMAP || cmd == ELF_C_RDWR_MMAP|| cmd == ELF_C_WRITE_MMAP|| cmd == ELF_C_READ_MMAP_PRIVATE);/* 直接尝试使用内存映射 */if (use_mmap) {/* 父文件为空 <--- 库文件 */if (parent == NULL) {if (maxsize == ~((size_t) 0)) {/* 我们此时不知道文件有多大,现在决定它 */struct stat st;/* 获取文件最大长度 */if (fstat (fildes, &st) == 0&& (sizeof (size_t) >= sizeof (st.st_size)|| st.st_size <= ~((size_t) 0)))maxsize = (size_t) st.st_size;}/* 我们尝试映射父文件(库)*/map_address = mmap (NULL, maxsize,(cmd == ELF_C_READ_MMAP ? PROT_READ : PROT_READ|PROT_WRITE),cmd == ELF_C_READ_MMAP_PRIVATE ||cmd == ELF_C_READ_MMAP ? MAP_PRIVATE : MAP_SHARED,fildes, offset);if (map_address == MAP_FAILED)map_address = NULL;} else {/* 如果父文件已经被加载,使用它 */assert (maxsize != ~((size_t) 0));map_address = parent->map_address;}}/* 如果文件已经被映射则从映射读取 */if (map_address != NULL) {assert (map_address != MAP_FAILED);/* 读取文件并建立elf文件结构 */struct Elf *result = __libelf_read_mmaped_file (fildes,map_address, offset, maxsize, cmd, parent);/* 如果一些事情出错在为`unmap`做一些初始的事情期间 */if (result == NULL &&(parent == NULL || parent->map_address != map_address))munmap (map_address, maxsize);else if (parent == NULL)/* 记住已经映射过内存,如果父节点存在则直接使用父节点的flags */result->flags |= ELF_F_MMAPPED;return result;}/* 不映射文件直接从文件读取 */return read_unmmaped_file (fildes, offset, maxsize, cmd, parent);}
一系列的内存映射操作,映射后使用__libelf_read_mmaped_file进行直接读取映射,如果不使用映射则设置use_mmap为0值并最后使用read_unmmaped_file直接从文件中读取。
这两个函数大致差不多,那么直接看read_unmmaped_file即可。
/* 不映射文件,直接读取 */static Elf *read_unmmaped_file (int fildes,off_t offset,size_t maxsize,Elf_Cmd cmd,Elf *parent) {/* 我们不得不找出这是一种什么样子的文件。我们处理ELF文件与库文件。* 为了找出是什么种类的文件,我们必须读取这些头。确定ELF文件则头有* EI_NIDENT个长度,但是我们不读取所有的ELF文件直到我们以后需要它。* 库文件的头有SARMAG个长度。读取这些最大的数量。*/union {Elf64_Ehdr ehdr;unsigned char header[MAX (sizeof (Elf64_Ehdr), SARMAG)];} mem;/* 重新读取文件头 */ssize_t nread = pread_retry (fildes, mem.header,MIN (MAX (sizeof (Elf64_Ehdr), SARMAG), maxsize), offset);if (unlikely (nread == -1))return NULL;/* 确定类型 */Elf_Kind kind = determine_kind (mem.header, nread);switch (kind) {case ELF_K_AR:return file_read_ar (fildes, NULL, offset, maxsize, cmd, parent);case ELF_K_ELF:if ((size_t) nread >= (mem.header[EI_CLASS] == ELFCLASS32? sizeof (Elf32_Ehdr) : sizeof (Elf64_Ehdr)))return file_read_elf (fildes, NULL, mem.header,offset, maxsize, cmd, parent);/* FALLTHROUGH */default:break;}/* 既然两种文件格式都不是,则直接分配一片内存使用 */return allocate_elf (fildes, NULL, offset, maxsize, cmd, parent,ELF_K_NONE, 0);}
上述代码中根据类型使用file_read_ar与file_read_elf读取对应的文件。如果都不是这两种格式则使用allocate_elf分配一个空的elf结构。下面是重头戏,库文件的结构其实很简单,并非本库的重点,重点还是elf文件的读取。file_read_ar这里忽略分析,其实这个函数不难,本身库文件格式就是一个非常简单的格式。下面分析读取elf文件。
static Elf *file_read_elf (int fildes,void *map_address,unsigned char *e_ident,off_t offset,size_t maxsize,Elf_Cmd cmd,Elf *parent) {/* 验证当前文件的类型 */if (unlikely ((e_ident[EI_CLASS] != ELFCLASS32&& e_ident[EI_CLASS] != ELFCLASS64)|| (e_ident[EI_DATA] != ELFDATA2LSB&& e_ident[EI_DATA] != ELFDATA2MSB))) {__libelf_seterrno (ELF_E_INVALID_FILE);return NULL;}/* 决定节数量 */size_t scncnt = get_shnum (map_address, e_ident, fildes, offset, maxsize);if (scncnt == (size_t) -1l)return NULL;/* 分配elf结构内存 */Elf *elf = allocate_elf (fildes, map_address, offset, maxsize, cmd, parent,ELF_K_ELF, scncnt * sizeof (Elf_Scn));if (elf == NULL)return NULL;/* 节索引最大值 */elf->state.elf.scnincr = 10;/* 设置elf的位数类型 */elf->class = e_ident[EI_CLASS];/* 32位 */if (e_ident[EI_CLASS] == ELFCLASS32) {/* 如果当前的体系结果对其粒度不是充分支持的话,这个指针也许不能直接使用 */Elf32_Ehdr *ehdr = (Elf32_Ehdr *) ((char *) map_address + offset);/* 设置节计数 */assert ((unsigned int) scncnt == scncnt);elf->state.elf32.scns.cnt = elf->state.elf32.scns.max = scncnt;/* 32位的bin */if (map_address != NULL && e_ident[EI_DATA] == MY_ELFDATA&& (ALLOW_UNALIGNED ||((((uintptr_t) ehdr) & (__alignof__ (Elf32_Ehdr) - 1)) == 0&& ((uintptr_t) ((char *) ehdr + ehdr->e_shoff)& (__alignof__ (Elf32_Shdr) - 1)) == 0&& ((uintptr_t) ((char *) ehdr + ehdr->e_phoff)& (__alignof__ (Elf32_Phdr) - 1)) == 0))) {/* 我们能使用映射内存指针 */elf->state.elf32.ehdr = ehdr;/* elf头 *//* 设置节表 */elf->state.elf32.shdr =(Elf32_Shdr *) ((char *) ehdr + ehdr->e_shoff);if (ehdr->e_phnum > 0)elf->state.elf32.phdr =(Elf32_Phdr *) ((char *) ehdr +ehdr->e_phoff);/* 遍历节 */for (size_t cnt = 0; cnt < scncnt; ++cnt) {elf->state.elf32.scns.data[cnt].index = cnt;elf->state.elf32.scns.data[cnt].elf = elf;/* 节数据关联节表 */elf->state.elf32.scns.data[cnt].shdr.e32 =&elf->state.elf32.shdr[cnt];/* 设置节的数据内容,这里就是映射后的原始数据 */elf->state.elf32.scns.data[cnt].rawdata_base =elf->state.elf32.scns.data[cnt].data_base =((char *) map_address + offset+ elf->state.elf32.shdr[cnt].sh_offset);/* list表明是节链表 */elf->state.elf32.scns.data[cnt].list = &elf->state.elf32.scns;/* 如果当前节是符号表,则反向设置它所属字符串表节的符号索引 */if (elf->state.elf32.shdr[cnt].sh_type == SHT_SYMTAB_SHNDX&& elf->state.elf32.shdr[cnt].sh_link < scncnt) {/* 进行字符串表的关联 */int sh_link = elf->state.elf32.shdr[cnt].sh_link;elf->state.elf32.scns.data[sh_link].shndx_index = cnt;}/* end if *//* 如果自身节的与符号表没有关联,则设定为-1 */if (elf->state.elf32.scns.data[cnt].shndx_index == 0)elf->state.elf32.scns.data[cnt].shndx_index = -1;}/* end for */} else {/* map_address == NULL,则直接从文件读取 *//* 复制elf头 */elf->state.elf32.ehdr = memcpy (&elf->state.elf32.ehdr_mem,e_ident,sizeof (Elf32_Ehdr));/* 字序转换 */if (e_ident[EI_DATA] != MY_ELFDATA) {CONVERT (elf->state.elf32.ehdr_mem.e_type);CONVERT (elf->state.elf32.ehdr_mem.e_machine);CONVERT (elf->state.elf32.ehdr_mem.e_version);CONVERT (elf->state.elf32.ehdr_mem.e_entry);CONVERT (elf->state.elf32.ehdr_mem.e_phoff);CONVERT (elf->state.elf32.ehdr_mem.e_shoff);CONVERT (elf->state.elf32.ehdr_mem.e_flags);CONVERT (elf->state.elf32.ehdr_mem.e_ehsize);CONVERT (elf->state.elf32.ehdr_mem.e_phentsize);CONVERT (elf->state.elf32.ehdr_mem.e_phnum);CONVERT (elf->state.elf32.ehdr_mem.e_shentsize);CONVERT (elf->state.elf32.ehdr_mem.e_shnum);CONVERT (elf->state.elf32.ehdr_mem.e_shstrndx);}for (size_t cnt = 0; cnt < scncnt; ++cnt) {elf->state.elf32.scns.data[cnt].index = cnt;elf->state.elf32.scns.data[cnt].elf = elf;elf->state.elf32.scns.data[cnt].list = &elf->state.elf32.scns;}}/* end else */elf->state.elf32.scns_last = &elf->state.elf32.scns;} else {/* is64 *//* 与32位逻辑相同 */}return elf;}
上面的代码,就是按照字节序转换读取elf文件头,值得一提的是对于节数据的填充,这里并非实际填充与转换将两个指针。
elf->state.elf32.scns.data[cnt].rawdata_base =elf->state.elf32.scns.data[cnt].data_base =((char *) map_address + offset+ elf->state.elf32.shdr[cnt].sh_offset);
在后面要确切使用时才会按照字节序进行转换。
这个库大多都是对文件目标的信息进行获取的。只有带有update字样的函数是写入函数。就是当你在修改了目标文件内容时进行使用,对齐更新,不过这个库有一个弊端就是写入是仅更新,也就是原先的节内容不能进行扩充,可以缩减。只有发生改变的节内容才进行更新。这有些局限性,不过按照给定节的偏移并不是那么容易。这样已经不错了。需要大量的修改文件内容的。需要自己重写组织结构。
这份库的历史很久,所以考虑的还是比较细的。写入函数考虑到了两种情况,一种是自己手动构建一个elf文件,另外一种是直接目标文件的修改。
这里从一个总入口elf_update函数聊起。
off_t elf_update (Elf *elf, Elf_Cmd cmd) {size_t shnum;off_t size;int change_bo = 0;/* 命令不对头* 打开的命令必须带有写入权限 */if (cmd != ELF_C_NULL&& cmd != ELF_C_WRITE&& unlikely (cmd != ELF_C_WRITE_MMAP)) {__libelf_seterrno (ELF_E_INVALID_CMD);return -1;}if (elf == NULL)return -1;/* 类型检查 */if (elf->kind != ELF_K_ELF) {__libelf_seterrno (ELF_E_INVALID_HANDLE);return -1;}/* 读写 */rwlock_wrlock (elf->lock);/* 确定我们有一个ELF头 */if (elf->state.elf.ehdr == NULL) {__libelf_seterrno (ELF_E_WRONG_ORDER_EHDR);size = -1;goto out;}/* 确定节数量 */shnum = (elf->state.elf.scns_last->cnt == 0? 0 : 1 + elf->state.elf.scns_last->data[elf->state.elf.scns_last->cnt - 1].index);/* 上面一堆操作最有用的就是的到了节数量,其余的都是在验证格式合法 *//* 随后就调用了updatenull_wrlock看名字就很有趣,翻译过来就是什么都没更新* 这个函数分为两个文件的实现32位版本与64位版本,单看32位即可*/size = (elf->class == ELFCLASS32? __elf32_updatenull_wrlock (elf, &change_bo, shnum): __elf64_updatenull_wrlock (elf, &change_bo, shnum));/* 验证命令是否合规 */if (likely (size != -1)&& (cmd == ELF_C_WRITE || cmd == ELF_C_WRITE_MMAP)) {/* 打开文件的权限必须有写权限 */if (elf->cmd != ELF_C_RDWR&& elf->cmd != ELF_C_RDWR_MMAP&& elf->cmd != ELF_C_WRITE&& unlikely (elf->cmd != ELF_C_WRITE_MMAP)) {__libelf_seterrno (ELF_E_UPDATE_RO);size = -1;} else if (unlikely (elf->fildes == -1)) {/* 文件句柄无效 */__libelf_seterrno (ELF_E_FD_DISABLED);size = -1;} else/* 写入到文件中 */size = write_file (elf, size, change_bo, shnum);}out:rwlock_unlock (elf->lock);return size;}
这个函数其实最主要的作用就是在更新数据前,验证所有即将写入文件数据格式的合法性,将一些不正确的,改写为默认的值,如果是自己创建的elf结构体,则加入了验证长度环节。为了当以后的步骤中起码各种头结构中的信息与偏移在一个合法的范围内。说白了就是加强的验证。
/* __elf32_updatenull_wrlock* elf ELF文件指针* change_bop 字节序是否发生改变* shnum 节数量*/off_t internal_function__elfw2(LIBELFBITS,updatenull_wrlock) (Elf *elf,int *change_bop,size_t shnum) {ElfW2(LIBELFBITS,Ehdr) *ehdr;int changed = 0;int ehdr_flags = 0;/* __elf32_getehdr_wrlock* 获取ehdr头*/ehdr = __elfw2(LIBELFBITS,getehdr_wrlock) (elf);/* 获取elf头的默认值 */if (ELFW(default_ehdr,LIBELFBITS) (elf, ehdr, shnum, change_bop) != 0)return -1;/* 获取elf头的长度 */off_t size = elf_typesize (LIBELFBITS, ELF_T_EHDR, 1);/* 设置程序头位置 */if (elf->state.ELFW(elf,LIBELFBITS).phdr == NULL&& (ehdr->e_type == ET_EXEC || ehdr->e_type == ET_DYN|| ehdr->e_type == ET_CORE))/* 获取程序头* __elf32_getphdr_wrlock*/(void) __elfw2(LIBELFBITS,getphdr_wrlock) (elf);/* 获取到程序段头了 */if (elf->state.ELFW(elf,LIBELFBITS).phdr != NULL) {/* 只有应用程序,共享库,与CORE文件有程序头 */if (ehdr->e_type != ET_EXEC && ehdr->e_type != ET_DYN&& unlikely (ehdr->e_type != ET_CORE)) {__libelf_seterrno (ELF_E_INVALID_PHDR);return -1;}if (elf->flags & ELF_F_LAYOUT) {/* 用户在支持填写e_phoff。使用它与e_phnum去决定最大长度扩展 */size = MAX ((size_t) size,ehdr->e_phoff+ elf_typesize (LIBELFBITS, ELF_T_PHDR, ehdr->e_phnum));} else {/* 如果发生改变则更新 */update_if_changed (ehdr->e_phoff,elf_typesize (LIBELFBITS, ELF_T_EHDR, 1),ehdr_flags);/* 这里不需要对齐 */size += elf_typesize (LIBELFBITS, ELF_T_PHDR, ehdr->e_phnum);}}/* 节数量大于0 */if (shnum > 0) {Elf_ScnList *list;bool first = true;assert (elf->state.ELFW(elf,LIBELFBITS).scns.cnt > 0);/* 节数量大于0xFF00 */if (shnum >= SHN_LORESERVE) {/* 我们不得在节表中填充第0个索引节* elf32.scns.data[0]*/Elf_Scn *scn0 = &elf->state.ELFW(elf,LIBELFBITS).scns.data[0];update_if_changed (scn0->shdr.ELFW(e,LIBELFBITS)->sh_size,shnum, scn0->shdr_flags);}/* 仔细检测所有的节并且找出它们有多大 */list = &elf->state.ELFW(elf,LIBELFBITS).scns;/* 加载所有的节头 */if (list->data[1].shdr.ELFW(e,LIBELFBITS) == NULL)(void) __elfw2(LIBELFBITS,getshdr_wrlock) (&list->data[1]);/* 遍历所有节表 */do {for (size_t cnt = first == true; cnt < list->cnt; ++cnt) {Elf_Scn *scn = &list->data[cnt];ElfW2(LIBELFBITS,Shdr) *shdr = scn->shdr.ELFW(e,LIBELFBITS);off_t offset = 0;assert (shdr != NULL);ElfW2(LIBELFBITS,Word) sh_entsize = shdr->sh_entsize;ElfW2(LIBELFBITS,Word) sh_align = shdr->sh_addralign ?: 1;/* 如果我们可以确定节的类型则设置sh_entsize的值 */switch (shdr->sh_type) {case SHT_SYMTAB:sh_entsize = elf_typesize (LIBELFBITS, ELF_T_SYM, 1);break;case SHT_RELA:sh_entsize = elf_typesize (LIBELFBITS, ELF_T_RELA, 1);break;case SHT_GROUP:/* 仅有重定位文件包含节组 */if (ehdr->e_type != ET_REL) {__libelf_seterrno (ELF_E_GROUP_NOT_REL);return -1;}/* FALLTHROUGH */case SHT_SYMTAB_SHNDX:sh_entsize = elf_typesize (32, ELF_T_WORD, 1);break;case SHT_HASH:sh_entsize = SH_ENTSIZE_HASH (ehdr);break;case SHT_DYNAMIC:sh_entsize = elf_typesize (LIBELFBITS, ELF_T_DYN, 1);break;case SHT_REL:sh_entsize = elf_typesize (LIBELFBITS, ELF_T_REL, 1);break;case SHT_DYNSYM:sh_entsize = elf_typesize (LIBELFBITS, ELF_T_SYM, 1);break;case SHT_SUNW_move:sh_entsize = elf_typesize (LIBELFBITS, ELF_T_MOVE, 1);break;case SHT_SUNW_syminfo:sh_entsize = elf_typesize (LIBELFBITS, ELF_T_SYMINFO, 1);break;default:break;}/* 如果节头包含错误的entry_size项则标记头为已经修改 */update_if_changed (shdr->sh_entsize,sh_entsize,scn->shdr_flags);if (scn->data_read == 0&& __libelf_set_rawdata_wrlock (scn) != 0)return -1;/* 迭代所有的数据块 */if (list->data[cnt].data_list_rear != NULL) {Elf_Data_List *dl = &scn->data_list;while (dl != NULL) {Elf_Data *data = &dl->data.d;if (dl == &scn->data_list && data->d_buf == NULL&& scn->rawdata.d.d_buf != NULL)data = &scn->rawdata.d;/* 数据版本非法 */if (unlikely (data->d_version == EV_NONE)|| unlikely (data->d_version >= EV_NUM)) {__libelf_seterrno (ELF_E_UNKNOWN_VERSION);return -1;}/* 检验数据是否对齐 */if (unlikely (! powerof2 (data->d_align))) {__libelf_seterrno (ELF_E_INVALID_ALIGN);return -1;}/* 节对齐粒度 */sh_align = MAX (sh_align, data->d_align);if (elf->flags & ELF_F_LAYOUT) {/* 用户指定偏移与长度,所以我们不得不检测这个块* 是否在节允许范围内*/if (unlikely ((GElf_Word) (data->d_off+ data->d_size)> shdr->sh_size)) {__libelf_seterrno (ELF_E_SECTION_TOO_SMALL);return -1;}} else {/* 决定是否填充一些杂物 */offset = ((offset + data->d_align - 1)& ~(data->d_align - 1));update_if_changed (data->d_off, offset, changed);offset += data->d_size;}/* 下一个数据块 */dl = dl->next;}} else/* 获取从原始数据(raw data)的节大小 */offset += scn->rawdata.d.d_size;if (elf->flags & ELF_F_LAYOUT) {size = MAX ((GElf_Word) size,shdr->sh_offset+ (shdr->sh_type != SHT_NOBITS? shdr->sh_size : 0));/* 对齐粒度必须是2的幂次方。这是ELF标准需要的。我们这里是附加测试 */if (unlikely (! powerof2 (shdr->sh_addralign))|| unlikely (shdr->sh_addralign < sh_align)) {__libelf_seterrno (ELF_E_INVALID_ALIGN);return -1;}} else {/* 设置这个节的对齐粒度 */update_if_changed (shdr->sh_addralign, sh_align,scn->shdr_flags);/* 获取对齐粒度后的长度 */size = (size + sh_align - 1) & ~(sh_align - 1);int offset_changed = 0;update_if_changed (shdr->sh_offset, (GElf_Word) size,offset_changed);changed |= offset_changed;if (offset_changed && scn->data_list_rear == NULL) {/* 节在文件中的位置发生改变。创建节的数据列表 */if (__elf_getdata_rdlock (scn, NULL) == NULL)return -1;}/* 检查节长度是否正确 */update_if_changed (shdr->sh_size, (GElf_Word) offset,changed);if (shdr->sh_type != SHT_NOBITS)size += offset;/* 节的标志发生改变 */scn->flags |= changed;}/* 检查节的长度是否是entry长度的倍数 */if (shdr->sh_entsize != 0&& unlikely (shdr->sh_size % shdr->sh_entsize != 0)&& (elf->flags & ELF_F_PERMISSIVE) == 0) {__libelf_seterrno (ELF_E_INVALID_SHENTSIZE);return -1;}}assert (list->next == NULL || list->cnt == list->max);first = false;} while ((list = list->next) != NULL);/* 保存节信息 */if (elf->flags & ELF_F_LAYOUT) {/* 支持用户自行填充e_phoff。使用它与e_phnum来决定最大的扩展 */size = MAX ((GElf_Word) size,(ehdr->e_shoff+ (elf_typesize (LIBELFBITS, ELF_T_SHDR, shnum))));} else {/* 对齐节头表* 是,我们使用`sizeof`并不是`__alignof__`直到我们不想严格使用* 体系架构中指定的对齐粒度*/#define SHDR_ALIGN sizeof (ElfW2(LIBELFBITS,Off))size = (size + SHDR_ALIGN - 1) & ~(SHDR_ALIGN - 1);update_if_changed (ehdr->e_shoff, (GElf_Word) size, elf->flags);update_if_changed (ehdr->e_shentsize,elf_typesize (LIBELFBITS, ELF_T_SHDR, 1),ehdr_flags);/* 核算节头长度 */size += elf_typesize (LIBELFBITS, ELF_T_SHDR, shnum);}}/* 设置标志 */elf->state.ELFW(elf,LIBELFBITS).ehdr_flags |= ehdr_flags;return size;}
上面有一个update_if_changed函数,其实这是一个宏,它的作用就是对比第一个参数与第二个参数的值,如果两个不一样,则将第一个参数的值设置为第二个参数的值,然后将后面的标志变量设置上DIRTY标志位,表明经过外部修改了。
这里是实现写入的入口,其实它也是一个接口函数,主要的实现是__elf32_updatemmap与__elf32_updatefile两个函数。一个是当文件使用映射时,一个是直接以文件句柄操作时的更新方式,内部流程大致相同。
static off_twrite_file (Elf *elf,/* Elf结构指针 */off_t size,/* 要写入的长度 */int change_bo,/* 是否发生改变 */size_t shnum/* 节数量 */) {/* ELF的位数 */int class = elf->class;/* 获取文件状态 */struct stat st;if (unlikely (fstat (elf->fildes, &st) != 0)) {__libelf_seterrno (ELF_E_WRITE_ERROR);return -1;}/* 验证参数size是否合规 */if (elf->parent == NULL && (elf->maximum_size == ~((size_t) 0)|| (size_t) size > elf->maximum_size)&& unlikely (ftruncate (elf->fildes, size) != 0)) {__libelf_seterrno (ELF_E_WRITE_ERROR);return -1;}/* 如果没有完成则尝试映射文件 */if (elf->map_address == NULL && elf->cmd == ELF_C_WRITE_MMAP) {#if _MUDFLAP/* Mudflap doesn't grok that our mmap'd data is ok. */#else/* 映射文件 */elf->map_address = mmap (NULL, size, PROT_READ | PROT_WRITE,MAP_SHARED, elf->fildes, 0);if (unlikely (elf->map_address == MAP_FAILED))elf->map_address = NULL;#endif}/* 文件已经被映射 */if (elf->map_address != NULL) {/* 按照位数更新映射 */if ((class == ELFCLASS32? __elf32_updatemmap (elf, change_bo, shnum): __elf64_updatemmap (elf, change_bo, shnum)) != 0)size = -1;} else {/* 文件没有被映射,则直接更新文件 */if ((class == ELFCLASS32? __elf32_updatefile (elf, change_bo, shnum): __elf64_updatefile (elf, change_bo, shnum)) != 0)size = -1;}if (size != -1&& elf->parent == NULL&& elf->maximum_size != ~((size_t) 0)&& (size_t) size < elf->maximum_size&& unlikely (ftruncate (elf->fildes, size) != 0)) {__libelf_seterrno (ELF_E_WRITE_ERROR);size = -1;}/* POSIX说明'ftruncate'与'write'也许会清除S_ISUID与S_ISGID位。因此我们要* 在调用这两个函数后写回去。 */if (size != -1&& unlikely (st.st_mode & (S_ISUID | S_ISGID))&& unlikely (fchmod (elf->fildes, st.st_mode) != 0)) {__libelf_seterrno (ELF_E_WRITE_ERROR);size = -1;}/* 更新尺寸 */if (size != -1 && elf->parent == NULL)elf->maximum_size = size;return size;}
elf32_updatefile与elf32_updatemmap两个实现大同小异,代码太长了,这里不进行分析了。主要就是将elf结构中的内容,按照结构输出到文件中。需要注意的是,对于节内容的更新,它是仅更新修改过的节内容,也就是说,不修改的节内容还在原来文件或者内存映射该存在的地方。这就是说,如果要修改一个elf文件时注意节的范围,以免越界。