转载

【Lua源码分析系列】2.分析思路

很抱歉,这周实在是太忙了。。。不废话,直接进入主题。

引言

所谓的分析思路,无非就是找到程序的入口点,按执行流程一步一步地阅读分析,或者从自己感兴趣的部分入手。这个系列暂时还不是对Lua的全面剖析,只是选取其中一部分来分析。因此我们要找到自己感兴趣的部分,有针对性地进行分析。

由于Lua的官方文档比较齐全,浏览一下官方提供的资料集会比较便于我们着手分析。

所谓的源码分析,就是从源代码构建可执行程序,调试调试,看看执行流程,看看主要的数据结构和算法、程序的运行状态之类的,最后再品味一下设计(问题背景、原因、优缺点),仅此而已。

那我们先来看看源代码的布局。

代码布局

可以从Makefile文件入手,不过这里用的是Windows + Visual Studio,可以省去这个步骤。看看从官方下载的源码包,提供了以下文件:

./lua-5.3.0/     Makefile    README     /doc/         contents.html      logo.gif      lua.1         lua.css            luac.1        manual.css         manual.html        osi-certified-72x60.png         readme.html     /src/         lapi.c       lapi.h       lauxlib.c    lauxlib.h         lbaselib.c   lbitlib.c    lcode.c      lcode.h         lcorolib.c   lctype.c     lctype.h     ldblib.c         ldebug.c     ldebug.h     ldo.c        ldo.h         ldump.c      lfunc.c      lfunc.h      lgc.c         lgc.h        linit.c      liolib.c     llex.c         llex.h       llimits.h    lmathlib.c   lmem.c         lmem.h       loadlib.c    lobject.c    lobject.h         lopcodes.c   lopcodes.h   loslib.c     lparser.c         lparser.h    lprefix.h    lstate.c     lstate.h         lstring.c    lstring.h    lstrlib.c    ltable.c         ltable.h     ltablib.c    ltm.c        ltm.h         lua.c        lua.h        lua.hpp      luac.c         luaconf.h    lualib.h     lundump.c    lundump.h         lutf8lib.c   lvm.c        lvm.h        lzio.c         lzio.h       Makefile

第一件事当然是看README啦还用说,当然专业的做法可以看Makefile,这里为了避免引入其他无关知识,还是选用简单的方法。

按照说明查看doc/readme.html文件其中的【Building Lua on other systems】节,我们发现它介绍了lua可执行程序大致的组成和依赖关系如下:

library:         lapi.c          lcode.c         lctype.c        ldebug.c         ldo.c           ldump.c         lfunc.c         lgc.c         llex.c          lmem.c          lobject.c       lopcodes.c         lparser.c       lstate.c        lstring.c       ltable.c         ltm.c           lundump.c       lvm.c           lzio.c         lauxlib.c       lbaselib.c      lbitlib.c       lcorolib.c         ldblib.c        liolib.c        lmathlib.c      loslib.c         lstrlib.c       ltablib.c       lutf8lib.c      loadlib.c         linit.c interpreter:         library,        lua.c compiler:         library,        luac.c

根据这个简单的依赖关系,我用VS2015建立了相应的工程,便于调试。其实官方资料集里也提供了现成的VS工程的下载链接:

TODO: 此处应有下载链接^_^

因为我们分析的重点是 编译原理和虚拟机的部分 ,而不是相关的库的实现部分。因此应该从 lua.c 或 luac.c 开始入手,其实我们从文件列表中也可以看出,里面有几个比较重要的文件:

llex.c    lopcodes.c    lparser.c    lvm.c

到底从哪里入手比较好,就见仁见智了。这里我还是采用了官方提供的资料来帮助选择。

官方的资料集的wiki中提供了一个 页面 。该页面介绍了这些文件的用途、编程约定、模块结构等等。

为了避免这个页面失效,下面还是可耻地复制粘贴略带翻译地提供给大家,网上也有一些翻译,只是他们翻译时省略掉了一些我觉得有用的信息。个别简单的,我就不翻译了。

Lua源码的模块结构

实用功能模块:

