首页 > 代码库 > Lua2.4 打印字节码 print.c

Lua2.4 打印字节码 print.c

现在编译器相关的部分就剩下 luac.c 中的 do_dump 函数的分析了。
这个函数里面主要有两种调用,存储字节码和打印字节码。
先来看一下打印字节吧,要打印字节码,需要在编译器的命令行选项中有 "-l" 选项。

static void do_dump(TFunc* tf)     /* only for tf==main */
{
 if (dumping) DumpHeader(D);
 while (tf!=NULL)
 {
  TFunc* nf;
  if (listing) PrintFunction(tf);
  if (dumping) DumpFunction(tf,D);
  nf=tf->next;     /* list only built after first main */
  luaI_freefunc(tf);
  tf=nf;
 }
}

打印字节码调用的就是上面的 PrintFunction,可以看出,它是通过 listing 条件控制的,而 listing 是在命令行中有 “-l" 选项时才会为 1。

实际中用的时候,发现这里有一个小错误。当 -p -l 选项同时使用时(只进行语法分析不 dump 字节码),PrintFunction 只被调用一次,也就是只打印主函数的字节码,其它自定义的函数的字节码不会被打印。去掉 -p 选项时(也就是同时存储字节码到默认输出 luac.out 文件)则正常。
举个例子,对于下面的 Lua 脚本:

function add(x, y)
    return x + y
end
print (add(3, 4))

当用 -p -l 执行它时,打印的结果如下:
main of "test.lua" (25 bytes at 00602090)
     0    PUSHFUNCTION    00602120    ; "test.lua":1
     5    STOREGLOBAL    13    ; add
     8    PUSHGLOBAL    7    ; print
    11    PUSHGLOBAL    13    ; add
    14    PUSHBYTE    3
    16    PUSHBYTE    4
    18    CALLFUNC    2 1
    21    CALLFUNC    1 0
    24    RETCODE0
奇怪,函数 add 哪儿去了?

当用 -l 执行它时,打印的结果如下:
main of "test.lua" (25 bytes at 001120D8)
     0    PUSHFUNCTION    00112168    ; "test.lua":1
     5    STOREGLOBAL    13    ; add
     8    PUSHGLOBAL    7    ; print
    11    PUSHGLOBAL    13    ; add
    14    PUSHBYTE    3
    16    PUSHBYTE    4
    18    CALLFUNC    2 1
    21    CALLFUNC    1 0
    24    RETCODE0

function "test.lua":1 (9 bytes at 00112168); used at main+1
     0    ADJUST    2
     2    PUSHLOCAL0    0    ;
     3    PUSHLOCAL1    1    ;
     4    ADDOP
     5    RETCODE    2
     7    RETCODE    2
看到了吧,这里多了下面的 add 函数的字节码。
这是为什么?应该是程序出现了错误。
分析了一下,发现,当有 -p 选项时,do_dump 代码里的 tf->next 为 NULL,而没有 -p 选项时是好的。调试了一下,发现,在执行 DumpFunction 之后,tf->next 被赋值,跟到 DumpFunction 里  ThreadCode 里发现了下面这句:

 case PUSHFUNCTION:
 {
  CodeCode c;
  p++;
  get_code(c,p);
  c.tf->marked=at;
  c.tf->next=NULL;    /* TODO: remove? */
  lastF=lastF->next=c.tf;
  break;
 }

而在有 -p 选项时,上面这句不执行,所以 tf->next 没有赋值,上面的打印字节码就打印不了函数 add 的字节码了。

正常情况下,主函数和所有的 Lua 脚本中定义的函数编译后形成一个 TFunc 链,函数的字节码存在 TFunc 的 code 字段中。

print.h 里定义了了一个字节码指令的名字数组,它在打印字节码的时候会用到。
看下 PrintFunction 的代码

void PrintFunction(TFunc* tf)
{
 if (IsMain(tf))
  printf("\nmain of \"%s\" (%d bytes at %p)\n",tf->fileName,tf->size,tf);
 else
  printf("\nfunction \"%s\":%d (%d bytes at %p); used at main+%d\n",
 tf->fileName,tf->lineDefined,tf->size,tf,tf->marked);
 V=tf->locvars;
 PrintCode(tf->code,tf->code+tf->size);
}

主函数和用户自定义的函数的打印出来的描述信息不一样,通过 IsMain 宏判断是否是主函数。
#define IsMain(f)    (f->lineDefined==0)
用户自定义函数的定义行一定为大于 0 的值,而主函数设定为 0。

然后,调用 PrintCode 打印字节码指令。

static void PrintCode(Byte* code, Byte* end)
{
 Byte* p;
 for (p=code; p!=end;)
 {
 OpCode op=(OpCode)*p;
 if (op>SETLINE) op=SETLINE+1;
 printf("%6d\t%s",p-code,OpCodeName[op]);
 switch (op)
 {
/*cases and other codes*/
}
}
}

打印指令的位置和指令的名字,就是 printf 那一句。
如果是非法指令的话,就是 op>SETLINE,会打印一个空字符串。因为 OpCodeName 数组中 "SETLINE" 下一个字符串就是空串 “”。
接着是一个 switch case 打印其它指令中的一些其它数据相关的信息。包括指令的数据部分和会用到的字符串相关的部分。对比打印出来的字节码很容易看出代码的用意,这部分相当于对字节码进行了一个简单的解析(说它简单是和虚拟机中真正执行字节码时的对比)。所以,就不对它进行一行行的分析了。

----------------------------------------
到目前为止的问题:
> do_dump 方法里调的 dump 相关的方法是干什么的?
----------------------------------------

Lua2.4 打印字节码 print.c