Lua1.1 虚拟机指令分析(一)

在语法分析 lua_parse 之后,调用 lua_execute 来执行语法分析生成的字节码。
虚拟机的指令是一个枚举型,就是在 opcode.h 中的 OpCode, 通过 lua_execute 中的那个 switch case 来看下指令对应的操作。
> PUSHNIL

   case PUSHNIL: tag(top++) = T_NIL; break;

设置栈顶的 Object 类型为 T_NIL,这个栈就是之前所说的那个 Lua 和 C 之间交互的那个栈。

> PUSH0, PUSH1, PUSH2

   case PUSH0: tag(top) = T_NUMBER; nvalue(top++) = 0; break;
   case PUSH1: tag(top) = T_NUMBER; nvalue(top++) = 1; break;
   case PUSH2: tag(top) = T_NUMBER; nvalue(top++) = 2; break;

设置栈顶的 Object 类型为 T_NUMBER,值为 0/1/2。 这个是指令优化,把操作数放到指令里,从而让指令更短,执行的更快些。
  这几个指令是 PUSHBYTE 指令的优化版,或者叫特化版更合适一些。

> PUSHBYTE

   case PUSHBYTE: tag(top) = T_NUMBER; nvalue(top++) = *pc++; break;

设置栈顶的 Object 类型为 T_NUMBER,值从当前字节码处(也就是 pc 指针处的一个字节)取得。值在字节码中占一个字节。

> PUSHWORD

   case PUSHWORD:
   {
    CodeWord code;
    get_word(code,pc);
    tag(top) = T_NUMBER; nvalue(top++) = code.w;
   }
   break;

设置栈顶的 Object 类型为 T_NUMBER,值从当前字节码处(也就是 pc 指针处的两个字节)取得。值在字节码中占两个字节。
  get_word 是 y.tab.c 中的 code_word 的相反过程,CodeWord 是个联合体

   typedef union
   {
    struct {char c1; char c2;} m;
    Word w;
   } CodeWord;

在 code_word 方法

   static void code_word (Word n)
   {
    CodeWord code;
    code.w = n;
    code_byte(code.m.c1);
    code_byte(code.m.c2);
   }

可以看到,设置的时候把值设置给 w, 之后调用 code_byte 分别对 w 的两个字节生成字节码。
  由于 CodeWord 是个联合体,这里的 w 和 m 是同一片内存,所以 code_word 可以按预期正确执行。
  这样的联合体操作是 C 语言里的一个小技巧,比如测字节序(例如你的处理器体系结构是大端序还是小端序)的时候就可以用这样的技巧。

> PUSHFLOAT

   case PUSHFLOAT:
   {
    CodeFloat code;
    get_float(code,pc);
    tag(top) = T_NUMBER; nvalue(top++) = code.f;
   }
   break;

设置栈顶的 Object 类型为 T_NUMBER,值从当前字节码处(也就是 pc 指针处的四个字节)取得。值在字节码中占四个字节。
  get_float 和上面的 get_word 情况一样,不再重复。

> PUSHSTRING

   case PUSHSTRING:
   {
    CodeWord code;
    get_word(code,pc);
    tag(top) = T_STRING; svalue(top++) = lua_constant[code.w];
   }
   break;

设置栈顶的 Object 类型为 T_STRING,值从常量表从取得,下标从当前字节码处取得。下标占两个字节。

> PUSHLOCAL0, PUSHLOCAL1, PUSHLOCAL2, PUSHLOCAL3, PUSHLOCAL4,
  PUSHLOCAL5, PUSHLOCAL6, PUSHLOCAL7, PUSHLOCAL8, PUSHLOCAL9,

   case PUSHLOCAL0: case PUSHLOCAL1: case PUSHLOCAL2:
   case PUSHLOCAL3: case PUSHLOCAL4: case PUSHLOCAL5:
   case PUSHLOCAL6: case PUSHLOCAL7: case PUSHLOCAL8:
   case PUSHLOCAL9: *top++ = *(base + (int)(opcode-PUSHLOCAL0)); break;

