LuaJit分析(六)luajit -bl 命令分析

Luajit -bl命令用于将luajit字节码文件或者lua脚本文件反汇编,输出汇编指令,很好奇怎么将字节码文件和lua脚本文件放在一块处理的,下面一步步分析:

luajit虚拟机由luajit.c文件生成,首先定位到main函数,代码如下:

int main(int argc, char **argv)
{int status;lua_State *L = lua_open();if (L == NULL) {l_message(argv[0], "cannot create state: not enough memory");return EXIT_FAILURE;}smain.argc = argc;smain.argv = argv;status = lua_cpcall(L, pmain, NULL);report(L, status);lua_close(L);return (status || smain.status > 0) ? EXIT_FAILURE : EXIT_SUCCESS;
}

进行一些初始化操作后,若没出现异常,则调用pmain函数,pmian函数代码如下:

static int pmain(lua_State *L)
{struct Smain *s = &smain;char **argv = s->argv;int argn;int flags = 0;globalL = L;if (argv[0] && argv[0][0]) progname = argv[0];LUAJIT_VERSION_SYM();  /* Linker-enforced version check. */argn = collectargs(argv, &flags);if (argn < 0) {  /* Invalid args? */print_usage();s->status = 1;return 0;}if ((flags & FLAGS_NOENV)) {lua_pushboolean(L, 1);lua_setfield(L, LUA_REGISTRYINDEX, "LUA_NOENV");}/* Stop collector during library initialization. */lua_gc(L, LUA_GCSTOP, 0);luaL_openlibs(L);lua_gc(L, LUA_GCRESTART, -1);createargtable(L, argv, s->argc, argn);if (!(flags & FLAGS_NOENV)) {s->status = handle_luainit(L);if (s->status != LUA_OK) return 0;}if ((flags & FLAGS_VERSION)) print_version();s->status = runargs(L, argv, argn);if (s->status != LUA_OK){return 0;}if (s->argc > argn) {s->status = handle_script(L, argv + argn);if (s->status != LUA_OK) return 0;}if ((flags & FLAGS_INTERACTIVE)) {print_jit_status(L);dotty(L);} else if (s->argc == argn && !(flags & (FLAGS_EXEC|FLAGS_VERSION))) {if (lua_stdin_is_tty()) {print_version();print_jit_status(L);dotty(L);} else {dofile(L, NULL);  /* Executes stdin as a file. */}}return 0;
}

pmain函数中有两个关键调用:

argn = collectargs(argv, &flags);
s->status = runargs(L, argv, argn);

分别用于获取输入的参数和获取参数后执行,runargs代码如下:

static int runargs(lua_State *L, char **argv, int argn)
{int i;for (i = 1; i < argn; i++) {if (argv[i] == NULL) continue;lua_assert(argv[i][0] == '-');switch (argv[i][1]) {case 'e': {const char *chunk = argv[i] + 2;if (*chunk == '\0') chunk = argv[++i];lua_assert(chunk != NULL);if (dostring(L, chunk, "=(command line)") != 0)return 1;break;}case 'l': {const char *filename = argv[i] + 2;if (*filename == '\0') filename = argv[++i];lua_assert(filename != NULL);if (dolibrary(L, filename))return 1;break;}case 'j': {  /* LuaJIT extension. */const char *cmd = argv[i] + 2;if (*cmd == '\0') cmd = argv[++i];lua_assert(cmd != NULL);if (dojitcmd(L, cmd))return 1;break;}case 'O':  /* LuaJIT extension. */if (dojitopt(L, argv[i] + 2))return 1;break;case 'b':  /* LuaJIT extension. */return dobytecode(L, argv+i);default: break;}}return LUA_OK;
}

runarg函数根据获取的参数执行对应的操作,现在我们关心的是 -bl命令,可以看到只要命令的第一个字节为b时,进入dobytecode函数,这就包括了 -b -bl -bg等,该函数代码如下:

