首页 > 代码库 > 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