LUA源码分析九:Debug."getlocal"

本文分析了LUA中Debug接口的'getlocal'函数,详细介绍了如何通过db_getlocal遍历并打印函数的局部变量。通过CallInfo结构获取函数调用信息,并探讨了tailcalls的概念。此外,还梳理了lua_getlocal的调用层次,重点关注了findlocal函数及其内部的luaF_getlocalname,解释了startpc和endpc在解析时的作用。

 

函数:{"getlocal", db_getlocal}

从db_getlocal开始跟调.该函数是把自身的所有变量打印出来。大体的思路是算到执行码,根据执行码的大小限制,遍历函数保存的变量信息,然后依次打印。

 

 

 

static int db_getlocal (lua_State *L) {
  int arg;
  lua_State *L1 = getthread(L, &arg);
  lua_Debug ar;
  const char *name;
  /*
  	根据level算出L->ci 和 L->base_ci之间的偏差值。见段1
  */
  if (!lua_getstack(L1, luaL_checkint(L, arg+1), &ar))  /* out of range? */
    return luaL_argerror(L, arg+1, "level out of range");
  /*
  	这里的ar只是有一个偏移值,具体的还在lua_getlocal里边。见段2  
  */
  name = lua_getlocal(L1, &ar, luaL_checkint(L, arg+2));
  if (name) {
    lua_xmove(L1, L, 1);
    lua_pushstring(L, name);
    lua_pushvalue(L, -2);
    return 2;
  }
  else {
    lua_pushnil(L);
    return 1;
  }
}

 

 

 

/*

段1

*/

CallInfo是要调用的函数信息,数组形式。这里通过level来算出是第几个CallInfo.

其中一个CallInfo里可能有tailcalls(这是什么还不知道,也算是一种函数调用的优化吧),而

tailcalls是计算到level里面的。比如

0x40 L->ci

0x30 CI(5)

0x20 CI(0)

0x10 CI(0)

..

0x00 L->base_ci, 可能base在这

括号表示tailcalls,这时level为7,取得的是0x10的目标CI,返回base_ci到0x10的偏差

 

 

 

LUA_API int lua_getstack (lua_State *L, int level, lua_Debug *ar) {
  int status;
  CallInfo *ci;
  lua_lock(L);
  /*
  	L->ci,顶部CallInfo信息
  	L->base_ci,底部CallInfo信息
  */
  for (ci = L->ci; level > 0 && ci > L->base_ci; ci--) {
    level--;
    if (f_isLua(ci))  /* Lua function? */
      level -= ci->tailcalls;  /* skip lost tail calls */
  }
  if (level == 0 && ci > L->base_ci) {  /* level found? */
    status = 1;
    /*
    	算出目标CallInfo的偏差值
    */
    ar->i_ci = cast_int(ci - L->base_ci);
  }
  else if (level < 0) {  /* level is of a lost tail call? */
    status = 1;
    ar->i_ci = 0;
  }
  else status = 0;  /* no such level */
  lua_unlock(L);
  return status;
}

 

 

 

/*

段2

*/

lua_getlocal函数的调用关系层如下

 

lua_getlocal (lua_State *L, const lua_Debug *ar, int n)
{
	//根据ar里的偏差值,取到目标ci
  CallInfo *ci = L->base_ci + ar->i_ci;
  findlocal
	  Proto *fp = getluaproto(ci);
	  name = luaF_getlocalname(fp, n, currentpc(L, ci))
}  

 重点是findlocal的封装。

getluaproto返回函数的属性,先对currentpc展开

 

static int currentpc (lua_State *L, CallInfo *ci) {
  if (!isLua(ci)) return -1;  /* function is not a Lua function? */
  /*
  	取的不一定是最末尾的ci
  */
  if (ci == L->ci)
    ci->savedpc = L->savedpc;
  /*
  pcRel宏扩展如下,算出ci的执行码和函数的执行码偏差值。中间可以看成塞入了其他
  的信息,比如全局变量也会对这个差值造成影响
 	#define pcRel(pc, p)	(cast(int, (pc) - (p)->code) - 1) 
  */
  return pcRel(ci->savedpc, ci_func(ci)->l.p);
}

 

来到最后的luaF_getlocalname

const char *luaF_getlocalname (const Proto *f, int local_number, int pc) {
  int i;
  /*
  	locvars表示存放的函数变量,大小为f->sizelocvars
  	startpc...endpc,表示在这个范围内的为active变量。
  */
  for (i = 0; i<f->sizelocvars && f->locvars[i].startpc <= pc; i++) {
    if (pc < f->locvars[i].endpc) {  /* is variable active? */
      local_number--;
      if (local_number == 0)
        return getstr(f->locvars[i].varname);
    }
  }
  return NULL;  /* not found */
}

 跟调了下l.p->code和startpc,endpc两个值,在parse的时候就被设置好。

startpc,endpc被设置成l.p->code类似的边界范围值,比如标识局部变量等。

而ci->savedpc和l.p->code之间势必还插入了其他的值,所以你会看到luaF_getlocalname中的判断过程

 

 

