diff --git a/.gitmodules b/.gitmodules index 3f804f6b7..7c87d52db 100644 --- a/.gitmodules +++ b/.gitmodules @@ -10,4 +10,6 @@ [submodule "3rd/frida_gum/gumpp"] path = 3rd/frida_gum/gumpp url = https://github.com/fesily/gumpp - +[submodule "3rd/json"] + path = 3rd/json + url = https://github.com/nlohmann/json diff --git a/3rd/json b/3rd/json new file mode 160000 index 000000000..546370c9e --- /dev/null +++ b/3rd/json @@ -0,0 +1 @@ +Subproject commit 546370c9e778d99e7176641123e5cc1d0b62acab diff --git a/compile/common/frida.lua b/compile/common/frida.lua index 4b33d9da6..bee9e4791 100644 --- a/compile/common/frida.lua +++ b/compile/common/frida.lua @@ -24,6 +24,11 @@ lm:source_set "frida" { '/wd5051', } }, + macos = { + frameworks = { + "CoreFoundation", + } + }, linux = { flags = "-fPIC", }, diff --git a/compile/common/launcher.lua b/compile/common/launcher.lua index d8348beb7..01f939e34 100644 --- a/compile/common/launcher.lua +++ b/compile/common/launcher.lua @@ -20,10 +20,12 @@ lm:lua_source 'launcher_source' { "3rd/bee.lua", "3rd/frida_gum/gumpp", "3rd/lua/lua54", + "3rd/json/single_include", "src/launcher", }, sources = { "src/launcher/**/*.cpp", + "!src/launcher/tools", "!src/launcher/hook/luajit_listener.cpp", }, defines = { diff --git a/compile/common/package_json.lua b/compile/common/package_json.lua index da4758c95..e9e4e75db 100644 --- a/compile/common/package_json.lua +++ b/compile/common/package_json.lua @@ -1,497 +1,532 @@ -local platform = ... -platform = platform or "unknown-unknown" - -local OS, ARCH = platform:match "^([^-]+)-([^-]+)$" - -local json = { - name = "lua-debug", - version = "1.61.0", - publisher = "actboy168", - displayName = "Lua Debug", - description = "VSCode debugger extension for Lua", - icon = "images/logo.png", - private = true, - author = { - name = "actboy168", - }, - bugs = { - url = "https://github.com/actboy168/lua-debug/issues", - }, - repository = { - type = "git", - url = "https://github.com/actboy168/lua-debug", - }, - keywords = { - "lua", - "debug", - "debuggers", - }, - categories = { - "Debuggers", - }, - engines = { - vscode = "^1.61.0", - }, - extensionKind = { - "workspace", - }, - main = "./js/extension.js", - activationEvents = { - "onCommand:extension.lua-debug.runEditorContents", - "onCommand:extension.lua-debug.debugEditorContents", - "onDebugInitialConfigurations", - "onDebugDynamicConfigurations", - "onDebugResolve:lua", - }, - capabilities = { - untrustedWorkspaces = { - description = "Debugging is disabled in Restricted Mode.", - supported = false, - }, - }, - contributes = { - breakpoints = { - { - language = "lua", - }, - { - language = "html", - }, - }, - commands = { - { - command = "extension.lua-debug.runEditorContents", - icon = "$(play)", - title = "Run File", - }, - { - command = "extension.lua-debug.debugEditorContents", - icon = "$(debug-alt-small)", - title = "Debug File", - }, - { - command = "extension.lua-debug.showIntegerAsDec", - title = "Show as Dec", - }, - { - command = "extension.lua-debug.showIntegerAsHex", - title = "Show as Hex", - }, - }, - configuration = { - properties = { - ["lua.debug.variables.showIntegerAsHex"] = { - default = false, - description = "Show integer as hex.", - type = "boolean", - }, - }, - }, - debuggers = { - { - type = "lua", - languages = { - "lua", - }, - label = "Lua Debug", - configurationSnippets = { - { - label = "Lua Debug: Launch Script", - description = "A new configuration for launching a lua debug program", - body = { - type = "lua", - request = "launch", - name = "${1:launch}", - stopOnEntry = true, - program = "^\"\\${workspaceFolder}/${2:main.lua}\"", - arg = { - }, - }, - }, - { - label = "Lua Debug: Attach", - description = "A new configuration for attaching a lua debug program", - body = { - type = "lua", - request = "attach", - name = "${1:attach}", - stopOnEntry = true, - address = "127.0.0.1:4278", - } - } - } - } - }, - menus = { - ["debug/variables/context"] = { - { - command = "extension.lua-debug.showIntegerAsDec", - group = "1_view", - when = "debugConfigurationType == 'lua' && debugProtocolVariableMenuContext == 'integer/hex'", - }, - { - command = "extension.lua-debug.showIntegerAsHex", - group = "1_view", - when = "debugConfigurationType == 'lua' && debugProtocolVariableMenuContext == 'integer/dec'", - }, - }, - ["editor/title/run"] = { - { - command = "extension.lua-debug.runEditorContents", - when = "resourceLangId == lua", - }, - { - command = "extension.lua-debug.debugEditorContents", - when = "resourceLangId == lua", - }, - }, - }, - }, -} - -local attributes = {} - -attributes.common = { - luaVersion = { - default = "5.4", - enum = { - "5.1", - "5.2", - "5.3", - "5.4", - "latest", - "jit", - }, - markdownDescription = "%lua.debug.launch.luaVersion.description%", - type = "string", - }, - outputCapture = { - default = { - }, - items = { - enum = { - "print", - "io.write", - "stdout", - "stderr", - }, - }, - markdownDescription = "From where to capture output messages: print or stdout/stderr streams.", - type = "array", - }, - pathFormat = { - default = "path", - enum = { - "path", - "linuxpath", - }, - markdownDescription = "Path format", - type = "string", - }, - sourceFormat = { - default = "path", - enum = { - "path", - "string", - "linuxpath", - }, - markdownDescription = "Source format", - type = "string", - }, - sourceMaps = { - default = { - { - "./*", - "${workspaceFolder}/*", - }, - }, - markdownDescription = "The source path of the remote host and the source path of local.", - type = "array", - }, - skipFiles = { - default = { - }, - items = { - type = "string", - }, - markdownDescription = "An array of glob patterns for files to skip when debugging.", - type = "array", - }, - stopOnEntry = { - default = false, - markdownDescription = "Automatically stop after entry.", - type = "boolean", - }, - stopOnThreadEntry = { - default = true, - markdownDescription = "Automatically stop after thread entry.", - type = "boolean", - }, - address = { - default = "127.0.0.1:4278", - markdownDescription = [[ -Debugger address. -1. IPv4 e.g. `127.0.0.1:4278` -2. IPv6 e.g. `[::1]:4278` -3. Unix domain socket e.g. `@c:\\unix.sock`]], - type = { - "string", - "null", - }, - }, - client = { - default = true, - markdownDescription = "Choose whether to `connect` or `listen`.", - type = "boolean", - }, - inject = { - default = "none", - markdownDescription = "How to inject debugger.", - enum = { - "none", - }, - type = "string", - }, -} - -if OS == "win32" then - attributes.common.inject.default = "hook" - table.insert(attributes.common.inject.enum, "hook") -else - if OS == "darwin" then - attributes.common.inject.default = "lldb" - table.insert(attributes.common.inject.enum, "hook") - else - attributes.common.inject.default = "gdb" - end - table.insert(attributes.common.inject.enum, "lldb") - table.insert(attributes.common.inject.enum, "gdb") - attributes.common.inject_executable = { - markdownDescription = "inject executable path", - type = { - "string", - "null", - }, - } -end - -attributes.attach = { -} - -if OS == "win32" or OS == "darwin" then - attributes.attach.processId = { - default = "${command:pickProcess}", - markdownDescription = "Id of process to attach to.", - type = "string", - } - attributes.attach.processName = { - default = "lua.exe", - markdownDescription = "Name of process to attach to.", - type = "string", - } - json.activationEvents[#json.activationEvents+1] = "onCommand:extension.lua-debug.pickProcess" - json.contributes.debuggers[1].variables = { - pickProcess = "extension.lua-debug.pickProcess", - } -end - -attributes.launch = { - luaexe = { - markdownDescription = "Absolute path to the lua exe.", - type = "string", - }, - program = { - default = "${workspaceFolder}/main.lua", - markdownDescription = "Lua program to debug - set this to the path of the script", - type = "string", - }, - arg = { - default = { - }, - markdownDescription = "Command line argument, arg[1] ... arg[n]", - type = "array", - }, - arg0 = { - default = { - }, - markdownDescription = "Command line argument, arg[-n] ... arg[0]", - type = { - "string", - "array", - }, - }, - path = { - default = "${workspaceFolder}/?.lua", - markdownDescription = "%lua.debug.launch.path.description%", - type = { - "string", - "array", - "null", - }, - }, - cpath = { - markdownDescription = "%lua.debug.launch.cpath.description%", - type = { - "string", - "array", - "null", - }, - }, - luaArch = { - markdownDescription = "%lua.debug.launch.luaArch.description%", - type = "string", - }, - cwd = { - default = "${workspaceFolder}", - markdownDescription = "Working directory at program startup", - type = { - "string", - "null", - }, - }, - env = { - additionalProperties = { - type = { - "string", - "null", - }, - }, - default = { - PATH = "${workspaceFolder}", - }, - markdownDescription = "Environment variables passed to the program. The value `null` removes thevariable from the environment.", - type = "object", - }, - console = { - default = "integratedTerminal", - enum = { - "internalConsole", - "integratedTerminal", - "externalTerminal", - }, - enummarkdownDescriptions = { - "%lua.debug.launch.console.internalConsole.description%", - "%lua.debug.launch.console.integratedTerminal.description%", - "%lua.debug.launch.console.externalTerminal.description%", - }, - markdownDescription = "%lua.debug.launch.console.description%", - type = "string", - }, - runtimeExecutable = { - default = OS == "win32" and "${workspaceFolder}/lua.exe" or "${workspaceFolder}/lua", - markdownDescription = "Runtime to use. Either an absolute path or the name of a runtime availableon the PATH.", - type = { - "string", - "null", - }, - }, - runtimeArgs = { - default = "${workspaceFolder}/main.lua", - markdownDescription = "Arguments passed to the runtime executable.", - type = { - "string", - "array", - "null", - }, - }, -} - -if OS == "win32" or OS == "darwin" then - local snippets = json.contributes.debuggers[1].configurationSnippets - snippets[#snippets+1] = { - label = "Lua Debug: Launch Process", - description = "A new configuration for launching a lua process", - body = { - type = "lua", - request = "launch", - name = "${1:launch process}", - stopOnEntry = true, - runtimeExecutable = "^\"\\${workspaceFolder}/lua.exe\"", - runtimeArgs = "^\"\\${workspaceFolder}/${2:main.lua}\"", - } - } - snippets[#snippets+1] = { - label = "Lua Debug: Attach Process", - description = "A new configuration for attaching a lua debug program", - body = { - type = "lua", - request = "attach", - name = "${1:attach}", - stopOnEntry = true, - processId = "^\"\\${command:pickProcess}\"", - } - } -end - -if OS == "win32" then - attributes.common.sourceCoding = { - default = "utf8", - enum = { - "utf8", - "ansi", - }, - markdownDescription = "%lua.debug.launch.sourceCoding.description%", - type = "string", - } - attributes.common.useWSL = { - default = true, - description = "Use Windows Subsystem for Linux.", - type = "boolean", - } - attributes.launch.luaexe.default = "${workspaceFolder}/lua.exe" - attributes.launch.cpath.default = "${workspaceFolder}/?.dll" -else - attributes.launch.luaexe.default = "${workspaceFolder}/lua" - attributes.launch.cpath.default = "${workspaceFolder}/?.so" -end - -local function SupportedArchs() - if OS == "win32" then - return "x86_64", "x86" - elseif OS == "darwin" then - if ARCH == "arm64" then - return "arm64", "x86_64" - else - return "x86_64" - end - elseif OS == "linux" then - if ARCH == "arm64" then - return "arm64" - else - return "x86_64" - end - end -end - -local Archs = { SupportedArchs() } -attributes.launch.luaArch.default = Archs[1] -attributes.launch.luaArch.enum = Archs - -for k, v in pairs(attributes.common) do - attributes.attach[k] = v - attributes.launch[k] = v -end -json.contributes.debuggers[1].configurationAttributes = { - launch = { properties = attributes.launch }, - attach = { properties = attributes.attach }, -} - -local configuration = json.contributes.configuration.properties -for _, name in ipairs { "luaArch", "luaVersion", "sourceCoding", "path", "cpath", "console" } do - local attr = attributes.launch[name] or attributes.attach[name] - if attr then - local cfg = {} - for k, v in pairs(attr) do - if k == 'markdownDescription' then - k = 'description' - end - if k == 'enummarkdownDescriptions' then - k = 'enumDescriptions' - end - cfg[k] = v - end - configuration["lua.debug.settings."..name] = cfg - end -end - -return json +local platform = ... +platform = platform or "unknown-unknown" + +local OS, ARCH = platform:match "^([^-]+)-([^-]+)$" + +local json = { + name = "lua-debug", + version = "1.61.0", + publisher = "actboy168", + displayName = "Lua Debug", + description = "VSCode debugger extension for Lua", + icon = "images/logo.png", + private = true, + author = { + name = "actboy168", + }, + bugs = { + url = "https://github.com/actboy168/lua-debug/issues", + }, + repository = { + type = "git", + url = "https://github.com/actboy168/lua-debug", + }, + keywords = { + "lua", + "debug", + "debuggers", + }, + categories = { + "Debuggers", + }, + engines = { + vscode = "^1.61.0", + }, + extensionKind = { + "workspace", + }, + main = "./js/extension.js", + activationEvents = { + "onCommand:extension.lua-debug.runEditorContents", + "onCommand:extension.lua-debug.debugEditorContents", + "onDebugInitialConfigurations", + "onDebugDynamicConfigurations", + "onDebugResolve:lua", + }, + capabilities = { + untrustedWorkspaces = { + description = "Debugging is disabled in Restricted Mode.", + supported = false, + }, + }, + contributes = { + breakpoints = { + { + language = "lua", + }, + { + language = "html", + }, + }, + commands = { + { + command = "extension.lua-debug.runEditorContents", + icon = "$(play)", + title = "Run File", + }, + { + command = "extension.lua-debug.debugEditorContents", + icon = "$(debug-alt-small)", + title = "Debug File", + }, + { + command = "extension.lua-debug.showIntegerAsDec", + title = "Show as Dec", + }, + { + command = "extension.lua-debug.showIntegerAsHex", + title = "Show as Hex", + }, + }, + configuration = { + properties = { + ["lua.debug.variables.showIntegerAsHex"] = { + default = false, + description = "Show integer as hex.", + type = "boolean", + }, + }, + }, + debuggers = { + { + type = "lua", + languages = { + "lua", + }, + label = "Lua Debug", + configurationSnippets = { + { + label = "Lua Debug: Launch Script", + description = "A new configuration for launching a lua debug program", + body = { + type = "lua", + request = "launch", + name = "${1:launch}", + stopOnEntry = true, + program = "^\"\\${workspaceFolder}/${2:main.lua}\"", + arg = { + }, + }, + }, + { + label = "Lua Debug: Attach", + description = "A new configuration for attaching a lua debug program", + body = { + type = "lua", + request = "attach", + name = "${1:attach}", + stopOnEntry = true, + address = "127.0.0.1:4278", + } + } + } + } + }, + menus = { + ["debug/variables/context"] = { + { + command = "extension.lua-debug.showIntegerAsDec", + group = "1_view", + when = "debugConfigurationType == 'lua' && debugProtocolVariableMenuContext == 'integer/hex'", + }, + { + command = "extension.lua-debug.showIntegerAsHex", + group = "1_view", + when = "debugConfigurationType == 'lua' && debugProtocolVariableMenuContext == 'integer/dec'", + }, + }, + ["editor/title/run"] = { + { + command = "extension.lua-debug.runEditorContents", + when = "resourceLangId == lua", + }, + { + command = "extension.lua-debug.debugEditorContents", + when = "resourceLangId == lua", + }, + }, + }, + }, +} + +local attributes = {} + +attributes.common = { + luaVersion = { + default = "5.4", + enum = { + "5.1", + "5.2", + "5.3", + "5.4", + "latest", + "jit", + }, + markdownDescription = "%lua.debug.launch.luaVersion.description%", + type = "string", + }, + outputCapture = { + default = { + }, + items = { + enum = { + "print", + "io.write", + "stdout", + "stderr", + }, + }, + markdownDescription = "From where to capture output messages: print or stdout/stderr streams.", + type = "array", + }, + pathFormat = { + default = "path", + enum = { + "path", + "linuxpath", + }, + markdownDescription = "Path format", + type = "string", + }, + sourceFormat = { + default = "path", + enum = { + "path", + "string", + "linuxpath", + }, + markdownDescription = "Source format", + type = "string", + }, + sourceMaps = { + default = { + { + "./*", + "${workspaceFolder}/*", + }, + }, + markdownDescription = "The source path of the remote host and the source path of local.", + type = "array", + }, + skipFiles = { + default = { + }, + items = { + type = "string", + }, + markdownDescription = "An array of glob patterns for files to skip when debugging.", + type = "array", + }, + stopOnEntry = { + default = false, + markdownDescription = "Automatically stop after entry.", + type = "boolean", + }, + stopOnThreadEntry = { + default = true, + markdownDescription = "Automatically stop after thread entry.", + type = "boolean", + }, + address = { + default = "127.0.0.1:4278", + markdownDescription = [[ +Debugger address. +1. IPv4 e.g. `127.0.0.1:4278` +2. IPv6 e.g. `[::1]:4278` +3. Unix domain socket e.g. `@c:\\unix.sock`]], + type = { + "string", + "null", + }, + }, + client = { + default = true, + markdownDescription = "Choose whether to `connect` or `listen`.", + type = "boolean", + }, + inject = { + default = "none", + markdownDescription = "How to inject debugger.", + enum = { + "none", + }, + type = "string", + }, + module = { + default = "null", + markdownDescription = "specify lua module path/name", + type = "string", + }, + signatures = { + default = "null", + type = "object", + markdownDescription = "signature info", + additionalProperties = { + type = "object", + properties = { + start_offset = { + type = "integer" + }, + end_offset = { + type = "integer" + }, + pattern = { + type = "string" + }, + pattern_offset = { + type = "integer" + }, + hit_offset = { + type = "integer", + } + }, + required = { "pattern" } + } + } +} + +if OS == "win32" then + attributes.common.inject.default = "hook" + table.insert(attributes.common.inject.enum, "hook") +else + if OS == "darwin" then + attributes.common.inject.default = "lldb" + table.insert(attributes.common.inject.enum, "hook") + else + attributes.common.inject.default = "gdb" + end + table.insert(attributes.common.inject.enum, "lldb") + table.insert(attributes.common.inject.enum, "gdb") + attributes.common.inject_executable = { + markdownDescription = "inject executable path", + type = { + "string", + "null", + }, + } +end + +attributes.attach = { +} + +if OS == "win32" or OS == "darwin" then + attributes.attach.processId = { + default = "${command:pickProcess}", + markdownDescription = "Id of process to attach to.", + type = "string", + } + attributes.attach.processName = { + default = "lua.exe", + markdownDescription = "Name of process to attach to.", + type = "string", + } + json.activationEvents[#json.activationEvents+1] = "onCommand:extension.lua-debug.pickProcess" + json.contributes.debuggers[1].variables = { + pickProcess = "extension.lua-debug.pickProcess", + } +end + +attributes.launch = { + luaexe = { + markdownDescription = "Absolute path to the lua exe.", + type = "string", + }, + program = { + default = "${workspaceFolder}/main.lua", + markdownDescription = "Lua program to debug - set this to the path of the script", + type = "string", + }, + arg = { + default = { + }, + markdownDescription = "Command line argument, arg[1] ... arg[n]", + type = "array", + }, + arg0 = { + default = { + }, + markdownDescription = "Command line argument, arg[-n] ... arg[0]", + type = { + "string", + "array", + }, + }, + path = { + default = "${workspaceFolder}/?.lua", + markdownDescription = "%lua.debug.launch.path.description%", + type = { + "string", + "array", + "null", + }, + }, + cpath = { + markdownDescription = "%lua.debug.launch.cpath.description%", + type = { + "string", + "array", + "null", + }, + }, + luaArch = { + markdownDescription = "%lua.debug.launch.luaArch.description%", + type = "string", + }, + cwd = { + default = "${workspaceFolder}", + markdownDescription = "Working directory at program startup", + type = { + "string", + "null", + }, + }, + env = { + additionalProperties = { + type = { + "string", + "null", + }, + }, + default = { + PATH = "${workspaceFolder}", + }, + markdownDescription = "Environment variables passed to the program. The value `null` removes thevariable from the environment.", + type = "object", + }, + console = { + default = "integratedTerminal", + enum = { + "internalConsole", + "integratedTerminal", + "externalTerminal", + }, + enummarkdownDescriptions = { + "%lua.debug.launch.console.internalConsole.description%", + "%lua.debug.launch.console.integratedTerminal.description%", + "%lua.debug.launch.console.externalTerminal.description%", + }, + markdownDescription = "%lua.debug.launch.console.description%", + type = "string", + }, + runtimeExecutable = { + default = OS == "win32" and "${workspaceFolder}/lua.exe" or "${workspaceFolder}/lua", + markdownDescription = "Runtime to use. Either an absolute path or the name of a runtime availableon the PATH.", + type = { + "string", + "null", + }, + }, + runtimeArgs = { + default = "${workspaceFolder}/main.lua", + markdownDescription = "Arguments passed to the runtime executable.", + type = { + "string", + "array", + "null", + }, + }, +} + +if OS == "win32" or OS == "darwin" then + local snippets = json.contributes.debuggers[1].configurationSnippets + snippets[#snippets+1] = { + label = "Lua Debug: Launch Process", + description = "A new configuration for launching a lua process", + body = { + type = "lua", + request = "launch", + name = "${1:launch process}", + stopOnEntry = true, + runtimeExecutable = "^\"\\${workspaceFolder}/lua.exe\"", + runtimeArgs = "^\"\\${workspaceFolder}/${2:main.lua}\"", + } + } + snippets[#snippets+1] = { + label = "Lua Debug: Attach Process", + description = "A new configuration for attaching a lua debug program", + body = { + type = "lua", + request = "attach", + name = "${1:attach}", + stopOnEntry = true, + processId = "^\"\\${command:pickProcess}\"", + } + } +end + +if OS == "win32" then + attributes.common.sourceCoding = { + default = "utf8", + enum = { + "utf8", + "ansi", + }, + markdownDescription = "%lua.debug.launch.sourceCoding.description%", + type = "string", + } + attributes.common.useWSL = { + default = true, + description = "Use Windows Subsystem for Linux.", + type = "boolean", + } + attributes.launch.luaexe.default = "${workspaceFolder}/lua.exe" + attributes.launch.cpath.default = "${workspaceFolder}/?.dll" +else + attributes.launch.luaexe.default = "${workspaceFolder}/lua" + attributes.launch.cpath.default = "${workspaceFolder}/?.so" +end + +local function SupportedArchs() + if OS == "win32" then + return "x86_64", "x86" + elseif OS == "darwin" then + if ARCH == "arm64" then + return "arm64", "x86_64" + else + return "x86_64" + end + elseif OS == "linux" then + if ARCH == "arm64" then + return "arm64" + else + return "x86_64" + end + end +end + +local Archs = { SupportedArchs() } +attributes.launch.luaArch.default = Archs[1] +attributes.launch.luaArch.enum = Archs + +for k, v in pairs(attributes.common) do + attributes.attach[k] = v + attributes.launch[k] = v +end + +table.insert(attributes.attach.luaVersion.enum, "unknown") +attributes.attach.luaVersion.default = "unknown" + +json.contributes.debuggers[1].configurationAttributes = { + launch = { properties = attributes.launch }, + attach = { properties = attributes.attach }, +} + +local configuration = json.contributes.configuration.properties +for _, name in ipairs { "luaArch", "luaVersion", "sourceCoding", "path", "cpath", "console" } do + local attr = attributes.launch[name] or attributes.attach[name] + if attr then + local cfg = {} + for k, v in pairs(attr) do + if k == 'markdownDescription' then + k = 'description' + end + if k == 'enummarkdownDescriptions' then + k = 'enumDescriptions' + end + cfg[k] = v + end + configuration["lua.debug.settings."..name] = cfg + end +end + +return json diff --git a/compile/common/runtime.lua b/compile/common/runtime.lua index 1e4115ef5..f527c1d92 100644 --- a/compile/common/runtime.lua +++ b/compile/common/runtime.lua @@ -100,6 +100,8 @@ for _, luaver in ipairs { "lua51", "lua52", "lua53", "lua54", "lua-latest", "lua }, visibility = "default", links = "m", + flags = "-g", + ldflags = "-g", linux = { defines = "LUA_USE_LINUX", links = { "pthread", "dl" }, diff --git a/compile/macos/make.lua b/compile/macos/make.lua index 23e56b6f7..dd7ff72a5 100644 --- a/compile/macos/make.lua +++ b/compile/macos/make.lua @@ -13,6 +13,7 @@ require "compile.common.lua-debug" lm.runtime_platform = lm.platform require "compile.macos.runtime" require "compile.macos.shellcode" +require "compile.macos.signature_compiler" if lm.platform == "darwin-arm64" then require "compile.common.run_luamake" @@ -56,6 +57,11 @@ else } end +lm:phony "compile_signature" { + deps = { "signature_compiler", "merge_launcher", "runtime", "lua-debug" }, + "$luamake", "lua", "compile/signature_compiler.lua" +} + lm:default { "common", "lua-debug", @@ -63,4 +69,5 @@ lm:default { "process_inject_helper", "merge_launcher", lm.platform == "darwin-arm64" and "x86_64", + "compile_signature" } diff --git a/compile/macos/runtime.lua b/compile/macos/runtime.lua index 4e8c1f2e3..a5326fa8a 100644 --- a/compile/macos/runtime.lua +++ b/compile/macos/runtime.lua @@ -3,10 +3,11 @@ local lm = require "luamake" require "compile.common.config" require "compile.common.runtime" require "compile.common.launcher" +require "compile.macos.signature_compiler" lm:lua_library 'launcher' { export_luaopen = "off", deps = { "launcher_source", }, -} +} \ No newline at end of file diff --git a/compile/macos/signature_compiler.lua b/compile/macos/signature_compiler.lua new file mode 100644 index 000000000..86aacb3d0 --- /dev/null +++ b/compile/macos/signature_compiler.lua @@ -0,0 +1,9 @@ +local lm = require "luamake" + +require "compile.common.frida" + +lm:executable("signature_compiler") { + deps = { "frida" }, + includes = { "3rd/frida_gum/gumpp", "3rd/bee.lua" }, + sources = { "src/launcher/tools/signature_compiler.cpp" }, +} diff --git a/compile/signature_compiler.lua b/compile/signature_compiler.lua new file mode 100644 index 000000000..71f0653f3 --- /dev/null +++ b/compile/signature_compiler.lua @@ -0,0 +1,124 @@ +local fs = require "bee.filesystem" +local sp = require "bee.subprocess" +local platform = require "bee.platform" + +package.path = package.path..";3rd/json.lua/?.lua" + +local json = require "json-beautify" + +local luadebug_files = {} + +local function scan(dir, platform_arch) + for path in fs.pairs(dir) do + if fs.is_directory(path) then + scan(path, platform_arch or path:filename():string()) + else + if path:filename():string():find("luadebug") then + local t = { platform_arch = platform_arch, luadebug = path:string() } + + local dir_path = path:parent_path() + local version = dir_path:filename():string() + t.version = version + + if platform.os == "windows" then + t.lua_modlue = dir_path / version..".dll" + else + t.lua_modlue = dir_path / "lua" + end + luadebug_files[#luadebug_files+1] = t + end + end + end +end + +scan((fs.path("publish") / "runtime"):string()) + +local option = { + ext = "so", + is_export = false, +} +if platform.os == "windows" then + option = { + ext = "dll", + is_export = true + } +end + +local launcher_path = (fs.path("publish") / "bin" / "launcher."..option.ext):string() + +local function compiler(executable, import_file, is_string, lua_modlue) + print(executable, import_file, is_string, lua_modlue, option.is_export) + local process = assert(sp.spawn { + executable, + import_file, + is_string and "true" or "false", + lua_modlue, + option.is_export and "true" or "false", + stdout = true, + stderr = true, + }) + local res = {} + for line in process.stdout:lines() do + local name, pattern, offset, hit_offset = line:match([[(.+):([0-9a-f%?]+)%(([%-%+]?%d+)%)@([%-%+]?%d+)]]) + offset = offset ~= "0" and tonumber(offset) or nil + hit_offset = hit_offset ~= "0" and tonumber(hit_offset) or nil + res[#res+1] = { + name = name, + pattern = pattern, + offset = offset, + hit_offset = hit_offset, + } + end + process.stdout:close() + + local code = process:wait() + if code ~= 0 then + print("signature_compiler error:\n", process.stderr:read "a") + os.exit(code, true) + end + return res +end + +local output_dir = fs.path("publish") / "signature" + +local function get_executable(platform_arch) + local bin_dir = fs.path("build") / platform_arch + + local modes = { + "release", + "debug", + } + for _, mode in ipairs(modes) do + local executable = bin_dir / mode / "bin" / "signature_compiler" + if fs.exists(executable) then + return executable:string() + end + end + print("can't find signature_compiler") + os.exit(1, true) +end + +for _, t in ipairs(luadebug_files) do + local output_dir = output_dir / t.platform_arch + fs.create_directories(output_dir) + local lua_modlue = t.lua_modlue + local executable = get_executable(t.platform_arch) + local t1 = compiler(executable, launcher_path, true, lua_modlue) + local t2 = compiler(executable, t.luadebug, false, lua_modlue) + local output = {} + local function scan_output(signatures) + for _, signature in ipairs(signatures) do + output[signature.name] = { + pattern = signature.pattern, + offset = signature.offset, + hit_offset = signature.hit_offset, + } + end + end + scan_output(t1) + scan_output(t2) + + local file = io.open((output_dir / (t.version..".json")):string(), "w") + file:write(json.beautify(output)) + file:close() +end diff --git a/extension/js/configurationProvider.js b/extension/js/configurationProvider.js index d5a6b5e87..9965f51fc 100644 --- a/extension/js/configurationProvider.js +++ b/extension/js/configurationProvider.js @@ -243,6 +243,9 @@ function resolveConfig(folder, config) { throw new Error('Missing `address` to debug'); } } + if (config.signature != null) { + config.signature.version = config.lua_version + } config.configuration = { variables: vscode.workspace.getConfiguration("lua.debug.variables") } diff --git a/extension/script/attach.lua b/extension/script/attach.lua index da30f4935..7f85417ff 100644 --- a/extension/script/attach.lua +++ b/extension/script/attach.lua @@ -1,4 +1,4 @@ -local path, pid = ... +local path, pid, version = ... if _VERSION == nil or type == nil or assert == nil @@ -19,6 +19,10 @@ if is_luajit and jit == nil then return "wait initialized" end +if version == "" then + version = nil +end + local function dofile(filename, ...) local load = _VERSION == "Lua 5.1" and loadstring or load local f = assert(io.open(filename)) @@ -26,20 +30,11 @@ local function dofile(filename, ...) f:close() return assert(load(str, "=(debugger.lua)"))(...) end -local function isLatest() - local ipc = dofile(path.."/script/common/ipc.lua") - local fd = ipc(path, pid, "luaVersion") - local result = false - if fd then - result = "latest" == fd:read "a" - fd:close() - end - return result -end + local dbg = dofile(path.."/script/debugger.lua", path) dbg:start { address = ("@%s/tmp/pid_%s"):format(path, pid), - latest = isLatest(), + version = version, } dbg:event "wait" -return "ok" \ No newline at end of file +return "ok" diff --git a/extension/script/debugger.lua b/extension/script/debugger.lua index b3b495ee2..bc1a77787 100644 --- a/extension/script/debugger.lua +++ b/extension/script/debugger.lua @@ -101,26 +101,42 @@ local function detectLuaDebugPath(cfg) end end - local rt = "/runtime/"..PLATFORM - if cfg.latest then - rt = rt.."/lua-latest" - elseif _VERSION == "Lua 5.4" then - rt = rt.."/lua54" - elseif _VERSION == "Lua 5.3" then - rt = rt.."/lua53" - elseif _VERSION == "Lua 5.2" then - rt = rt.."/lua52" - elseif _VERSION == "Lua 5.1" then - if (tostring(assert):match('builtin') ~= nil) then - rt = rt.."/luajit" - jit.off() - else - rt = rt.."/lua51" + local function get_luadebug_dir() + local version = cfg.version + if not version then + if tostring(assert):match('builtin') ~= nil then + version = "jit" + jit.off() + else + version = _VERSION + end end - else - error(_VERSION.." is not supported.") + + local t = { + ["Lua 5.4"] = "lua54", + ["Lua 5.3"] = "lua53", + ["Lua 5.2"] = "lua52", + ["Lua 5.1"] = "lua51", + latest = "lua-latest", + lua54 = "lua54", + lua53 = "lua53", + lua52 = "lua52", + lua51 = "lua51", + luajit = "luajit", + ["5.4"] = "lua54", + ["5.3"] = "lua53", + ["5.2"] = "lua52", + ["5.1"] = "lua51", + jit = 'luajit' + } + if not t[version] then + error(version.." is not supported.") + end + return t[version] end + local rt = "/runtime/"..PLATFORM.."/"..get_luadebug_dir() + local ext = isWindows() and "dll" or "so" return root..rt..'/luadebug.'..ext end diff --git a/extension/script/frontend/debuger_factory.lua b/extension/script/frontend/debuger_factory.lua index 577738cc4..49f862f93 100644 --- a/extension/script/frontend/debuger_factory.lua +++ b/extension/script/frontend/debuger_factory.lua @@ -221,6 +221,9 @@ local function create_process_in_console(args, callback) if not process then return nil, err end + if callback then + callback(process) + end if args.inject ~= "none" then local ok, errmsg = process_inject.inject(process, "launch", args) if not ok then @@ -231,9 +234,6 @@ local function create_process_in_console(args, callback) end end end - if callback then - callback(process) - end if need_resume then process:resume() end diff --git a/extension/script/frontend/proxy.lua b/extension/script/frontend/proxy.lua index f31cfb89b..5343c47f6 100644 --- a/extension/script/frontend/proxy.lua +++ b/extension/script/frontend/proxy.lua @@ -4,22 +4,30 @@ local fs = require 'bee.filesystem' local sp = require 'bee.subprocess' local platform_os = require 'frontend.platform_os' local process_inject = require 'frontend.process_inject' +local json = require 'common.json' local server local client local initReq local m = {} local function getUnixAddress(pid) + --TODO: clear unix file local path = WORKDIR / "tmp" fs.create_directories(path) return "@"..(path / ("pid_%d"):format(pid)):string() end -local function ipc_send_latest(pid) +local function ipc_send_config(pid, args) + --TODO: clear config file fs.create_directories(WORKDIR / "tmp") local ipc = require "common.ipc" - local fd = assert(ipc(WORKDIR, pid, "luaVersion", "w")) - fd:write("latest") + local fd = assert(ipc(WORKDIR, pid, "config", "w")) + local config = { + version = args.luaVersion, + module = args.module, + signatures = args.signatures + } + fd:write(json.encode(config)) fd:close() end @@ -56,13 +64,11 @@ end local function attach_process(pkg, pid) local args = pkg.arguments - if args.luaVersion == "latest" then - ipc_send_latest(pid) - end + ipc_send_config(pid, args) local ok, errmsg = process_inject.inject(pid, "attach", args) if not ok then - return false, errmsg - end + return false, errmsg + end server = network(getUnixAddress(pid), true) server.sendmsg(initReq) @@ -80,8 +86,8 @@ local function proxy_attach(pkg) local args = pkg.arguments platform_os.init(args) if platform_os() ~= "Windows" and platform_os() ~= "macOS" then - attach_tcp(pkg, args) - return + attach_tcp(pkg, args) + return end if args.processId then local processId = tonumber(args.processId) @@ -92,7 +98,7 @@ local function proxy_attach(pkg) return end if args.processName then - local pids = require "frontend.query_process"(args.processName) + local pids = require "frontend.query_process" (args.processName) if #pids == 0 then response_error(pkg, ('Cannot found process `%s`.'):format(args.processName)) return @@ -113,7 +119,7 @@ local function create_server(args, pid) local s, address if args.address ~= nil then s = network(args.address, args.client) - address = (args.client and "s:" or "c:") .. args.address + address = (args.client and "s:" or "c:")..args.address else pid = pid or sp.get_id() s = network(getUnixAddress(pid), true) @@ -158,11 +164,12 @@ local function proxy_launch_console(pkg) response_error(pkg, "`runtimeExecutable` need specify `inject` or `address`.") return end - local process, err = debuger_factory.create_process_in_console(args, function (process) + local process, err = debuger_factory.create_process_in_console(args, function(process) local address server, address = create_server(args, process:get_id()) - if args.luaVersion == "latest" and type(address) == "number" then - ipc_send_latest(address) + + if type(address) == "number" then + ipc_send_config(address, args) end end) if not process then diff --git a/src/launcher/autoattach/autoattach.cpp b/src/launcher/autoattach/autoattach.cpp index 9f99c5216..8e90fb61f 100644 --- a/src/launcher/autoattach/autoattach.cpp +++ b/src/launcher/autoattach/autoattach.cpp @@ -2,7 +2,7 @@ #include #include #include -#include +#include #include #include @@ -15,6 +15,16 @@ namespace luadebug::autoattach { fn_attach debuggerAttach; +#ifdef _WIN32 +# define EXT ".dll" +#else +# define EXT ".so" +#endif + constexpr auto lua_module_backlist = { + "launcher" EXT, + "luadebug" EXT, + }; + constexpr auto find_lua_module_key = "lua_newstate"; constexpr auto lua_module_strings = std::array { "luaJIT_BC_%s", // luajit @@ -27,7 +37,23 @@ 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) { + static bool is_lua_module(const char* module_path, const config::Config& config, bool check_export = true, bool check_strings = false) { + auto str = std::string_view(module_path); + // blacklist module + auto root = config::get_plugin_root(); + if (root) { + if (str.find((*root).string()) != std::string_view::npos) { + // in luadebug root dir + for (auto& s : lua_module_backlist) { + if (str.find(s) != std::string_view::npos) + return false; + } + } + } + + auto target_lua_module = config.lua_module; + if (!target_lua_module.empty() && str.find(target_lua_module) != std::string_view::npos) return true; + 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; // TODO: when signature mode, check strings @@ -42,7 +68,7 @@ namespace luadebug::autoattach { } static void start(); - static bool load_lua_module(const std::string& path) { + static bool load_lua_module(const std::string& path, const config::Config& config) { constexpr auto check_export = #ifdef _WIN32 true @@ -50,7 +76,7 @@ namespace luadebug::autoattach { false #endif ; - if (!is_lua_module(path.c_str(), check_export, true)) { + if (!is_lua_module(path.c_str(), config, check_export, true)) { return false; } // find lua module lazy @@ -58,15 +84,19 @@ namespace luadebug::autoattach { return true; } - attach_status attach_lua_vm(lua::state L) { - return debuggerAttach(L); + attach_status attach_lua_vm(lua::state L, lua_version version) { + return debuggerAttach(L, version); } void start() { + auto config = config::init_from_file(); + if (!config) { + log::info("can't load config"); + } bool found = false; lua_module rm = {}; - Gum::Process::enumerate_modules([&rm, &found](const Gum::ModuleDetails& details) -> bool { - if (is_lua_module(details.path())) { + Gum::Process::enumerate_modules([&rm, &found, conf = &(*config)](const Gum::ModuleDetails& details) -> bool { + if (is_lua_module(details.path(), *conf)) { auto range = details.range(); rm.memory_address = range.base_address; rm.memory_size = range.size; @@ -78,12 +108,15 @@ namespace luadebug::autoattach { return true; }); if (!found) { - if (!wait_dll(load_lua_module)) { + if (!wait_dll([conf = std::move(*config)](const std::string& path) { + return load_lua_module(path, conf); + })) { log::fatal("can't find lua module"); } return; } log::info("find lua module path:{}", rm.path); + rm.config = std::move(*config); if (!rm.initialize(attach_lua_vm)) { return; } diff --git a/src/launcher/autoattach/autoattach.h b/src/launcher/autoattach/autoattach.h index 05df43ef8..184a349f4 100644 --- a/src/launcher/autoattach/autoattach.h +++ b/src/launcher/autoattach/autoattach.h @@ -1,5 +1,6 @@ #pragma once +#include #include namespace luadebug::autoattach { @@ -8,6 +9,6 @@ namespace luadebug::autoattach { fatal, wait, }; - typedef attach_status (*fn_attach)(lua::state L); + typedef attach_status (*fn_attach)(lua::state L, lua_version verison); void initialize(fn_attach attach, bool ap); } diff --git a/src/launcher/autoattach/lua_module.cpp b/src/launcher/autoattach/lua_module.cpp index e5a800cb4..d0d5f1507 100644 --- a/src/launcher/autoattach/lua_module.cpp +++ b/src/launcher/autoattach/lua_module.cpp @@ -1,45 +1,18 @@ #include #include +#include +#include #include +#include #include #include #include namespace luadebug::autoattach { - static lua_version lua_version_from_string [[maybe_unused]] (const std::string_view& v) { - if (v == "luajit") - return lua_version::luajit; - if (v == "lua51") - return lua_version::lua51; - if (v == "lua52") - return lua_version::lua52; - if (v == "lua53") - return lua_version::lua53; - if (v == "lua54") - return lua_version::lua54; - return lua_version::unknown; - } - - static const char* lua_version_to_string(lua_version v) { - switch (v) { - case lua_version::lua51: - return "lua51"; - case lua_version::lua52: - return "lua52"; - case lua_version::lua53: - return "lua53"; - case lua_version::lua54: - return "lua54"; - case lua_version::luajit: - return "luajit"; - default: - return "unknown"; - } - } static bool in_module(const lua_module& m, void* addr) { - return addr > m.memory_address && addr <= (void*)((intptr_t)m.memory_address + m.memory_size); + 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) { @@ -86,19 +59,79 @@ namespace luadebug::autoattach { default: return lua_version::unknown; } + // TODO: from signature + } + + bool load_luadebug_dll(lua_version version, lua::resolver& resolver) { + auto luadebug_path = config::get_luadebug_path(version); + if (!luadebug_path) + return false; + auto luadebug = (*luadebug_path).string(); + std::string error; + if (!Gum::Process::module_load(luadebug.c_str(), &error)) { + log::fatal("load debugger [{}] failed: {}", luadebug, error); + } + Gum::Process::module_enumerate_import(luadebug.c_str(), [&](const Gum::ImportDetails& details) -> bool { + if (std::string_view(details.name).find_first_of("lua") != 0) { + return true; + } + if (auto address = (void*)resolver.find(details.name)) { + *details.slot = address; + log::info("find signature {} to {}", details.name, address); + } + return true; + }); + return true; } bool lua_module::initialize(fn_attach attach_lua_vm) { - resolver.module_name = path; - auto error_msg = lua::initialize(resolver); + version = get_lua_version(*this); + log::info("current lua version: {}", lua_version_to_string(version)); + + resolver = std::make_unique(path); + if (version != lua_version::unknown && config.is_signature_mode()) { + // 尝试从自带的库里加载函数 + auto runtime_dir = config::get_lua_runtime_dir(version); + if (runtime_dir) { + auto dllpath = +#ifdef _WIN32 + (*runtime_dir / (lua_version_to_string(version) + std::string(".dll"))).string(); +#else + (*runtime_dir / "lua.so").string(); +#endif + std::string error; + if (!Gum::Process::module_load(dllpath.c_str(), &error)) { + log::info("load lua module {}, failed: {}", dllpath, error); + } + else { + auto signature_resolver_ptr = std::make_unique(); + signature_resolver_ptr->module_name = dllpath; + signature_resolver_ptr->config = &config; + signature_resolver_ptr->resolver = std::move(resolver); + signature_resolver_ptr->reserve_resolver = + std::make_unique(dllpath); + resolver = std::move(signature_resolver_ptr); + } + } + } + + auto error_msg = lua::initialize(*resolver); if (error_msg) { log::fatal("lua initialize failed, can't find {}", error_msg); return false; } - version = get_lua_version(*this); + version = config.version; + if (version == lua_version::unknown) { + version = get_lua_version(*this); + } log::info("current lua version: {}", lua_version_to_string(version)); - watchdog = create_watchdog(attach_lua_vm, version, resolver); + if (version != lua_version::unknown) { + if (!load_luadebug_dll(version, *resolver)) + return false; + } + + watchdog = create_watchdog(attach_lua_vm, version, *resolver); if (!watchdog) { // TODO: more errmsg log::fatal("watchdog initialize failed"); diff --git a/src/launcher/autoattach/lua_module.h b/src/launcher/autoattach/lua_module.h index f520b6e73..f07483c7a 100644 --- a/src/launcher/autoattach/lua_module.h +++ b/src/launcher/autoattach/lua_module.h @@ -1,28 +1,22 @@ #pragma once #include +#include #include #include namespace luadebug::autoattach { struct watchdog; - enum class lua_version { - unknown, - luajit, - lua51, - lua52, - lua53, - lua54, - }; struct lua_module { std::string path; std::string name; void* memory_address = 0; size_t memory_size = 0; lua_version version = lua_version::unknown; - lua_resolver resolver; + std::unique_ptr resolver; struct watchdog* watchdog = nullptr; + config::Config config; bool initialize(fn_attach attach_lua_vm); }; diff --git a/src/launcher/autoattach/lua_version.cpp b/src/launcher/autoattach/lua_version.cpp new file mode 100644 index 000000000..c1cdba2b5 --- /dev/null +++ b/src/launcher/autoattach/lua_version.cpp @@ -0,0 +1,37 @@ +#include + +namespace luadebug::autoattach { + const char* lua_version_to_string(lua_version v) { + switch (v) { + case lua_version::lua51: + return "lua51"; + case lua_version::lua52: + return "lua52"; + case lua_version::lua53: + return "lua53"; + case lua_version::lua54: + return "lua54"; + case lua_version::luajit: + return "luajit"; + case lua_version::latest: + return "lua-latest"; + default: + return "unknown"; + } + } + lua_version lua_version_from_string(const std::string_view& v) { + if (v == "jit" || v == "luajit") + return lua_version::luajit; + else if (v == "5.1" || v == "lua51") + return lua_version::lua51; + else if (v == "5.2" || v == "lua52") + return lua_version::lua52; + else if (v == "5.3" || v == "lua53") + return lua_version::lua53; + else if (v == "5.4" || v == "lua54") + return lua_version::lua54; + else if (v == "latest" || v == "lua-latest") + return lua_version::latest; + return lua_version::unknown; + } +} \ No newline at end of file diff --git a/src/launcher/autoattach/lua_version.h b/src/launcher/autoattach/lua_version.h new file mode 100644 index 000000000..406009d70 --- /dev/null +++ b/src/launcher/autoattach/lua_version.h @@ -0,0 +1,15 @@ +#pragma once +#include +namespace luadebug::autoattach { + enum class lua_version { + unknown, + luajit, + lua51, + lua52, + lua53, + lua54, + latest, + }; + const char* lua_version_to_string(lua_version v); + lua_version lua_version_from_string(const std::string_view& v); +} \ No newline at end of file diff --git a/src/launcher/autoattach/wait_dll.cpp b/src/launcher/autoattach/wait_dll.cpp index 5063c7bea..fa1bc115b 100644 --- a/src/launcher/autoattach/wait_dll.cpp +++ b/src/launcher/autoattach/wait_dll.cpp @@ -32,7 +32,7 @@ extern "C" bool gum_darwin_query_all_image_infos(mach_port_t task, _GumDarwinAll #endif namespace luadebug::autoattach { #ifdef _WIN32 - bool wait_dll(bool (*loaded)(std::string const&)) { + bool wait_dll(WaitDllCallBack_t loaded) { typedef struct _LDR_DLL_UNLOADED_NOTIFICATION_DATA { ULONG Flags; // Reserved. PCUNICODE_STRING FullDllName; // The full path name of the DLL module. @@ -91,19 +91,19 @@ namespace luadebug::autoattach { 0, [](LdrDllNotificationReason NotificationReason, PLDR_DLL_NOTIFICATION_DATA const NotificationData, PVOID Context) { if (NotificationReason == LdrDllNotificationReason::LDR_DLL_NOTIFICATION_REASON_LOADED) { auto path = fs::path(std::wstring(NotificationData->Loaded.FullDllName->Buffer, NotificationData->Loaded.FullDllName->Length)).string(); - auto f = (decltype(loaded))Context; - if (f(path)) { + auto ptr = (WaitDllCallBack_t*)Context; + if ((*ptr)(path)) { if (dllNotification.Cookie) dllNotification.dllUnregisterNotification(dllNotification.Cookie); + delete ptr; } } }, - loaded, &dllNotification.Cookie + (void*)new WaitDllCallBack_t(std::move(loaded)), &dllNotification.Cookie ); return true; } #elif defined(__APPLE__) - using WaitDllCallBack_t = bool (*)(std::string const&); struct WaitDllListener : Gum::NoLeaveInvocationListener { WaitDllCallBack_t loaded; Gum::RefPtr interceptor; @@ -145,7 +145,7 @@ namespace luadebug::autoattach { if (!interceptor) return false; auto listener = new WaitDllListener; - listener->loaded = loaded; + listener->loaded = std::move(loaded); listener->interceptor = interceptor; return interceptor->attach((void*)infos.notification_address, listener, nullptr); } @@ -153,7 +153,7 @@ namespace luadebug::autoattach { // TODO: support linux - bool wait_dll(bool (*loaded)(std::string const&)) { + bool wait_dll(WaitDllCallBack_t loaded) { return false; } #endif diff --git a/src/launcher/autoattach/wait_dll.h b/src/launcher/autoattach/wait_dll.h index 8ace7a505..0c56ef908 100644 --- a/src/launcher/autoattach/wait_dll.h +++ b/src/launcher/autoattach/wait_dll.h @@ -1,7 +1,11 @@ #pragma once +#include + +#include #include namespace luadebug::autoattach { - bool wait_dll(bool (*loaded)(std::string const&)); + using WaitDllCallBack_t = std::function; + bool wait_dll(WaitDllCallBack_t loaded); } diff --git a/src/launcher/config/config.cpp b/src/launcher/config/config.cpp new file mode 100644 index 000000000..d8d6e5129 --- /dev/null +++ b/src/launcher/config/config.cpp @@ -0,0 +1,168 @@ +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +using namespace std::string_view_literals; +namespace luadebug::config { + using namespace autoattach; + static lua_version get_lua_version(nlohmann::json& values) { + const auto key = "version"sv; + + auto it = values.find(key); + if (it == values.end()) { + return lua_version::unknown; + } + + return lua_version_from_string(it->get()); + } + + static std::string get_lua_module(nlohmann::json& values) { + const auto key = "module"sv; + + auto it = values.find(key); + if (it == values.end()) { + return {}; + } + auto value = it->get(); + if (!fs::exists(value)) + return value; + + do { + if (!fs::is_symlink(value)) + return value; + auto link = fs::read_symlink(value); + if (link.is_absolute()) + value = link.string(); + else + value = (fs::path(value).parent_path() / link).lexically_normal().string(); + } while (true); + } + + static std::map get_lua_signature(const nlohmann::json& values) { + const auto signture_key = "signatures"sv; + auto it = values.find(signture_key); + if (it == values.end()) { + return {}; + } + try { + std::map signatures; + for (auto& [key, val] : it->items()) { + // searilize json to signature + signature res = {}; + val["start_offset"].get_to(res.start_offset); + val["end_offset"].get_to(res.end_offset); + val["pattern"].get_to(res.pattern); + val["pattern_offset"].get_to(res.pattern_offset); + val["hit_offset"].get_to(res.hit_offset); + + signatures.emplace(key, res); + } + return signatures; + } catch (const nlohmann::json::exception& e) { + log::info("get_lua_signature error: {}", e.what()); + } + return {}; + } + + bool Config::is_signature_mode() const { + return !signatures.empty(); + } + + std::optional init_from_file() { + nlohmann::json values; + + auto tmp = get_tmp_dir(); + if (!tmp) + return std::nullopt; + auto filename = ((*tmp) / std::format("ipc_{}_config", Gum::Process::get_id())).string(); + + std::ifstream s(filename, s.binary); + if (!s.is_open()) + return std::nullopt; + try { + s >> values; + } catch (const nlohmann::json::exception& e) { + log::info("init_from_file error: {}", e.what()); + } + + Config config; + config.version = get_lua_version(values); + config.lua_module = get_lua_module(values); + config.signatures = get_lua_signature(values); + + return config; + } + + std::optional get_plugin_root() { + auto dllpath = bee::path_helper::dll_path(); + if (!dllpath) { + return std::nullopt; + } + return dllpath.value().parent_path().parent_path(); + } + + std::optional get_tmp_dir() { + auto root = get_plugin_root(); + if (!root) + return std::nullopt; + return (*root) / "tmp"; + } + + std::optional get_runtime_dir() { + auto root = get_plugin_root(); + if (!root) + return std::nullopt; + auto os = +#if defined(_WIN32) + "windows"; +#elif defined(__ENVIRONMENT_MAC_OS_X_VERSION_MIN_REQUIRED__) + "darwin"; +#else + "linux"; +#endif + auto arch = +#if defined(_M_ARM64) || defined(__aarch64__) + "arm64"; +#elif defined(_M_IX86) || defined(__i386__) + "x86"; +#elif defined(_M_X64) || defined(__x86_64__) + "x64"; +#else +# error "Unknown architecture" +#endif + auto platform = std::format("{}-{}", os, arch); + return (*root) / "runtime" / platform; + } + + std::optional get_lua_runtime_dir(lua_version version) { + auto runtime = get_runtime_dir(); + if (!runtime) + return std::nullopt; + return (*runtime) / lua_version_to_string(version); + } + + std::optional get_luadebug_path(lua_version version) { + auto runtime = get_lua_runtime_dir(version); + if (!runtime) + return std::nullopt; +#define LUADEBUG_FILE "luadebug" + +#if defined(_WIN32) +# define EXT ".dll" +#else +# define EXT ".so" +#endif + return (*runtime) / (LUADEBUG_FILE EXT); + } + +} // namespace luadebug::config diff --git a/src/launcher/config/config.h b/src/launcher/config/config.h new file mode 100644 index 000000000..59e5087f6 --- /dev/null +++ b/src/launcher/config/config.h @@ -0,0 +1,27 @@ +#pragma once +#include +#include +#include + +#include +#include +#include +#include +#include + +namespace luadebug::config { + using lua_version = autoattach::lua_version; + using signature = autoattach::signature; + std::optional get_plugin_root(); + std::optional get_tmp_dir(); + std::optional get_runtime_dir(); + std::optional get_lua_runtime_dir(lua_version version); + std::optional get_luadebug_path(lua_version version); + struct Config { + lua_version version = lua_version::unknown; + std::string lua_module; + std::map signatures; + bool is_signature_mode() const; + }; + std::optional init_from_file(); +} // namespace luadebug::config \ No newline at end of file diff --git a/src/launcher/hook/create_watchdog.cpp b/src/launcher/hook/create_watchdog.cpp index e62dcef1c..772f915f3 100644 --- a/src/launcher/hook/create_watchdog.cpp +++ b/src/launcher/hook/create_watchdog.cpp @@ -27,6 +27,7 @@ namespace luadebug::autoattach { { watch_point::type::common, "luaV_gettable" }, { watch_point::type::common, "luaV_settable" }, }; + case lua_version::latest: case lua_version::lua54: return { { watch_point::type::common, "luaD_poscall" }, @@ -35,6 +36,7 @@ namespace luadebug::autoattach { }; default: return { + { watch_point::type::common, "lua_newstate" }, { watch_point::type::common, "lua_settop" }, { watch_point::type::common, "luaL_openlibs" }, { watch_point::type::common, "lua_newthread" }, @@ -53,6 +55,7 @@ namespace luadebug::autoattach { if (context->init(resolver, get_watch_points(version))) { // TODO: fix other thread pc context->hook(); + context->version = version; return context; } if (version == lua_version::unknown) { @@ -61,6 +64,7 @@ namespace luadebug::autoattach { if (context->init(resolver, get_watch_points(lua_version::unknown))) { // TODO: fix other thread pc context->hook(); + context->version = version; return context; } return nullptr; diff --git a/src/launcher/hook/watchdog.cpp b/src/launcher/hook/watchdog.cpp index 83271c52f..d84ae0e5e 100644 --- a/src/launcher/hook/watchdog.cpp +++ b/src/launcher/hook/watchdog.cpp @@ -116,7 +116,7 @@ namespace luadebug::autoattach { void watchdog::attach_lua(lua::state L, lua::debug ar, lua::hook fn) { reset_luahook(L, ar); - switch (attach_lua_vm(L)) { + switch (attach_lua_vm(L, version)) { case attach_status::fatal: case attach_status::success: // TODO: how to free so diff --git a/src/launcher/hook/watchdog.h b/src/launcher/hook/watchdog.h index dbc8cf71f..986df44d5 100644 --- a/src/launcher/hook/watchdog.h +++ b/src/launcher/hook/watchdog.h @@ -38,5 +38,8 @@ namespace luadebug::autoattach { int origin_hookmask; int origin_hookcount; fn_attach attach_lua_vm; + + public: + lua_version version; }; } diff --git a/src/launcher/main.cpp b/src/launcher/main.cpp index b99d9342e..a2aba6ea8 100644 --- a/src/launcher/main.cpp +++ b/src/launcher/main.cpp @@ -2,22 +2,23 @@ #include #include #include + #ifndef _WIN32 -# include # define DLLEXPORT __attribute__((visibility("default"))) # define DLLEXPORT_DECLARATION #else -# include # define DLLEXPORT __declspec(dllexport) # define DLLEXPORT_DECLARATION __cdecl #endif + +#include #include #include namespace luadebug::autoattach { - static std::string readfile(const fs::path& filename) { + static std::string readfile(const fs::path &filename) { #ifdef _WIN32 - FILE* f = _wfopen(filename.c_str(), L"rb"); + FILE *f = _wfopen(filename.c_str(), L"rb"); #else FILE* f = fopen(filename.c_str(), "rb"); #endif @@ -34,7 +35,7 @@ namespace luadebug::autoattach { return tmp; } - static attach_status attach(lua::state L) { + static attach_status attach(lua::state L, lua_version verison) { log::info("attach lua vm entry"); auto r = bee::path_helper::dll_path(); if (!r) { @@ -49,12 +50,10 @@ namespace luadebug::autoattach { return attach_status::fatal; } lua::call(L, root.generic_u8string().c_str()); -#ifdef _WIN32 - lua::call(L, std::to_string(GetCurrentProcessId()).c_str()); -#else - lua::call(L, std::to_string(getpid()).c_str()); -#endif - if (lua::pcall(L, 2, 1, 0)) { + lua::call(L, std::to_string(Gum::Process::get_id()).c_str()); + lua::call(L, verison == lua_version::unknown ? "" : lua_version_to_string(verison)); + + if (lua::pcall(L, 3, 1, 0)) { /* 这里失败无法调用log::fatal,因为无法知道调试器已经加载到哪一步才失败的。 所以调试器不应该把错误抛到这里。 diff --git a/src/launcher/resolver/lua_delayload.h b/src/launcher/resolver/lua_delayload.h index b1434399b..69d62363d 100644 --- a/src/launcher/resolver/lua_delayload.h +++ b/src/launcher/resolver/lua_delayload.h @@ -18,6 +18,7 @@ namespace luadebug::lua { using hook = void (*)(state, debug); struct resolver { + virtual ~resolver() = default; virtual intptr_t find(std::string_view name) const = 0; }; } diff --git a/src/launcher/resolver/lua_resolver.cpp b/src/launcher/resolver/lua_resolver.cpp index a38f14b8f..a5e4d6504 100644 --- a/src/launcher/resolver/lua_resolver.cpp +++ b/src/launcher/resolver/lua_resolver.cpp @@ -1,4 +1,5 @@ #include +#include #include #include @@ -14,11 +15,11 @@ namespace luadebug { } 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()); + return (intptr_t)Gum::Process::module_find_export_by_name(module_name.c_str(), 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()); + return (intptr_t)Gum::Process::module_find_symbol_by_name(module_name.c_str(), name.data()); } intptr_t lua_resolver::find(std::string_view name) const { @@ -42,4 +43,7 @@ namespace luadebug { } return 0; } + lua_resolver::lua_resolver(std::string_view name) + : module_name(std::string(name)) { + } } diff --git a/src/launcher/resolver/lua_resolver.h b/src/launcher/resolver/lua_resolver.h index 47e886bad..6ca7c98a5 100644 --- a/src/launcher/resolver/lua_resolver.h +++ b/src/launcher/resolver/lua_resolver.h @@ -2,13 +2,16 @@ #include +#include #include namespace luadebug { struct lua_resolver : lua::resolver { + lua_resolver(std::string_view name); + ~lua_resolver() = default; 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; + std::string module_name; }; } \ No newline at end of file diff --git a/src/launcher/resolver/lua_signature.cpp b/src/launcher/resolver/lua_signature.cpp new file mode 100644 index 000000000..9e65189e9 --- /dev/null +++ b/src/launcher/resolver/lua_signature.cpp @@ -0,0 +1,55 @@ +#include +#include +#include + +#include +namespace luadebug::autoattach { + intptr_t signature::find(const char* module_name) const { + auto all_address = Gum::search_module_function(module_name, pattern.c_str()); + if (all_address.empty()) + return 0; + + void* find_address = nullptr; + if (all_address.size() == 1) + find_address = all_address[0]; + else { + Gum::MemoryRange range; + Gum::Process::enumerate_modules([&](const Gum::ModuleDetails& details) { + if (details.name() == std::string_view(module_name) || details.path() == std::string_view(module_name)) { + range = details.range(); + return false; + } + return true; + }); + + range.size -= end_offset; + range.base_address = (void*)((uint8_t*)range.base_address + start_offset); + + // 限定匹配地址范围 + auto iter = std::remove_if(all_address.begin(), all_address.end(), [&](void* address) { + return !range.contains(address); + }); + all_address.erase(iter, all_address.end()); + + auto hit_index = all_address.size() > hit_offset ? hit_offset : 0; + find_address = all_address[hit_index]; + } + if (find_address != 0) + return (uintptr_t)((uint8_t*)find_address + pattern_offset); + return 0; + } + + intptr_t signature_resolver::find(std::string_view name) const { + auto addr = resolver->find(name); + if (addr) + return addr; + + if (auto it = config->signatures.find(std::string(name)); it != config->signatures.end()) { + addr = it->second.find(module_name.data()); + if (addr) + return addr; + } + log::info("reserve_resolver find: {}", name); + return reserve_resolver->find(name); + } +} \ No newline at end of file diff --git a/src/launcher/resolver/lua_signature.h b/src/launcher/resolver/lua_signature.h new file mode 100644 index 000000000..379d26779 --- /dev/null +++ b/src/launcher/resolver/lua_signature.h @@ -0,0 +1,29 @@ +#pragma once +#include + +#include +#include +#include +namespace luadebug::config { + struct Config; +} +namespace luadebug::autoattach { + + struct signature { + int32_t start_offset = 0; + int32_t end_offset = 0; + std::string pattern; + int32_t pattern_offset = 0; + uint8_t hit_offset = 0; + intptr_t find(const char* module_name) const; + }; + + struct signature_resolver : lua::resolver { + ~signature_resolver() = default; + intptr_t find(std::string_view name) const override; + std::string module_name; + config::Config* config; + std::unique_ptr resolver; + std::unique_ptr reserve_resolver; + }; +} \ No newline at end of file diff --git a/src/launcher/tools/signature_compiler.cpp b/src/launcher/tools/signature_compiler.cpp new file mode 100644 index 000000000..73cc18fb2 --- /dev/null +++ b/src/launcher/tools/signature_compiler.cpp @@ -0,0 +1,191 @@ +#include + +#include +#include +#include +#include +#include +#include + +using namespace std::literals; +const char* module_name; +struct Pattern { + std::string name; + Gum::signature signature; + size_t hit_offset; +}; + +int imports(std::string file_path, bool is_string, std::set& imports_names) { + std::string error; + if (!Gum::Process::module_load(file_path.c_str(), &error)) { + std::cerr << "module_load " << file_path << " failed: " << error << std::endl; + return 1; + } + if (is_string) { + // check address is a string begin + for (auto str : Gum::search_module_string(file_path.c_str(), "lua")) { + std::string_view name(str); + if (str[-1] != 0) { + continue; + } + // check name has '_' and not has ' ' '.' '*' '/' '\\' ':' '<' '>' '|' '"' '?' + if (name.find('_') == std::string_view::npos) { + continue; + } + if (name.find_first_of(" .*/\\:<>|\"?%") != std::string_view::npos) { + continue; + } + if (name.find("lua") != 0) { + continue; + } + + imports_names.emplace(name); + } + } + else { + Gum::Process::module_enumerate_import(file_path.c_str(), [&](const Gum::ImportDetails& details) { + auto name = std::string_view(details.name); + if (name.find("lua") == 0) { + imports_names.insert(details.name); + } + return true; + }); + } +#ifndef NDEBUG + for (const auto& name : imports_names) { + std::cerr << name << std::endl; + } +#endif + return 0; +} +bool starts_with(std::string_view _this, std::string_view __s) noexcept { + return _this.size() >= __s.size() && + _this.compare(0, __s.size(), __s) == + 0; +} +constexpr auto insn_max_limit = 100; +constexpr auto insn_min_limit = 4; + +bool compiler_signature(const char* name, void* address, std::list& patterns, int limit = insn_min_limit) { + auto signature = Gum::get_function_signature(address, limit); + auto& pattern = signature.pattern; + std::vector find_address = Gum::search_module_function(module_name, pattern.c_str()); + if (find_address.size() > 1 && limit <= insn_max_limit) { + return compiler_signature(name, address, patterns, limit + 1); + } + if (find_address.size() != 1) { + std::cerr << name << " address:" << address << " pattern:" << pattern << std::endl; + if (find_address.empty()) { + for (size_t i = 0; i < pattern.size(); i += 9) { + if (i + 8 < pattern.size()) { + pattern.insert(i + 8, "\n"); + } + } + std::cerr << name << ": invalid pattern[can't search pattern]\n" + << pattern << std::endl; + exit(1); + } + else { + if (find_address.size() > 8) { + std::cerr << name << ": invalid pattern[too many matched]:" << find_address.size() + << " hited" << std::endl; + } + else { + for (auto addr : find_address) { + std::cerr << "\taddress:" << addr << std::endl; + } + } + } + } + bool isvalid = false; + size_t offset = 0; + for (auto addr : find_address) { + if ((void*)((uint8_t*)addr + signature.offset) == address) { + isvalid = true; + break; + } + offset++; + } + + if (!isvalid) { + std::cerr << name << ": invalid pattern[address not match]:" << address << std::endl; + return false; + } + else { + patterns.emplace_back(Pattern { name, signature, offset }); + return true; + } +} + +int main(int narg, const char* argvs[]) { + if (narg != 5) { + std::cerr << "Usage: " + << "signautre import_file is_string target_file is_export" + << std::endl; + return 1; + } + + Gum::runtime_init(); + auto import_file = fs::absolute(argvs[1]).generic_string(); + auto is_string = argvs[2] == "true"sv ? true : false; + std::set imports_names; + if (auto ec = imports(import_file, is_string, imports_names); ec != 0) { + return ec; + } + auto target_path = fs::absolute(argvs[3]).generic_string(); + auto is_export = argvs[4] == "true"sv ? true : false; + std::string error; + module_name = target_path.c_str(); + std::cerr << "signature:" << module_name << std::endl; + + if (!Gum::Process::module_load(module_name, &error)) { + std::cerr << "Load " << module_name << " failed: " << error << std::endl; + return 1; + } + + // imports_names remove lua_ident luaJIT_version_* + { + auto iter = imports_names.begin(); + while (iter != imports_names.end()) { + const auto& name = *iter; + if (name == "lua_ident" || starts_with(name, "luaJIT_version_")) { + iter = imports_names.erase(iter); + } + else { + ++iter; + } + } + } + + std::list patterns; + if (is_export) { + Gum::Process::module_enumerate_export(module_name, [&](const Gum::ExportDetails& details) { + if (imports_names.find(details.name) != imports_names.end()) { + if (compiler_signature(details.name, details.address, patterns)) { + imports_names.erase(details.name); + } + } + return true; + }); + } + else { + Gum::Process::module_enumerate_symbols(module_name, [&](const Gum::SymbolDetails& details) { + if (imports_names.find(details.name) != imports_names.end()) { + if (compiler_signature(details.name, details.address, patterns)) { + imports_names.erase(details.name); + } + } + return true; + }); + } + + for (const auto& pattern : patterns) { + std::cout << pattern.name << ":" << pattern.signature.pattern << "(" << (intptr_t)pattern.signature.offset << ")" + << "@" << pattern.hit_offset << std::endl; + } + for (auto name : imports_names) { + std::cerr << "can't find signature: " << name << std::endl; + } + + return 0; +} diff --git a/src/launcher/util/log.cpp b/src/launcher/util/log.cpp index 1ac969dd0..56995ca08 100644 --- a/src/launcher/util/log.cpp +++ b/src/launcher/util/log.cpp @@ -15,6 +15,10 @@ # include #endif +#include + +#include + namespace luadebug::log { static bool attach_mode = false; @@ -57,18 +61,11 @@ namespace luadebug::log { } void notify_frontend(const std::string& msg) { - auto dllpath = bee::path_helper::dll_path(); - if (!dllpath) { + auto tmp = config::get_tmp_dir(); + if (!tmp) return; - } - auto rootpath = dllpath.value().parent_path().parent_path(); - auto path = std::format("{}/tmp/pid_{}", rootpath.generic_u8string(), -#if defined(_WIN32) - GetCurrentProcessId() -#else - getpid() -#endif - ); + auto path = ((*tmp) / std::format("pid_{}", Gum::Process::get_id())).string(); + if (!socket::initialize()) { return; }