这一节看看 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。
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,代码如下:
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
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 做了哪些事儿,而这里是把它还原回去了。
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; } }
到此,程序已经准备好运行了,开始虚拟机跑字节码了,也就是编译之后的内容了。