在 Visual Studio 中远程调试 Linux 上的 Python 代码

本文介绍如何配置 Visual Studio 安装以支持在远程 Linux 计算机上调试 Python 代码。 本演练基于 Visual Studio 2019 版本 16.6。

Visual Studio 可以在 Windows 计算机上在本地和远程启动和调试 Python 应用程序。 Visual Studio 还支持使用 debugpy 库在 CPython 以外的其他作系统、设备或 Python 实现上远程调试。

Visual Studio 2019 版本 16.4 及更早版本使用 ptvsd 库。 在 Visual Studio 2019 版本 16.5 及更高版本中,debugpy 库将替换 ptvsd。 使用 debugpy 时,被调试的 Python 代码会托管一个调试服务器,Visual Studio 可以附加到这个服务器上进行调试。 此托管需要对代码进行少量修改才能导入和启用服务器。 可能还需要调整远程计算机上的网络或防火墙配置,以允许 TCP 连接。

先决条件

  • 安装了支持 Python 工作负载的 Visual Studio。 有关详细信息,请参阅 在 Visual Studio中安装 Python 支持。

  • 在 macOS 或 Linux 等作系统上运行 Python 的远程计算机。

  • 端口 5678(入站)在远程计算机的防火墙上打开,这是远程调试的默认端口。

设置 Linux 计算机

可以在 Azure 上轻松创建 Linux 虚拟机,并使用 Windows 中的远程桌面 访问它。 虚拟机上的 Ubuntu 方便是因为 Python 是默认安装的。 如果配置不同,请参阅 安装 Python 解释器 了解其他 Python 下载位置。

配置防火墙

必须在远程计算机的防火墙上打开入站端口 5678 以支持远程调试。

有关如何为 Azure 虚拟机创建防火墙规则的详细信息,请参阅以下文章:

准备用于调试的脚本