设置栈顶的 Object 为局部变量 N (0<=N<=9),这个也是指令优化。把当前指令 opcode 减去 PUSHLOCAL0 取得偏移量 N。
  局部变量是从栈的 base 算起的偏移量,也可以理解为数组的下标。
  这几个指令是 PUSHLOCAL 的特化版。

> PUSHLOCAL

   case PUSHLOCAL: *top++ = *(base + (*pc++)); break;

设置栈顶的 Object 为局部变量 N (N 的偏移量从字节码中取得)。

> PUSHGLOBAL,

   case PUSHGLOBAL:
   {
    CodeWord code;
    get_word(code,pc);
    *top++ = s_object(code.w);
   }
   break;

设置栈顶的 Object 为全局变量 N (N 从全局符号表中取得,它的下标从字节码中取得,占两个字节)。

> PUSHINDEXED,

   case PUSHINDEXED:
    --top;
    if (tag(top-1) != T_ARRAY)
    {
     lua_reportbug ("indexed expression not a table");
     return 1;
    }
    {
     Object *h = lua_hashdefine (avalue(top-1), top);
     if (h == NULL) return 1;
     *(top-1) = *h;
    }
   break;

设置数组元素索引,当前的栈的 top 处为要设置的元素索引,top-1 为数组。
  通过 lua_hashdefine 把 top 做为索引设置进数组,返回其所在键值对儿 Node 值 val 地址。
  设置到 top-1 处,以备后续使用。

> PUSHMARK

   case PUSHMARK: tag(top++) = T_MARK; break;

设置栈顶的 Object 为 T_MARK,这个用于标记,比如在函数调用时会在函数入栈后再入栈一个 T_MARK。

> PUSHOBJECT

   case PUSHOBJECT: *top = *(top-3); top++; break;

设置栈顶的 Object 为栈顶的倒数第 4 个 Object。

> STORELOCAL0, STORELOCAL1, STORELOCAL2, STORELOCAL3, STORELOCAL4,
  STORELOCAL5, STORELOCAL6, STORELOCAL7, STORELOCAL8, STORELOCAL9,

   case STORELOCAL0: case STORELOCAL1: case STORELOCAL2:
   case STORELOCAL3: case STORELOCAL4: case STORELOCAL5:
   case STORELOCAL6: case STORELOCAL7: case STORELOCAL8:
   case STORELOCAL9: *(base + (int)(opcode-STORELOCAL0)) = *(--top); break;

设置局部变量 N (0<=N<=9)为栈顶的 Object,这个也是指令优化。把当前指令 opcode 减去 STORELOCAL0 取得偏移量 N。
  局部变量是从栈的 base 算起的偏移量,也可以理解为数组的下标。
  这几个指令是 STORELOCAL 的特化版。

> STORELOCAL

   case STORELOCAL: *(base + (*pc++)) = *(--top); break;

设置局部变量 N (N 的偏移量从字节码中取得)为栈顶的 Object。

> STOREGLOBAL

   case STOREGLOBAL:
   {
    CodeWord code;
    get_word(code,pc);
    s_object(code.w) = *(--top);
   }
   break;

设置全局变量 N (N 从全局符号表中取得,它的下标从字节码中取得,占两个字节)为栈顶的 Object。

> STOREINDEXED0

   case STOREINDEXED0:
    if (tag(top-3) != T_ARRAY)
    {
     lua_reportbug ("indexed expression not a table");
     return 1;
    }
    {
     Object *h = lua_hashdefine (avalue(top-3), top-2);
     if (h == NULL) return 1;
     *h = *(top-1);
    }
    top -= 3;
   break;

设置数组元素,数组位于 top-3, 数组索引位于 top-2, 数组值位于 top-1。
  设置完成后,这三个 Object 出栈。

> STOREINDEXED

   case STOREINDEXED:
   {
    int n = *pc++;
    if (tag(top-3-n) != T_ARRAY)
    {
     lua_reportbug ("indexed expression not a table");
     return 1;
    }
    {
     Object *h = lua_hashdefine (avalue(top-3-n), top-2-n);
     if (h == NULL) return 1;
     *h = *(top-1);
    }
    top--;
   }
   break;

