标题: 用VSCode+debugpy调试IDAPython插件 创建: 2025-06-26 17:18 更新: 2025-07-04 17:07 链接: https://scz.617.cn/python/202506261718.txt https://www.52pojie.cn/thread-2043161-1-1.html -------------------------------------------------------------------------- 目录: ☆ 背景介绍 ☆ 便携版VSCode 1) 官方方案 2) 我的方案 3) 基础配置 4) 安装、配置扩展 5) 构建用于远程调试的Project 5.1) launch.json 5.2) pathMappings ☆ debugpy模块 1) 向IDA安装debugpy模块 2) frozen modules ☆ 远程调试普通Python代码 1) debugpy_test_0.py 2) debugpy_test_1.py ☆ 远程调试IDAPython插件 1) debugpy_test_2.py 2) debugpy日志 3) MyCode判定 3.1) IDE_PROJECT_ROOTS环境变量 3.2) debugpy支持rules字段 (推荐) 3.3) "justMyCode": false 4) debugpy_test_3.py 5) 解读debugpy.listen() 5.1) in_process_debug_adapter=True 5.2) debugpy.configure(python=...) 6) 排错小结 ☆ VSCode IDACode扩展 1) 在IDA中安装IDACode插件 2) 在VSCode中安装、配置IDACode扩展 3) IDACode技术原理 3.1) S端 3.2) C端 4) 用VSCode IDACode扩展远程调试IDAPython脚本 4.1) debugpy_test_4.py 4.2) IDAPython脚本测试记录 5) 用VSCode IDACode扩展远程调试IDAPython插件 5.1) debugpy_test_5.py 5.2) IDAPython插件测试记录 6) 对VSCode IDACode扩展的改造 6.1) 修改package.json 6.2) 修改extension.js 6.3) 使修改生效 6.4) 改造后的IDAPython插件测试记录 7) 吐槽VSCode IDACode扩展 -------------------------------------------------------------------------- ☆ 背景介绍 约定一下本文中的术语,IDA中Alt-F7加载的some.py称之为「IDAPython脚本」, "Edit->Plugins"加载的some.py称之为「IDAPython插件」,后者有PLUGIN_ENTRY()。 IDA有大量IDAPython插件,某些插件非常复杂,靠print理解代码逻辑并不方便,若 能在某种IDE中调试IDAPython插件,对学习、编写插件非常有益。 编写本文时相关组件版本信息如下 x64/Win10 IDA 8.4.1 Python 3.12.4 debugpy 1.8.14 VSCode 1.101.1 本文介绍如何用「VSCode+debugpy」远程调试位于IDA进程空间的IDAPython插件。理 解原理后,可在其他平台、其他组件中达成同一目的。 付费版"PyCharm Pro"支持debugpy远程调试,据说社区版PyCharm不支持,未实测。 我的IDA目录自带Python引擎,「IDA+IDAPython」全部便携化,不依赖其他目录下的 Python,有如下目录或文件 X:\Green\IDA\python312.dll X:\Green\IDA\Lib\site-packages\ 关于便携化IDAPython,参看 《Portable IDA+IDAPython》 https://scz.617.cn/python/202011182246.txt ☆ 便携版VSCode 参看 -------------------------------------------------------------------------- Visual Studio Code Portable mode https://code.visualstudio.com/docs/editor/portable https://code.visualstudio.com/download Command Line Interface (CLI) https://code.visualstudio.com/docs/configure/command-line -------------------------------------------------------------------------- 1) 官方方案 由于各种原因,我用便携版VSCode。官方直接支持,下载ZIP包,解压释放。官方文 档建议释放后构造如下目录结构 -------------------------------------------------------------------------- VSCode | Code.exe | \---data // 本不存在,需手工创建 +---extensions // 对应安装版的"%USERPROFILE%\.vscode\extensions" +---tmp \---user-data // 对应安装版的"%APPDATA%\Code" -------------------------------------------------------------------------- 当data目录存在时,优先于命令行参数"--user-data-dir"、"--extensions-dir"。 当"data\tmp"目录存在时,用作临时目录,否则用系统级的"%TMP%"、"%TEMP%"。 2) 我的方案 我没有创建"VSCode\data"目录,此目录与二进制无关,置于他处更妥。但各种 VSCode扩展希望与主程序在一起,为此创建"VSCode\extensions"目录。用命令行参 数指定相应目录。 X:\Green\VSCode\Code.exe --user-data-dir X:\work\VSCode\data\user-data --extensions-dir X:\Green\VSCode\extensions 3) 基础配置 -------------------------------------------------------------------------- File Preferences Settings (Ctrl+,) User Features Extensions Auto check Updates (Applies to all profiles) Off Auto Update (Applies to all profiles) None Application Update Enable Windows Background Updates (Applies to all profiles) Off Mode (Applies to all profiles) None Show Release Notes (Applies to all profiles) Off Telemetry Feedback Off Telemetry Level (Applies to all profiles) Off -------------------------------------------------------------------------- 主要是关闭自动升级、遥测等。这些设置位于如下文件 X:\work\VSCode\data\user-data\User\settings.json -------------------------------------------------------------------------- { "update.enableWindowsBackgroundUpdates": false, "update.mode": "none", "update.showReleaseNotes": false, "telemetry.feedback.enabled": false, "telemetry.telemetryLevel": "off", "workbench.editor.enablePreview": false, "extensions.autoUpdate": false, "extensions.autoCheckUpdates": false, "IDACode.executeOnSave": false, } -------------------------------------------------------------------------- 4) 安装、配置扩展 参看 -------------------------------------------------------------------------- Python extension for Visual Studio Code https://marketplace.visualstudio.com/items?itemName=ms-python.python Python Debugger extension for Visual Studio Code https://github.com/microsoft/vscode-python-debugger/blob/main/README.md -------------------------------------------------------------------------- File Preferences Extensions (Ctrl+Shift+X) Python Python Debugger -------------------------------------------------------------------------- 我只关心远程调试,并不想在VSCode中进行Python开发。装"Python Debugger"扩展 时发现自动同步安装了"Python"扩展,二者有绑定。 Extensions面板打开后,一般位于UI左侧,在Extensions面板的右上角有三个点,点 击它,部分操作如下 -------------------------------------------------------------------------- Views Installed Recommended Enabled Disabled Check for Extension Updates Disable Auto Update for All Extensions Enable All Extensions Disable All Installed Extensions Show Running Extensions -------------------------------------------------------------------------- 手工禁用了Git、GitHub相关扩展。 5) 构建用于远程调试的Project 参看 -------------------------------------------------------------------------- Python debugging in VS Code https://code.visualstudio.com/docs/python/debugging Debug code with Visual Studio Code https://code.visualstudio.com/docs/debugtest/debugging -------------------------------------------------------------------------- File Open Folder (Ctrl+K O) X:\work\VSCode\IDAPython (假设用这个目录存放Project相关内容) Open Recent Clear Recently Opened -------------------------------------------------------------------------- 假设已在UI中打开IDAPython目录,再做后续操作 -------------------------------------------------------------------------- Run Add Configuration More Python Debugger options Python Debugger: Python File IDAPython (与前面的IDAPython目录名一致) 编辑IDAPython相关的launch.json -------------------------------------------------------------------------- 上面这个Run操作,还有一条路径 -------------------------------------------------------------------------- Run and Debug (Ctrl+Shift+D) UI左侧带虫子图案的图标 create a launch.json file More Python Debugger options Python Debugger: Python File IDAPython (与前面的IDAPython目录名一致) 编辑IDAPython相关的launch.json -------------------------------------------------------------------------- 5.1) launch.json 参看 -------------------------------------------------------------------------- Visual Studio Code debug configuration https://code.visualstudio.com/docs/debugtest/debugging-configuration Variables reference https://code.visualstudio.com/docs/reference/variables-reference -------------------------------------------------------------------------- 在UI中编辑Project相关的launch.json -------------------------------------------------------------------------- { "configurations": [ { "name": "Python Debugger: Attach", "type": "debugpy", "request": "attach", "connect": { "host": "127.0.0.1", "port": 5678 }, "justMyCode": true } ] } -------------------------------------------------------------------------- 此文件实际位于 X:\work\VSCode\IDAPython\.vscode\launch.json 假设将来被调试的some.py通过debugpy模块侦听"127.0.0.1:5678",此IP/PORT可变, 但C/S两侧要保持一致。 justMyCode为true,表示只调试MyCode,不调试库代码。如何判定MyCode?有一些坑, 后面再提。 5.2) pathMappings 虽说是远程调试IDAPython插件,但实际上C/S都在同一主机,前述launch.json足够 了。有时C/S不在同一主机,远程调试会涉及pathMappings,launch.json可能形如 -------------------------------------------------------------------------- { "configurations": [ { "name": "Python Debugger: Attach", "type": "debugpy", "request": "attach", "connect": { "host": "127.0.0.1", "port": 5678 }, "justMyCode": true, "pathMappings": [ { "localRoot": "${workspaceFolder}", "remoteRoot": "X:/Green/IDA/plugins" }, { "localRoot": "...", "remoteRoot": "X:\\Green\\IDA\\plugins" } ] } ] } -------------------------------------------------------------------------- 本例中"${workspaceFolder}"对应"X:\work\VSCode\IDAPython"。 localRoot、remoteRoot可使用斜杠(/)、转义反斜杠(\\),在Windows平台上语义相 同。 pathMappings中可指定多对localRoot、remoteRoot,从上至下依次使用,直至匹配。 pathMappings会经DAP由C端传递到S端,这是C/S两侧共享的信息。在VSCode中手工设 置断点,C端向S端提交断点请求,文件路径使用localRoot;S端收到断点请求,利用 pathMappings中的remoteRoot进行文件路径转换。据AI说DAP是这么设计的,未深究。 S端看到的some.py路径经DAP传递到C端,VSCode根据pathMappings转换出some.py在C 端的路径,并在UI中显示C端some.py。若pathMappings有误,UI仍能处理调试事件, 但无法显示some.py内容。 非必要不指定pathMappings。 参看"debugpy\_vendored\pydevd\pydevd_file_utils.py"。看过一段代码,据说是 在S端指定pathMappings信息 -------------------------------------------------------------------------- import pydevd_file_utils sth = [(r'X:\localRoot\src', '/remoteRoot/src')] pydevd_file_utils.setup_client_server_paths( sth ) -------------------------------------------------------------------------- ☆ debugpy模块 参看 -------------------------------------------------------------------------- debugpy - a debugger for Python https://github.com/microsoft/debugpy/ Enable debugger logs https://github.com/microsoft/debugpy/wiki/Enable-debugger-logs FAQ https://github.com/microsoft/debugpy/wiki/FAQ Debug Adapter Protocol (DAP) https://microsoft.github.io/debug-adapter-protocol https://pypi.org/project/ptvsd/ https://github.com/Microsoft/ptvsd/ -------------------------------------------------------------------------- 过去有个支持"Debug Adapter Protocol (DAP)"的Python模块ptvsd,现已废弃,官 方不建议继续使用,转用debugpy模块。Python 3.9到3.12都能用debugpy。 简单测过,Python 3.11、3.12用ptvsd模块有幺蛾子,可能3.9能用ptvsd。 1) 向IDA安装debugpy模块 X:\Green\Python\install\Python312\python.exe -m pip install debugpy 复制 X:\Green\Python\install\Python312\Lib\site-packages\debugpy\ 到 X:\Green\IDA\Lib\site-packages\debugpy\ 在IDA的Python提示符中测试四个import import debugpy import debugpy._vendored.force_pydevd import encodings.idna import unicodedata 第二个import可能遭遇 LookupError: unknown encoding: idna 第三、四个import可能遭遇 ModuleNotFoundError: No module named 'unicodedata' 遭遇这些问题时,需复制 X:\Green\Python\install\Python312\DLLs\unicodedata.pyd 到 X:\Green\IDA\DLLs\ 2) frozen modules 在IDA的Python提示符中测试 import debugpy._vendored.force_pydevd 提示 Debugger warning: It seems that frozen modules are being used, which may make the debugger miss breakpoints. Please pass -Xfrozen_modules=off to python to disable frozen modules. Note: Debugging will proceed. Set PYDEVD_DISABLE_FILE_VALIDATION=1 to disable this validation. 对于IDAPython环境,无法指定"-Xfrozen_modules=off",幸好我们只调试MyCode, 不调试库代码,没啥影响。可在cmd中"set PYDEVD_DISABLE_FILE_VALIDATION=1"再 执行ida64.exe,可消除警告,但不真地解决"frozen modules"带来的调试问题。对 于使用Python 3.12的IDAPython,此问题暂时无解。 ☆ 远程调试普通Python代码 本节演示debugpy基本套路,不涉及调试IDAPython插件。参看 -------------------------------------------------------------------------- Remote Debugging with PyCharm https://www.jetbrains.com/help/pycharm/remote-debugging-with-product.html Remote Debugging - [2019-11-03] https://community.shotgridsoftware.com/t/remote-debugging/3869 -------------------------------------------------------------------------- 1) debugpy_test_0.py X:\work\VSCode\other\debugpy_test_0.py -------------------------------------------------------------------------- #!/usr/bin/env python # -*- encoding: utf-8 -*- import debugpy def foo () : print( "scz is here" ) # # 若生效,将断在pass处 # debugpy.breakpoint() pass print( "ok" ) # # 若生效,将断在breakpoint()之后那条语句,即foo()处 # debugpy.breakpoint() foo() -------------------------------------------------------------------------- 提前创建"X:\work\VSCode\other\logs\"目录,此非必要步骤,只是学习过程中有 debugpy日志时,便于排错。 python.exe -Xfrozen_modules=off -m debugpy --listen 127.0.0.1:5678 --wait-for-client --log-to X:\work\VSCode\other\logs X:\work\VSCode\other\debugpy_test_0.py 前面Project目录用"X:\work\VSCode\IDAPython\",测试时故意将待远程调试的 debugpy_test_0.py置于"X:\work\VSCode\other\",以此表明C/S两侧。 S端对应被调试的Python代码,用上述命令启动S端后,可检查所侦听的端口 netstat -na | findstr :5678 在VSCode中"Run and Debug (Ctrl+Shift+D) (UI左侧带虫子图案的图标)",面板正 上方有个下拉列表,其中有"Python Debugger: Attach",对应launch.json中name字 段。下拉列表左侧有个绿色箭头,点击它,则C端根据launch.json设置主动连接S端, UI中已断在foo()处,可以"Step Into"、"Step Over"等等,略。 2) debugpy_test_1.py X:\work\VSCode\other\debugpy_test_1.py -------------------------------------------------------------------------- #!/usr/bin/env python # -*- encoding: utf-8 -*- import debugpy def foo () : print( "scz is here" ) debugpy.breakpoint() pass print( "ok" ) def bar () : # # 对应"--log-to"参数 # debugpy.log_to( r'X:\work\VSCode\other\logs' ) if not debugpy.is_client_connected() : # # 对应"--listen"、"--wait-for-client"参数 # debugpy.listen( ("127.0.0.1", 5678) ) debugpy.wait_for_client() debugpy.breakpoint() foo() if "__main__" == __name__ : bar() -------------------------------------------------------------------------- python.exe -Xfrozen_modules=off X:\work\VSCode\other\debugpy_test_1.py debugpy.wait_for_client()会产生阻塞,S端等待C端连接。C端连接后S端将继续执 行,必须显式放置debugpy.breakpoint(),效果是断在breakpoint()之后那条语句, 而非断在breakpoint()这一行,有点反直觉。 ☆ 远程调试IDAPython插件 本节演示用debugpy调试plugins目录下的IDAPython插件。 1) debugpy_test_2.py X:\Green\IDA\plugins\debugpy_test_2.py IDAPython插件不能用UTF-8,否则"Edit->Plugins"里看不到插件。 -------------------------------------------------------------------------- #!/usr/bin/env python # -*- coding: cp936 -*- import idaapi import debugpy import os # # If not to hit breakpoint, try to set IDE_PROJECT_ROOTS # # r"X:\Green\IDA\plugins" # os.environ["IDE_PROJECT_ROOTS"] = idaapi.get_ida_subdirs( "plugins" )[-1] # # os.environ["DEBUGPY_LOG_DIR"] = r"X:\work\VSCode\other\logs" # debugpy.log_to( r"X:\work\VSCode\other\logs" ) # def foo () : print( "scz is here" ) print( "ok" ) class TestPlugin ( idaapi.plugin_t ) : # # Temporarily set this flag to enable reloading of script # flags = idaapi.PLUGIN_UNL comment = "IDAPython TestPlugin" help = "IDAPython TestPlugin" wanted_name = "IDAPython TestPlugin" wanted_hotkey = "" def init ( self ) : return idaapi.PLUGIN_KEEP def term ( self ) : pass def run ( self, arg ) : # # X:/Green/IDA/plugins/debugpy_test_2.py # X:\Green\IDA\plugins\debugpy_test_2.py # # print( __file__ ) # print( os.path.abspath( __file__ ) ) if not debugpy.is_client_connected() : debugpy.listen( ("127.0.0.1", 5678), in_process_debug_adapter=True ) debugpy.wait_for_client() idaapi.msg( 'Hit breakpoint\n' ) debugpy.breakpoint() foo() def PLUGIN_ENTRY () : return TestPlugin() -------------------------------------------------------------------------- 先在IDA中"Edit->Plugins"加载插件,"netstat -na | findstr :5678"检查侦听端 口,再在VSCode中用launch.json发起调试,会断在foo()处。插件执行完,不要在 VSCode中选"Disconnect (Shift+F5)",在IDA中再次"Edit->Plugins"加载,可重新 调试,仍断在foo()处。 debugpy_test_2.py有两个大坑,后面会逐一解释。 若未设置IDE_PROJECT_ROOTS,可能VSCode断不下来。怀疑python312._pth中将"."引 入,带来此问题,但我未测试移除"."的情形。非便携版IDAPython环境不一定遭遇此 问题。 debugpy.listen()指定in_process_debug_adapter为True。 2) debugpy日志 debugpy_test_2.py最初未设置IDE_PROJECT_ROOTS环境变量,加载插件, debugpy.breakpoint()未生效,表现为VSCode断不下来。 为排查此问题,可启用debugpy日志,下列办法均可 -------------------------------------------------------------------------- a. 在some.py中调用debugpy.log_to() b. 在IDA的Python提示符中先执行 os.environ["DEBUGPY_LOG_DIR"] = r"X:\work\VSCode\other\logs" 再"Edit->Plugins"加载插件 c. 在cmd中先设置DEBUGPY_LOG_DIR环境变量,再执行ida64.exe set DEBUGPY_LOG_DIR=X:\work\VSCode\other\logs X:\Green\IDA\ida64.exe -------------------------------------------------------------------------- 注意,这些办法指定的不是日志文件,而是存放日志的目录。 3) MyCode判定 断点不生效的最大可能是debugpy未将some.py判定为MyCode,而将之视为库代码。针 对此猜测检查日志"debugpy.pydevd..log",其中可能有 IDE_PROJECT_ROOTS [] File not traced (not in project): X:\Green\IDA\plugins\some.py File not traced (not in project - force_check_project_scope): X:\Green\IDA\plugins\some.py 在debugpy源码中搜特征字符串"not in project",定位相关代码 -------------------------------------------------------------------------- # # debugpy\_vendored\pydevd\pydevd.py # # 返回True时表示不跟踪some.py,若希望some.py中断点生效,需返回False # # original_filename对应some.py,这是绝对路径 # def apply_files_filter(self, frame, original_filename, force_check_project_scope): -------------------------------------------------------------------------- # # debugpy\_vendored\pydevd\_pydevd_bundle\pydevd_filtering.py # # 检查some.py是否属于Project # def in_project_roots(self, received_filename): def __init__(self): self._exclude_filters = [] self._project_roots = [] self._library_roots = [] ... # # 读取IDE_PROJECT_ROOTS环境变量 # project_roots = os.getenv("IDE_PROJECT_ROOTS", None) if project_roots is not None: # # 支持多个目录,类似PATH环境变量,分隔符是OS相关的 # project_roots = project_roots.split(os.pathsep) else: project_roots = [] self.set_project_roots(project_roots) # # LIBRARY_ROOTS、PYDEVD_FILTERS环境变量 # library_roots = os.getenv("LIBRARY_ROOTS", None) if library_roots is not None: library_roots = library_roots.split(os.pathsep) else: library_roots = self._get_default_library_roots() # # 设置库目录 # self.set_library_roots(library_roots) ... pydevd_filters = os.getenv("PYDEVD_FILTERS", "") ... -------------------------------------------------------------------------- 3.1) IDE_PROJECT_ROOTS环境变量 适当设置IDE_PROJECT_ROOTS可使debugpy判定some.py属于MyCode,从而跟踪some.py, 其中的断点将生效。若未跟踪some.py,其中的断点不生效。 下列办法均可 -------------------------------------------------------------------------- a. 在some.py中增加 os.environ["IDE_PROJECT_ROOTS:] = r"X:\Green\IDA\plugins" os.environ["IDE_PROJECT_ROOTS"] = idaapi.get_ida_subdirs( "plugins" )[-1] b. 在IDA的Python提示符中先执行 os.environ["IDE_PROJECT_ROOTS:] = r"X:\Green\IDA\plugins" os.environ["IDE_PROJECT_ROOTS:] = idaapi.get_ida_subdirs( "plugins" )[-1] os.getenv("IDE_PROJECT_ROOTS", None) 再"Edit->Plugins"加载插件 c. 在cmd中先设置IDE_PROJECT_ROOTS环境变量,再执行ida64.exe set IDE_PROJECT_ROOTS=X:\Green\IDA\plugins X:\Green\IDA\ida64.exe -------------------------------------------------------------------------- 用VSCode实测,可远程调试debugpy_test_2.py,断点生效。若启用debugpy日志,在 日志中看到 IDE_PROJECT_ROOTS ['X:\Green\IDA\plugins'] File traced: X:\Green\IDA\plugins\debugpy_test_2.py (force_check_project_scope) File traced: X:\Green\IDA\plugins\debugpy_test_2.py 3.2) debugpy支持rules字段 (推荐) 除了IDE_PROJECT_ROOTS环境变量,还有其他办法影响debugpy的MyCode判定。看增强 过的launch.json -------------------------------------------------------------------------- { "configurations": [ { "name": "Python Debugger: Attach", "type": "debugpy", "request": "attach", "connect": { "host": "127.0.0.1", "port": 5678 }, "justMyCode": true, "rules": [ { "path": "X:\\Green\\IDA\\plugins\\**", "include": true } ] } ] } -------------------------------------------------------------------------- "*"匹配单个路径段中任意字符(不包括路径分隔符/或\),"**"匹配零个或多个目录; 上例path字段相当于指定目录树。 C端用上述launch.json发起调试,S端path下的some.py死活都被判定为MyCode,无需 设置IDE_PROJECT_ROOTS。rules字段相当于MyCode白名单,优先级很高,无视库目录 树。 rules字段可在C端设置,经DAP发往S端。IDE_PROJECT_ROOTS必须在S端设置。 "Python Debugger"扩展不认rules字段,提示"Property rules is not allowed", VSCode UI中对rules字段标了波浪线。但rules字段经DAP发送到S端,debugpy支持 rules字段。 rules字段是MyCode判定问题的最优解。 3.3) "justMyCode": false 不知DAP层面justMyCode缺省值是不是true? 各种VSCode扩展有自己的全局justMyCode缺省值,互不影响。"Python Debugger"扩 展的全局justMyCode缺省为true,设置在这里 -------------------------------------------------------------------------- File Preferences Settings (Ctrl+,) User Extensions Python Debugger Debug Just My Code On -------------------------------------------------------------------------- launch.json中justMyCode设置优先级更高。 若生效的justMyCode为false,S端不会检查some.py是否属于MyCode,统统跟踪。好 处是,在VSCode中多次Continue后,会断在some.py中,不必在S端设置 IDE_PROJECT_ROOTS环境变量;坏处是库代码也被调试,"Step Into"干扰太多。 并不推荐justMyCode为false,实在没办法时可以一试。 4) debugpy_test_3.py X:\Green\IDA\plugins\debugpy_test_3.py -------------------------------------------------------------------------- #!/usr/bin/env python # -*- coding: cp936 -*- import idaapi import debugpy import os os.environ["IDE_PROJECT_ROOTS"] = idaapi.get_ida_subdirs( "plugins" )[-1] def foo () : print( "scz is here" ) print( "ok" ) class TestPlugin ( idaapi.plugin_t ) : flags = idaapi.PLUGIN_UNL comment = "IDAPython TestPlugin" help = "IDAPython TestPlugin" wanted_name = "IDAPython TestPlugin" wanted_hotkey = "" def init ( self ) : return idaapi.PLUGIN_KEEP def term ( self ) : pass def run ( self, arg ) : if not debugpy.is_client_connected() : # # Either one is fine # # debugpy.configure( python="X:/Green/IDA/python.exe" ) # debugpy.configure( {"python": "X:\\Green\\IDA\\python.exe"} ) # debugpy.configure( python=r"X:\Green\IDA\python.exe" ) debugpy.listen( ("127.0.0.1", 5678) ) debugpy.wait_for_client() idaapi.msg( 'Hit breakpoint\n' ) debugpy.breakpoint() foo() def PLUGIN_ENTRY () : return TestPlugin() -------------------------------------------------------------------------- debugpy_test_3.py未显式设置in_process_debug_adapter为True,但用 debugpy.configure()显式指定python.exe的路径。 5) 解读debugpy.listen() debugpy.listen()在"debugpy\server\api.py"中,查看此函数代码 -------------------------------------------------------------------------- def listen(address, settrace_kwargs, in_process_debug_adapter=False): ... if in_process_debug_adapter: ... _settrace( host=host, port=port, wait_for_ready_to_run=False, block_until_connected=False, **settrace_kwargs ) return ... # # in_process_debug_adapter为False时,流程至此 # adapter_args = [ _config.get("python", sys.executable), os.path.dirname(adapter.__file__), "--for-server", str(endpoints_port), "--host", host, "--port", str(port), "--server-access-token", server_access_token, ] ... _adapter_process = subprocess.Popen( adapter_args, close_fds=True, creationflags=creationflags, env=python_env, ) -------------------------------------------------------------------------- 5.1) in_process_debug_adapter=True debugpy.listen()的in_process_debug_adapter参数缺省为False,此时架构示意如下 +----------+ +------------------------+ +---------------------+ | | (DAP Protocol) | | (Internal Protocol) | | | VSCode |<-------------->| Debug Adapter Process |<------------------->| Your Python Script | | (Client) | | (一个独立的Python进程) | | (Target Process) | | | | (Server) | | (内置debugpy服务器) | +----------+ +------------------------+ +---------------------+ 左边由VSCode负责,中间与右边由debugpy负责。 in_process_debug_adapter为True时,架构示意如下 +----------+ +--------------------------------------------+ | | (DAP Protocol) | | | VS Code |<-------------->| Your Python Script (Target Process) | | (Client) | | +--------------------+ +-----------------+ | | | | |Debug Adapter Thread|+|内置debugpy服务器| | | | | +--------------------+ +-----------------+ | +----------+ +--------------------------------------------+ 左边由VSCode负责,右边由debugpy负责。 5.2) debugpy.configure(python=...) 假设IDAPython插件未显式设置in_process_debug_adapter为True,此时会创建独立 的Adapter进程。构建adapter_args时会调用 _config.get("python", sys.executable) 其意图是获取python.exe的路径。debugpy.configure(python=...)所设置的值会被 _config.get("python")获取。若未设置python变量,将用sys.executable,IDA的 Python提示符中sys.executable是"X:\Green\IDA\ida64.exe",不是python.exe。 "adapter.__file__"是文件名 X:\Green\IDA\Lib\site-packages\debugpy\adapter\__init__.py os.path.dirname(adapter.__file__)是目录名 X:\Green\IDA\Lib\site-packages\debugpy\adapter adapter_args是 [ r"X:\Green\IDA\ida64.exe", r"X:\Green\IDA\Lib\site-packages\debugpy\adapter", ... ] 后面subprocess.Popen(adapter_args,...)启动Adapter进程,Windows平台会调用 CreateProcess()执行 X:\Green\IDA\ida64.exe X:\Green\IDA\Lib\site-packages\debugpy\adapter 显然无法启动Adapter进程。上述命令试图用IDA打开名为adapter的文件,确实能看 到新的IDA进程启动中,但没有名为adapter的文件,这是个目录名。于是弹框报错 Can't find input file 'X:\Green\IDA\Lib\site-packages\debugpy\adapter' 复杂环境中使用debugpy模块,选择"in_process_debug_adapter=True"、 "debugpy.configure(python=...)"中的哪个,看个人喜好,我倾向于前者。 in_process_debug_adapter为False时,若生成debugpy日志,有三个文件 debugpy.server-16032.log // 对应Target Process debugpy.pydevd.16032.log // 重点看这个日志 debugpy.adapter-6596.log // 对应Debug Adapter Process in_process_debug_adapter为True时,若生成debugpy日志,有两个文件 debugpy.server-5212.log // 对应Target Process debugpy.pydevd.5212.log // 重点看这个日志 6) 排错小结 假设用「VSCode+debugpy」远程调试IDAPython插件,但断不下来,需要排错。 用如下方式启动IDA set DEBUGPY_LOG_DIR=X:\work\VSCode\other\logs set IDE_PROJECT_ROOTS=X:\Green\IDA\plugins X:\Green\IDA\ida64.exe "Edit->Plugins"加载插件,尝试VSCode远程调试。关闭IDA,确保debugpy日志被写入 文件。重点检查"debugpy.pydevd..log",在其中搜如下特征字符串 -------------------------------------------------------------------------- IDE_PROJECT_ROOTS Collecting default library roots. LIBRARY_ROOTS File not traced not in project not in project - force_check_project_scope some.py (被调试的IDAPython插件) -------------------------------------------------------------------------- 大概率是some.py的路径用startswith()检查LIBRARY_ROOTS有命中,或者说some.py 位于debugpy所认定的库目录树中。 我的便携版IDAPython环境将"X:\Green\IDA"算在库目录中,安装版IDAPython环境可 能不会遭遇MyCode判定问题。 ☆ VSCode IDACode扩展 参看 -------------------------------------------------------------------------- VSCode IDACode扩展 https://marketplace.visualstudio.com/items?itemName=Layle.idacode https://github.com/ioncodes/idacode https://github.com/ioncodes/idacode/releases/download/0.3.0/ida.zip -------------------------------------------------------------------------- "VSCode IDACode扩展"主要用于调试IDAPython脚本(Alt-F7加载的那种),也可调试 IDAPython插件(plugins目录的那种)。涉及两部分组件;一部分是C端的VSCode扩展, 在VSCode中安装、使用;另一部分是S端的IDA插件,上面那个ida.zip即是,需放到 plugins目录。底层依赖debugpy、tornado模块,需配套安装。 作者声称在"IDA 8.4/9.0+Python 3.12"中可用,我没有测试IDA 9.0。 「0x指纹」提供IDACode调试功能的测试记录。 1) 在IDA中安装IDACode插件 IDAPython环境中需安装两个模块 python.exe -m pip install debugpy tornado tornado模块用到_overlapped.pyd,便携环境中需补之。 从github下载ida.zip,展开成 X:\Green\IDA\plugins\idacode.py X:\Green\IDA\plugins\idacode_utils\ 根据实际情况修改"idacode_utils\settings.py",主要是改python.exe的路径。 2) 在VSCode中安装、配置IDACode扩展 X:\Green\VSCode\Code.exe --user-data-dir X:\work\VSCode\data\user-data --extensions-dir X:\Green\VSCode\extensions 启动VSCode,Ctrl+Shift+X,安装IDACode扩展,检查配置,C/S两侧IP/PORT要一致 -------------------------------------------------------------------------- File Preferences Settings (Ctrl+,) User Extensions IDACode Debug: Port 7066 (debugpy用) Execute On Save Off (缺省是On) Host 127.0.0.1 Port 7065 (Tornado HTTP服务) Save On Execute On -------------------------------------------------------------------------- View Command Palette (Ctrl+Shift+P) -------------------------------------------------------------------------- Ctrl+Shift+P,有四个与IDACode扩展相关的命令 IDACode: Connect to IDA IDACode: Attach a debugger to IDA IDACode: Connect and attach a debugger to IDA // 前两个命令的合成版 IDACode: Execute script in IDA 前两个命令很少用到,主要用后两个命令。还可在下列位置查看这些命令 -------------------------------------------------------------------------- File Preferences Extensions (Ctrl+Shift+X) IDACode FEATURES Commands -------------------------------------------------------------------------- 3) IDACode技术原理 3.1) S端 简单看了ida.zip中的源码,是对debugpy模块的封装 -------------------------------------------------------------------------- idacode.py idacode_utils\plugin.py 名为IDACode的IDAPython插件。检查IDAPython插件所用python.dll与外部 python.exe版本是否一致;新开线程启动Tornado HTTP服务;进行某种hook。 idacode_utils\socket_handler.py Tornado HTTP服务的主体代码所在,处理自定义事件,比如set_workspace、 attach_debugger、execute_script。 set_workspace设置workspace路径,为hook做准备。 attach_debugger处理debugpy模块相关动作,开启debugpy日志,调用 debugpy.configure()、debugpy.listen()。 execute_script调用idaapi.execute_sync()、idaapi.IDAPython_ExecScript() 执行目标IDAPython脚本,相当于Alt-F7,目标脚本将被debugpy模块调试。 idacode_utils\settings.py IDACode插件的某些配置,python.exe的绝对路径,Tornado HTTP服务端口, debugpy侦听端口,是否启用debugpy日志。 idacode_utils\hooks.py 对os.getcwd()进行hook,对以workspace路径为根的路径,均返回workspace路 径,不返回更深的路径。 此hook的意图不是很明确,可能与extension.js中pathMappings的remoteRoot为 "."有关,未深究。 idacode_utils\dbg.py 封装有bp(cond,msg),调用breakpoint(),支持条件断点。若使用dbg.bp(),将 断在自身,而非后一条语句,这点与debugpy.breakpoint()不同。 dbg.bp()只能用于IDAPython脚本(Alt-F7加载的那种),不能用于IDAPython插件。 -------------------------------------------------------------------------- IDACode在S端引入额外的Tornado HTTP服务,C端先与之通信。 IDACode未动用"in_process_debug_adapter=True",而是debugpy.configure()。 IDACode调试IDAPython插件(plugins目录的那种)时,若遇上断点不生效,可设置 IDE_PROJECT_ROOTS环境变量。 IDACode的debugpy日志目录用tempfile.gettempdir()的返回值,即%TMP%、%TEMP%。 3.2) C端 C端代码位于 X:\Green\VSCode\extensions\layle.idacode-0.3.0\ "out\extension.js"中有 -------------------------------------------------------------------------- vscode.debug.startDebugging(undefined, { name: 'Python: Remote Attach', type: 'python', request: 'attach', port: debugPort, host: host, pathMappings: [ { localRoot: '${workspaceFolder}', remoteRoot: '.' } ] }); -------------------------------------------------------------------------- type用老式的"python",新式是"debugpy"。未显式指定justMyCode,也无配置项, 缺省是true。 package.json中有 -------------------------------------------------------------------------- "configuration": { "type": "object", "title": "IDACode", "properties": { "IDACode.host": { "type": "string", "default": "127.0.0.1", "description": "The host running IDA." }, "IDACode.port": { "type": "integer", "default": 7065, "description": "The port IDA is listening on." }, "IDACode.debug.port": { "type": "integer", "default": 7066, "description": "The port the IDA debug server is listening on." }, "IDACode.saveOnExecute": { "type": "boolean", "default": true, "description": "Save all open editors when executing." }, "IDACode.executeOnSave": { "type": "boolean", "default": true, "description": "Execute script on save." } } } -------------------------------------------------------------------------- 4) 用VSCode IDACode扩展远程调试IDAPython脚本 4.1) debugpy_test_4.py X:\work\VSCode\other\debugpy_test_4.py -------------------------------------------------------------------------- #!/usr/bin/env python # -*- coding: cp936 -*- # # 测试两种加载方式 # # 1. 在IDA中Alt-F7加载 # 2. 用VSCode IDACode扩展远程加载 # import idaapi def foo () : print( "scz is here" ) print( "ok" ) def main () : foo() if "__main__" == __name__ : # # 可用debugpy.breakpoint(),此处刻意演示dbg.bp()。参看 # # idacode_utils\dbg.py # idacode_utils\socket_handler.py # # 虽然没有"import dbg",但IDAPython_ExecScript()第二形参env提供了dbg、 # __idacode__、__name__ # if '__idacode__' in globals() and __idacode__ : # # 不同于debugpy.breakpoint(),dbg.bp()将断在自身,而非后一条语句 # dbg.bp( __idacode__, 'Hit breakpoint' ) main() -------------------------------------------------------------------------- 4.2) IDAPython脚本测试记录 初学者欲复现时,请严格遵循下述步骤,勿自作聪明。 IDA打开some.i64,"Edit->Plugins->IDACode",此操作将侦听"127.0.0.1:7065", 这是Tornado HTTP服务。在IDA Output窗口看到 [IDACode] Listening on 127.0.0.1:7065 在cmd中用如下命令检查侦听端口 netstat -na | findstr :706 在VSCode中打开待调试IDAPython脚本所在目录 -------------------------------------------------------------------------- File Open Folder (Ctrl+K O) X:\work\VSCode\other (debugpy_test_4.py位于其中) -------------------------------------------------------------------------- 应避免使用中文目录名,否则可能出现断点不生效的现象 打开debugpy_test_4.py Ctrl+Shift+P,选择或输入"IDACode: Connect and attach a debugger to IDA", UI正上方有个输入框,提示信息是 Enter the path to the folder containing the script (Press 'Enter' to confirm or 'Escape' to cancel) 检查输入框中路径,应该是"X:\work\VSCode\other",回车确认。IDA Output窗口看 到 [IDACode] Client connected [IDACode] Set workspace folder to X:\work\VSCode\other [IDACode] IDACode debug server listening on 127.0.0.1:7066 在cmd中用如下命令检查侦听端口 netstat -na | findstr :706 Ctrl+Shift+P,选择或输入"IDACode: Execute script in IDA",断在dbg.bp()处, 之后可用常规调试动作。debugpy_test_4.py的输出在VSCode和IDA中均有。 假设修改了debugpy_test_4.py,保存后,再次"IDACode: Execute script in IDA", 相当于远程Alt-F7,可立即开始新的调试。不要选"Disconnect (Shift+F5)",断开 后重连,不是你想要的效果,IDA报端口占用,只能重启IDA恢复。 5) 用VSCode IDACode扩展远程调试IDAPython插件 5.1) debugpy_test_5.py X:\Green\IDA\plugins\debugpy_test_5.py -------------------------------------------------------------------------- #!/usr/bin/env python # -*- coding: cp936 -*- import idaapi import debugpy import os os.environ["IDE_PROJECT_ROOTS"] = idaapi.get_ida_subdirs( "plugins" )[-1] def foo () : print( "scz is here" ) print( "ok" ) class TestPlugin ( idaapi.plugin_t ) : flags = idaapi.PLUGIN_UNL comment = "IDAPython TestPlugin" help = "IDAPython TestPlugin" wanted_name = "IDAPython TestPlugin" wanted_hotkey = "" def init ( self ) : return idaapi.PLUGIN_KEEP def term ( self ) : pass def run ( self, arg ) : # # dbg.bp()只能用于IDAPython脚本,不能用于IDAPython插件 # debugpy.breakpoint() foo() def PLUGIN_ENTRY () : return TestPlugin() -------------------------------------------------------------------------- 若遭遇断点不生效,可设置IDE_PROJECT_ROOTS环境变量。 5.2) IDAPython插件测试记录 初学者欲复现时,请严格遵循下述步骤,勿自作聪明。 IDA打开some.i64,"Edit->Plugins->IDACode",此操作将侦听"127.0.0.1:7065", 这是Tornado HTTP服务。在IDA Output窗口看到 [IDACode] Listening on 127.0.0.1:7065 在cmd中用如下命令检查侦听端口 netstat -na | findstr :706 在VSCode中打开任意目录 -------------------------------------------------------------------------- File Open Folder (Ctrl+K O) X:\anywhere (不要求是"X:\Green\IDA\plugins") -------------------------------------------------------------------------- 打开dummy.py,名字无所谓,内容为空无所谓,但必须打开一个。 Ctrl+Shift+P,选择或输入"IDACode: Connect and attach a debugger to IDA", UI正上方有个输入框,提示信息是 Enter the path to the folder containing the script (Press 'Enter' to confirm or 'Escape' to cancel) 检查输入框中路径,本例此路径任意,回车确认。IDA Output窗口看到 [IDACode] Client connected [IDACode] Set workspace folder to X:\anywhere [IDACode] IDACode debug server listening on 127.0.0.1:7066 在cmd中用如下命令检查侦听端口 netstat -na | findstr :706 这次不要用"IDACode: Execute script in IDA",此操作用于调试IDAPython脚本, 不用于调试plugins目录的IDAPython插件。 在IDA中"Edit->Plugins->IDAPython TestPlugin"加载debugpy_test_5.py。 一切正常的话,VSCode中将自动打开 X:\Green\IDA\plugins\debugpy_test_5.py 断在foo()处,之后可用常规调试动作。 在IDA中再次"Edit->Plugins->IDAPython TestPlugin",可重新调试;不要在VSCode 中选"Disconnect (Shift+F5)",不是你想要的效果。 6) 对VSCode IDACode扩展的改造 X:\Green\VSCode\extensions\layle.idacode-0.3.0\out\extension.js VSCode IDACode扩展在extension.js中内置launch.json,效果是justMyCode固定为 true,无法测试false情形。"Step Into"无法进入库代码,会提示 Frame skipped from debugging during step-in. Note: may have been skipped because of "justMyCode" option (default == true). Try setting "justMyCode": false in the debug configuration (e.g., launch.json). 此外,VSCode IDACode扩展无法在C端解决MyCode判定问题。为此可做一些Hacking。 6.1) 修改package.json X:\Green\VSCode\extensions\layle.idacode-0.3.0\package.json 修改package.json中,增加justMyCode配置项 -------------------------------------------------------------------------- "configuration": { "type": "object", "title": "IDACode", "properties": { ... "IDACode.justMyCode": { "type": "boolean", "default": true, "description": "Debug Just My Code." } } } -------------------------------------------------------------------------- 6.2) 修改extension.js X:\Green\VSCode\extensions\layle.idacode-0.3.0\out\extension.js 修改extension.js内置的launch.json如下 -------------------------------------------------------------------------- vscode.debug.startDebugging(undefined, { "name": "Python Debugger: Attach", "type": "debugpy", "request": "attach", "connect": { "host": host, "port": debugPort }, "justMyCode": getConfig('justMyCode'), pathMappings: [ { localRoot: '${workspaceFolder}', remoteRoot: '.' } ], "rules": [ { "path": "${workspaceFolder}" + "\\**", "include": true } ] }); -------------------------------------------------------------------------- 显式增加justMyCode字段、rules字段 6.3) 使修改生效 在VSCode中关闭所有打开的文件、目录,退出VSCode -------------------------------------------------------------------------- File Close Folder (不要忘了这步) -------------------------------------------------------------------------- 重新打开VSCode X:\Green\VSCode\Code.exe --user-data-dir X:\work\VSCode\data\user-data --extensions-dir X:\Green\VSCode\extensions 检查IDACode配置 -------------------------------------------------------------------------- File Preferences Settings (Ctrl+,) User Extensions IDACode Just My Code On (可以调成Off) -------------------------------------------------------------------------- 若未看到新增的配置项,很可能是缓存未清,确保关闭打开过的目录再试。 6.4) 改造后的IDAPython插件测试记录 初学者欲复现时,请严格遵循下述步骤,勿自作聪明。 IDA打开some.i64,"Edit->Plugins->IDACode",此操作将侦听"127.0.0.1:7065", 这是Tornado HTTP服务。在IDA Output窗口看到 [IDACode] Listening on 127.0.0.1:7065 在cmd中用如下命令检查侦听端口 netstat -na | findstr :706 在VSCode中打开IDAPython插件目录(plugins) -------------------------------------------------------------------------- File Open Folder (Ctrl+K O) X:\Green\IDA\plugins -------------------------------------------------------------------------- 打开dummy.py,名字无所谓,内容为空无所谓,但必须打开一个。 Ctrl+Shift+P,选择或输入"IDACode: Connect and attach a debugger to IDA", UI正上方有个输入框,提示信息是 Enter the path to the folder containing the script (Press 'Enter' to confirm or 'Escape' to cancel) 检查输入框中路径,应该是"X:\Green\IDA\plugins",回车确认。IDA Output窗口看 到 [IDACode] Client connected [IDACode] Set workspace folder to X:\Green\IDA\plugins [IDACode] IDACode debug server listening on 127.0.0.1:7066 在cmd中用如下命令检查侦听端口 netstat -na | findstr :706 这次不要用"IDACode: Execute script in IDA",此操作用于调试IDAPython脚本, 不用于调试plugins目录的IDAPython插件。 在IDA中"Edit->Plugins"加载待调试插件some.py。 一切正常的话,VSCode中将自动打开 X:\Green\IDA\plugins\some.py 断在debugpy.breakpoint()之后的代码处,之后可用常规调试动作。 在IDA中再次"Edit->Plugins"加载some.py,可重新调试;不要在VSCode中选 "Disconnect (Shift+F5)",不是你想要的效果。 7) 吐槽VSCode IDACode扩展 原版VSCode IDACode扩展整体框架有些复杂,调试IDAPython脚本比较方便,调试 IDAPython插件时没有优势,改造之后还可以。 注意set_workspace时回车确认,此步易忽略,造成后续出错。 IDACode践行代码即文档,假设用户都是高级程序员,不需要写太详细的使用说明, 能自行摸索出各种用法,我这是说反话呢。头一次遇上只能Ctrl+Shift+P触发操作, 一轮不够,还得两轮,能再扯淡些吗? 感谢「0x指纹」提供IDACode测试记录,否则入门都有困难。