1. 程式人生 > >Mach-O檔案格式說明及從中提取機器指令

Mach-O檔案格式說明及從中提取機器指令

https://blog.csdn.net/kakukemeit/article/details/38734647

Mach-O 可執行檔案

http://objccn.io/issue-6-3/

 

Mach-O檔案格式說明及從中提取機器指令

http://hi.baidu.com/sangwf/item/5824a73b51b6f5f62784f40d

 

 

在寫一個簡單的作業系統,boot部分都是用匯編實現,但繼續實現核心,再用匯編就太累了,是時候轉向C了。在《Linux核心完全剖析》及《自己動手寫作業系統》中,C語言的編譯都是在Linux下完成的,輸出的目標檔案及執行程式是ELF格式的,而在Mac OS下,輸出格式是Mach-O的。如何從mach-o檔案中提取機器指令,網上的文章很稀少。而我又不想直接再裝個linux搞這個東西,加上對Mac OS感興趣,藉機研究一下Mach-O執行程式的檔案格式。

 

首先,你要了解執行程式只是一種特殊的格式,和word文件、excel文件,並沒有太大的區別。只是執行程式能夠被載入器所載入到作業系統中,建立專門的程序,並執行程式中的指令。而對於word文件之類的,是先打開了word執行程式,然後讀入了文件內容。

 

對於Mach-O檔案的格式,可以直接在apple官方文章下載,在google搜尋“Mach-O File Format”即可,備註裡有連結。由於說明文件是英文的,理解起來比較痛苦,再加上裡面的圖畫的少了一個關鍵的資訊,導致理解上可能存在問題。另外,關於load_command結構體的說明,看了之後可能也會產生歧義。因此,我決定寫一篇,試圖快速將清楚Mach-O的基本格式。

 

一個Mach-O檔案的內容可以通過下圖來表示:

 

首先是一個mach_header結構體,描述了整個檔案的基本資訊。(如果是64位執行程式,則是mach_header_64,兩者差別不大)。mach_header結構體的定義是:

struct mach_header

{

        uint32_t magic;

       cpu_type_t cputype;

       cpu_subtype_t cpusubtype;

       uint32_t filetype;

       uint32_t ncmds;

       uint32_t sizeofcmds;

       uint32_t flags;

};

 

其中magic用來標識執行程式的型別,比如區分是32位或64位。另一個重要引數是ncmds,說明後續有多少個command。

 

緊接著mach_header,就是連續的這些load command。這些load command基本描述了具體的一些段資訊,以及各種在系統中的載入特性。command種類很多,想詳細瞭解,就看參考手冊吧。每一個command都具有不同的結構體格式,在前兩個欄位都是相同的。為了方便程式設計,定義了一個結構體:

struct load_command

{

       uint32_t cmd;

       uint32_t cmdsize;

};

 

cmd表示command編號,cmdsize表示這個command的實際長度。注意,這個結構體並不是獨立存在的,它只是為了表示方便,真正的command的後面還有一部分內容。比如segment_command,其結構體是:

struct segment_command

{

       uint32_t cmd;

       uint32_t cmdsize;

       char segname[16];

       uint32_t vmaddr;

       uint32_t vmsize;

       uint32_t fileoff;

       uint32_t filesize;

       vm_prot_t maxprot;

       vm_prot_t initprot;

       uint32_t nsects;

       uint32_t flags;

};

 

看到了吧?前面兩個欄位就是一個load_command。

 

對於segment_command,緊接著就存放了segment_command所包含的section的說明。這也是官方文件的結構圖上所沒有畫清楚的,竟然將這個資訊省略了,看起來會雲裡霧裡的。其中的nsects說明了後面跟了幾個section。section結構體的定義是:

struct section

{

       char sectname[16];

       char segname[16];

       uint32_t addr;

       uint32_t size;

       uint32_t offset;

       uint32_t align;

       uint32_t reloff;

       uint32_t nreloc;

       uint32_t flags;

       uint32_t reserved1;

       uint32_t reserved2;

};

 

最重要的定義是size和offset。size表示這個section的內容的長度,而offset表示在執行程式中的檔案偏移。通過這兩個資訊,我們對於機器指令的section,就能讀出機器指令了。

 

實際上,Mac OS下有一個工具叫otool,就能讀取出Mach-O檔案各種資訊,類似於linux的objdump,可以檢視ELF的各種資訊。比如otool -t -v filename,就能打印出執行程式的機器指令。我也寫了一個程式,用於提取機器指令,見:https://github.com/sangwf/walleos/blob/master/tools/macho_parse.c

 

通過這個程式,我成功將機器指令提取,並在walleos下成功呼叫。

 

會對這篇文章感興趣的人可能比較少,隨著mac os的普及,我相信越來越多。

 

注1:

Mach-O的格式文件見:https://developer.apple.com/library/mac/documentation/DeveloperTools/Conceptual/MachORuntime/Mach-O_File_Format.pdf