ldebug.c - 调试接口。包括以下功能:

  1. 访问调试钩子(lua_sethook, lua_gethook, lua_gethookcount),

  2. 访问运行时栈信息 (lua_getstack / lua_getlocal / lua_setlocal),

  3. 检查字节码(luaG_checkopenop / luaG_checkcode),

  4. 引发错误(luaG_typeerror / luaG_concaterror / luaG_aritherror /luaG_ordererror / luaG_errormsg / luaG_runerror)

lzio.c     - 一种缓冲输入流接口。

lmem.c  - 内存管理接口。它实现了这些内存分配函数:luaM_realloc / luaM_growaux_

lgc.c      - 增量式GC (内存管理)

基本数据类型的实现模块:

lstate.c - 全局状态。包括:

  • 用于打开和关闭Lua states的函数(lua_newstate/lua_close)

  • 线程相关函数 (luaE_newthread / luaE_freethread)

lobject.c - 一些针对Lua对象的通用函数。包括:

  • 数据类型与其字符串形式的互相转换

  • 原始数据相等性测试(luaO_rawequalObj)

  • 日志基础设施2(luaO_log2)

lstring.c - string table (持有由Lua处理的所有字符串)

lfunc.c   - 用来操纵原型和闭包的辅助函数。

ltable.c  - Lua tables (hash)

语法分析与代码生成相关模块:

lcode.c       - Lua的代码生成器. 由 lparser.c 来使用

llex.c          - 词法分析器. 由 lparser.c 来使用

lparser.c    - Lua 解析器.

lundump.c - 加载经过预编译的Lua代码块:

  • 实现了用于加载预编译后的代码块的luaU_undump 函数。

  • 还提供了用来解析函数头的 luaU_header 函数(在luaU_undump()内部被调用)。

ldump.c - 用于保存经过预编译的Lua代码块:

  • 实现了用于转储函数对象的luaU_dump()函数。这种函数对象是从文件或字符串预编译而来的Lua代码块。

处理执行Lua字节码的模块:

lopcodes.c - 由Lua虚拟机使用的操作码。

  • 通过 luaP_opnames 和 luaP_opmodes这两个映射表,定义了所有操作码的名称和相关信息。

lvm.c - Lua虚拟机:

  • 用于执行字节码 (luaV_execute).

  • 还暴露了少量函数供 lapi.c 使用(如:luaV_concat).

ldo.c - Lua函数调用和栈管理。处理函数调用 (luaD_call / luaD_pcall), 栈生长, 协程处理等ltm.c - 标签方法(tag methods)。 实现了查询对象中的元方法的功能。

标准库的实现模块:

lbaselib.c - (base functions)

lstrlib.c     - string

ltablib.c    - table

lmathlib.c - math

loslib.c     - os

liolib.c      - io

loadlib.c   - package

ldblib.c     - debug

以下模块定义了 C API:

lapi.c     - Lua API. Lua C API的主要实现部分(lua_* functions).

lauxlib.c - 定义了 luaL_* 函数

linit.c      - 实现了 luaL_openlibs 用于从C语言环境中加载上述模块。

lua 和 luac 程序的实现模块:

lua.c   - 独立的Lua 解析器

print.c - 定义了 "PrintFunction?" 函数,这些函数用于打印字节码 (通过luac.c "-l" 选项来使用)

luac.c  - Lua 编译器 (保存字节码到文件/列出字节码)

感兴趣的模块

搞清楚Lua的模块结构后,我们需要着重分析的模块就出来了:

  • lua 和 luac 程序的实现模块

  • 处理执行Lua字节码的模块

  • 语法分析与代码生成相关模块

  • 基本数据类型的实现模块

当然,到了后期,如果有时间,我还会再分析一下Lua的垃圾收集器,但最近实在是太忙太忙了。

从Makefile来看模块间的依赖关系

以下是对 lua-5.3.0/src/Makefile 文件重要部分的节选

LUA_A=    liblua.a CORE_O=    lapi.o lcode.o lctype.o ldebug.o ldo.o ldump.o lfunc.o lgc.o llex.o /     lmem.o lobject.o lopcodes.o lparser.o lstate.o lstring.o ltable.o /     ltm.o lundump.o lvm.o lzio.o LIB_O=    lauxlib.o lbaselib.o lbitlib.o lcorolib.o ldblib.o liolib.o  /     lmathlib.o loslib.o lstrlib.o ltablib.o lutf8lib.o loadlib.o linit.o BASE_O= $(CORE_O) $(LIB_O) $(MYOBJS)  LUA_T=    lua LUA_O=    lua.o  LUAC_T=    luac LUAC_O=    luac.o

