這一節看看 luaI_undump1 是如何場景恢復的。
函數
/* ** load one chunk from a file. ** return list of functions found, headed by main, or NULL at EOF. */ TFunc* luaI_undump1(FILE* D) { while (1) { int c=getc(D); if (c==ID_CHUNK) { LoadChunk(D); return Main; } else if (c==EOF) return NULL; else lua_error("not a lua binary file"); } }
程序一開始,先檢查文件的第一個字符是不是 ID_CHUNK,若是是,Load 一個塊,返回主函數 Main。
這個 Main 在 LoadChunk(D) 裏被賦值了。
若是第一個字符不是 ID_CHUNK ,說明不是目標二進制文件,返回空。
其它狀況,出錯。
接着看看 LoadChunk。
lua
static void LoadChunk(FILE* D) { LoadHeader(D); while (1) { int c=getc(D); if (c==ID_FUN) LoadFunction(D); else { ungetc(c,D); break; } } }
回憶一下 dump 時的操做,這裏恰好是 dump 的逆過程。對比 dump 時的寫,這裏的讀就比較容易閱讀。
先讀全局的一些信息,和 DumpHeader 相對應。
先回憶一下 DumpHeader,代碼以下:
code
void DumpHeader(FILE* D) { Word w=TEST_WORD; float f=TEST_FLOAT; fputc(ID_CHUNK,D); fputs(SIGNATURE,D); fputc(VERSION,D); fwrite(&w,sizeof(w),1,D); fwrite(&f,sizeof(f),1,D); }
再看 LoadHeader
ip
static void LoadHeader(FILE* D) /* TODO: error handling */ { Word w,tw=TEST_WORD; float f,tf=TEST_FLOAT; LoadSignature(D); getc(D); /* skip version */ fread(&w,sizeof(w),1,D); /* test word */ if (w!=tw) { swapword=1; warn("different byte order"); } fread(&f,sizeof(f),1,D); /* test float */ if (f!=tf) { Byte* p=(Byte*)&f; /* TODO: need union? */ Byte t; swapfloat=1; t=p[0]; p[0]=p[3]; p[3]=t; t=p[1]; p[1]=p[2]; p[2]=t; if (f!=tf) /* TODO: try another perm? */ lua_error("different float representation"); else warn("different byte order in floats"); } }
是否是看起來差很少。
上來先讀一個 SIGNATURE,若是不是的話,出錯。undump 時的每一位是必須嚴格相等的,若是任何一處和預期的不一樣,就直接出錯了。
getc(D),跳過版本信息,註釋的比較清楚。
接着下面開始讀 TEST_WORD 和 TEST_FLOAT 以肯定字節序。若是讀到的和這裏的宏定義的不同,表示須要交換字節序。swapword 和 swapfloat 置位。
回到 LoadChunk,LoadHeader 以後,開始 LoadFunction,一如 dump 時的順序。
函數在 dump 的時候是在標記 ID_FUN 以後開始的,因此這裏的 LoadFunction 先判斷是不是 ID_FUN,若是不是的話,表示表示是別的內容,放回字符並返回。
內存
static void LoadFunction(FILE* D) { TFunc* tf=new(TFunc); tf->next=NULL; tf->locvars=NULL; tf->size=LoadSize(D); tf->lineDefined=LoadWord(D); if (IsMain(tf)) /* new main */ { tf->fileName=LoadNewString(D); Main=lastF=tf; } else /* fix PUSHFUNCTION */ { CodeCode c; Byte* p; tf->marked=LoadWord(D); tf->fileName=Main->fileName; p=Main->code+tf->marked; c.tf=tf; *p++=c.m.c1; *p++=c.m.c2; *p++=c.m.c3; *p++=c.m.c4; lastF=lastF->next=tf; } tf->code=LoadBlock(tf->size,D); if (swapword || swapfloat) FixCode(tf->code,tf->code+tf->size); while (1) /* unthread */ { int c=getc(D); if (c==ID_VAR) /* global var */ { int i=LoadWord(D); char* s=LoadString(D); int v=luaI_findsymbolbyname(s); Unthread(tf->code,i,v); } else if (c==ID_STR) /* constant string */ { int i=LoadWord(D); char* s=LoadString(D); int v=luaI_findconstantbyname(s); Unthread(tf->code,i,v); } else { ungetc(c,D); break; } } }
函數一開始新建一個 TFunc。
接着讀取它的 size 和 lineDefined。
判斷函數是否爲主函數,若是是主函數,則設置文件名,和 Main 字段。lastF 字段是指最後一次讀取到的函數。
若是函數非主函數,讀取它的 marked 字段。這個字段在 dump 那一節忘記說了,非主函數的 marked 是指它在主函數裏的字節碼偏移量。
看這裏是如何用的,把當前函數的地址賦到主函數相應的字節碼處。
字符串
p=Main->code+tf->marked; c.tf=tf; *p++=c.m.c1; *p++=c.m.c2; *p++=c.m.c3; *p++=c.m.c4;
通過這個賦值,原來 dump 的主函數裏 PUSHFUNCTION 後面的 marked 值就變成了真正的這個函數的內存地址了。
而後,把非主函數連接到函數鏈表中 lastF=lastF->next=tf;
接着 LoadBlock 讀取字節碼。
若是須要交換字節序,剛用 FixCode 交換字節序。
接下來的 while(1) 循環就是把 dump 的符號和字符串設置回運行環境中。
符號以 ID_VAR 打頭,每設置一個符號到運行環境中,都會把相應的字節碼中引用到它的地方做相應的 Unthread,這個是 dump 時的 ThreadCode 的逆過程。對比 ThreadCode 比較容易理解這裏的 Unthread,或者看下前面的那個 dump 裏的例子,也是比較清楚 ThreadCode 作了哪些事兒,而這裏是把它還原回去了。
get
static void Unthread(Byte* code, int i, int v) { while (i!=0) { CodeWord c; Byte* p=code+i; get_word(c,p); i=c.w; c.w=v; p[-2]=c.m.c1; p[-1]=c.m.c2; } }
到此,程序已經準備好運行了,開始虛擬機跑字節碼了,也就是編譯以後的內容了。
虛擬機