diff --git a/.clang-format b/.clang-format index be1ce9885..24d8a7e45 100644 --- a/.clang-format +++ b/.clang-format @@ -15,3 +15,4 @@ NamespaceIndentation: All PackConstructorInitializers: Never QualifierAlignment: Left SpaceBeforeCpp11BracedList: true +SortIncludes: false \ No newline at end of file diff --git a/.gitignore b/.gitignore index f0731bf56..8d43cbe22 100644 --- a/.gitignore +++ b/.gitignore @@ -5,4 +5,5 @@ /build/ /extension/package.json .DS_Store -.idea/ \ No newline at end of file +.idea/ +luamake/ \ No newline at end of file diff --git a/.gitmodules b/.gitmodules index 3f804f6b7..af3563817 100644 --- a/.gitmodules +++ b/.gitmodules @@ -3,7 +3,7 @@ url = https://github.com/actboy168/bee.lua.git [submodule "3rd/lua/luajit"] path = 3rd/lua/luajit - url = https://github.com/fesily/luajit2 + url = https://github.com/fesily/luajit [submodule "3rd/json.lua"] path = 3rd/json.lua url = https://github.com/actboy168/json.lua diff --git a/.luarc.json b/.luarc.json index b7253c816..639c085fa 100644 --- a/.luarc.json +++ b/.luarc.json @@ -37,5 +37,9 @@ "trailing-space": "Warning" } }, - "workspace.checkThirdParty": false + "workspace.checkThirdParty": false, + "type.inferParamType": true, + "workspace.library": [ + "${addons}/bee/module/library" + ] } \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json index dcc0c3cc7..66b465c5d 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,3 +1,12 @@ { "C_Cpp.autoAddFileAssociations": false, + "terminal.integrated.env.linux": { + "PATH": "${workspaceFolder}/luamake:${env:PATH}" + }, + "terminal.integrated.env.windows": { + "PATH": "${workspaceFolder}/luamake:${env:PATH}" + }, + "terminal.integrated.env.osx": { + "PATH": "${workspaceFolder}/luamake:${env:PATH}" + } } \ No newline at end of file diff --git a/3rd/frida_gum/gumpp b/3rd/frida_gum/gumpp index bd7de637a..98c2c3f5f 160000 --- a/3rd/frida_gum/gumpp +++ b/3rd/frida_gum/gumpp @@ -1 +1 @@ -Subproject commit bd7de637a9487858b9e9fe347e917a947503f14d +Subproject commit 98c2c3f5fbc37303cf22f7eb1cfcbdeb9257d9d3 diff --git a/3rd/lua/luajit b/3rd/lua/luajit index 2793f67cf..19878ec05 160000 --- a/3rd/lua/luajit +++ b/3rd/lua/luajit @@ -1 +1 @@ -Subproject commit 2793f67cf6ff66eea324c6b47bea773754e91eac +Subproject commit 19878ec05c239ccaf5f3d17af27670a963e25b8b diff --git a/Makefile b/Makefile new file mode 100644 index 000000000..db27c4b4c --- /dev/null +++ b/Makefile @@ -0,0 +1,30 @@ + +.PHONY: all download_deps build_luamake init_luamake install + +all: download_deps build_luamake + luamake/luamake build --release + +build_luamake: init_luamake + @echo "Building luamake..." + git submodule update --init --recursive + cd luamake && compile/build.sh + +download_deps: build_luamake + luamake/luamake lua compile/download_deps.lua + +init_luamake: + @if [ -d "luamake" ]; then \ + if [ -d "luamake/.git" ]; then \ + echo "Updating luamake repository..."; \ + (cd luamake && git pull && git submodule update --remote); \ + else \ + echo "Error: 'luamake' exists but is not a Git repository."; \ + fi; \ + else \ + echo "Cloning luamake repository..."; \ + git clone --recurse-submodules https://github.com/actboy168/luamake.git; \ + fi + +install: all + @echo "Installing Lua Debugger..." + luamake/luamake lua compile/install.lua diff --git a/compile/download_deps.lua b/compile/download_deps.lua index a39ad8837..1d36acaf2 100644 --- a/compile/download_deps.lua +++ b/compile/download_deps.lua @@ -4,7 +4,7 @@ local sp = require 'bee.subprocess' local output_dir = "3rd/frida_gum/" -local version = "16.0.10" +local version = "17.2.6" local file_fmt = "%s-%s." .. (platform.os == "windows" and "exe" or "tar.xz") local url_fmt = ("https://github.com/frida/frida/releases/download/%s/frida-gum-devkit-%s") diff --git a/compile/install.lua b/compile/install.lua new file mode 100644 index 000000000..d7ea6aa67 --- /dev/null +++ b/compile/install.lua @@ -0,0 +1,35 @@ +local fs = require 'bee.filesystem' +local home = assert(os.getenv("HOME") or os.getenv("HOMEPATH"), "HOME environment variable is not set") + +local vscode_dir = { + ".vscode", + ".vscode-server", +} + +local sourceDir = ... + +for i, v in ipairs(vscode_dir) do + local vscode_dir = fs.path(home) / v / "extensions" + if not fs.exists(vscode_dir) or not fs.is_directory(vscode_dir) then + goto continue + end + local extensions_json = vscode_dir / "extensions.json" + if not fs.exists(extensions_json) or not fs.is_regular_file(extensions_json) then + goto continue + end + local fp = io.open(extensions_json:string(), "r") + local content = fp:read("*a") + fp:close() + if not content:find("lua-debug", 1, true) then + goto continue + end + for dir in fs.pairs(vscode_dir) do + if dir:string():find("lua-debug", 1, true) then + local fn = assert(loadfile('compile/copy.lua')) + fn(sourceDir or 'publish', dir:string()) + os.exit(0) + end + end + ::continue:: +end +os.exit(1) \ No newline at end of file diff --git a/compile/luajit/defined.lua b/compile/luajit/defined.lua index 8c62c3c03..1c0fdd798 100644 --- a/compile/luajit/defined.lua +++ b/compile/luajit/defined.lua @@ -1,4 +1,5 @@ local lm = require "luamake" +local fs = require 'bee.filesystem' local arch = lm.runtime_platform:sub(lm.runtime_platform:find("-") + 1) if arch == "ia32" then @@ -9,12 +10,34 @@ local luaver = "luajit" local LUAJIT_ENABLE_LUA52COMPAT = "LUAJIT_ENABLE_LUA52COMPAT" local LUAJIT_NUMMODE = "LUAJIT_NUMMODE=2" local luajitDir = '3rd/lua/' .. luaver .. "/src" +local is_old_version_luajit = fs.exists(fs.path(luajitDir) / 'lj_init.c') local _M = { arch = arch, LUAJIT_ENABLE_LUA52COMPAT = LUAJIT_ENABLE_LUA52COMPAT, LUAJIT_NUMMODE = LUAJIT_NUMMODE, - luajitDir = luajitDir + luajitDir = luajitDir, + is_old_version_luajit = is_old_version_luajit } +if not is_old_version_luajit then + local sp = require 'bee.subprocess' + local sys = require 'bee.sys' + sp.spawn({ + sys.exe_path(), + "lua", + 'compile/luajit/relver.lua', + luajitDir.."/..", + luajitDir.."/luajit_relver.txt" + }):wait() + sp.spawn({ + sys.exe_path(), + "lua", + luajitDir.."/host/genversion.lua", + luajitDir.."/luajit_rolling.h", + luajitDir.."/luajit_relver.txt", + luajitDir.."/luajit.h" + }):wait() +end + return _M diff --git a/compile/luajit/make.lua b/compile/luajit/make.lua index 96d68d1a4..1684e9233 100644 --- a/compile/luajit/make.lua +++ b/compile/luajit/make.lua @@ -1,4 +1,5 @@ local lm = require "luamake" +local fs = require 'bee.filesystem' local bindir = "publish/runtime/" .. lm.runtime_platform @@ -9,6 +10,7 @@ local arch = defined.arch local LUAJIT_ENABLE_LUA52COMPAT = defined.LUAJIT_ENABLE_LUA52COMPAT local LUAJIT_NUMMODE = defined.LUAJIT_NUMMODE local luajitDir = defined.luajitDir +local is_old_version_luajit = defined.is_old_version_luajit local LJLIB_C = { luajitDir .. "/lib_base.c ", @@ -98,35 +100,39 @@ lm:build "lj_vm.obj" { outputs = lm.bindir .. "/lj_vm.obj", } -local lj_str_hash_flags = { - "-fno-stack-protector", - U_FORTIFY_SOURCE, - "-fPIC", -} +local has_str_hash = fs.exists(luajitDir.. '/lj_str_hash.c') +if has_str_hash then + local lj_str_hash_flags = { + "-fno-stack-protector", + U_FORTIFY_SOURCE, + "-fPIC", + } -if arch == "x64" then - table.insert(lj_str_hash_flags, "-msse4.2") -end + if arch == "x64" then + table.insert(lj_str_hash_flags, "-msse4.2") + end -lm:source_set("lj_str_hash.c") { - rootdir = luajitDir, - sources = { "lj_str_hash.c" }, - includes = { '.' }, - defines = { - LUAJIT_UNWIND_EXTERNAL, - _FILE_OFFSET_BITS, - _LARGEFILE_SOURCE, - LUA_MULTILIB, - LUAJIT_ENABLE_LUA52COMPAT, - LUAJIT_NUMMODE, - }, - linux = { + lm:source_set("lj_str_hash.c") { + rootdir = luajitDir, + sources = { "lj_str_hash.c" }, + includes = { '.' }, defines = { - "_GNU_SOURCE", - } - }, - flags = lj_str_hash_flags -} + LUAJIT_UNWIND_EXTERNAL, + _FILE_OFFSET_BITS, + _LARGEFILE_SOURCE, + LUA_MULTILIB, + LUAJIT_ENABLE_LUA52COMPAT, + LUAJIT_NUMMODE, + }, + linux = { + defines = { + "_GNU_SOURCE", + } + }, + flags = lj_str_hash_flags + } +end + lm:executable("luajit/lua") { rootdir = luajitDir, @@ -139,12 +145,14 @@ lm:executable("luajit/lua") { "lj_libdef.h", "lj_recdef.h", }, - deps = "lj_str_hash.c", + deps = { + has_str_hash and "lj_str_hash.c", + }, sources = { "luajit.c", "lj_*.c", "lib_*.c", - "!lj_str_hash.c", + has_str_hash and "!lj_str_hash.c", lm.bindir .. "/lj_vm.obj", }, includes = { diff --git a/compile/luajit/make_buildtools.lua b/compile/luajit/make_buildtools.lua index 1840cb79e..8179ccc2a 100644 --- a/compile/luajit/make_buildtools.lua +++ b/compile/luajit/make_buildtools.lua @@ -67,4 +67,4 @@ lm:executable("buildvm") { ".", "../../../../" .. lm.bindir } -} \ No newline at end of file +} diff --git a/compile/luajit/make_windows.lua b/compile/luajit/make_windows.lua index e717b36fe..709fdbb43 100644 --- a/compile/luajit/make_windows.lua +++ b/compile/luajit/make_windows.lua @@ -1,8 +1,10 @@ local lm = require "luamake" local luaver = "luajit" -local luajitDir = '3rd/lua/'..luaver +local luajitDir = '3rd/lua/' .. luaver +local luajitSrcDir = luajitDir .. '/src' local bindir = "publish/runtime/"..lm.runtime_platform +local is_old_version_luajit = require "compile.luajit.defined".is_old_version_luajit lm:exe "minilua" { rootdir = luajitDir, @@ -170,7 +172,7 @@ lm:shared_library "luajit/luajit" { }, sources = { "!src/luajit.c", - "!src/lj_init.c", + is_old_version_luajit and "!src/lj_init.c", "src/lj_*.c", "src/lib_*.c", }, @@ -189,7 +191,7 @@ lm:exe "luajit/lua" { }, sources = { "src/luajit.c", - "src/lj_init.c", + is_old_version_luajit and "src/lj_init.c", }, includes = { ".", diff --git a/compile/luajit/relver.lua b/compile/luajit/relver.lua new file mode 100644 index 000000000..a99fd0add --- /dev/null +++ b/compile/luajit/relver.lua @@ -0,0 +1,30 @@ +local fs = require 'bee.filesystem' +local subprocess = require 'bee.subprocess' +local platform = require 'bee.platform' + +local luajitDir = arg[1] +local output = arg[2] +---@type bee.subprocess.spawn.options +local args = fs.exists(luajitDir.."/.git") and { + "git", + "show", + "-s", + "--format=%ct" +} or { + platform.os ~= "windows" and "cat" or "type", + luajitDir.."/.relver" +} +args.cwd = luajitDir +args.stdout = true +args.stderr = true +args.searchPath = true + +local proc, err = subprocess.spawn(args) +if not proc then + print(err) + os.exit(1) +end + +proc:wait() + +io.open(output, 'w+'):write(proc.stdout:read('a')) diff --git a/compile/windows/init_luamake.bat b/compile/windows/init_luamake.bat new file mode 100644 index 000000000..c0f25990f --- /dev/null +++ b/compile/windows/init_luamake.bat @@ -0,0 +1,14 @@ +if exist luamake\.git ( + + pushd luamake + + git pull + git submodule update --remote + + popd + +) else ( + + git clone https://github.com/actboy168/luamake.git --recurse-submodules + +) diff --git a/docs/capabilities.md b/docs/capabilities.md index 576f08cfc..bd71baa29 100644 --- a/docs/capabilities.md +++ b/docs/capabilities.md @@ -31,7 +31,7 @@ | supportsDataBreakpoints | 🟨 | | supportsReadMemoryRequest | 🟩 | | supportsWriteMemoryRequest | 🟩 | -| supportsDisassembleRequest | 🟨 | +| supportsDisassembleRequest | 🟩 | | supportsCancelRequest | 🟨 | | supportsBreakpointLocationsRequest | 🟨 | | supportsClipboardContext | 🟩 | diff --git a/docs/luadebug/visitor.lua b/docs/luadebug/visitor.lua index 9472d7001..6e0abce8c 100644 --- a/docs/luadebug/visitor.lua +++ b/docs/luadebug/visitor.lua @@ -247,11 +247,28 @@ end function visitor.assign(v, new) end +---@class visitor.funcinfo +---@field source string? +---@field short_src string? +---@field what string? +---@field name string? +---@field namewhat string? +---@field linedefined integer? +---@field lastlinedefined integer? +---@field currentline integer? +---@field func refvalue? +---@field nparams integer? +---@field istailcall boolean? +---@field ftransfer integer? +---@field ntransfer integer? +---@field savepc string? 'p' +---@field cuurentpc integer? 'P' + --- ---@param frame integer | refvalue ---@param what string ----@param result table | nil ----@return table +---@param result visitor.funcinfo | nil +---@return visitor.funcinfo ---返回关于一个函数信息的表,如果有result则会将信息填在result中并返回。等同于debug.getinfo(frame, what)。 --- function visitor.getinfo(frame, what, result) @@ -321,4 +338,18 @@ end function visitor.cfunctioninfo(fun) end +---@class visitor.funcbc +---@field address string +---@field instruction string +---@field line? integer + +--- +---解析proto的内容 +---@param fun integer | refvalue +---@param start? integer +---@param count? integer +---@return visitor.funcbc[]?, string? +function visitor.funcbc(fun, start, count) +end + return visitor diff --git a/extension/script/attach.lua b/extension/script/attach.lua index 0f23ecec0..873d756b2 100644 --- a/extension/script/attach.lua +++ b/extension/script/attach.lua @@ -36,6 +36,8 @@ local function getLuaVersion() fd:close() return result end + +---@module "script.debugger" local dbg = dofile(path.."/script/debugger.lua") dbg:start { address = ("@%s/tmp/pid_%s"):format(path, pid), diff --git a/extension/script/backend/bootstrap.lua b/extension/script/backend/bootstrap.lua index 45ab5b875..200af3c65 100644 --- a/extension/script/backend/bootstrap.lua +++ b/extension/script/backend/bootstrap.lua @@ -1,17 +1,18 @@ local thread = require "bee.thread" local channel = require "bee.channel" - +local tag = require 'backend.tag' local m = {} local function hasMaster() - return channel.query "DbgMaster" ~= nil + local ok, masterChannel = pcall(channel.query, tag.getChannelKeyMaster()) + return ok and masterChannel end local function initMaster(rootpath, address) if hasMaster() then return end - local chan = channel.create "DbgMaster" + local chan = channel.create (tag.getChannelKeyMaster()) local mt = thread.create(([[ local rootpath = %q package.path = rootpath.."/script/?.lua" @@ -43,12 +44,32 @@ local function startWorker(rootpath) require 'backend.worker' end -function m.start(rootpath, address) +local function startDebugger(rootpath) + local function mydofile(filename) + local load = _VERSION == "Lua 5.1" and loadstring or load + local f = assert(io.open(filename)) + local str = f:read "*a" + f:close() + return assert(load(str, "=(debugger.lua)"))(filename) + end + ---@module 'script.debugger' + local debugger = mydofile(rootpath.."/script/debugger.lua") + + debugger:start({ address = ('127.0.0.1:44711'):format(rootpath), debug_debugger = true }) + +end + +function m.start(rootpath, address, worker_tag, debug_debugger) + require 'backend.tag'.setTag(worker_tag) initMaster(rootpath, address) startWorker(rootpath) + if debug_debugger == 'worker' then + startDebugger(rootpath) + end end -function m.attach(rootpath) +function m.attach(rootpath, worker_tag) + require 'backend.tag'.setTag(worker_tag) if hasMaster() then startWorker(rootpath) end diff --git a/extension/script/backend/master/request.lua b/extension/script/backend/master/request.lua index 590aab639..736af40d3 100644 --- a/extension/script/backend/master/request.lua +++ b/extension/script/backend/master/request.lua @@ -630,6 +630,38 @@ function request.writeMemory(req) }) end +function request.disassemble(req) + local args = req.arguments + local memoryReference = args.memoryReference + if type(memoryReference) ~= 'string' then + response.error(req, "No memoryReference:"..memoryReference) + return + end + local pos = memoryReference:find("_") + local log = require 'common.log' + log.error(memoryReference, pos) + memoryReference = tonumber(memoryReference:sub(1, pos - 1)) + local threadId = memoryReference >> 24 + local frameId = memoryReference & 0x00FFFFFF + if not threadId or not frameId then + response.error(req, "Error memoryReference") + return + end + if not checkThreadId(req, threadId) then + return + end + mgr.workerSend(threadId, { + cmd = 'disassemble', + command = req.command, + seq = req.seq, + memoryReference = frameId, + offset = args.offset, + instructionOffset = args.instructionOffset, + instructionCount = args.instructionCount, + resolveSymbols = args.resolveSymbols, + }) +end + function request.customRequestShowIntegerAsDec(req) response.success(req) mgr.workerBroadcast { diff --git a/extension/script/backend/master/threads.lua b/extension/script/backend/master/threads.lua index 57a0533dd..f78700e65 100644 --- a/extension/script/backend/master/threads.lua +++ b/extension/script/backend/master/threads.lua @@ -57,6 +57,9 @@ function CMD.stackTrace(w, req) end for _, frame in ipairs(req.body.stackFrames) do frame.id = (w << 24) | frame.id + if frame.instructionPointerReference then + frame.instructionPointerReference = frame.id .. '_' .. frame.instructionPointerReference + end if frame.source and frame.source.sourceReference then frame.source.sourceReference = (w << 32) | frame.source.sourceReference end @@ -174,4 +177,13 @@ function CMD.setThreadName(w, name) mgr.setThreadName(w, name) end +function CMD.disassemble(w, req) + if not req.success then + response.error(req, req.message) + return + end + + response.success(req, req.body) +end + return CMD diff --git a/extension/script/backend/tag.lua b/extension/script/backend/tag.lua new file mode 100644 index 000000000..18680cf36 --- /dev/null +++ b/extension/script/backend/tag.lua @@ -0,0 +1,28 @@ +local c = {} + +local tag = nil + +function c.setTag(t) + if tag and tag ~= t then + error(('luadebugger worker has set tag: [%s] -> [%s]'):format(tag, t)) + end + tag = t +end + +local function randomTag() + if not tag then + tag = tostring(math.random(9999)) + end +end + +function c.getChannelKeyMaster() + randomTag() + return 'DbgMaster'..(tag or '') +end + +function c.getChannelKeyWorker(id) + randomTag() + return ('DbgWorker(%s)'..(tag or '')):format(id) +end + +return c diff --git a/extension/script/backend/worker.lua b/extension/script/backend/worker.lua index 821299931..9bdaabadf 100644 --- a/extension/script/backend/worker.lua +++ b/extension/script/backend/worker.lua @@ -13,9 +13,11 @@ local thread = require 'bee.thread' local fs = require 'backend.worker.filesystem' local log = require 'common.log' local channel = require "bee.channel" +local tag = require 'backend.tag' local initialized = false local suspend = false +---@type visitor.funcinfo local info = {} local state = 'running' local stopReason = 'step' @@ -34,9 +36,9 @@ local baseL local CMD = {} local WorkerIdent = tostring(thread.id) -local WorkerChannel = ('DbgWorker(%s)'):format(WorkerIdent) +local WorkerChannel = tag.getChannelKeyWorker(WorkerIdent) -local masterThread = assert(channel.query 'DbgMaster') +local masterThread = assert(channel.query (tag.getChannelKeyMaster())) local workerThread = channel.create(WorkerChannel) local function workerThreadUpdate(timeout) @@ -188,7 +190,7 @@ end local function stackTrace(res, coid, start, levels) for depth = start, start + levels - 1 do - if not rdebug.getinfo(depth, "Slnf", info) then + if not rdebug.getinfo(depth, "Slnfp", info) then return true, depth - start end local r = { @@ -208,6 +210,7 @@ local function stackTrace(res, coid, start, levels) r.line = info.currentline r.presentationHint = 'label' end + r.instructionPointerReference = info.savepc else r.presentationHint = 'label' end @@ -413,6 +416,36 @@ function CMD.writeMemory(pkg) } end +function CMD.disassemble(pkg) + local coid = (pkg.memoryReference >> 16) + 1 + local depth = pkg.memoryReference & 0xFFFF + local offset = pkg.offset --TODO: offset ~= 0 + local instructionOffset = pkg.instructionOffset + local instructionCount = pkg.instructionCount + local resolveSymbols = pkg.resolveSymbols --TODO + hookmgr.sethost(assert(findFrame(coid))) + + local instructions, err = rdebug.funcbc(depth, instructionOffset, instructionCount) + if not instructions then + sendToMaster 'disassemble' { + command = pkg.command, + seq = pkg.seq, + success = false, + message = err + } + end + + sendToMaster 'disassemble' { + command = pkg.command, + seq = pkg.seq, + success = true, + body = { + instructions = instructions + } + } + +end + function CMD.setExpression(pkg) local depth = pkg.frameId & 0xFFFF local ok, result = evaluate.set(depth, pkg.expression, pkg.value) diff --git a/extension/script/common/capabilities.lua b/extension/script/common/capabilities.lua index 159e15210..2cce52274 100644 --- a/extension/script/common/capabilities.lua +++ b/extension/script/common/capabilities.lua @@ -12,6 +12,7 @@ return { supportSuspendDebuggee = true, supportsDelayedStackTraceLoading = true, supportsLoadedSourcesRequest = true, + supportsDisassembleRequest = true, supportsLogPoints = true, supportsTerminateThreadsRequest = true, supportsSetExpression = true, @@ -20,6 +21,7 @@ return { supportsWriteMemoryRequest = true, supportsClipboardContext = true, supportsExceptionFilterOptions = true, + completionTriggerCharacters = { '.', ':' }, supportsANSIStyling = true, exceptionBreakpointFilters = { { diff --git a/extension/script/common/log.lua b/extension/script/common/log.lua index 4c5ce2432..8c9906049 100644 --- a/extension/script/common/log.lua +++ b/extension/script/common/log.lua @@ -1,3 +1,10 @@ +---@class common.log +---@field trace fun(...) +---@field debug fun(...) +---@field info fun(...) +---@field warn fun(...) +---@field error fun(...) +---@field fatal fun(...) local log = { } log.file = nil diff --git a/extension/script/debugger.lua b/extension/script/debugger.lua index f17662161..24769b2e0 100644 --- a/extension/script/debugger.lua +++ b/extension/script/debugger.lua @@ -135,28 +135,35 @@ local function detectLuaDebugPath(cfg) return root..rt..'/luadebug.'..ext end +---@param cfg luadebugger.config local function initDebugger(dbg, cfg) if type(cfg) == "string" then cfg = { address = cfg } end - local luadebug = os.getenv "LUA_DEBUG_CORE" + local key_core = cfg.debug_debugger and "LUA_DEBUG_CORE_DEBUGGER" or "LUA_DEBUG_CORE"..(cfg.tag or '') + local key_path = cfg.debug_debugger and "LUA_DEBUG_PATH_DEBUGGER" or "LUA_DEBUG_PATH"..(cfg.tag or '') + + local luadebug = os.getenv (key_core) local updateenv = false if not luadebug then luadebug = detectLuaDebugPath(cfg) updateenv = true end if IsWindows then + if cfg.debug_debugger then + package.loadlib(luadebug, 'setflag_debugself')() + end assert(package.loadlib(luadebug, 'init'))(cfg.luaapi) end ---@type LuaDebug dbg.rdebug = assert(package.loadlib(luadebug, 'luaopen_luadebug'))() - if not os.getenv "LUA_DEBUG_PATH" then - dbg.rdebug.setenv("LUA_DEBUG_PATH", selfsource) + if not os.getenv(key_path) then + dbg.rdebug.setenv(key_path, selfsource) end if updateenv then - dbg.rdebug.setenv("LUA_DEBUG_CORE", luadebug) + dbg.rdebug.setenv(key_core, luadebug) end local function utf8(s) @@ -169,32 +176,48 @@ local function initDebugger(dbg, cfg) dbg.address = cfg.address and utf8(cfg.address) or nil end +---@class luadebugger.config +---@field address string +---@field client? boolean +---@field luaapi? string notice: buffer of FindLuaApi* +---@field luaVersion? 'lua51' | 'lua52' | 'lua53' | 'lua54' | 'luajit' | 'lua-latest' +---@field ansi? boolean +---@field tag? string channel name tag for multi lua vm +---@field debug_debugger? true for debug debugger +---@field debug_debugger_master? true +---@field debug_debugger_worker? true + local dbg = {} +---@param cfg luadebugger.config function dbg:start(cfg) initDebugger(self, cfg) self.rdebug.start(([[ local rootpath = %q package.path = rootpath.."/script/?.lua" - require "backend.bootstrap". start(rootpath, %q..%q) + require "backend.bootstrap". start(rootpath, %q..%q, %q, %s) ]]):format( self.root, cfg.client == true and "connect:" or "listen:", - dbg.address + dbg.address, + cfg.tag or '', + cfg.debug_debugger_master and 'true' or 'false' )) return self end +---@param cfg luadebugger.config function dbg:attach(cfg) initDebugger(self, cfg or {}) self.rdebug.start(([[ local rootpath = %q package.path = rootpath..'/script/?.lua' - require 'backend.bootstrap'. attach(rootpath) + require 'backend.bootstrap'. attach(rootpath, %q) ]]):format( - self.root + self.root, + cfg.tag or '' )) return self end diff --git a/extension/script/launch.lua b/extension/script/launch.lua index d4057e87c..18566dada 100644 --- a/extension/script/launch.lua +++ b/extension/script/launch.lua @@ -9,6 +9,7 @@ local function dofile(filename) f:close() return assert(load(str, "=(debugger.lua)"))(filename) end +---@module "script.debugger" local dbg = dofile(path.."/script/debugger.lua") dbg:set_wait("DBG", function(str) local params = {} diff --git a/make.bat b/make.bat new file mode 100644 index 000000000..81fa9501e --- /dev/null +++ b/make.bat @@ -0,0 +1,15 @@ +git submodule update --init --recursive + +call compile\windows\init_luamake.bat +if not exist luamake\luamake.exe ( + pushd luamake + call compile\build.bat + popd +) + +call luamake\luamake.exe lua compile/download_deps.lua +IF "%~1"=="" ( + call luamake\luamake.exe build --debug +) ELSE ( + call luamake\luamake.exe build --platform %1 +) \ No newline at end of file diff --git a/src/launcher/autoattach/autoattach.cpp b/src/launcher/autoattach/autoattach.cpp index b062b460c..0e0d017ea 100644 --- a/src/launcher/autoattach/autoattach.cpp +++ b/src/launcher/autoattach/autoattach.cpp @@ -26,13 +26,13 @@ namespace luadebug::autoattach { "R. Ierusalimschy, L. H. de Figueiredo, W. Celes" " $", // others }; - static bool is_lua_module(const char* module_path, bool check_export = true, bool check_strings = false) { - if (check_export && Gum::Process::module_find_export_by_name(module_path, find_lua_module_key)) return true; - if (Gum::Process::module_find_symbol_by_name(module_path, find_lua_module_key)) return true; + static bool is_lua_module(const Gum::Module& m, bool check_export = true, bool check_strings = false) { + if (check_export && m.find_export_by_name(find_lua_module_key)) return true; + if (m.find_symbol_by_name(find_lua_module_key)) return true; // TODO: when signature mode, check strings if (check_strings) { for (auto str : lua_module_strings) { - if (!Gum::search_module_string(module_path, str).empty()) { + if (!Gum::search_module_string(m, str).empty()) { return true; } } @@ -49,7 +49,8 @@ namespace luadebug::autoattach { false #endif ; - if (!is_lua_module(path.c_str(), check_export, true)) { + Gum::Module* m = Gum::Process::find_module_by_name(path.c_str()); + if (!is_lua_module(*m, check_export, true)) { return false; } // find lua module lazy @@ -63,14 +64,14 @@ namespace luadebug::autoattach { } bool found = false; - lua_module rm(ctx->mode); - Gum::Process::enumerate_modules([&rm, &found](const Gum::ModuleDetails& details) -> bool { - if (is_lua_module(details.path())) { - auto range = details.range(); - rm.memory_address = range.base_address; - rm.memory_size = range.size; - rm.path = details.path(); - rm.name = details.name(); + lua_module lm(ctx->mode); + Gum::Process::enumerate_modules([&lm, &found](const Gum::Module& m) -> bool { + if (is_lua_module(m)) { + auto range = m.range(); + lm.memory_address = range.base_address; + lm.memory_size = range.size; + lm.path = m.path(); + lm.name = m.name(); found = true; return false; } @@ -87,11 +88,11 @@ namespace luadebug::autoattach { return; } - log::info("find lua module path:{}", rm.path); - if (!rm.initialize()) { + log::info("find lua module path:{}", lm.path); + if (!lm.initialize()) { return; } - ctx->lua_module = std::move(rm); + ctx->lua_module = std::move(lm); } void start(work_mode mode) { diff --git a/src/launcher/autoattach/lua_module.cpp b/src/launcher/autoattach/lua_module.cpp index 8ee7b0225..e0c5f94fb 100644 --- a/src/launcher/autoattach/lua_module.cpp +++ b/src/launcher/autoattach/lua_module.cpp @@ -43,7 +43,7 @@ namespace luadebug::autoattach { return addr > m.memory_address && addr <= (void*)((intptr_t)m.memory_address + m.memory_size); } - static lua_version get_lua_version(const lua_module& m) { + static lua_version get_lua_version(const lua_module& lm) { /* luaJIT_version_2_1_0_beta3 luaJIT_version_2_1_0_beta2 @@ -51,11 +51,12 @@ namespace luadebug::autoattach { luaJIT_version_2_1_0_alpha */ for (void* addr : Gum::SymbolUtil::find_matching_functions("luaJIT_version_2_1_0*", true)) { - if (in_module(m, addr)) + if (in_module(lm, addr)) return lua_version::luajit; } - auto p = Gum::Process::module_find_symbol_by_name(m.path.c_str(), "lua_ident"); - ; + auto m = Gum::Process::find_module_by_name(lm.path.c_str()); + + auto p = m->find_symbol_by_name("lua_ident"); const char* lua_ident = (const char*)p; if (!lua_ident) return lua_version::unknown; diff --git a/src/launcher/resolver/lua_resolver.cpp b/src/launcher/resolver/lua_resolver.cpp index ce8b96494..38a3103b2 100644 --- a/src/launcher/resolver/lua_resolver.cpp +++ b/src/launcher/resolver/lua_resolver.cpp @@ -2,6 +2,7 @@ #include #include +#include namespace luadebug { static int (*_lua_pcall)(intptr_t L, int nargs, int nresults, int errfunc); @@ -13,15 +14,26 @@ namespace luadebug { return _luaL_loadbuffer(L, buff, size, name); } + static void initluamodule(const lua_resolver* resolver) { + if (resolver->luamodule) return; + resolver->luamodule = Gum::Process::find_module_by_name(resolver->module_name.data()); + if (!resolver->luamodule) { + throw std::runtime_error("Lua module not found: " + std::string(resolver->module_name)); + } + } + intptr_t lua_resolver::find_export(std::string_view name) const { - return (intptr_t)Gum::Process::module_find_export_by_name(module_name.data(), name.data()); + initluamodule(this); + return (intptr_t)luamodule->find_export_by_name(name.data()); } intptr_t lua_resolver::find_symbol(std::string_view name) const { - return (intptr_t)Gum::Process::module_find_symbol_by_name(module_name.data(), name.data()); + initluamodule(this); + return (intptr_t)luamodule->find_symbol_by_name(name.data()); } intptr_t lua_resolver::find(std::string_view name) const { + initluamodule(this); using namespace std::string_view_literals; for (auto& finder : { &lua_resolver::find_export, &lua_resolver::find_symbol }) { if (auto result = (this->*finder)(name)) { diff --git a/src/launcher/resolver/lua_resolver.h b/src/launcher/resolver/lua_resolver.h index 47e886bad..3613f3ae4 100644 --- a/src/launcher/resolver/lua_resolver.h +++ b/src/launcher/resolver/lua_resolver.h @@ -4,11 +4,14 @@ #include +#include + namespace luadebug { struct lua_resolver : lua::resolver { intptr_t find(std::string_view name) const override; intptr_t find_export(std::string_view name) const; intptr_t find_symbol(std::string_view name) const; std::string_view module_name; + mutable Gum::Module* luamodule = nullptr; }; } \ No newline at end of file diff --git a/src/luadebug/compat/5x/callinfo.cpp b/src/luadebug/compat/5x/callinfo.cpp index e8df449cc..cb31207f0 100644 --- a/src/luadebug/compat/5x/callinfo.cpp +++ b/src/luadebug/compat/5x/callinfo.cpp @@ -1,3 +1,6 @@ +#include + +#include #include #include "compat/internal.h" @@ -44,3 +47,41 @@ Proto* lua_getproto(lua_State* L, int idx) { auto func = LUA_STKID(L->top) + idx; return func2proto(func); } + +#if LUA_VERSION_NUM == 501 +static int currentpc(lua_State* L, CallInfo* ci) { + if (!isLua(ci)) return -1; /* function is not a Lua function? */ + if (ci == L->ci) + ci->savedpc = L->savedpc; + return pcRel(ci->savedpc, ci_func(ci)->l.p); +} + +#else +# ifndef ci_func +# define ci_func(ci) (clLvalue((ci)->func)) +# endif +static int currentpc(CallInfo* ci) { + lua_assert(isLua(ci)); + return pcRel(ci->u.l.savedpc, ci_func(ci)->p); +} + +#endif + +int lua_getcurrentpc(lua_State* L, CallInfo* ci) { +#if LUA_VERSION_NUM == 501 + return currentpc(L, ci); +#else + return currentpc(ci); +#endif +} + +const Instruction* lua_getsavedpc(lua_State* L, CallInfo* ci) { + if (!isLua(ci)) return nullptr; /* function is not a Lua function? */ +#if LUA_VERSION_NUM == 501 + if (ci == L->ci) + ci->savedpc = L->savedpc; + return ci->savedpc; +#else + return ci->u.l.savedpc; +#endif +} \ No newline at end of file diff --git a/src/luadebug/compat/5x/opcode.cpp b/src/luadebug/compat/5x/opcode.cpp new file mode 100644 index 000000000..538e8a0c2 --- /dev/null +++ b/src/luadebug/compat/5x/opcode.cpp @@ -0,0 +1,243 @@ +#include +#include +#include +#include +#define lprefix_h 1 +#include + +#include "compat/internal.h" + +#if LUA_VERSION_NUM == 504 +# include +const auto& luaP_opnames = opnames; +static int getbaseline(const Proto* f, int pc, int* basepc) { + if (f->sizeabslineinfo == 0 || pc < f->abslineinfo[0].pc) { + *basepc = -1; /* start from the beginning */ + return f->linedefined; + } else { + int i = cast_uint(pc) / MAXIWTHABS - 1; /* get an estimate */ + /* estimate must be a lower bound of the correct base */ + lua_assert(i < 0 || (i < f->sizeabslineinfo && f->abslineinfo[i].pc <= pc)); + while (i + 1 < f->sizeabslineinfo && pc >= f->abslineinfo[i + 1].pc) + i++; /* low estimate; adjust it */ + *basepc = f->abslineinfo[i].pc; + return f->abslineinfo[i].line; + } +} + +int luaG_getfuncline(const Proto* f, int pc) { + if (f->lineinfo == NULL) /* no debug information? */ + return -1; + else { + int basepc; + int baseline = getbaseline(f, pc, &basepc); + while (basepc++ < pc) { /* walk until given instruction */ + lua_assert(f->lineinfo[basepc] != ABSLINEINFO); + baseline += f->lineinfo[basepc]; /* correct line */ + } + return baseline; + } +} +#endif + +#define TOVOID(p) ((const void*)(p)) +#define MYK(x) (-1 - (x)) + +int LuaOpCode::line(Proto* f) const { +#if LUA_VERSION_NUM == 501 + return getline(f, pc); +#elif LUA_VERSION_NUM == 502 || LUA_VERSION_NUM == 503 + return getfuncline(f, pc); +#elif LUA_VERSION_NUM == 504 + return luaG_getfuncline(f, pc); +#endif +} + +std::string LuaOpCode::tostring() const { + std::string ret { name }; + ret.push_back('\t'); +#if LUA_VERSION_NUM == 504 + switch (o) { + case OP_UNM: + case OP_BNOT: + case OP_NOT: + case OP_LEN: + case OP_CONCAT: + case OP_SETUPVAL: + case OP_GETUPVAL: + case OP_LOADNIL: + case OP_MOVE: + ret += std::format("{} {}", a, b); + break; + case OP_LOADF: + case OP_LOADI: + ret += std::format("{} {}", a, sbx); + break; + case OP_LOADK: + ret += std::format("{} {}", a, bx); + break; + case OP_LOADFALSE: + case OP_LFALSESKIP: + case OP_LOADTRUE: + case OP_LOADKX: + case OP_CLOSE: + case OP_TBC: + ret += std::format("{}", a); + break; + case OP_GETFIELD: + case OP_NEWTABLE: + case OP_GETI: + case OP_ADDK: + case OP_SUBK: + case OP_MULK: + case OP_POWK: + case OP_MODK: + case OP_GETTABLE: + case OP_BANDK: + case OP_DIVK: + case OP_BORK: + case OP_BXORK: + case OP_IDIVK: + case OP_GETTABUP: + case OP_ADD: + case OP_SUB: + case OP_MUL: + case OP_MOD: + case OP_POW: + case OP_DIV: + case OP_IDIV: + case OP_BAND: + case OP_BOR: + case OP_BXOR: + case OP_SHL: + case OP_SHR: + case OP_MMBIN: + case OP_SETLIST: + case OP_CALL: + ret += std::format("{} {} {}", a, b, c); + break; + case OP_SETFIELD: + case OP_SETI: + case OP_SETTABLE: + case OP_SELF: + case OP_SETTABUP: +# define ISK (isk ? "k" : "") + ret += std::format("{} {} {}{}", a, b, c, ISK); + break; + case OP_SHRI: + case OP_SHLI: + case OP_ADDI: + ret += std::format("{} {} {}", a, b, sc); + break; + case OP_MMBINI: + ret += std::format("{} {} {} {}", a, sb, c, isk); + break; + case OP_MMBINK: + ret += std::format("{} {} {} {}", a, b, c, isk); + break; + case OP_JMP: + ret += std::format("{}", GETARG_sJ(*ins)); + break; + case OP_EQ: + case OP_LT: + case OP_LE: + case OP_EQK: + ret += std::format("{} {} {}", a, b, isk); + break; + case OP_EQI: + case OP_LTI: + case OP_LEI: + case OP_GTI: + case OP_GEI: + ret += std::format("{} {} {}", a, sb, isk); + break; + case OP_TEST: + ret += std::format("{} {}", a, isk); + break; + case OP_TESTSET: + ret += std::format("{} {} {}", a, b, isk); + break; + case OP_TAILCALL: + case OP_RETURN: + ret += std::format("{} {} {}{}", a, b, c, ISK); + break; + case OP_RETURN0: + break; + case OP_RETURN1: + case OP_VARARGPREP: + ret += std::format("{}", a); + break; + case OP_FORLOOP: + case OP_FORPREP: + case OP_TFORLOOP: + case OP_CLOSURE: + case OP_TFORPREP: + ret += std::format("{} {}", a, bx); + break; + case OP_TFORCALL: + case OP_VARARG: + ret += std::format("{} {}", a, c); + break; + case OP_EXTRAARG: + ret += std::format("{}", ax); + break; +# if 0 + default: + printf("%d %d %d",a,b,c); + printf(COMMENT "not handled"); + break; +# endif + } + +#else + switch (getOpMode(o)) { + case iABC: + ret += std::format("{}{}{}", a, getBMode(o) != OpArgN ? std::format(" {}", ISK(b) ? (MYK(INDEXK(b))) : b) : "", getCMode(o) != OpArgN ? std::format(" {}", ISK(c) ? (MYK(INDEXK(c))) : c) : ""); + break; + case iABx: +# if LUA_VERSION_NUM == 501 + ret += std::format("{} {}", a, (getBMode(o) == OpArgK) ? MYK(bx) : bx); +# else + ret += std::format("{} {}{}", a, (getBMode(o) == OpArgK) ? std::format(" {}", MYK(bx)) : "", (getBMode(o) == OpArgU) ? std::format(" {}", bx) : ""); +# endif + break; + case iAsBx: +# if LUA_VERSION_NUM == 501 + ret += std::format("{}", o == OP_JMP ? std::format("{}", sbx) : std::format("{} {}", a, sbx)); +# else + ret += std::format("{} {}", a, sbx); +# endif + break; +# if LUA_VERSION_NUM > 501 + case iAx: + ret += std::format("{}", MYK(ax)); + break; + +# endif + } +#endif + return ret; +} + +bool lua_getopcode(Proto* f, lua_Integer pc, LuaOpCode& ret) { + if (pc >= f->sizecode) return false; + const auto i = f->code[pc]; + ret.o = GET_OPCODE(i); + ret.a = GETARG_A(i); + ret.b = GETARG_B(i); + ret.c = GETARG_C(i); +#if LUA_VERSION_NUM > 501 + ret.ax = GETARG_Ax(i); +# if LUA_VERSION_NUM == 504 + ret.sb = GETARG_sB(i); + ret.sc = GETARG_sC(i); + ret.isk = GETARG_k(i); +# endif +#endif + ret.bx = GETARG_Bx(i); + ret.sbx = GETARG_sBx(i); + ret.pc = 0; + ret.name = luaP_opnames[ret.o]; + ret.ins = &(f->code[pc]); + return true; +} diff --git a/src/luadebug/compat/internal.h b/src/luadebug/compat/internal.h index 8f48fe566..cf3304ef5 100644 --- a/src/luadebug/compat/internal.h +++ b/src/luadebug/compat/internal.h @@ -1,7 +1,10 @@ #pragma once -#include "compat/lua.h" +#include + +#include +#include "compat/lua.h" #ifdef LUAJIT_VERSION const char* lua_cdatatype(lua_State* L, int idx); int lua_isinteger(lua_State* L, int idx); @@ -9,6 +12,7 @@ int lua_isinteger(lua_State* L, int idx); const void* lua_tocfunction_pointer(lua_State* L, int idx); +typedef uint32_t Instruction; #ifdef LUAJIT_VERSION union TValue; struct GCproto; @@ -17,12 +21,30 @@ using Proto = GCproto; #else struct CallInfo; struct Proto; +struct LuaOpCode { + int o; + int a, b, c, bx, sbx; +# if LUA_VERSION_NUM > 501 + int ax; +# if LUA_VERSION_NUM == 504 + int sb, sc, isk; +# endif +# endif + int pc; + const char* name; + uint32_t* ins; + std::string tostring() const; + int line(Proto* f) const; +}; +bool lua_getopcode(Proto* f, lua_Integer pc, LuaOpCode& ret); #endif Proto* lua_getproto(lua_State* L, int idx); CallInfo* lua_getcallinfo(lua_State* L); Proto* lua_ci2proto(CallInfo* ci); CallInfo* lua_debug2ci(lua_State* L, const lua_Debug* ar); +int lua_getcurrentpc(lua_State* L, CallInfo* ci); +const Instruction* lua_getsavedpc(lua_State* L, CallInfo* ci); #ifdef LUAJIT_VERSION int lua_isluafunc(lua_State* L, lua_Debug* ar); diff --git a/src/luadebug/compat/jit/callinfo.cpp b/src/luadebug/compat/jit/callinfo.cpp index d767db306..3d5792555 100644 --- a/src/luadebug/compat/jit/callinfo.cpp +++ b/src/luadebug/compat/jit/callinfo.cpp @@ -2,7 +2,9 @@ #include #include #include +#include #include +#include #include "compat/internal.h" @@ -31,6 +33,70 @@ static cTValue* debug_frame(lua_State* L, int level, int* size) { return NULL; /* Level not found. */ } +#define NO_BCPOS (~(BCPos)0) +static const BCIns* debug_frameins(lua_State* L, GCfunc* fn, cTValue* nextframe) { + const BCIns* ins; + lj_assertL(fn->c.gct == ~LJ_TFUNC || fn->c.gct == ~LJ_TTHREAD, "function or frame expected"); + if (!isluafunc(fn)) { /* Cannot derive a PC for non-Lua functions. */ + return nullptr; + } else if (nextframe == NULL) { /* Lua function on top. */ + void* cf = cframe_raw(L->cframe); + if (cf == NULL || (char*)cframe_pc(cf) == (char*)cframe_L(cf)) + return nullptr; + ins = cframe_pc(cf); /* Only happens during error/hook handling. */ + } else { + if (frame_islua(nextframe)) { + ins = frame_pc(nextframe); + } else if (frame_iscont(nextframe)) { + ins = frame_contpc(nextframe); + } else { + /* Lua function below errfunc/gc/hook: find cframe to get the PC. */ + void* cf = cframe_raw(L->cframe); + TValue* f = L->base - 1; + for (;;) { + if (cf == NULL) + return nullptr; + while (cframe_nres(cf) < 0) { + if (f >= restorestack(L, -cframe_nres(cf))) + break; + cf = cframe_raw(cframe_prev(cf)); + if (cf == NULL) + return nullptr; + } + if (f < nextframe) + break; + if (frame_islua(f)) { + f = frame_prevl(f); + } else { + if (frame_isc(f) || (frame_iscont(f) && frame_iscont_fficb(f))) + cf = cframe_raw(cframe_prev(cf)); + f = frame_prevd(f); + } + } + ins = cframe_pc(cf); + if (!ins) return nullptr; + } + } + return ins; +} +/* Return bytecode position for function/frame or NO_BCPOS. */ +static BCPos debug_framepc(lua_State* L, GCfunc* fn, cTValue* nextframe) { + const BCIns* ins = debug_frameins(L, fn, nextframe); + GCproto* pt; + BCPos pos; + if (!ins) return NO_BCPOS; + pt = funcproto(fn); + pos = proto_bcpos(pt, ins) - 1; +#if LJ_HASJIT + if (pos > pt->sizebc) { /* Undo the effects of lj_trace_exit for JLOOP. */ + GCtrace* T = (GCtrace*)((char*)(ins - 1) - offsetof(GCtrace, startins)); + lj_assertL(bc_isret(bc_op(ins[-1])), "return bytecode expected"); + pos = proto_bcpos(pt, mref(T->startpc, const BCIns)); + } +#endif + return pos; +} + CallInfo* lua_getcallinfo(lua_State* L) { int size; return const_cast(debug_frame(L, 0, &size)); @@ -60,3 +126,11 @@ CallInfo* lua_debug2ci(lua_State* L, const lua_Debug* ar) { int lua_isluafunc(lua_State* L, lua_Debug* ar) { return isluafunc(frame_func(lua_debug2ci(L, ar))); } + +int lua_getcurrentpc(lua_State* L, CallInfo* ci) { + return debug_framepc(L, frame_func(ci), ci); +} + +const Instruction* lua_getsavedpc(lua_State* L, CallInfo* ci) { + return debug_frameins(L, frame_func(ci), ci); +} \ No newline at end of file diff --git a/src/luadebug/luadbg/onelua.c b/src/luadebug/luadbg/onelua.c index 11398990f..3069d3975 100644 --- a/src/luadebug/luadbg/onelua.c +++ b/src/luadebug/luadbg/onelua.c @@ -10,7 +10,9 @@ #define LUAI_MAXCCALLS 1000 /* no need to change anything below this line ----------------------------- */ - +#ifdef _WIN32 +#define LUA_BUILD_AS_DLL +#endif #define LUA_CORE #define LUA_LIB #include "lprefix.h" diff --git a/src/luadebug/rdebug_init.cpp b/src/luadebug/rdebug_init.cpp index 57cc8b487..04240cda3 100644 --- a/src/luadebug/rdebug_init.cpp +++ b/src/luadebug/rdebug_init.cpp @@ -16,4 +16,9 @@ extern "C" __declspec(dllexport) int init(lua_State* hL) { return 0; } +extern "C" __declspec(dllexport) int setflag_debugself(lua_State* hL) { + luadebug::win32::setflag_debugself(true); + return 0; +} + #endif diff --git a/src/luadebug/rdebug_visitor.cpp b/src/luadebug/rdebug_visitor.cpp index 6d89b9f6d..86a66d179 100644 --- a/src/luadebug/rdebug_visitor.cpp +++ b/src/luadebug/rdebug_visitor.cpp @@ -797,13 +797,15 @@ namespace luadebug::visitor { #if LUA_VERSION_NUM == 501 uint8_t nparams = 0; #endif - switch (luadbg_type(L, 1)) { - case LUADBG_TNUMBER: + case LUADBG_TNUMBER: { frame = area.checkinteger(L, 1); if (lua_getstack(hL, frame, &ar) == 0) return 0; - if (lua_getinfo(hL, options.data(), &ar) == 0) + std::array what {}; + auto sz = std::min(what.size() - 1, options.size()); + std::copy_if(options.cbegin(), options.cbegin() + sz, what.begin(), [](auto c) { return c != 'p' && c != 'P'; }); + if (lua_getinfo(hL, what.data(), &ar) == 0) return 0; #if LUA_VERSION_NUM == 501 if (flags.test('u')) { @@ -815,16 +817,18 @@ namespace luadebug::visitor { #endif if (flags.test('f')) lua_pop(hL, 1); break; + } case LUADBG_TUSERDATA: { if (!copy_from_dbg(L, hL, area, 1, LUADBG_TFUNCTION)) { return area.raise_error("Need a function ref"); } - if (flags.test('f')) { + if (flags.test('f') || flags.test('p') || flags.test('P')) { return area.raise_error("invalid option"); } - char what[8]; + std::array what {}; what[0] = '>'; - strcpy(what + 1, options.data()); + auto sz = std::min(what.size() - 2, options.size()); + std::copy_if(options.cbegin(), options.cbegin() + sz, what.begin() + 1, [](auto c) { return c != 'p' && c != 'P'; }); #if LUA_VERSION_NUM == 501 if (flags.test('u')) { Proto* proto = lua_getproto(hL, -1); @@ -833,7 +837,7 @@ namespace luadebug::visitor { } } #endif - if (lua_getinfo(hL, what, &ar) == 0) { + if (lua_getinfo(hL, what.data(), &ar) == 0) { return 0; } break; @@ -905,6 +909,20 @@ namespace luadebug::visitor { luadbg_setfield(L, 3, "ntransfer"); break; #endif + case 'p': + if (ar.i_ci != 0) { + auto ins = lua_getsavedpc(hL, lua_debug2ci(hL, &ar)); + luadbg_pushfstring(L, "%p", ins); + luadbg_setfield(L, 3, "savepc"); + } + break; + case 'P': + if (ar.i_ci != 0) { + auto pc = lua_getcurrentpc(hL, lua_debug2ci(hL, &ar)); + luadbg_pushinteger(L, pc); + luadbg_setfield(L, 3, "cuurentpc"); + } + break; } } @@ -1047,6 +1065,67 @@ namespace luadebug::visitor { return 1; } + static int visitor_funcbc(luadbg_State* L, lua_State* hL, protected_area& area) { +#ifndef LUAJIT_VERSION + lua_Debug ar; + int frame = 0; + int pc = 0; + Proto* proto = nullptr; + switch (luadbg_type(L, 1)) { + case LUADBG_TNUMBER: + frame = area.checkinteger(L, 1); + if (lua_getstack(hL, frame, &ar) == 0) + return 0; + { + CallInfo* ci = lua_debug2ci(hL, &ar); + proto = lua_ci2proto(ci); + pc = lua_getcurrentpc(hL, ci); + } + break; + + case LUADBG_TUSERDATA: + if (!copy_from_dbg(L, hL, area, 1, LUADBG_TFUNCTION)) { + return area.raise_error("Need a function ref"); + } + proto = lua_getproto(hL, -1); + break; + default: + return area.raise_error("Need stack level (integer) or function ref"); + } + if (!proto) { + return area.raise_error("Can't get proto"); + } + + auto pushBc = [&L, &proto](const LuaOpCode& op) { + luadbg_createtable(L, 0, 2); + luadbg_pushfstring(L, "0x%p", (void*)op.ins); + luadbg_setfield(L, -2, "address"); + luadbg_pushstring(L, op.tostring().c_str()); + luadbg_setfield(L, -2, "instruction"); + luadbg_pushnumber(L, op.line(proto)); + luadbg_setfield(L, -2, "line"); + }; + int offset = area.optinteger(L, 2, 0); + luadbg_Integer count = area.optinteger(L, 3, (luadbg_Integer)(((uint32_t)-1) / 2)); + int index = 1; + pc += offset; + if (pc < 0) pc = 0; + luadbg_createtable(L, 1, 0); + while (count > 0) { + LuaOpCode op = {}; + if (!lua_getopcode(proto, (lua_Integer)pc, op)) + break; + pushBc(op); + luadbg_seti(L, -2, index); + index++; + pc++; + count--; + } + return 1; +#endif + return 0; + } + static int luaopen(luadbg_State* L) { luadbgL_Reg l[] = { { "getlocal", protected_call }, @@ -1080,6 +1159,7 @@ namespace luadebug::visitor { { "costatus", protected_call }, { "gccount", protected_call }, { "cfunctioninfo", protected_call }, + { "funcbc", protected_call }, { NULL, NULL }, }; debughost::get(L); diff --git a/src/luadebug/rdebug_win32.cpp b/src/luadebug/rdebug_win32.cpp index 565854ddc..b7bbe0e94 100644 --- a/src/luadebug/rdebug_win32.cpp +++ b/src/luadebug/rdebug_win32.cpp @@ -5,10 +5,13 @@ # include # include +# include + namespace luadebug::win32 { typedef FARPROC (*FindLuaApi)(const char* name); static HMODULE luadll = 0; static FindLuaApi luaapi = 0; + static bool debugSelf = false; HMODULE get_luadll() { return luadll; @@ -24,11 +27,27 @@ namespace luadebug::win32 { luaapi = (FindLuaApi)fn; } + void setflag_debugself(bool flag) { + if (debugSelf == flag) return; + debugSelf = flag; + // set lua dll self + caller_is_luadll(&set_luaapi); + // set lua api finder + luaapi = +[](const char* name) { + auto n = "luadbg" + std::string { name }.substr(3); + return ::GetProcAddress(luadll, n.c_str()); + }; + } + + const char* get_lua_module_key() { + return debugSelf ? "luadbg_newstate" : "lua_newstate"; + } + bool caller_is_luadll(void* callerAddress) { if (luadll) return true; HMODULE caller = NULL; if (GetModuleHandleExW(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS, (LPCWSTR)callerAddress, &caller) && caller) { - if (GetProcAddress(caller, "lua_newstate")) { + if (GetProcAddress(caller, get_lua_module_key())) { set_luadll(caller); return true; } @@ -81,7 +100,7 @@ namespace luadebug::win32 { if ((hModule = enumerate_modules(hProcess, hModule, &inh)) == NULL) { break; } - if (GetProcAddress(hModule, "lua_newstate")) { + if (GetProcAddress(hModule, get_lua_module_key())) { set_luadll(hModule); return true; } diff --git a/src/luadebug/rdebug_win32.h b/src/luadebug/rdebug_win32.h index 5e5658b9a..14e83c92c 100644 --- a/src/luadebug/rdebug_win32.h +++ b/src/luadebug/rdebug_win32.h @@ -12,6 +12,7 @@ namespace luadebug::win32 { bool caller_is_luadll(void* callerAddress); bool find_luadll(); void putenv(const char* envstr); + void setflag_debugself(bool flag); } #endif