文章目录
- [1 情况概要(和文件结构)](#1 情况概要(和文件结构))
- [2 具体设置和启动步骤](#2 具体设置和启动步骤)
-
- [2.1 具体配置](#2.1 具体配置)
-
- [Step 1 针对attach debugger到子进程](#Step 1 针对attach debugger到子进程)
- [Step 2 针对子进程的暂停](#Step 2 针对子进程的暂停)
- [(可选) Step 3 判断哪个进程id是需要的子进程](#(可选) Step 3 判断哪个进程id是需要的子进程)
- [2.2 启动步骤和过程](#2.2 启动步骤和过程)
- [3 其他问题解决](#3 其他问题解决)
-
- 3.1
- [3.2 ptrace: Operation not permitted](#3.2 ptrace: Operation not permitted)
- 其他解决方案*2
-
- 方案一(ChatGPT提供,对我不可行)
- [方案二(Github issue翻到)](#方案二(Github issue翻到))
- 参考
1 情况概要(和文件结构)
环境:Linux Ubuntu
最近在跑大模型,遇到一份代码的具体程序是通过subprocess.run()
来启动的,而vscode的debug功能无法追踪进去。也就是说,我有父进程launch.py来启动subprocess.run()
,调用子进程文件run.py
。我可以单步调试追踪到subprocess
这一步并进入run()
函数,但是无法继续进入子进程的工作,无法追踪到run.py
看我真正想看的代码。
这里父进程文件和子进程文件的概念我没有细究,大概意思是主动调用subprocess.run()
来创建子进程的是父进程文件,被subprocess.run()
跑起来的是子进程文件。
附上文件结构的简单示意。
python
def python_launch(args):
"""
Vanilla python launcher for degbugging purposes
"""
# ....
# 构造cmd
cmd = f"python {args.run_file}" # 省略一堆参数
# 启动subprocess
subprocess.run(cmd, shell=True)
- run.py:子进程,主要内容为
python
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)
json
{
"name": "Python: Attach to Subprocess", //随便起名
"type": "python",
"request": "attach", //附加到子进程
"processId": "${command:pickProcess}" //选择进程id
}
这里用${command:pickProcess}
是采用vscode自带的命令,从进程列表中手动选择,而不是写死进程id。
实测这些内容就够了,不需要其他key比如ChatGPT建议的justMyCode和subProcess。
Step 2 针对子进程的暂停
在子进程文件,你需要断点的代码前面,或者索性最前面,加上一行:
python
input("Continue...")
这行会让代码停下,直到你在命令行中随便敲点什么,回车也行,才继续执行。
(可选) Step 3 判断哪个进程id是需要的子进程
因为我实在小白,我不确定哪个进程才是我要的,所以我在子进程文件里加了几行,输出父进程id和子进程id。
python
print(f"Parent PID (PPID): {os.getppid()}")
print(f"Current PID: {os.getpid()}")
Current PID就是我们要的子进程id
以上,配置完了,我的run.py最终长这样:
python
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 启动步骤和过程
需要你操作的地方写了人工,其他的是自动执行顺序。
-
(人工)正常通过命令行执行sh test.sh,启动脚本
-
程序进入
launch.py
,启动subprocess -
程序进入
run.py
3.1. 输出父进程id和子进程id供参考
3.2. 在
input(Continue...)
处暂停,等待键入字符才能继续执行
-
(人工)启动debug的Subprocess Attach,选择子进程,附加到正确的id上,等待...
-
attach到子进程,可能需要一会。
在DEBUG CONSOLE里会提示结果,要么是attach成功(如图),要么是弹窗报错然后在DEBUG CONSOLE里看错误信息。
此时可以看到调试工具栏表示正在执行、没有暂停,因为我们还在被input()暂停着。
-
(人工)在命令行里敲个回车,跳出
input(Continue...)
-
断点停止在run.py中的对应位置
-
(人工)正常调试
3 其他问题解决
3.1
json
--- Starting attach to pid: 1103 ---
/bin/sh: 1: gdb: not found
解决方案是安装gdb
sh
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是啥用)
python
print(f"UID: {os.getuid()}, EUID: {os.geteuid()}")
但是到处折腾了一圈别的方案没解决,还是回来把这个值设为0了,真就解决了。
这个值的设置需要root权限。废话,如果本来就是用root在跑的话权限统一也不会报这个错了。
sh
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!
在子进程文件里写
python
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,用的监听路径,大概如下:
json
{
"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...小白还不知道不开子进程会不会有影响,就没碰