按照以下步骤准备用于在 Linux 上调试 Python 代码的脚本。

  1. 在远程计算机上,使用以下代码创建名为 guessing-game.py 的 Python 文件:

    import random
    
    guesses_made = 0
    name = input('Hello! What is your name?\n')
    number = random.randint(1, 20)
    print('Well, {0}, I am thinking of a number between 1 and 20.'.format(name))
    
    while guesses_made < 6:
        guess = int(input('Take a guess: '))
        guesses_made += 1
        if guess < number:
            print('Your guess is too low.')
        if guess > number:
            print('Your guess is too high.')
        if guess == number:
            break
    if guess == number:
        print('Good job, {0}! You guessed my number in {1} guesses!'.format(name, guesses_made))
    else:
        print('Nope. The number I was thinking of was {0}'.format(number))
    
  2. 使用 pip3 install debugpy 命令将 debugpy 包安装到环境中。

    说明

    记录已安装的 debugpy 版本是个好主意,以防以后需要用来排除故障。 debugpy 清单 还显示可用版本。

  3. 通过在 guessing-game.py 文件的顶部添加以下代码,在其他代码之前启用远程调试。 (虽然不是严格的要求,但在调用 listen 函数之前无法调试生成的任何后台线程。

    import debugpy
    debugpy.listen(('0.0.0.0', 5678))
    
  4. 保存文件并运行程序:

    python3 guessing-game.py
    

    listen 函数的调用在后台运行,并在与程序交互时等待传入连接。 如果需要,可以在调用 listen 函数后调用 wait_for_client 函数,以阻止程序执行,直到调试器附加。

提示

除了 listenwait_for_client 函数外,debugpy 还提供帮助程序函数 breakpoint。 如果附加了调试器,则此函数充当编程断点。 如果附加了调试器,则另一个函数 is_client_connected1返回 True。 在调用任何其他 debugpy 函数之前,无需检查此结果。

从 Python 工具远程附加

以下步骤演示如何设置断点以停止远程进程。

  1. 在本地计算机上创建远程文件的副本,并在 Visual Studio 中打开它。 文件所在的位置并不重要,但其名称应与远程计算机上的脚本名称匹配。

  2. (可选)若要在本地计算机上将 IntelliSense 用于 debugpy,请将 debugpy 包安装到 Python 环境中。

  3. 选择“调试”>“附加到进程”

  4. 在“附加到进程”对话框中,将“连接类型”设置为“Python 远程 (debugpy)”。

  5. 连接目标 字段中,输入命令 tcp://<ip_address>:5678

    • tcp:// 将连接类型指定为传输控制协议(TCP)。
    • <ip_address> 是远程计算机的 IP 地址,可以是显式地址或名称,如 myvm.cloudapp.net
    • :5678 是远程调试端口号。
  6. 选择 Enter 以填充该计算机上的可用 debugpy 进程列表:

    屏幕截图,显示如何输入连接目标以查看可用调试进程列表。

    如果在填充此列表后碰巧在远程计算机上启动另一个程序,请选择 刷新 按钮。

  7. 选择要调试的进程,然后选择 附加,或双击该进程。

  8. Visual Studio 在脚本继续在远程计算机上运行时切换到调试模式,提供所有常规 调试 功能。

    可以在 if guess < number: 行上设置断点,然后切换到远程计算机并输入另一个猜测。 本地计算机上的 Visual Studio 在断点处停止,显示局部变量等:

    显示命中断点时 Visual Studio 如何暂停调试的屏幕截图。

  9. 停止调试时,Visual Studio 将从程序分离。 程序继续在远程计算机上运行。 debugpy 还会继续侦听调试程序附加,因此你可以随时重新附加到进程。

排查连接问题

查看以下几点,帮助排查连接问题。

  • 确保为“连接类型”选择“Python 远程 (debugpy)”

  • 确认 连接目标中的机密 与远程代码中的机密完全匹配。

  • 确认 连接目标中的 IP 地址 与远程计算机的 IP 地址匹配。

  • 验证远程计算机上的远程调试端口是否已打开,并且连接目标包括端口后缀,例如 :5678

    若要使用不同的端口,请在调用 listen 函数时指定端口号,如 debugpy.listen((host, port))中所示。 在这种情况下,请务必在防火墙中打开特定端口。

  • 确认远程计算机上安装的 debugpy 版本(由 pip3 list 命令返回)与 Visual Studio Python 工具(PTVS)版本匹配。

    下表列出了有效的版本对。 根据需要,更新远程计算机上的 debugpy 版本。

    Visual Studio Python 工具 debugpy
    2019 16.6 1.0.0b5 1.0.0b5
    2019 16.5 1.0.0b1 1.0.0b1

说明

Visual Studio 2019 版本 16.0-16.4 已使用 ptvsd,而不是 debugpy。 这些版本的本演练中的过程类似,但函数名称不同。 Visual Studio 2019 版本 16.5 使用 debugpy,但函数名称与 ptvsd 中的名称相同。 使用 enable_attach,而不是 listen。 使用 wait_for_attach,而不是 wait_for_client。 使用 break_into_debugger,而不是 breakpoint

使用 ptvsd 3.x 进行遗留系统调试

ptvsd 3.x 旧版调试器是 Visual Studio 2017 版本 15.7 及更早版本中的默认值。

根据 Visual Studio 配置,可能需要使用 ptvsd 3.x 进行远程调试:

  • Visual Studio 2017 版本 15.7 及更低版本(Python 2.6、3.1 到 3.4 或 IronPython)
  • Visual Studio 2019 版本 16.5 及更高版本(Python 2.6、3.1 到 3.4 或 IronPython)
  • 早期 4.x 版本

如果配置实现了较旧的版本方案,Visual Studio 会显示错误,调试器不支持此 Python 环境

设置远程调试

若要准备使用 ptvsd 3.x 进行远程调试,请完成以下步骤:

  1. 设置机密,用于限制对正在运行的脚本的访问。

    在 ptvsd 3.x 中,enable_attach 函数要求将“secret”作为第一个参数传递。

    • 附加远程调试器时,请使用 enable_attach(secret="<secret>") 命令输入机密。

    尽管可以使用 enable_attach(secret=None) 命令允许任何人进行连接,但不建议使用此选项。

  2. tcp://<secret>@<ip_address>:5678形式创建连接目标 URL。

    • tcp:// 将连接类型指定为 TCP。
    • <secret> 是在 Python 代码中使用 enable_attach 函数传递的字符串。
    • <ip_address> 是远程计算机的 IP 地址,可以是显式地址或名称,如 myvm.cloudapp.net
    • :5678 是远程调试端口号。

使用 TCPS 协议进行安全连接

默认情况下,与 ptvsd 3.x 远程调试服务器的连接仅受机密保护,所有数据以纯文本传递。 为提供更安全的连接方式,ptvsd 3.x 支持 SSL,方法是使用 TCP 协议的安全版本 (TCPS)

使用以下步骤将 ptvsd 3.x 配置为使用 TCPS 协议:

  1. 在远程计算机上,使用 openssl 命令为密钥和自签名证书生成单独的文件:

    openssl req -new -x509 -days 365 -nodes -out cert.cer -keyout cert.key
    
    • openssl 提示符下,输入用于连接 公用名的主机名或 IP 地址。

    有关详细信息,请参阅 Python ssl 模块文档中 自签名证书。 请注意,Python 文档中所述的命令仅生成单个组合文件。

  2. 在代码中,修改对 enable_attach 函数的调用,加入 certfilekeyfile 参数,并使用文件名作为参数的值。 这些参数的含义与标准 ssl.wrap_socket Python 函数的含义相同。

    ptvsd.enable_attach(secret='my_secret', certfile='cert.cer', keyfile='cert.key')
    

    还可以在本地计算机上的代码文件中进行相同的更改。 由于此代码实际上未运行,因此不需要严格执行。

  3. 在远程计算机上重启 Python 程序,使其可供调试。

  4. 通过使用 Visual Studio 将证书添加到 Windows 计算机上的受信任根 CA 来保护通道:

    1. 将证书文件从远程计算机复制到本地计算机。

    2. 打开 控制面板,转到 Windows 工具>管理计算机证书

    3. 在“certlm [证书 - 本地计算机]”对话框中,展开“受信任的根证书颁发机构”节点,右键单击“证书”,然后依次选择“所有任务”>“导入”。

    4. 浏览并选择从远程计算机复制过来的 .cer 文件。

    5. 继续执行对话框提示以完成导入过程。

  5. 重复 Visual Studio 中的附加过程,如前面的从 Python 工具远程附加中所述。

    对于此实例,请将 tcps:// 定义为 连接目标(或 限定符)的协议。

    屏幕截图,显示如何使用 SSL 将 TCPS 指定为远程调试传输。

解决连接问题

在连接尝试期间,Visual Studio 可能会遇到问题。 查看以下情景,并根据需要采取相应的措施。

  • Visual Studio 在通过 SSL 进行连接时警告潜在的证书问题。

    Action:可以忽略消息并继续。

    谨慎

    请记住,尽管通道仍被加密,但仍可能面临中间人攻击的风险。

  • Visual Studio 显示“远程证书不受信任”警告。

    问题:证书未正确添加到受信任的根 CA。

    操作:重新检查将证书添加到 Windows 计算机上受信任的根 CA 中的步骤,然后重试连接。

    显示远程 SSL 证书不受信任的警告的屏幕截图。

  • Visual Studio 显示 远程证书名称与主机名 警告不匹配。

    问题:未为证书的 公用名 指定正确的主机名或 IP 地址。

    操作:重新检查使用 TCPS 保护连接中的步骤。 创建证书时,请务必使用正确的 公用名,然后重试连接。

    显示远程 SSL 证书与主机名不匹配的警告的屏幕截图。