首页 > 代码库 > Lua2.4 场景恢复 undump.c

Lua2.4 场景恢复 undump.c

这一节看看 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;
 }
}

到此,程序已经准备好运行了,开始虚拟机跑字节码了,也就是编译之后的内容了。

Lua2.4 场景恢复 undump.c