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 相关的方法是干什么的?
----------------------------------------

时间: 2024-08-09 22:50:05

Lua2.4 打印字节码 print.c的相关文章

Lua1.1 打印字节码

如何打印出字节码:代码里做如下修改,把打印字节码的宏开关打开.y.tab.c18 行#define LISTING 0改为#define LISTING 1 因为 PrintCode 的定义在调用之后,所以加个前置声明:做出下修改:y.tab.c329 行添加static void PrintCode (Byte *code, Byte *end);保证在 lua_parse 调用它的时候,是已经声明了的. 把打印字节码打开之后,执行脚本的时候就会先打字节码打印出来.看看字节码打印出来是什么样,

Lua2.4 保存字节码 dump.c

严格意义上说,把 dump 这部分叫保存字节码并不准确.因为除了保存 TFunc 里的字节码 code 之外,还保存了其它的内容.比如函数头,字节序及字节码需要的数据等.所以,准确的说应该叫保存字节码及环境,或者叫做保存世界,就是字节码生成之后的运行时相关信息也保存了下来.可以从保存下来的这些信息恢复出字节码执行时需要的运行时,默认的保存文件就是之前所说的那个 luac.out 的二进制文件.咬文嚼字一下,dump 这里翻译为保存意思应该差不多,undump 则是它的相反操作,可以叫做恢复.du

Java并发编程原理与实战八:产生线程安全性问题原因(javap字节码分析)

前面我们说到多线程带来的风险,其中一个很重要的就是安全性,因为其重要性因此,放到本章来进行讲解,那么线程安全性问题产生的原因,我们这节将从底层字节码来进行分析. 一.问题引出 先看一段代码 package com.roocon.thread.t3; public class Sequence { private int value; public int getNext(){ return value++; } public static void main(String[] args) { S

Java 编程的动态性,第 7 部分: 用 BCEL 设计字节码--转载

在本系列的最后三篇文章中,我展示了如何用 Javassist 框架操作类.这次我将用一种很不同的方法操纵字节码——使用 Apache Byte Code Engineering Library (BCEL).与 Javassist 所支持的源代码接口不同,BCEL 在实际的 JVM 指令层次上进行操作.在希望对程序执行的每一步进行控制时,底层方法使 BCEL 很有用,但是当两者都可以胜任时,它也使 BCEL 的使用比 Javassist 要复杂得多. 我将首先讨论 BCEL 基本体系结构,然后本

Python字节码与解释器学习

参考:http://blog.jobbole.com/55327/ http://blog.jobbole.com/56300/ http://blog.jobbole.com/56761/ 1. 在交互式命令行中执行命令的内部过程 当你敲下return键的时候,python完成了以下四步:词法分析.句法分析.编译.解释.词法分析的工作就是将你刚才输入的那行代码分解为一些符号token(译者注:包括标示符,关键字,数字, 操作符等).句法分析程序再接收这些符号,并用一种结构来展现它们之间的关系(

第七节,初识模块字节码和注释

模块 模块(也可以理解为调用代码文件,被调用的文件或者一个功能叫做模块) 调用模块最大的好处就是实现了代码复用,不用重复写代码 举例: 文件1.py调用文件adc.py 文件1.py代码如下 1 #!/usr/bin/env python 2 # -*- coding: utf-8 -*- 3 #import调用了adc这个文件 4 import adc 文件adc.py代码如下 1 #!/usr/bin/env python 2 # -*- coding: utf-8 -*- 3 # 打印(被

通过字节码展示Java8 Lambda的实现

Java8 增加了 Lambda 表达式,很大程度使代码变的更加简洁紧凑了,那么 Java8 是如何实现 Lambda 表达式的呢? 直接看一个简单的创建线程的例子. public class TestLambda { public static void main(String[] args) { new Thread(() -> System.out.print("thread")); } } 执行javac TestLambda.java编译生成文件TestLambda.c

java从字节码角度解析案例(转)

本文来自于参考博客 1. 下面是一到Java笔试题: 1 public class Test2 2 { 3 public void add(Byte b) 4 { 5 b = b++; 6 } 7 public void test() 8 { 9 Byte a = 127; 10 Byte b = 127; 11 add(++a); 12 System.out.print(a + " "); 13 add(b); 14 System.out.print(b + ""

《python源码剖析-字节码和虚拟机》

|   分类于 python源码剖析  | https://fanchao01.github.io/blog/categories/python%E6%BA%90%E7%A0%81%E5%89%96%E6%9E%90/ https://fanchao01.github.io/blog/2014/12/26/python-GIL/ python源码剖析-字节码和虚拟机 发表于 2016-11-13   |   分类于 python源码剖析  | Python会将代码先编译成字节码,然后在虚拟机中动