根据我们感兴趣的模块,大概是以下目标文件:

  1. lua.o

  2. luac.o

  3. llex.o

  4. ldo.o

  5. lstate.o

  6. lparser.o

  7. lopcodes.o

  8. lvm.o

  9. lcode.o

  10. ldump.o

  11. lundump.o

  12. ltm.o

  13. lobject.o

  14. lstring.o

  15. ltable.o

  16. lfunc.o

其实还是挺多的,不过比较重要的都用粗体标出来了。大致就是按这个顺序去分析。这些其实也可以直接看源文件中引用的头文件,但那样太麻烦了,并且,Lua源码有时候并不是在文件的顶部写include指令,而是在中间的某个地方,比较蛋疼。看Makefile是比较方便而且专业的做法。我们继续看一下Makefile 文件中上述目标文件依赖关系:

lua.o: lua.c lprefix.h lua.h luaconf.h lauxlib.h lualib.h  luac.o: luac.c lprefix.h lua.h luaconf.h lauxlib.h lobject.h llimits.h /   lstate.h ltm.h lzio.h lmem.h lundump.h ldebug.h lopcodes.h  llex.o: llex.c lprefix.h lua.h luaconf.h lctype.h llimits.h ldo.h /   lobject.h lstate.h ltm.h lzio.h lmem.h lgc.h llex.h lparser.h lstring.h /   ltable.h  ldo.o: ldo.c lprefix.h lua.h luaconf.h lapi.h llimits.h lstate.h /   lobject.h ltm.h lzio.h lmem.h ldebug.h ldo.h lfunc.h lgc.h lopcodes.h /   lparser.h lstring.h ltable.h lundump.h lvm.h  lstate.o: lstate.c lprefix.h lua.h luaconf.h lapi.h llimits.h lstate.h /   lobject.h ltm.h lzio.h lmem.h ldebug.h ldo.h lfunc.h lgc.h llex.h /   lstring.h ltable.h  lparser.o: lparser.c lprefix.h lua.h luaconf.h lcode.h llex.h lobject.h /   llimits.h lzio.h lmem.h lopcodes.h lparser.h ldebug.h lstate.h ltm.h /   ldo.h lfunc.h lstring.h lgc.h ltable.h  lopcodes.o: lopcodes.c lprefix.h lopcodes.h llimits.h lua.h luaconf.h  lvm.o: lvm.c lprefix.h lua.h luaconf.h ldebug.h lstate.h lobject.h /   llimits.h ltm.h lzio.h lmem.h ldo.h lfunc.h lgc.h lopcodes.h lstring.h /   ltable.h lvm.h  lcode.o: lcode.c lprefix.h lua.h luaconf.h lcode.h llex.h lobject.h /   llimits.h lzio.h lmem.h lopcodes.h lparser.h ldebug.h lstate.h ltm.h /   ldo.h lgc.h lstring.h ltable.h lvm.h

这里简要地说一下Makefile语法:

冒号左边的.o是编译时生成的目标文件,冒号右边是生成这个目标文件需要的源文件, 表示还没写完,下一行继续写。上一段Makefile代码中,等号左边是一个变量,等号右边是这个变量的值。

关于Makefile:

Makefile是GNU/Linux系统中用于自动化构建的DSL,供gnu make使用,和Android开发中的.gradle文件作用类似。不过VS和Qt也都使用了各自的Makefile格式和工具。VS有nmake,Qt有qmake,跨平台的有cmake,这里就不展开说了,自己去看文档吧。

现在,我们可以比较有针对性地去看源文件了。

关于下一期

下一期将介绍一下Lua源码中的编程约定(其实还是做个搬运工+翻译工,这里的翻译就凑合着看吧,翻译质量应该没有太大问题)。前面这几期都是比较无聊又不可或缺的,只能忍忍啦。预报一下,后天将更新第三弹。

参考资料

原文  https://segmentfault.com/a/1190000005162326
正文到此结束
Loading...