在第二章的結尾,作者貼上了一段ELF解析器的代碼,我進行了反複的嘗試,加深了對ELF格式的了解。這個其實就是一個簡單的readelf,列印所有的section header名稱和在可執行程式中的虛拟位址,program header的位址。
為了便于了解,在某些地方我加入了自己的注釋。例如mmap()處的注釋,line92之前的3行對e_shstrndx的解釋,還有開頭對3個結構體的解釋。
之前在看《深入了解Linux核心》的時候看到過mmap(),這次再次遇到才真正查了一下mmap()的具體功能,也知識有了膚淺的認識。我沒有想到的是共享記憶體居然使用的就是mmap()技術實作的,大家對同一塊記憶體進行映射(map),大家就能操作相同一塊記憶體,可讀也可以寫,這樣也就實作了程序間通信。除了共享記憶體外還有一個更廣的應用場景,就是劃撥一塊硬碟作為記憶體使用,也是這個思想。
下面我把代碼貼出來,就是一個簡單的main()函數,一點也不複雜。按照第一行的注釋進行編譯即可。代碼裡用到的結構體都在elf.h系統頭檔案中定義。任何一個完整的linux系統中都存在,在linux核心代碼中也存在。如果使用window編輯的話,建議拷貝一份elf.h到自己的工程中。
/* elfparse.c - gcc elfparse.c -o elfparse */
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <elf.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/mman.h>
#include <stdint.h>
#include <sys/stat.h>
#include <fcntl.h>
int main(int argc, char **argv)
{
printf("XBter!\n");
int fd, i;
uint8_t *mem;
struct stat st;
char *StringTable, *interp;
Elf32_Ehdr *ehdr;//ELF file header
Elf32_Phdr *phdr;//program header
Elf32_Shdr *shdr;//section header
if(argc < 2){
printf("Usage: %s <executable>\n", argv[0]);
exit(0);
}
if((fd = open(argv[1], O_RDONLY)) < 0){
perror("open");
exit(-1);
}
if(fstat(fd, &st) < 0){
perror("fstat");
exit(-1);
}
/* Map the executable into memory */
mem = mmap(NULL, st.st_size, PROT_READ, MAP_PRIVATE, fd, 0);
if(mem == MAP_FAILED){/*mmap may have many fuctions. I read*/
perror("mmap");/*a blog, share memory, Changing disk to*/
exit(-1);/*memory when memory is not enough may all use*/
}/*mmap(). This is real mapping. This is the blog address: */
/*https://blog.csdn.net/yanerhao/article/details/77829191 */
/**
* The initial ELF Header starts at offset 0
* of our mapped memory
*/
ehdr = (Elf32_Ehdr*)mem;
/**
* The shdr table and phdr table offsets are
* given by e_shoff and e_phoff members of the
* ELF32_Ehdr
*/
phdr = (Elf32_Phdr*)&mem[ehdr->e_phoff];
shdr = (Elf32_Shdr*)&mem[ehdr->e_shoff];
/**
* Check to see if the ELF magic (The first 4 bytes)
* match up as 0x7f E L F (0x7f 45 4c 46)
*/
if(mem[0] != 0x7f && strcmp(&mem[1], "ELF")){
fprintf(stderr, "%s is not an ELF file\n", argv[1]);
exit(-1);
}
/**
* We are only parsing executables with this code
* so ET_EXEC marks an executables.
*/
if(ehdr->e_type != ET_EXEC){
fprintf(stderr, "%s is not an executable\n", argv[1]);
exit(-1);
}
printf("Program Entry point: 0x%x\n", ehdr->e_entry);
/**
* We find the string table for the section header
* names with e_shstrndx which gives the index of
* which section holds the string table.
* e_shstrndx is the index of section .shstrtab
* .shstrtab holds all sections's name, just a strings
* ending with /0.
*/
StringTable = &mem[shdr[ehdr->e_shstrndx].sh_offset];
/**
* Print each section header name and address.
* Notice we get the index into the string table
* that contains each section header name with
* the shdr.sh_name member.
*/
printf("Section header total:%d list:\n\n", ehdr->e_shnum);
for(i = 0; i < ehdr->e_shnum; i++)
printf("[%d]%s: 0x%x\n", i, &StringTable[shdr[i].sh_name], shdr[i].sh_addr);
/**
* Print out each segment name, and address.
* Except for PT_INTERP we print the path to
* the dynamic linker (Interpreter).
*/
printf("\nProgram header total:%d list:\n\n", ehdr->e_phnum);
for(i = 0; i < ehdr->e_phnum; i++){
// printf("phdr[%d].p_type=%d\n", i, phdr[i].p_type);
switch(phdr[i].p_type){
case PT_LOAD:
/**
* We know that text segment starts
* at offset 0. And only one other
* possible loadable segment exists
* which is the data segment.
*/
if(phdr[i].p_offset == 0)
printf("Text segment: 0x%x\n", phdr[i].p_vaddr);
else
printf("Data segment: 0x%x\n",phdr[i].p_vaddr);
break;
case PT_INTERP:
interp = strdup((char*)&mem[phdr[i].p_offset]);
printf("Interpreter: %s\n", interp);
break;
case PT_NOTE:
printf("Note segment: 0x%x\n", phdr[i].p_vaddr);
break;
case PT_DYNAMIC:
printf("Dynamic segment: 0x%x\n", phdr[i].p_vaddr);
break;
case PT_PHDR:
printf("Phdr segment: 0x%x\n", phdr[i].p_vaddr);
break;
}
}
exit(0);
}