-- Chainer v0.3 - Fixed & Enhanced (Full Version) -- 兼容 GameGuardian v82.1+ -- 支持自动导出可执行脚本 + 内存映射匹配 + 类型过滤 + 安全写入 gg.require("82.1", 15185) gg.toast("📌 Chainer v0.3 启动中...") -- ======================== -- 🔒 辅助函数提前声明(避免 nil 调用) -- ======================== function serialize(f, var) if type(var) == "table" then f:write("{") for k, v in pairs(var) do if type(k) == "string" then k = string.format("%q", k) end f:write('[' .. k .. ']=') serialize(f, v) f:write(",") end f:write("}") elseif type(var) == "string" then f:write(string.format("%q", var)) else f:write(tostring(var)) end end function savevar(file, var) local f = io.open(file, "w") if not f then return false end f:write("return ") serialize(f, var) f:close() return true end -- ======================== -- 🧠 打印链式结构 -- ======================== local chains = 0 function printChain(pre, u) if u.offset == nil then chains = chains + 1 return chains .. ": " .. pre .. " = " .. u.value else local ret = "" for offset, v in pairs(u.offset) do ret = ret .. "\n\n" .. printChain(pre .. string.format(" -> +0x%X", offset), v) end return ret ~= "" and ret:sub(3) or "" end end -- ======================== -- 🎯 初始化信息 -- ======================== local ti = gg.getTargetInfo() if not ti then gg.alert("❌ 无法获取目标进程信息") os.exit() end local x64 = ti.x64 local pkg = ti.packageName -- 检查搜索结果 if gg.getResultsCount() == 0 then gg.alert("⚠️ 搜索列表为空,请先执行数值搜索") os.exit() end -- 设置内存区域限制(提升性能 + 防误扫) gg.setRanges(gg.REGION_C_ALLOC | gg.REGION_C_BSS | gg.REGION_ANONYMOUS) -- ======================== -- 🔍 获取 .so 映射范围 -- ======================== function getRanges() local archs = {[0x3] = 'x86', [0x28] = 'ARM', [0x3E] = 'x86-64', [0xB7] = 'AArch64'} local ranges = {} local list = gg.getRangesList("^/data.*%.so$") local arch = 'unknown' for _, r in ipairs(list) do if r.type:sub(2, 2) == "-" then -- 只读段判断是否为 ELF local vals = gg.getValues({ {address = r.start, flags = gg.TYPE_DWORD}, {address = r.start + 0x12, flags = gg.TYPE_WORD} }) if vals[1].value == 0x464C457F then -- ELF magic: \x7fELF arch = archs[vals[2].value] or 'unknown' end end if r.type:sub(2, 2) == "w" then -- 可写段加入扫描 r.arch = arch table.insert(ranges, r) end end return ranges end local ranges = getRanges() if #ranges == 0 then gg.alert("❌ 未找到可用的 .so 内存段") os.exit() end -- ======================== -- ⚙️ 配置加载与用户输入 -- ======================== local cfg_file = gg.getFile() .. ".cfg" local chunk = loadfile(cfg_file) local cfg = chunk and chunk() or {} while true do local def = cfg[pkg] or {3, 0x100} -- 默认深度=3, 偏移=0x100 local input = gg.prompt( {"🔍 搜索深度", "📏 最大偏移 (hex)"}, def, {"number", "hex"} ) if not input then os.exit() end local depth = tonumber(input[1]) local maxOffset = tonumber(input[2]) if not depth or depth < 1 or depth > 10 then gg.toast("❌ 深度必须是 1~10") continue end if not maxOffset or maxOffset <= 0 then gg.toast("❌ 偏移必须大于 0") continue end cfg[pkg] = {depth, maxOffset} savevar(cfg_file, cfg) -- 开始扫描 local level = {} local out = {} local old_results = gg.getResults(100000) local start_time = os.clock() for lvl = 0, depth do if lvl > 0 then level[lvl] = gg.getResults(100000) gg.toast("⛓️ 第 " .. lvl .. "/" .. depth .. " 层") gg.internal3(maxOffset) -- 内部优化调用 end for _, range in ipairs(ranges) do local results = gg.getResults(100000, 0, range.start, range['end']) if #results > 0 then gg.removeResults(results) loadChain(lvl, results) results.map = range table.insert(out, results) end end if gg.getResultsCount() == 0 then break end end local elapsed = os.clock() - start_time gg.loadResults(old_results) -- 生成链文本 local chain_text = "" chains = 0 for _, p in ipairs(out) do for _, u in ipairs(p) do chain_text = chain_text .. "\n\n" .. printChain( string.format("%s +0x%X [0x%X]", p.map.internalName:gsub("^.*/", ""), u.address - p.map.start, u.address ), u ) end end chain_text = chain_text:sub(3) local btn = gg.alert( string.format("✅ 发现 %d 条链\n耗时 %.2fs\n深度:%d | 偏移:0x%X", chains, elapsed, depth, maxOffset), "继续", "重试", "退出并保存" ) if btn == 1 then break end if btn == 3 then local f = io.open("/storage/emulated/0/文明重启/cs.lua", "w") if f then f:write(chain_text) f:close() gg.toast("📄 已保存至 /sdcard/文明重启/cs.lua") end os.exit() end end if #out == 0 then gg.toast("❌ 无有效链") os.exit() end -- ======================== -- 💾 导出可运行脚本路径 -- ======================== local script_path = gg.getFile():gsub("[^/]*$", "") .. ti.packageName for i = 1, 1000 do local f = io.open(script_path .. i .. ".lua") if not f then script_path = script_path .. i .. ".lua" break end if f then f:close() end end local user_path = gg.prompt({"📁 导出脚本路径"}, {script_path}, {"file"})[1] if not user_path then os.exit() end -- ======================== -- 📦 封装数据用于运行时还原 -- ======================== out.packageName = ti.packageName out.versionCode = ti.versionCode out.versionName = ti.versionName out.x64 = ti.x64 v = out -- ======================== -- 🔁 运行时还原函数(注入到新脚本) -- ======================== function out() local cur = gg.getTargetInfo() if not cur or cur.packageName ~= v.packageName or cur.versionCode ~= v.versionCode then local msg = "⚠️ 脚本版本不匹配\n\n" .. "当前应用: " .. (cur and cur.packageName or "未知") .. "\n" .. "期望应用: " .. v.packageName .. "\n" .. "当前版本: " .. (cur and cur.versionName or "N/A") .. "\n" .. "期望版本: " .. v.versionName gg.alert(msg) return end local ranges = getRanges() local d, go, ret = {}, true, {} -- 映射新的内存地址 for _, p in ipairs(v) do if not p.map.new then local name = p.map.internalName:gsub("^.*/", "") for _, r in ipairs(ranges) do local rname = r.internalName:gsub("^.*/", "") if name == rname and p.map.state == r.state then if p.map.arch ~= r.arch then gg.alert("⛔ 架构不匹配:\n" .. name .. "\n原:" .. p.map.arch .. " → 当前:" .. r.arch) end p.map.new = r break end end end if p.map.new then for _, u in ipairs(p) do u.address = u.address - p.map.start + p.map.new.start d[u] = u end end end -- 追踪指针链 while go do local s = gg.getValues(d) d, go = {}, false for old_node, new_val in pairs(s) do if not old_node.offset then table.insert(ret, new_val) else if not cur.x64 then new_val.value = bit.band(new_val.value, 0xFFFFFFFF) end for offset, next_node in pairs(old_node.offset) do next_node.address = new_val.value + offset d[next_node] = next_node go = true end end end end -- 🔍 过滤数值(例如 float > 0) local filtered = {} local min_val = 0 local data_type = "FLOAT" -- 支持: FLOAT, DWORD, QWORD local flag_map = {FLOAT = 4, DWORD = 1, QWORD = 2} local flags = flag_map[data_type] or 1 for _, r in ipairs(ret) do if (data_type == "FLOAT" and type(r.value) == "number" and r.value > min_val) or ((data_type == "DWORD" or data_type == "QWORD") and r.value > min_val) then table.insert(filtered, r) end end -- 📝 生成过滤脚本 local filter_path = "/storage/emulated/0/过滤结果.lua" local f = io.open(filter_path, "w") if f then f:write("gg.require('" .. gg.VERSION .. "', " .. gg.BUILD .. ")\n") f:write("gg.clearResults()\nd={\n") for _, res in ipairs(filtered) do f:write(string.format("{a=%d,f=%d,v=%.9g},\n", res.address, flags, res.value)) end f:write("}\nfor i,v in ipairs(d)do v.address=v.a v.flags=v.f end\n") f:write("gg.setValues(d)\ngg.loadResults(d)\n") f:write("gg.toast('✅ 加载 '..#d..' 个结果')\n") f:close() gg.toast("💾 过滤脚本已保存") end -- 🎯 加载到 GG 列表 if #filtered > 0 then gg.clearResults() gg.setValues(filtered) gg.loadResults(filtered) gg.toast("🎯 成功加载 " .. #filtered .. " 项") else gg.toast("📭 无符合条件的结果") end end -- ======================== -- 🧩 注入函数体并生成新脚本 -- ======================== local code = 'gg.require("'..gg.VERSION..'", '..gg.BUILD..')\n\nv = ' -- 提取 getRanges 和 out 函数源码 local info_get = debug.getinfo(getRanges, "S") local info_out = debug.getinfo(out, "S") for line_num, line in io.lines(gg.getFile()) do if line_num >= info_get.linedefined and line_num <= info_get.lastlinedefined then code = code .. line .. '\n' end if line_num >= info_out.linedefined and line_num <= info_out.lastlinedefined then code = code .. line .. '\n' end end code = code .. '\n\nout()\n' -- 写入最终脚本 local fp = io.open(user_path, "w") if fp then fp:write(code) fp:close() gg.toast("🎉 脚本已生成:\n" .. user_path) else gg.alert("❌ 无法写入文件:\n" .. user_path) end -- 恢复可见性 gg.setVisible(true)
最新发布
09-21
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值