设置指定偏移量的数组元素,偏移量从字节码中取得,数组位于 top-3-n,索引位于 top-2-n,值位于栈顶。
  设置完成后,栈顶值出栈。

> STORELIST0,
  STORELIST

   case STORELIST0:
   case STORELIST:
   {
    int m, n;
    Object *arr;
    if (opcode == STORELIST0) m = 0;
    else m = *(pc++) * FIELDS_PER_FLUSH;
    n = *(pc++);
    arr = top-n-1;
    if (tag(arr) != T_ARRAY)
    {
     lua_reportbug ("internal error - table expected");
     return 1;
    }
    while (n)
    {
     tag(top) = T_NUMBER; nvalue(top) = n+m;
     *(lua_hashdefine (avalue(arr), top)) = *(top-1);
     top--;
     n--;
    }
   }
   break;

设置数组值,m 为下标,n 为数组元素个数。要设置的数组值都位于栈上,栈顶为最后一个元素。
  所以在 while 循环里给数组赋值是先给下标大的赋值,再给小的赋值,每赋值一个就出栈一个。

> STORERECORD

   case STORERECORD:
   {
    int n = *(pc++);
    Object *arr = top-n-1;
    if (tag(arr) != T_ARRAY)
    {
     lua_reportbug ("internal error - table expected");
     return 1;
    }
    while (n)
    {
     CodeWord code;
     get_word(code,pc);
     tag(top) = T_STRING; svalue(top) = lua_constant[code.w];
     *(lua_hashdefine (avalue(arr), top)) = *(top-1);
     top--;
     n--;
    }
   }
   break;

给记录赋值,n 为要赋值的个数。被赋值的元素索引从字节码中取得,右值从栈上取得,赋值后出栈。
  记录是指下标为常量字符串的表,数组是指下标为整数的表。具体的区别请参见手册。

> ADJUST

   case ADJUST:
   {
    Object *newtop = base + *(pc++);
    while (top < newtop) tag(top++) = T_NIL;
    top = newtop; /* top could be bigger than newtop */
   }
   break;

调整栈元素个数,元素个数从字节码取出。如果新的栈顶高于当前栈顶,当前栈顶到新的栈顶之间的元素赋空。
  一般函数调用之后会调用它调整栈。 ADJUST 的下一个字节码(pc)是需要返回的函数返回值个数。
  while 补空的意义就是如果需要返回的函数个数大于实际返回的,则补空。
  (反之,需要的返回值少于实际返回的个数的话,多余的会被丢弃,这是通过设置 top 实现的。)

> CREATEARRAY

   case CREATEARRAY:
    if (tag(top-1) == T_NIL)
     nvalue(top-1) = 101;
    else
    {
     if (tonumber(top-1)) return 1;
     if (nvalue(top-1) <= 0) nvalue(top-1) = 101;
    }
    avalue(top-1) = lua_createarray(nvalue(top-1));
    if (avalue(top-1) == NULL)
     return 1;
    tag(top-1) = T_ARRAY;
   break;

新建数组,元素个数从栈顶取得,如果没有指定或者指定一个负数,把它修正为 101。如果栈顶不是个数字,出错。
  新建数组,赋值给栈顶元素,并设置栈顶 Object 类型为 T_ARRAY。
(未完待续)

时间: 2024-12-22 07:35:59

Lua1.1 虚拟机指令分析(一)的相关文章

Lua1.1 虚拟机指令分析(二)

