linux 可执行文件_linux中ELF二进制程序解析
0. 簡介
在Linux系統的可執行文件(ELF文件)中,開頭是一個文件頭,用來描述程序的布局,整個文件的屬性等信息,包括文件是否可執行、靜態還是動態鏈接及入口地址等信息;如下圖所示:
程序文件中包含了程序頭,程序的入口地址等信息不需要寫死,調用代碼可以通用,根據實際情況加載;此時的文件不是純碎的二進制可執行文件了,因為包含的程序頭不是可執行代碼;將這種包含程序頭的文件讀入到內存后,從程序頭中讀取入口地址,跳轉到入口地址執行;
0.1 文件格式
Linux環境中,目標文件是源代碼編譯后未鏈接的中間文件,如:gcc編譯生成的.o文件;可執行文件(.o)、動態鏈接庫(.so)、靜態鏈接庫(.a)文件都是按照ELF可執行文件格式存儲的;
ELF指:Executable and Linkable Format,可執行鏈接格式;本文中的目標文件指各種類型符合ELF規范的我呢見,如:二進制可執行文件、Linux下的.o目標文件和.so動態庫文件;
可執行文件(Executable file):經過編譯鏈接后,可以直接執行的程序文件,如:ELF文件;
共享目標文件(Shared object file):動態鏈接庫,在可執行文件被加載的過程中動態鏈接,成為程序代碼的一部分;
可重定位文件(Relocatable file):可重定位文件即目標文件和靜態庫文件,是源文件編譯后但未完成鏈接的半成品,被用于與其他目標文件合并鏈接,以構建出二進制可執行文件;
核心轉儲文件(Core dump file):當進程意外終止時,系統可以將該進程的地址空間的內容及終止時的一些信息轉儲到核心轉儲文件;
0.2 段和節
程序中的段(Segment)和節(Secton)是真正的程序體;段包括代碼段和數據段等,段是由節組成的,多個節經過鏈接后被合并為一個段;
段和節的信息用header描述,程序頭是program header,節頭是section header;段和節的大小和數量都是不固定的,需要用專門的數據結構來描述,即程序頭表(program header table)和節頭表(section header table),這是兩個數組,元素分別是程序頭(program header)和節頭(section header);程序頭表(program header table)中的元素全是程序頭(program header),節頭表(section header table)中的元素全是節頭(section header);程序頭表是用來描述段(Segment)的,成為段頭表;段是程序本身的組成部分;
由于段和節的大小和數量都是不固定的,程序頭表和節頭表的大小也不固定,兩個表在程序文件中的位置也不固定;需要在一個固定的位置,用一個固定大小的數據結構來描述程序頭表和節頭表的大小和位置信息,即位于文件最開始部分的ELF header;
1. ELF header
ELF目標文件的最開始是ELF文件頭(ELF Header),包含了描述整個文件的基本屬性;ELF文件分為文件頭和文件體兩部分;先用ELF header從文件全局概要出程序中程序頭表、節頭表的位置和大小等信息;然后從程序頭表和節頭表中分別解析出各個段和節的位置和大小等信息;
可執行文件和待重定位文件,文件最開頭的部分是ELF header;程序頭表對于可執行文件是必須的,而對于待重定位文件是可選的;
ELF文件頭在32位系統中用Elf32_Ehdr結構體表示,在64位系統中用Elf64_Ehdr結構體表示;兩者內容是一樣的,區別是有些成員的大小不同;
// include/uapi/linux/elf.h #define EI_NIDENT 16 ? typedef struct elf32_hdr{unsigned char e_ident[EI_NIDENT];Elf32_Half e_type;Elf32_Half e_machine;Elf32_Word e_version;Elf32_Addr e_entry; /* Entry point */Elf32_Off e_phoff;Elf32_Off e_shoff;Elf32_Word e_flags;Elf32_Half e_ehsize;Elf32_Half e_phentsize;Elf32_Half e_phnum;Elf32_Half e_shentsize;Elf32_Half e_shnum;Elf32_Half e_shstrndx; } Elf32_Ehdr; ? typedef struct elf64_hdr {unsigned char e_ident[EI_NIDENT]; /* ELF "magic number" */Elf64_Half e_type;Elf64_Half e_machine;Elf64_Word e_version;Elf64_Addr e_entry; /* Entry point virtual address */Elf64_Off e_phoff; /* Program header table file offset */Elf64_Off e_shoff; /* Section header table file offset */Elf64_Word e_flags;Elf64_Half e_ehsize;Elf64_Half e_phentsize;Elf64_Half e_phnum;Elf64_Half e_shentsize;Elf64_Half e_shnum;Elf64_Half e_shstrndx; } Elf64_Ehdr;e_ident[EI_NIDENT]是16字節大小的數組,用來表示ELF字符等信息,開頭四個字節是固定不變的elf文件魔數,0x7f 0x45 0x4c 0x46;
e_type:2字節,用來指定ELF目標文件的類型;
// include/uapi/linux/elf.h /* These constants define the different elf file types */ #define ET_NONE 0 // 未知目標文件格式 #define ET_REL 1 // 可重定位文件 #define ET_EXEC 2 // 可執行文件 #define ET_DYN 3 // 動態共享目標文件 #define ET_CORE 4 // core文件,程序崩潰時內存映像的轉儲格式 #define ET_LOPROC 0xff00 // 特定處理器文件的擴展下界 #define ET_HIPROC 0xffff // 特定處理器文件的擴展上界e_machine:2字節,用來描述ELF目標文件的體系結構類型,即要在哪種硬件平臺運行;
// include/uapi/linux/elf-em.h /* These constants define the various ELF target machines */ #define EM_NONE 0 #define EM_M32 1 #define EM_SPARC 2 #define EM_386 3 #define EM_68K 4 #define EM_88K 5 #define EM_486 6 /* Perhaps disused */ #define EM_860 7 #define EM_MIPS 8 /* MIPS R3000 (officially, big-endian only) *//* Next two are historical and binaries andmodules of these types will be rejected byLinux. */ #define EM_MIPS_RS3_LE 10 /* MIPS R3000 little-endian */ #define EM_MIPS_RS4_BE 10 /* MIPS R4000 big-endian */ ? #define EM_PARISC 15 /* HPPA */ #define EM_SPARC32PLUS 18 /* Sun's "v8plus" */ #define EM_PPC 20 /* PowerPC */ ......2. 程序頭表
程序頭表(也稱為段表)是一個描述文件中各個段的數組,程序頭表描述了文件中各個段在文件中的偏移位置及段的屬性等信息;從程序頭表里可以得到每個段的所有信息,包括代碼段和數據段等;各個段的內容緊跟ELF文件頭保存;程序頭表中各個段用Elf32_Phdr或Elf64_Phdr結構體表示;
// include/uapi/linux/elf.h typedef struct elf32_phdr{Elf32_Word p_type;Elf32_Off p_offset;Elf32_Addr p_vaddr;Elf32_Addr p_paddr;Elf32_Word p_filesz;Elf32_Word p_memsz;Elf32_Word p_flags;Elf32_Word p_align; } Elf32_Phdr; ? typedef struct elf64_phdr {Elf64_Word p_type;Elf64_Word p_flags;Elf64_Off p_offset; /* Segment file offset */Elf64_Addr p_vaddr; /* Segment virtual address */Elf64_Addr p_paddr; /* Segment physical address */Elf64_Xword p_filesz; /* Segment size in file */Elf64_Xword p_memsz; /* Segment size in memory */Elf64_Xword p_align; /* Segment alignment, file & memory */ } Elf64_Phdr;Elf32_Phdr或Elf64_Phdr結構體用來描述位于磁盤上的程序中的一個段;
p_type:4字節,用來指明程序中該段的類型;
// include/uapi/linux/elf.h /* These constants are for the segment types stored in the image headers */ #define PT_NULL 0 // 忽略 #define PT_LOAD 1 // 可加載程序段 #define PT_DYNAMIC 2 // 動態鏈接信息 #define PT_INTERP 3 // 動態加載器名稱 #define PT_NOTE 4 // 輔助的附加信息 #define PT_SHLIB 5 // 保留 #define PT_PHDR 6 // 程序頭表 #define PT_TLS 7 /* Thread local storage segment */ #define PT_LOOS 0x60000000 /* OS-specific */ #define PT_HIOS 0x6fffffff /* OS-specific */ #define PT_LOPROC 0x70000000 #define PT_HIPROC 0x7fffffffp_flags:4字節,用來指明與本段相關的標志;
// include/uapi/linux/elf.h /* These constants define the permissions on sections in the program header, p_flags. */ #define PF_R 0x4 // 可讀 #define PF_W 0x2 // 可寫 #define PF_X 0x1 // 可執行3. 節頭表
節頭表中各個節用Elf32_Shdr或Elf64_Shdr結構體表示;
// include/uapi/linux/elf.h typedef struct elf32_shdr {Elf32_Word sh_name;Elf32_Word sh_type;Elf32_Word sh_flags;Elf32_Addr sh_addr;Elf32_Off sh_offset;Elf32_Word sh_size;Elf32_Word sh_link;Elf32_Word sh_info;Elf32_Word sh_addralign;Elf32_Word sh_entsize; } Elf32_Shdr; ? typedef struct elf64_shdr {Elf64_Word sh_name; /* Section name, index in string tbl */Elf64_Word sh_type; /* Type of section */Elf64_Xword sh_flags; /* Miscellaneous section attributes */Elf64_Addr sh_addr; /* Section virtual addr at execution */Elf64_Off sh_offset; /* Section file offset */Elf64_Xword sh_size; /* Size of section in bytes */Elf64_Word sh_link; /* Index of another section */Elf64_Word sh_info; /* Additional section information */Elf64_Xword sh_addralign; /* Section alignment */Elf64_Xword sh_entsize; /* Entry size if section holds table */ } Elf64_Shdr;Elf32_Shdr或Elf64_Shdr結構體用來描述位于磁盤上的程序中的一個節;
成員32位64位說明sh_name44節名稱,值是字符串的一個索引;節名稱字符串以'0'結尾,統一存儲在字符串表中,使用該字段存儲節名稱字符串在字符串表中的索引位置;sh_type44節的類型;sh_flags48節的標志;sh_addr48節在內存中的起始地址,指定節映射到虛擬地址空間中的位置;sh_offset48節在文件中的起始位置;sh_size48節的大小;sh_link44引用另一個節頭表項,根據節類型有不同的解釋;sh_info44節的附加信息,與sh_link聯合使用;sh_addralign48節數據在內存中的對齊方式;sh_entsize48指定節中各數據項的長度,各數據項長度要相同;
sh_name
節名稱sh_name的值是字符串的一個索引,節名稱字符串以'0'結尾,字符串統一存放在字符串表中,使用sh_name的值作為字符串表的索引,找到對應的字符串即為節名稱;
字符串表中包含多個以'0'結尾的字符串;在目標文件中,這些字符串通常是符號的名字或節的名字,需要引用某些字符串時,只需要提供該字符串在字符串表中的序號即可;
字符串表中的第一個字符串(序號為0)是空串,即'0',可以用于表示沒有名字或一個空的名字;如果字符串表為空,節頭中的sh_size值為0;
sh_type:節的類型;
// include/uapi/linux/elf.h /* sh_type */ #define SHT_NULL 0 // 表示該節是無效節頭,沒有對應的節 #define SHT_PROGBITS 1 #define SHT_SYMTAB 2 #define SHT_STRTAB 3 #define SHT_RELA 4 #define SHT_HASH 5 #define SHT_DYNAMIC 6 #define SHT_NOTE 7 #define SHT_NOBITS 8 #define SHT_REL 9 #define SHT_SHLIB 10 #define SHT_DYNSYM 11 #define SHT_NUM 12 #define SHT_LOPROC 0x70000000 #define SHT_HIPROC 0x7fffffff #define SHT_LOUSER 0x80000000 #define SHT_HIUSER 0xffffffffsh_flags:節的標志;
// include/uapi/linux/elf.h /* sh_flags */ #define SHF_WRITE 0x1 // 可寫 #define SHF_ALLOC 0x2 // 該節包含內存在進程運行時需要占用內存單元 #define SHF_EXECINSTR 0x4 // 該節內容是指令代碼 #define SHF_MASKPROC 0xf00000004. readelf命令
readelf命令用于查看ELF格式的文件信息;
$ readelf -H Usage: readelf <option(s)> elf-file(s)Display information about the contents of ELF format filesOptions are:-a --all Equivalent to: -h -l -S -s -r -d -V -A -I-h --file-header Display the ELF file header-l --program-headers Display the program headers--segments An alias for --program-headers-S --section-headers Display the sections' header--sections An alias for --section-headers-e --headers Equivalent to: -h -l -S......命令如下:
$ readelf -h file // 顯示ELF文件頭 $ readelf -l file // 顯示所有的程序頭 $ readelf -S file // 顯示所有的節頭 $ readelf -e file // 顯示ELF文件頭、程序頭、節頭全部的頭信息,相當于同時運行參數-h、-l、-S5. xxd命令
由于ELF文件是二進制文件,和普通文件不同,不能使用一般的vim等編輯工具直接打開;而xxd命令主要用來查看文件的十六進制格式,同樣也能夠以二進制格式查看文件;
$ man xxd NAMExxd - make a hexdump or do the reverse.SYNOPSISxxd -h[elp]xxd [options] [infile [outfile]] ......xxd命令常用的選項:
-a: 自動跳過空白內容,默認關閉; -u: 使用大寫字符的十六進制,默認小寫; -c: 后邊加上數字,表示每行顯示多少字節的十六進制數,默認16字節,最大256字節; -g: 設定以多少個字節為一塊,默認2字節; -l: 顯示多少字節內容; -b: 表示以二進制的形式查看文件內容; -s: 后面接[+-]和address,+表示從address處開始的內容,-表示從距末尾address處開始的內容;6. 總結
在Linux系統的可執行文件(ELF文件)中,開頭是一個文件頭,用來描述程序的布局,整個文件的屬性等信息,包括文件是否可執行、靜態還是動態鏈接及入口地址等信息;生成的文件不是純碎的二進制可執行文件了,因為包含的程序頭不是可執行代碼;將這種包含程序頭的文件讀入到內存后,從程序頭中讀取入口地址,跳轉到入口地址執行;可以參考《linux中ELF二進制程序解析》,使用中的實例可以參考《linux中ELF二進制文件解析----ELF文件實例解析》;
參考資料
《操作系統真象還原》
程序編譯-匯編-鏈接的理解!—03-ELF頭和節頭表
ELF文件-節和節頭
回到目錄
《新程序員》:云原生和全面數字化實踐50位技術專家共同創作,文字、視頻、音頻交互閱讀總結
以上是生活随笔為你收集整理的linux 可执行文件_linux中ELF二进制程序解析的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: lwip协议栈优化_干货分享 | KNI
- 下一篇: 里计算两个数的总和_2个公式,在Exce