ELF文件格式的详解
-
1.說明
-
2.elf文件的基本格式
-
3.elf文件的頭部信息
-
4.elf文件的節(jié)區(qū)(Section)
-
4.1 節(jié)區(qū)的作用
-
4.2 節(jié)區(qū)的組成
-
-
5.elf文件的段(Segment)
-
6.用python解析elf文件
-
7.總結
1.說明
ELF的英文全稱是The Executable and Linking Format,最初是由UNIX系統(tǒng)實驗室開發(fā)、發(fā)布的ABI(Application Binary Interface)接口的一部分,也是Linux的主要可執(zhí)行文件格式。
從使用上來說,主要的ELF文件的種類主要有三類:
-
可執(zhí)行文件(.out):Executable File,包含代碼和數據,是可以直接運行的程序。其代碼和數據都有固定的地址 (或相對于基地址的偏移 ),系統(tǒng)可根據這些地址信息把程序加載到內存執(zhí)行。
-
可重定位文件(.o文件):Relocatable File,包含基礎代碼和數據,但它的代碼及數據都沒有指定絕對地址,因此它適合于與其他目標文件鏈接來創(chuàng)建可執(zhí)行文件或者共享目標文件。
-
共享目標文件(.so):Shared Object File,也稱動態(tài)庫文件,包含了代碼和數據,這些數據是在鏈接時被鏈接器(ld)和運行時動態(tài)鏈接器(ld.so.l、libc.so.l、ld-linux.so.l)使用的。
本文主要從elf文件的組成構造的角度來進行分析,將elf文件的解析通過一步一步的分析得到里面的信息,同時通過python腳本解析,可以直觀的看到文件的信息,通過本文的閱讀,將對elf文件格式有著更加深刻的理解。
2.elf文件的基本格式
elf文件是有一定的格式的,從文件的格式上來說,分為匯編器的鏈接視角與程序的執(zhí)行視角兩種去分析ELF文件。
從程序執(zhí)行視角來說,這就是Linux加載器加載的各種Segment的集合。比如只讀代碼段、數據的讀寫段、符號段等等。而從鏈接的視角上來看,elf又分為各種的sections。
注意Section Header Table和Program Header Table并不是一定要位于文件開頭和結尾的,其位置由ELF Header指出,上圖這么畫只是為了清晰。
為了徹底的弄清楚elf文件的內容,可以先從ELF文件的頭部開始分析。
3.elf文件的頭部信息
對于elf頭部文件信息,首先可以可以查看一下內存的布局情況:
根據readelf可以得到該文件的頭部信息的情況。
根據定義,elf32的結構體定義,在Linux上可以在/usr/include/elf.h中找到
#define?EI_NIDENT?(16)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;上述的Elf32_Half定義
/*?Type?for?a?16-bit?quantity.??*/ typedef?uint16_t?Elf32_Half;其中Elf32_Word的定義
/*?Types?for?signed?and?unsigned?32-bit?quantities.??*/ typedef?uint32_t?Elf32_Word;然后Elf32_Addr與Elf32_Off定義
/*?Type?of?addresses.??*/ typedef?uint32_t?Elf32_Addr;/*?Type?of?file?offsets.??*/ typedef?uint32_t?Elf32_Off;有了這些數據結構的信息,然后對應具體的數據細節(jié)如下:
-
e_ident[EI_NIDENT]
文件的標識以及標識描述了elf如何編碼等信息。
Magic:?? 7f 45 4c 46 01 01 01 00?00?00?00?00?00?00?00?00關于該結構體的索引可以看下面的表格:
| EI_MAG0 | 0 | 文件標識(0x7f) |
| EI_MAG1 | 1 | 文件標識(E) |
| EI_MAG2 | 2 | 文件標識(L) |
| EI_MAG3 | 3 | 文件標識(F) |
| EI_CLASS | 4 | 文件類 |
| EI_DATA | 5 | 數據編碼 |
| EI_VERSION | 6 | 文件版本 |
| EI_PAD | 7 | 補齊字節(jié)開始處 |
| EI_NIDENT | 16 | e_ident[]大小 |
EI_CLASS的內容,當取值為0時,是非法類別,1是32位的目標,2是64位的目標。這里是1所以程序是32位的目標。
EI_DATA表示數據的編碼,當為0時,表示非法數據編碼,1表示高位在前,2表示低位在前。
EL_VERSION表示了elf的頭部版本號碼。
前面四個基本上確定的,內容第一個字符為7f,后面用ELF字符串表示該文件為ELF格式。
-
e_type
該數據類型是uint16_t數據類型的,占兩個字節(jié)。通過字段查看,可以看到這個值為00 02。表格定義如下:
| ET_NONE | 0x0000 | 未知目標文件格式 |
| ET_ERL | 0x0001 | 可重定位文件 |
| ET_EXEC | 0x0002 | 可執(zhí)行文件 |
| ET_DYN | 0x0003 | 共享目標文件 |
| ET_CORE | 0x0004 | Core文件(轉儲格式) |
| ET_LOPROC | 0xff00 | 特定處理器文件 |
| ET_HIPROC | 0xffff | 特定處理器文件 |
對應表格內容,可以看到類型為EXEC即可執(zhí)行文件類型。
-
e_machine
由字段可以看到為00 28,關于這個字段的解析,基本上就是表示該elf文件是針對哪個處理器架構的。
下面只列出幾個常見的架構的序號
| EM_NONE | 0 | No machine |
| EM_SPARC | 2 | SPARC |
| EM_386 | 3 | Intel 80386 |
| EM_MIPS | 8 | MIPS I Architecture |
| EM_PPC | 0x14 | PowerPC |
| EM_ARM | 0x28 | Advanced RISC Machines ARM |
通過上述的表格,可以看到該架構是ARM處理器上運行的程序。
-
e_version
該字段占四個字節(jié),表示當前文件版本的信息。現在取值為00 00 00 01。從取值上來看
| EV_NONE | 0 | 非法版本 |
| EV_CURRENT | 1 | 當前版本 |
-
e_entry
這里表示程序的入口地址,目前為四字節(jié),所以通過字段解析到的內容為00 00 80 00。得到可執(zhí)行程序的入口地址為0x8000。
-
e_phoff
該字段表示程序表頭偏移。占四個字節(jié),根據字段解析,可以查看當前的偏移量為00 00 00 34。也就是實際的偏移量為52個字節(jié)。這52個字節(jié)其實就是頭部的信息數據結構體的大小。
-
e_shoff
該區(qū)域比較重要,記錄了section的偏移地址。為四字節(jié),解析出來的字段為0x00 04 24 5c。所以得到地址為0x4245c。
根據這個偏移得到section的內容:
通過readelf -t也可以得到類似的結果。
關于節(jié)區(qū)如何解析。后面再進行描述。
-
e_flags
特定處理器格式的標志,這里的字段解析為05 00 02 00。與特定的處理器相關。
-
e_ehsize
elf文件的頭部大小。該取值與頭文件結構體的大小相關,目前為52字節(jié),即00 34。
-
e_phentsize
程序頭部表項大小,當前取值為00 20,為32個字節(jié),這里表示
關于程序表項的解析,后面再進行具體分析。
-
e_phnum
目前取值為00 01,這里表示程序頭的個數當前只有一個程序頭,如果有多個程序頭表,那么會在elf頭文件之后,也就是52個字節(jié)之后,依次向下排列。因為這里是1,所以只有1個程序頭。
-
e_shentsize
表示節(jié)區(qū)頭部表格大小,解析字段為00 28,也就是第一個節(jié)區(qū)的大小為40個字節(jié)的偏移處。根據e_shoff可以知道。
將從e_shoff的區(qū)域向后面偏移40個字節(jié),得到第一個節(jié)區(qū)的內容。
-
e_shnum
節(jié)區(qū)的數量,由字段解析得到數據為00 11。此時得到節(jié)區(qū)的數量為17個。通過readelf -t也可以解析到節(jié)區(qū)的數量為17個。
bigmagic@bigmagic:~/work/python_elf/elf$?readelf?-t?rtthread.elf? There?are?17?section?headers,?starting?at?offset?0x4245c:-
e_shstrndx
標記字符串節(jié)區(qū)的索引。當前的解析為00 0e。也就是14個節(jié)區(qū)為字符節(jié)區(qū)。
到這里,頭部信息的相關字段就解析完成了。
4.elf文件的節(jié)區(qū)(Section)
elf文件中的節(jié)是從編譯器鏈接角度來看文件的組成的。從鏈接器的角度上來看,包括指令、數據、符號以及重定位表等等。
4.1 節(jié)區(qū)的作用
在可從定位的可執(zhí)行文件中,節(jié)區(qū)描述了文件的組成,節(jié)的位置等信息。通過readelf -s可以查看信息。
這些節(jié)信息通過特定的地址偏移組成了一個elf文件的整體。
4.2 節(jié)區(qū)的組成
關于理解ELF中的Section。首先需要知道程序的鏈接視圖,在編譯器將一個一個.o文件鏈接成一個可以執(zhí)行的elf文件的過程中,同時也生成了一個表。這個表記錄了各個Section所處的區(qū)域。在程序中,程序的section header有多個,但是大小是一樣。拿elf32文件來說
typedef?struct {Elf32_Word?sh_name;??/*?Section?name?(string?tbl?index)?*/Elf32_Word?sh_type;??/*?Section?type?*/Elf32_Word?sh_flags;??/*?Section?flags?*/Elf32_Addr?sh_addr;??/*?Section?virtual?addr?at?execution?*/Elf32_Off?sh_offset;??/*?Section?file?offset?*/Elf32_Word?sh_size;??/*?Section?size?in?bytes?*/Elf32_Word?sh_link;??/*?Link?to?another?section?*/Elf32_Word?sh_info;??/*?Additional?section?information?*/Elf32_Word?sh_addralign;??/*?Section?alignment?*/Elf32_Word?sh_entsize;??/*?Entry?size?if?section?holds?table?*/ }?Elf32_Shdr;根據e_shoff可以找到section的地址,根據e_shentsize可以找到具體的第一個section的內容。
如果要找到每個段的具體細節(jié),首先可以根據e_shstrndx找到節(jié)的字段。由于e_shstrndx=14。而且每個為40字節(jié)。那么一共是560字節(jié)的偏移。從e_shoff的地址0x4245c開始,首先偏移了e_shentsize也就是40個字節(jié)。然后向下得到40x14個Section表項。最后可以得到e_shstrndx對應的節(jié)區(qū)。
為什么首先需要得到這個字符串節(jié)區(qū),通過這個就可以得到節(jié)區(qū)的名字了。然后通過計算,節(jié)區(qū)字符串存在的區(qū)域:
每個字符串以\0結尾。大小為0000ab也就是171個字節(jié)。接下來我們來舉個具體的例子來解析Section。比如要讀取.text的段。那么首先看一下細節(jié)。
首先從字段結構體上進行分析:
-
sh_name
表示從e_shstrndx的偏移地址開始,得到的字符字符串信息為該段的名字。目前解析到的為0x1b。最后算出得到實際的名稱為.text。
-
sh_type
字段的類型為01,關于sh_type的類型,解析如下:
/*?Legal?values?for?sh_type?(section?type).??*/#define?SHT_NULL????????????0????????/*?Section?header?table?entry?unused?*/ #define?SHT_PROGBITS????????1????????/*?Program?data?*/ #define?SHT_SYMTAB??????????2????????/*?Symbol?table?*/ #define?SHT_STRTAB??????????3????????/*?String?table?*/ #define?SHT_RELA????????????4????????/*?Relocation?entries?with?addends?*/ #define?SHT_HASH????????????5????????/*?Symbol?hash?table?*/ #define?SHT_DYNAMIC?????????6????????/*?Dynamic?linking?information?*/ #define?SHT_NOTE????????????7????????/*?Notes?*/ #define?SHT_NOBITS??????????8????????/*?Program?space?with?no?data?(bss)?*/ #define?SHT_REL?????????????9????????/*?Relocation?entries,?no?addends?*/ #define?SHT_SHLIB??????????10????????/*?Reserved?*/ #define?SHT_DYNSYM?????????11????????/*?Dynamic?linker?symbol?table?*/ #define?SHT_INIT_ARRAY?????14????????/*?Array?of?constructors?*/ #define?SHT_FINI_ARRAY?????15????????/*?Array?of?destructors?*/ #define?SHT_PREINIT_ARRAY??16????????/*?Array?of?pre-constructors?*/ #define?SHT_GROUP??????????17????????/*?Section?group?*/ #define?SHT_SYMTAB_SHNDX???18????????/*?Extended?section?indeces?*/ #define????SHT_NUM?????????19????????/*?Number?of?defined?types.??*/ #define?SHT_LOOS???????????0x60000000????/*?Start?OS-specific.??*/ #define?SHT_GNU_ATTRIBUTES?0x6ffffff5????/*?Object?attributes.??*/ #define?SHT_GNU_HASH???????0x6ffffff6????/*?GNU-style?hash?table.??*/ #define?SHT_GNU_LIBLIST????0x6ffffff7????/*?Prelink?library?list?*/ #define?SHT_CHECKSUM???????0x6ffffff8????/*?Checksum?for?DSO?content.??*/ #define?SHT_LOSUNW?????????0x6ffffffa????/*?Sun-specific?low?bound.??*/ #define?SHT_SUNW_move??????0x6ffffffa #define?SHT_SUNW_COMDAT????0x6ffffffb #define?SHT_SUNW_syminfo???0x6ffffffc #define?SHT_GNU_verdef?????0x6ffffffd????/*?Version?definition?section.??*/ #define?SHT_GNU_verneed????0x6ffffffe????/*?Version?needs?section.??*/ #define?SHT_GNU_versym?????0x6fffffff????/*?Version?symbol?table.??*/ #define?SHT_HISUNW?????????0x6fffffff????/*?Sun-specific?high?bound.??*/ #define?SHT_HIOS???????????0x6fffffff????/*?End?OS-specific?type?*/ #define?SHT_LOPROC?????????0x70000000????/*?Start?of?processor-specific?*/ #define?SHT_HIPROC?????????0x7fffffff????/*?End?of?processor-specific?*/ #define?SHT_LOUSER?????????0x80000000????/*?Start?of?application-specific?*/ #define?SHT_HIUSER?????????0x8fffffff????/*?End?of?application-specific?*/當前為1,所以得到數據為程序數據。比如.text .data .rodata等等。
-
sh_flags
表示段的標志,A表示分配的內存、AX表示分配可執(zhí)行、WA表示分配內存并且可以修改。
-
sh_addr
加載后程序段的虛擬地址
-
sh_offset
表示段在文件中的偏移。
-
sh_size
段的長度
-
sh_addralign
段對齊
-
sh_entsize
每項固定的大小
5.elf文件的段(Segment)
關于Linking View與Execution View的具體含義,可以查看
http://www.skyfree.org/linux/references/ELF_Format.pdf這里有一張圖值得研究一下:
對于鏈接視圖,也就是我們前面分析的Section,可以理解目標代碼文件的內容布局。而右邊的ELF的執(zhí)行視圖,則可以理解為可執(zhí)行的文件內容布局。鏈接視圖由sections組成,而可執(zhí)行的文件的內容由segment組成。
兩者是有一些區(qū)別的,我們平時在進行程序構建的時候理解的.text、.bss、.data段,這些都是section,也就節(jié)區(qū)的概念。這些段通過section header table進行組織與重定位。
但是對于segment來說,程序代碼段、數據段是Segment。代碼段又可以分為.text,數據段又分為.data、.bss等。
通過readelf -l可以查看具體的可執(zhí)行文件的細節(jié)。
這里的信息和程序的加載直接相關。具體的elf文件加載過程這篇文章不會多說,后面會寫文章專門敘述。本文的目的是elf文件格式的解析過程。
6.用python解析elf文件
為了驗證上述的分析過程是否合理,可以通過python腳本來解析elf文件。得到elf文件相關的信息。目前采用的是python3進行解析。
第一步:程序組織
當前組織的程序分為三步:
1.校驗elf文件
2.顯示elf頭部信息
3.解析段信息
if?__name__?==?'__main__':file?=?sys.argv[1]verify_elf(file)display_elfhdr(file)display_sections(file)首先需要導入sys模塊import sys。
當程序執(zhí)行的時候輸入python elf_parse.py rtthread.elf就可以向下執(zhí)行了。
第二步:校驗elf
該函數的作用主要是校驗elf文件,并且將相關的信息存到字典里面。
elfhdr?=?{} def?verify_elf(filename):f?=?open(filename,'rb')elfident?=?f.read(16)magic?=?[i?for?i?in?elfident]if(?magic[0]?!=?127?or?magic[1]!=?ord('E')?or?magic[2]?!=?ord('L')?or?magic[3]?!=?ord('F')):print?("your?input?file?%s?not?a?elf?file"?%filename)returnelse:temp?=?f.read(struct.calcsize('2H5I6H'))temp?=?struct.unpack('2H5I6H',temp)global?elfhdrelfhdr['magic']?=?magicelfhdr['e_type']=?temp[0]elfhdr['e_machine']?=?temp[1]elfhdr['e_version']?=?temp[2]elfhdr['e_entry']?=?temp[3]elfhdr['e_phoff']?=?temp[4]elfhdr['e_shoff']?=?temp[5]elfhdr['e_flags']?=?temp[6]elfhdr['e_ehsize']?=?temp[7]elfhdr['e_phentsize']?=?temp[8]elfhdr['e_phnum']?=?temp[9]elfhdr['e_shentsize']?=?temp[10]elfhdr['e_shnum']?=?temp[11]elfhdr['e_shstrndx']?=?temp[12]f.close()校驗的方法是讀取前面的四個字節(jié),是否第一個字節(jié)為7E,后面為ELF字符,如果滿足,則表示為ELF文件。
后面是將數組進行了一個填充。
第三步:展示elf文件的頭部信息
def?display_elfhdr(elffile):global?elfhdrprint?("ELF?Header")magic?=?elfhdr['magic']print?("??Magic:??%d?%d?%d?%d?%d?%d?%d?%d?%d?%d?%d?%d?%d?%d?%d?%d"?%(magic[0]?,magic[1],magic[2],magic[3],magic[4],magic[5],magic[6],magic[7],magic[8],magic[9],magic[10],magic[11],magic[12],magic[13],magic[14],magic[15]))if?magic[4]?==?1?:print?("??Class:???????????????????????????ELF32")else:print?("??Class:???????????????????????????ELF64")if?magic[5]?==?1?:print?("??Data:????????????????????????????2's?complement,little?endian")else:print?("Data:??????????????????????????????2's?complement,bigendian")print?("??Version:?????????????????????????%d(current)"?%magic[6])if?magic[7]?==?0:os_abi?=?'System?V?ABI'elif?magic[7]==?1:os_abi?=?'HP-Ux?operating?system'elif?magic[7]?==?255:os_abi?=?'Standalone?(embedded)?application'print?("??OS/ABI:??????????????????????????%s"?%os_abi)print?("??ABI?Version:?????????????????????%d"?%magic[8])if?elfhdr['e_type']?==?0:type?=?'No?file?type'elif?elfhdr['e_type']?==?1:type?=?'Relocatable?object?file'elif?elfhdr['e_type']?==?2:type?=?'Executable?file'elif?elfhdr['e_type']?==?3:type?=?'Core?file'print?("??Type:????????????????????????????%s"?%type)print?("??Machine:?????????????????????????%d"?%elfhdr['e_machine'])print?("??Version:?????????????????????????0x%x"?%elfhdr['e_version'])print?("??Entry?point?address:?????????????0x%x"?%elfhdr['e_entry'])print?("??Start?of?program?headers:????????%d?(bytes?into?file)"?%elfhdr['e_phoff'])print?("??Start?of?section?headers:????????%d?(bytes?into?file)"?%elfhdr['e_shoff'])print?("??Flags:???????????????????????????0x%x"?%elfhdr['e_flags'])print?("??Size?of?this?header:?????????????%d?(bytes)"?%elfhdr['e_ehsize'])print?("??Size?of?program?headers:?????????%d?(bytes)"?%elfhdr['e_phentsize'])print?("??Number?of?program?headers:???????%d?"?%elfhdr['e_phnum'])print?("??Size?of?section?headers:?????????%d?(bytes)"?%elfhdr['e_shentsize'])print?("??Number?of?section?headers:???????%d"?%elfhdr['e_shnum'])print?("??Section?header?string?table?index:?%d"%elfhdr['e_shstrndx'])該函數主要是解析了elf頭部信息中對應的相關字節(jié),并且做了解析過程。
第四步:解析sections
在解析具體的段的時候,主要是利用地址偏移找到相關的符號表名稱,然后根據偏移算出細節(jié)。
def?display_sections(elffile):verify_elf(elffile)sections?=?[]global?elfhdrsec_start?=?elfhdr['e_shoff']sec_size?=?elfhdr['e_shentsize']f?=?open(elffile,'rb')f.seek(sec_start)for?i?in?range(0,elfhdr['e_shnum']):temp?=?f.read(sec_size)temp?=?struct.unpack('10I',temp)sec?=?{}sec['sh_name']?=?temp[0]sec['sh_type']?=?temp[1]sec['sh_flags']?=?temp[2]sec['sh_addr']?=?temp[3]sec['sh_offset']?=?temp[4]sec['sh_size']?=?temp[5]sec['sh_link']?=?temp[6]sec['sh_info']?=?temp[7]sec['sh_addralign']?=?temp[8]sec['sh_entsize']?=?temp[9]sections.append(sec)print?("There?are?%d?section?headers,starting?at?offset?0x%x:\n"?%(elfhdr['e_shnum'],sec_start))print?("Section?Headers:")print?("??[Nr]?Name???????????????Type????????????Address??????????Offset")print?("???????Size???????????????Entsize?????????Flags??Link??Info?Align")start?=?sections[elfhdr['e_shstrndx']]['sh_offset']for?i?in?range(0,elfhdr['e_shnum']):offset?=?start?+?sections[i]['sh_name']name?=?get_name(f,offset)type2str?=?['NULL','PROGBITS','SYMTAB','STRTAB','RELA','HASH','DYNAMIC','NOTE','NOBITS','REL','SHLIB','DYNSYM']flags?=?sections[i]['sh_flags']if?(flags?==?1):flagsstr?=?'W'elif?(flags?==?2):flagsstr?=?'A'elif?(flags?==?4):flagsstr?=?'X'elif?(flags?==?3):flagsstr?=?'W'?+?'A'elif?(flags?==?6):flagsstr?=?'A'?+??'X'elif?(flags?==?0x0f000000?or?flags?==?0xf0000000):flagsstr?=?'MS'else:flagsstr?=?''print?("??[%d]??%s??????????????%s?????????????%x?????????????%x"?%(i,str(name,encoding='utf-8'),type2str[sections[i]['sh_type']?&?0x7],sections[i]['sh_addr'],sections[i]['sh_addralign']))print?("??????%x???????????????????%x???????%s????????%d?????%d?????%x"?%(sections[i]['sh_size'],sections[i]['sh_entsize'],flagsstr,sections[i]['sh_link'],sections[i]['sh_info'],sections[i]['sh_addralign']))f.close() def?get_name(f,offset):name?=?b''f.seek(offset)while?1:c?=?f.read(1)if?c?==?b'\x00':breakelse:name?+=?creturn?name第五步:結果展示
上述過程如果無誤,那么可以看到解析出來的elf文件信息了。
這樣就完成了一個elf文件的解析過程。
7.總結
ELF文件經常的見到,但是要具體的分析ELF文件中所對應的具體的含義卻需要費一番功夫。本文主要通過對elf文件的構造、具體的含義以及如何去分析elf文件的角度,全面的進行elf文件格式的剖析。在程序鏈接、程序加載執(zhí)行上會有更多不一樣的理解
總結
以上是生活随笔為你收集整理的ELF文件格式的详解的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 会计 应用计算机,会计从业会计电算化:计
- 下一篇: 如何添加共享计算机用户,如何正确设置共享