(接上篇)> EQOP    case EQOP:    {     Object *l = top-2;     Object *r = top-1;     --top;     if (tag(l) != tag(r))      tag(top-1) = T_NIL;     else     {      switch (tag(l))      {       case T_NIL: tag(top-1) = T_NUMBER; break;       case T_NUMBER:

从虚拟机指令执行的角度分析JAVA中多态的实现原理

从虚拟机指令执行的角度分析JAVA中多态的实现原理 前几天突然被一个"家伙"问了几个问题,其中一个是:JAVA中的多态的实现原理是什么? 我一想,这肯定不是从语法的角度来阐释多态吧,隐隐约约地记得是与Class文件格式中的方法表有关,但是不知道虚拟机在执行的时候,是如何选择正确的方法来执行的了.so,趁着周末,把压箱底的<深入理解Java虚拟机>拿出来,重新看了下第6.7.8章中的内容,梳理一下:从我们用开发工具(Intellij 或者Eclipse)写的 .java 源程

自学linux指令分析-ls

自学linux指令分析-ls 1·命令格式 ls [option] [directory-list] ls [参数][目录名] 2·命令参数    -a, –all 列出目录下的所有文件,包括以 . 开头的隐含文件. -A, –almost-all 列出除了 . 及 .. 以外的任何项目 –author 印出每个文件的作者 -b, –escape 把文件名中不可输出的字符用反斜杠加字符编号的形式列出. –block-size=大小块以指定<大小>的字节为单位 -B, –ignore-backu

自学linux指令分析-head

自学linux指令分析-head 1·命令格式 head [参数][文件] 2·命令参数 -q                     隐藏文件名 -v                     显示文件名 -c<字节>           显示字节数 -n<行数>           显示的行数 3.命令功能 头部  显示文件头部-n, 行数,默认显示头部10行 4.命令范列 [[email protected] ~]# head -5 ett.txt 12345

自学linux指令分析-vi

自学linux指令分析-vi 1·命令格式 vi  file-list vi [文件名] 2.命令功能 vi编辑器是所有Unix及Linux系统下标准的编辑器,它的强大不逊色于任何最新的文本编辑器. 3.使用方式 执行 vi oldboy.txt进入 vi 编辑器(默认是命令模式),点击 a 或者 i 进入编辑模式, 敲入内容I am studying linux,然后按键盘上的esc键退出编辑模式(进入命令模式), 最后敲 ;wq保存并退出,wq解释 write quit. 如果只是查看,可以

自学linux指令分析-seq

自学linux指令分析-seq 1·命令格式 seq - print a sequence of numbers 打印数字序列 2·命令参数 -f                      用来格式化输出 -s                      横着打出来,  seq -s  "  "   5     1 2 3 4 5 -w                     输出同宽数列,不足的位数用0补齐 3.命令功能 打印数字序列,类似echo {1..5} 4.命令范列 [[em

Atitit&#160;.jvm&#160;虚拟机指令详细解释

Atitit .jvm 虚拟机指令详细解释 1. 一.未归类系列A1 2. 数据mov系列2 2.1. 二.const系列2 2.2. 三.push系列2 2.3. ldc系列 该系列命令负责把数值常量或String常量值从常量池中推送至栈顶.3 2.4. 5.1.load系列A 该系列命令负责把本地变量的送到栈顶.3 2.5. 5.2.load系列B 该系列命令负责把数组的某项送到栈顶.4 2.6. 6.1.store系列A 该系列命令负责把栈顶的值存入本地变量.5 2.7. 6.2.stor

Dalvik指令分析(一) 字节码转换为smali代码

有过android应用反编译或者再打包的朋友都有使用过apktool的经验,apktool能将dex文件的 字节码转换为smali代码,这个工具是怎么做到对dex进行解析并生成smali代码的呢?这就需要对 dex文件的格式很熟悉.需要掌握dalvik指令的字节码格式,并能翻译成对应的smali代码. 我准备写一系列的文章来分析dex文件的格式.dalvik字节码的格式.以及dex to smali的方法, 基于此可以做很多的应用,比如安全扫描.应用加固等等! Dalvik指令介绍请参考官方文档

指令分析的一般性总结(一)

指令编码是指令惟一的标识,是指令相互区别的依据.所谓指令分析,最重要的就是识别指令,根据指令惟一的标识和时序节拍等,来确定这条指令应该如何动作.另外,每条指令所要完成的动作又都是有顺序的,这种顺序是用节拍变量来指示出来的.控制器就是能够识别指令,并且通过节拍来驱动每条指令完成相应必要动作的设备.