/* Save or list bytecode. */
static int dobytecode(lua_State *L, char **argv)
{int narg = 0;lua_pushliteral(L, "bcsave");if (loadjitmodule(L))return 1;if (argv[0][2]) {      // -b 后面有其它的参数 如 -bl  -bgnarg++;argv[0][1] = '-';lua_pushstring(L, argv[0]+1);}for (argv++; *argv != NULL; narg++, argv++){lua_pushstring(L, *argv);}report(L, lua_pcall(L, narg, 0, 0));return -1;
}

从注释可以看出,该函数用于保存或反汇编字节码,它通过bcsave.lua脚本执行操作,先通过lua_pushstring压入参数,argv[0][2]为真表示 -b后面还有字符, 如 -bl命令,并将 -bl替换成 -l再压入参数。通过循环压入了文件名参数,先不管luajit内部怎么获取到bcsave脚本的,我们直接进入bcsave.lua查看源码:

-- Public module functions.
return {start = docmd -- Process -b command line option.
}

这个语句说明先执行docmd函数,从注释可以看出 改文件也是处理 -b类命令的,docmd代码如下:

local function docmd(...)local arg = {...}local n = 1local list = falselocal ctx = {strip = true, arch = jit.arch, os = string.lower(jit.os),type = false, modname = false,}while n <= #arg dolocal a = arg[n]if type(a) == "string" and string.sub(a, 1, 1) == "-" and a ~= "-" thentable.remove(arg, n)if a == "--" then break endfor m=2,#a dolocal opt = string.sub(a, m, m)if opt == "l" thenlist = trueelseif opt == "s" thenctx.strip = trueelseif opt == "g" thenctx.strip = falseelseif arg[n] == nil or m ~= #a then usage() endif opt == "e" thenif n ~= 1 then usage() endarg[1] = check(loadstring(arg[1]))elseif opt == "n" thenctx.modname = checkmodname(table.remove(arg, n))elseif opt == "t" thenctx.type = checkarg(table.remove(arg, n), map_type, "file type")elseif opt == "a" thenctx.arch = checkarg(table.remove(arg, n), map_arch, "architecture")elseif opt == "o" thenctx.os = checkarg(table.remove(arg, n), map_os, "OS name")elseusage()endendendelsen = n + 1endendif list thenif #arg == 0 or #arg > 2 then usage() endbclist(arg[1], arg[2] or "-")elseif #arg ~= 2 then usage() endbcsave(ctx, arg[1], arg[2])end
end

从中可以看出,当命令为 -l时,执行bclist函数,否则执行 bcsave函数。这篇文章我们只关心 -bl的处理过程,此时arg[1]为 -bl后的参数,即文件名,arg[2]为空时,输入 -,bclist代码如下:

local function bclist(input, output)local f = readfile(input)require("jit.bc").dump(f, savefile(output, "w"), true)
end

它调用了jit/bc.lua中的dump函数,其中参数为readfile 和savefile返回的结果。先看readfile函数:

local function readfile(input)if type(input) == "function" then return input endif input == "-" then input = nil endreturn check(loadfile(input))
end

readfile函数中,参数是function类型时,直接返回本身,lua中“-”和 nil都可以表示空,否则返回库函数 loadfile的结果。因此将源码和字节码统一处理的关键在于loadfile函数。loadfile库函数的定义在lib_base.c中,代码如下:

LJLIB_CF(loadfile)
{GCstr *fname = lj_lib_optstr(L, 1);GCstr *mode = lj_lib_optstr(L, 2);int status;lua_settop(L, 3);  /* Ensure env arg exists. */status = luaL_loadfilex(L, fname ? strdata(fname) : NULL,mode ? strdata(mode) : NULL);return load_aux(L, status, 3);
}
Loadfile函数调动了luaL_loadfilex函数,该函数定义如下:
LUALIB_API int luaL_loadfilex(lua_State *L, const char *filename,const char *mode)
{FileReaderCtx ctx;int status;const char *chunkname;if (filename) {ctx.fp = fopen(filename, "rb");if (ctx.fp == NULL) {lua_pushfstring(L, "cannot open %s: %s", filename, strerror(errno));return LUA_ERRFILE;}chunkname = lua_pushfstring(L, "@%s", filename);} else {ctx.fp = stdin;chunkname = "=stdin";}status = lua_loadx(L, reader_file, &ctx, chunkname, mode);if (ferror(ctx.fp)) {L->top -= filename ? 2 : 1;lua_pushfstring(L, "cannot read %s: %s", chunkname+1, strerror(errno));if (filename)fclose(ctx.fp);return LUA_ERRFILE;}if (filename) {L->top--;copyTV(L, L->top-1, L->top);fclose(ctx.fp);}return status;
}

luaL_loadfilex根据传进来的filename读取文件后,实际调用lua_loadx函数,lua_loadx函数定义如下:

LUA_API int lua_loadx(lua_State *L, lua_Reader reader, void *data,const char *chunkname, const char *mode)
{LexState ls;int status;ls.rfunc = reader;ls.rdata = data;ls.chunkarg = chunkname ? chunkname : "?";ls.mode = mode;lj_buf_init(L, &ls.sb);status = lj_vm_cpcall(L, NULL, &ls, cpparser);lj_lex_cleanup(L, &ls);lj_gc_check(L);return status;
}

lua_loadx实际是执行了cpparser函数,该函数判断读入的函数是lua脚本时,执行lj_parse函数进行词法转换。是luajit字节码文件时,执行lj_bcread读取字节码函数信息,因此最终返回的都是编译后的函数。cpparser的进一步跟踪参考字节码自定义的内容:

luajit自定义修改

下面是savefile函数的代码:

local function savefile(name, mode)if name == "-" then return io.stdout endreturn check(io.open(name, mode))
end

当指定的参数为 “-”时,也就是 -bl后没有参数时,返回 stdout,即最后反汇编是在控制台输出,否则返回一个打开的文件标识符。

bc.lua中定义了 dump = bcdump,bcdump函数代码如下:

-- Dump bytecode instructions of a function.
local function bcdump(func, out, all)if not out then out = stdout endlocal fi = funcinfo(func)if all and fi.children thenfor n=-1,-1000000000,-1 dolocal k = funck(func, n)if not k then break endif type(k) == "proto" then bcdump(k, out, true) endendendout:write(format("-- BYTECODE -- %s-%d\n", fi.loc, fi.lastlinedefined))local target = bctargets(func)for pc=1,1000000000 dolocal s = bcline(func, pc, target[pc] and "=>")if not s then break endout:write(s)endout:write("\n")out:flush()
end

从备注可以看出就是输出字节码指令,这是一个递归函数,先根据传入的第一个参数,使用funcinfo函数获取函数信息,funcinfo是一个库函数,位于jit.util. funcinfo,如果它有孩子,则递归调用全部为原型类型的孩子。它通过循环获取所有的指令,并调用bcline函数输出每一行的指令,bcline函数代码如下:

-- Return one bytecode line.
local function bcline(func, pc, prefix)local ins, m = funcbc(func, pc)if not ins then return endlocal ma, mb, mc = band(m, 7), band(m, 15*8), band(m, 15*128)local a = band(shr(ins, 8), 0xff)local oidx = 6*band(ins, 0xff)local op = sub(bcnames, oidx+1, oidx+6)local s = format("%04d %s %-6s %3s ",pc, prefix or "  ", op, ma == 0 and "" or a)local d = shr(ins, 16)if mc == 13*128 then -- BCMjumpreturn format("%s=> %04d\n", s, pc+d-0x7fff)endif mb ~= 0 thend = band(d, 0xff)elseif mc == 0 thenreturn s.."\n"endlocal kcif mc == 10*128 then -- BCMstrkc = funck(func, -d-1)kc = format(#kc > 40 and '"%.40s"~' or '"%s"', gsub(kc, "%c", ctlsub))elseif mc == 9*128 then -- BCMnumkc = funck(func, d)if op == "TSETM " then kc = kc - 2^52 endelseif mc == 12*128 then -- BCMfunclocal fi = funcinfo(funck(func, -d-1))if fi.ffid thenkc = vmdef.ffnames[fi.ffid]elsekc = fi.locendelseif mc == 5*128 then -- BCMuvkc = funcuvname(func, d)endif ma == 5 then -- BCMuvlocal ka = funcuvname(func, a)if kc then kc = ka.." ; "..kc else kc = ka endendif mb ~= 0 thenlocal b = shr(ins, 24)if kc then return format("%s%3d %3d  ; %s\n", s, b, d, kc) endreturn format("%s%3d %3d\n", s, b, d)endif kc then return format("%s%3d      ; %s\n", s, d, kc) endif mc == 7*128 and d > 32767 then d = d - 65536 end -- BCMlitsreturn format("%s%3d\n", s, d)
end

从函数注释即可以看出是输出字节码指令的一行,它通过bcnames得到opcode的具体值,通过vmdef.ffnames得到调用的库函数的函数名bcnames和ffnames都在vmdef.lua中定义,同时该文件由buildvm.c自动生成,vmdef.lua中完整定义了各个指令下标的opcode名和各个库函数的符号名

总结:当执行luajit -bl命令时,实际上是调用的 jit/bcsave.lua文件,通过loadfile库函数加载文件,经过luajit处理后,得到原型数据。接着调用bc.lua中的bcdump函数,递归解析各个原型,通过bcline函数,解析各个指令,bcline函数中用到了vmdef.lua中定义的opcode和各个库函数的符号名,这些符号名由buildvm.c在编译过程中自动生成。因此vmdef.lua是在反汇编时用于opcode下标到opcode符号名的转换。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/diannao/52777.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【ceph学习】ceph如何进行数据的读写(3)

本章摘要 上文说到&#xff0c;osdc中封装请求&#xff0c;使用message中的相关机制将请求发送出去。 本文详细介绍osd服务端如何进行请求的接收。 osd初始化 osd启动时&#xff0c;定义了message变量ms_public&#xff0c;该变量绑定public网络&#xff0c;负责接收客户端的…

Java使用POI创建带样式和公式的Excel文件

这篇文章将演示如何使用POI 创建带样式和公式的Excel文件。 代码 import org.apache.poi.ss.usermodel.*; import org.apache.poi.xssf.usermodel.XSSFWorkbook;import java.io.FileOutputStream; import java.io.IOException;public class ExcelDemo {public static void mai…

FPGA第 5 篇,FPGA技术优略势,FPGA学习方向,FPGA学习路线(FPGA专业知识的学习方向,FPGA现场可编程门阵列学习路线和方向)

前言 前几篇讲了一下FPGA的发展和应用&#xff0c;以及未来前景。具体详细&#xff0c;请看 FPGA发展和应用&#xff0c;以及未来前景https://blog.csdn.net/weixin_65793170/category_12665249.html 这里我们来&#xff0c;记录一下&#xff0c;FPGA专业知识的学习路线 一.…

Python(C++)自动微分导图

&#x1f3af;要点 反向传播矢量化计算方式前向传递和后向传递计算方式图节点拓扑排序一阶二阶前向和伴随模式计算二元分类中生成系数高斯噪声和特征二元二次方程有向无环计算图超平面搜索前向梯度下降算法快速傅里叶变换材料应力和切线算子GPU CUDA 神经网络算术微分 Pytho…

理解 decltype() 指定符(C++ 11 及以上版本)

目录 1. 功能 2. 语法格式 3. 理解 3.1 第一点 1.2 第二点 4. 例释 在 C 编程语言中&#xff0c;decltype 是一个用于检查实体的声明类型或表达式的类型和值类别的关键字。该关键字在 C11 中引入&#xff0c;主要用于泛型编程中&#xff0c;因为在泛型编程中&#x…

数据类型 NVARCHAR2 与 VARCHAR2 的对比

数据类型 NVARCHAR2 与 VARCHAR2 的对比 在数据库系统中&#xff0c;字符数据类型是用于存储文本数据的关键部分。在达梦数据库&#xff08;DM Database&#xff09;以及许多其他关系数据库管理系统&#xff08;例如 Oracle&#xff09;&#xff0c;常见的字符数据类型有 NVAR…

C语言阴阳迷宫

目录 开头程序程序的流程图程序游玩的效果下一篇博客要说的东西 开头 大家好&#xff0c;我叫这是我58。 程序 #define _CRT_SECURE_NO_WARNINGS 1 #include <stdio.h> #include <stdlib.h> #include <string.h> #include <Windows.h> enum WASD {W…

CityHash、FarmHash

CityHash和FarmHash都是由Google开发的非加密哈希函数&#xff0c;专为快速处理大量数据而设计。它们在数据中心和大规模分布式系统中尤其有用&#xff0c;用于任务如数据分区、查找优化、数据校验等。这两种哈希函数都以其高效的性能和良好的分布特性而著称。 CityHash City…

设计模式 -- 外观模式(Facade Pattern)

1 问题引出 组建一个家庭影院 DVD 播放器、投影仪、自动屏幕、环绕立体声、爆米花机,要求完成使用家庭影院的功能&#xff0c;其过程为&#xff1a; 直接用遥控器&#xff1a;统筹各设备开关 开爆米花机&#xff0c;放下屏幕 &#xff0c;开投影仪 &#xff0c;开音响&#xf…

【人工智能】AI算法系统设计与算法建模的详细阐述

&#x1f3c6;&#x1f3c6;欢迎大家来到我们的天空&#x1f3c6;&#x1f3c6; &#x1f3c6;&#x1f3c6;如果文章内容对您有所触动&#xff0c;别忘了点赞、关注&#xff0c;收藏&#xff01; &#x1f3c6; 作者简介&#xff1a;我们的天空 &#x1f3c6;《头衔》&#x…

自定义全局变量在uniapp的Vuex应用

本文介绍了uniapp使用自定义全局变量的方法。当同一业务在连续页面操作时&#xff0c;存在部分筛选变量需要始终保持一致&#xff0c;比如时间筛选条件等&#xff0c;来回跳转页面时如果采用变量传递&#xff0c;常较为繁琐&#xff0c;存在遗漏传递或未清除上一次变量值&#…

图像金字塔的作用

1. 概述 图像金字塔是图像多尺度表达的一种&#xff0c;主要应用与图像分割&#xff0c;是一种以多分辨率来解释图像的有效但概念简单的结构。图像金字塔实际上是一张图片在不同尺度下的集合&#xff0c;即原图的上采样和下采样集合。金字塔的底部是高分辨率图像&#xff0c;而…

LuaJit分析(九)LuaJit中的JIT原理分析

Jit in luajit Luajit是一款高性能的lua解释器&#xff0c;与官方的lua解释器相比&#xff0c;luajit的高速除了将解释器直接以汇编代码实现外&#xff0c;还支持jit模式&#xff08;Just in time&#xff09;。Jit模式即将luajit的字节码编译成处理器能够直接执行的机器码&am…

vue3如何监听reactive对象是哪个属性发生的变化

在 Vue 3 中&#xff0c;如果你想监听 reactive 对象中的某个属性发生的变化&#xff0c;你可以使用 watch 函数进行监听。watch 函数允许你观察 reactive 对象的某个属性或者整个对象&#xff0c;并在变化时执行相应的操作。 1. 监听 reactive 对象的某个属性 如果你只想监听…

C++学习/复习补充记录 --- 图论(深搜,广搜)

数据结构与算法 | 深搜&#xff08;DFS&#xff09;与广搜&#xff08;BFS&#xff09;_深搜广搜算法-CSDN博客 深度优先搜索理论基础 深搜和广搜的区别&#xff1a; &#xff08;通俗版&#xff09; dfs是可一个方向去搜&#xff0c;不到黄河不回头&#xff0c;直到遇到绝境了…

在Unity中使用C#进行Xml序列化时保留特定小数位的方法参考

序列化方法代码参考&#xff1a; using System.IO; using System.Xml.Serialization;public class XmlTool {public static string ToXml<T>(T obj){XmlSerializer xmlSerializer new XmlSerializer(typeof(T));using var stringWriter new StringWriter();//让xml文档…

linux驱动 -- 输入子系统

1:输入子系统介绍 一个统一的输入设备的开发框架&#xff0c; 统一生成设备文件&#xff0c; 统一返回固定格式值。 2:输入子系统开发设备 键盘、鼠标、触摸屏等等。 3&#xff1a;输入子系统运行框架 应用层&#xff1a;操作设备文件openclosereadwrite 输入子系统&#xff…

Netty 学习笔记

Java 网络编程 早期的 Java API 只支持由本地系统套接字库提供的所谓的阻塞函数&#xff0c;下面的代码展示了一个使用传统 Java API 的服务器代码的普通示例 // 创建一个 ServerSocket 用以监听指定端口上的连接请求 ServerSocket serverSocket new ServerSocket(5000); //…

OS常规测试方法-PPMU

step 0: 检查工作&#xff1a; 检查每根pin连接到指定的PPMU资源是否正确继电器资源在PRJ文件中是否定义正确 step 1 设计者设计的测试电路继电器重置初始化close应该闭合的继电器 step 2 DPS pin电压置0V&#xff0c;同时考虑电流量程wait闭合测试机DPS通道RELAYwait st…

android13 隐藏状态栏里面的飞行模式 隐藏蓝牙 隐藏网络

总纲 android13 rom 开发总纲说明 目录 1.前言 2.问题分析 3.代码分析 4.代码修改 5.编译运行 6.彩蛋 1.前言 android13 隐藏状态栏里面的飞行模式,或者其他功能,如网络,蓝牙等等功能,隐藏下图中的一些图标。 2.问题分析 这里如果直接找这个布局的话,需要跟的逻…