对subprocess启动的子进程使用VSCode python debugger

文章目录

  • 1 情况概要(和文件结构)
  • 2 具体设置和启动步骤
  • 2.1 具体配置
  • Step 1 针对attach debugger到子进程
  • Step 2 针对子进程的暂停
  • (可选) Step 3 判断哪个进程id是需要的子进程
  • 2.2 启动步骤和过程
  • 3 其他问题解决
  • 3.1
  • 3.2 ptrace: Operation not permitted
  • 其他解决方案*2
  • 方案一(ChatGPT提供,对我不可行)
  • 方案二(Github issue翻到)
  • 参考
  • 1 情况概要(和文件结构)

    环境:Linux Ubuntu

    最近在跑大模型,遇到一份代码的具体程序是通过subprocess.run()来启动的,而vscode的debug功能无法追踪进去。也就是说,我有父进程launch.py来启动subprocess.run(),调用子进程文件run.py。我可以单步调试追踪到subprocess这一步并进入run()函数,但是无法继续进入子进程的工作,无法追踪到run.py看我真正想看的代码。

    这里父进程文件和子进程文件的概念我没有细究,大概意思是主动调用subprocess.run()来创建子进程的是父进程文件,被subprocess.run()跑起来的是子进程文件。

    附上文件结构的简单示意。

    1. test.sh:python launch.py并传一堆参数

    2. launch.py:父进程,主要内容为

    def python_launch(args):
        """
        Vanilla python launcher for degbugging purposes
        """
        # ....
        # 构造cmd
        cmd = f"python {args.run_file}" # 省略一堆参数
        # 启动subprocess
        subprocess.run(cmd, shell=True)
        
    
    1. run.py:子进程,主要内容为
    def main(cfg):
     
        # 环境变量设置,数据读取,新建文件夹之类的之类的
        # ...
        
        # 初始化trainer并训练
        trainer = build_trainer(cfg)
        trainer.run()
    
    

    2 具体设置和启动步骤

    我们的目标是,我从某个地方把程序起起来并创建了子进程,然后把debugger连接到子进程上,于是它可以在子进程的断点地方停下、正常调试。

    那么我们需要做到两件事情:1)把Debugger attach到子进程;2)子进程要能等待我们attach,而不是一股脑往下运行,那就来不及停在断点。

    2.1 具体配置

    我们分别对这两件事情做配置。

    Step 1 针对attach debugger到子进程

    采用vscode的python debugger插件,新建debugger config如下(编辑的是launch.json)

    {
        "name": "Python: Attach to Subprocess", //随便起名
        "type": "python",
        "request": "attach", //附加到子进程
        "processId": "${command:pickProcess}" //选择进程id
    }
    

    这里用${command:pickProcess}是采用vscode自带的命令,从进程列表中手动选择,而不是写死进程id。
    实测这些内容就够了,不需要其他key比如ChatGPT建议的justMyCode和subProcess。

    Step 2 针对子进程的暂停

    在子进程文件,你需要断点的代码前面,或者索性最前面,加上一行:

        input("Continue...")
    

    这行会让代码停下,直到你在命令行中随便敲点什么,回车也行,才继续执行。

    (可选) Step 3 判断哪个进程id是需要的子进程

    因为我实在小白,我不确定哪个进程才是我要的,所以我在子进程文件里加了几行,输出父进程id和子进程id。

        print(f"Parent PID (PPID): {os.getppid()}")
        print(f"Current PID: {os.getpid()}")
    

    Current PID就是我们要的子进程id

    以上,配置完了,我的run.py最终长这样:

    def main(cfg):
    
        #### for debug #########
        # 输出父进程和子进程id
        print(f"Parent PID (PPID): {os.getppid()}")
        print(f"Current PID: {os.getpid()}")
        
        # 可选,用来检查user id,原因后面说
        print(f"UID: {os.getuid()}, EUID: {os.geteuid()}") 
        
        # 暂停
        input("Continue...")
        #########################
        
        # 环境变量设置,数据读取,新建文件夹之类的之类的
        # ...
        
        trainer = build_trainer(cfg)
        trainer.run()
        
    

    2.2 启动步骤和过程

    需要你操作的地方写了人工,其他的是自动执行顺序。

    1. (人工)正常通过命令行执行sh test.sh,启动脚本

    2. 程序进入launch.py,启动subprocess

    3. 程序进入run.py
      3.1. 输出父进程id和子进程id供参考
      3.2. 在input(Continue...)处暂停,等待键入字符才能继续执行

    4. (人工)启动debug的Subprocess Attach,选择子进程,附加到正确的id上,等待…

    5. attach到子进程,可能需要一会。
      在DEBUG CONSOLE里会提示结果,要么是attach成功(如图),要么是弹窗报错然后在DEBUG CONSOLE里看错误信息。
      此时可以看到调试工具栏表示正在执行、没有暂停,因为我们还在被input()暂停着。

    6. (人工)在命令行里敲个回车,跳出input(Continue...)

    7. 断点停止在run.py中的对应位置

    8. (人工)正常调试

    3 其他问题解决

    3.1

    --- Starting attach to pid: 1103 ---
    /bin/sh: 1: gdb: not found
    

    解决方案是安装gdb

    apt-get update
    apt-get upgrade -y
    apt-get install gdb
    

    3.2 ptrace: Operation not permitted

    解释一下前面python里为什么写了一句进程创建用户id查询

    我在第5步attach环节遇到了一个错误,ptrace: Operation not permitted,是个权限错误。

    --- Starting attach to pid: 654608 ---
    Could not attach to process.  If your uid matches the uid of the target
    process, check the setting of /proc/sys/kernel/yama/ptrace_scope, or try
    again as the root user.  For more details, see /etc/sysctl.d/10-ptrace.conf
    ptrace: Operation not permitted.
    

    问了下ChatGPT:

    /proc/sys/kernel/yama/ptrace_scope 文件
    这个文件控制了 ptrace 系统调用的访问权限。ptrace 是 Linux 的一个系统调用,允许一个进程跟踪和控制另一个进程,是调试器工作的核心。
    ptrace_scope 参数的值
    0: 允许任何进程使用 ptrace 附加到其他进程(受用户权限约束)。
    1: 仅允许父进程(如直接启动的调试器进程)附加到子进程。
    2: 禁止所有非父子关系的调试。
    3: 禁止所有 ptrace 附加操作。
    默认值通常为 1,为了安全性避免恶意进程滥用 ptrace。

    查询了一下这个值(cat /proc/sys/kernel/yama/ptrace_scope),输出是1。但是显然我的父进程和子进程都是同一个user启用的,不懂为什么说我权限错误。

    查询进程创建用户有好几个方法,我是在python里写了一句这个。(解释了前文的查询用户id是啥用)

    print(f"UID: {os.getuid()}, EUID: {os.geteuid()}") 
    

    但是到处折腾了一圈别的方案没解决,还是回来把这个值设为0了,真就解决了。

    这个值的设置需要root权限。废话,如果本来就是用root在跑的话权限统一也不会报这个错了。

    echo 0 | sudo tee /proc/sys/kernel/yama/ptrace_scope
    

    ChatGPT也说了,这个值设置为0的话不太安全,建议用完就给它改回去。于是写了个脚本方便切换值,插入在.bashrc文件里(vim ~/.bashrc

    ptrace_scope() {
    	current_value=$(cat /proc/sys/kernel/yama/ptrace_scope) 
    	echo "[INTRO] Script for changing between 0 & 1 value for /proc/sys/kernel/yama/ptrace_scope. The value should vary from [0,3] for different safety levels, default as 1"
        echo; # 换行
        echo "current value = $current_value" # 获取当前值
        
        # 根据当前值,在0和1之间切换
        if [ "$current_value" -eq 1 ]; then
            echo "Now changing to 0..."
            echo 0 | sudo tee /proc/sys/kernel/yama/ptrace_scope
        elif [ "$current_value" -eq 0 ]; then
            echo "Now changing to 1 ..."
            echo 1 | sudo tee /proc/sys/kernel/yama/ptrace_scope
        else
            echo "[Warning] Unknown ptrace_scope current value. Nothing takes effect."
            exit 1
        fi
    
        # 检查是否执行成功,并提醒恢复0值
        current_value=$(cat /proc/sys/kernel/yama/ptrace_scope)
        echo "[INFO] ptrace_scope changed to $current_value"
        echo;
        if [ "$current_value" -eq 1 ]; then
            echo "[INFO] Safe default setting ^-^"
        elif [ "$current_value" -eq 0 ]; then
            echo "[INFO] Unsafe setting, remember to change back to value = 1."
        fi
    
        echo;
    }
    

    记得source ~/.bashrc使配置生效。

    效果如下,通过ptrace_scope change指令实现0-1切换。

    其他解决方案*2

    方案一(ChatGPT提供,对我不可行)

    针对debugger监听子进程这个事情,ChatGPT还给了我一套方案,但用不了。记一下大概配置,哪哪都折腾过了,这个debugpy的wait for client啊就是不知道wait到了什么,直接就监听到了,但我明明还没开debugger的attach!

    在子进程文件里写

    import debugpy
    
    int main():
    	# 启动调试服务
    	debugpy.listen(("0.0.0.0", 5678))
    	print("Debugpy is listening on port 5678")
    	# 等待客户端连接
    	debugpy.wait_for_client()
    	print("Debugger is attached!")
    
    	# trainer.......
    

    在debugger config里写一个针对test脚本执行的config,和debug普通python文件一个写法。另外再写一个attach用的config,用的监听路径,大概如下:

    {
        "name": "Python: Attach to Subprocess",
        "type": "python",
        "request": "attach",
        "connect": {
            "host": "localhost",
            "port": 5678
        },
        "justMyCode": false,
        "subProcess": false
    }
    

    方案二(Github issue翻到)

    还看到一个更暴力的是是直接改写,不用subprocess,见GitHub: embodied-generalist/issues/33
    因为它只是inference但是我要train…小白还不知道不开子进程会不会有影响,就没碰

    参考

    1. StackOverflow Attaching a VSCode Debugger to a Sub Process in Python
    2. GitHub: embodied-generalist/issues/33

    作者:芝麻团坚果

    物联沃分享整理
    物联沃-IOTWORD物联网 » 对subprocess启动的子进程使用VSCode python debugger

    发表回复