调试Linux系统挂起和休眠问题的最佳实践

  1. 概述
  2. 当谈到Linux系统的挂起/休眠时, 我们指的是以下三种受支持的Linux系统休眠状态:
  1. STI(Suspend To Idle)是一种通用的、纯软件、轻量级系统睡眠状态。与特定于平台的驱动程序增强一起,可以使用这种状态来达到Intel®平台上的S0i3状态。要进入这种状态,运行shell命令:echo freeze > /sys/power/state
  2. STR(Suspend To RAM)提供了显著的功耗节省,因为系统中除了内存之外的所有东西都进入了低功耗状态。应将内存置于自刷新模式以保留其内容。ACPI平台挂起到RAM后在S3中。要进入这种状态,运行shell命令:echo mem > /sys/power/state
  3. 休眠(Hibernate To Disk)的操作类似于挂起内存,但是包括最后一个步骤,即把内存内容写到磁盘。在恢复时,将读取此值并将内存恢复到presuspend状态。通常,ACPI平台挂载到磁盘后处于S4状态。要进入这种状态,运行shell命令:echo disk > /sys/power/state

有关每个系统低功耗状态的详细信息,请参考系统电源管理休眠状态

关于Linux 挂起/休眠的sysfs接口的更多细节,如上面提到的/sys/power/state,请参考https://www.kernel.org/doc/Documentation/power/interface.txt

英特尔开源技术中心(OTC)中国内核电源团队多年来一直致力于Linux电源管理子系统的质量改进工作。我们处理问题的经验,无论是通过电子邮件在Linux邮件列表或通过https://bugzilla.kernel.org/显示如下:

  1. 低效率。这是因为在大多数情况下,开发人员无法在本地重现问题。因此,我们依赖于用户提供问题症状的详细描述、必要的调试信息以及一些测试结果(如果需要的话)。由于时区不同,为进一步调试做好准备可能需要几天甚至几周的时间。
  2. 额外的工作。这是因为Linux STI/STR/HTD是系统级低功耗状态,需要硬件、固件、Linux PM内核和许多其他内核组件(尤其是设备驱动程序)才能很好地协同工作。这意味着任何单个组件中的一个错误都可能破坏系统挂起/休眠。对于由其他组件引起的问题,即使我们找到了根本原因,在大多数情况下,我们仍然需要依赖组件所有者提供适当的修复。

因此,编写这个文档是为了帮助用户做一些调试例程,以便他们可以做以下事情:

  1. 如果可能的话,找出根本原因或缩小问题范围。
  2. 适当地提出问题,提供:
    1. 对问题的精确描述。
    2. 测试必要的调试例程的结果。
    3. 精确的组件和所有者,例如:Linux PM核心,驱动程序,BIOS等。

在本文档中

  1. 第2章介绍了一些可以在第4章中使用的常见调试方法。
  2. 第3章介绍了根据我们对Linux PM维护的经验引入了一些典型的问题,这些问题可以破坏Linux 挂起/休眠。
  3. 第4章根据问题的不同症状给出了详细的步骤说明。

因此,当用户遇到与挂起/休眠相关的问题时,我们建议用户在第4章中找到描述遇到的问题的适当部分,并直接按照该部分的逐步说明进行操作。

  • 通用的挂起/休眠调试方法
  • 这里列出了一些常见的调试方法,对于遇到挂起/休眠相关问题的人非常有用。

  • initcall_debug
  • 在内核命令行cmdline中添加`initcall_debug`启动选项将跟踪启动、挂起和恢复期间的initcalls和驱动程序pm回调。检查指定的驱动程序/组件是否失败是很有用的。确保在调试STI/STR/HTD相关问题时始终启用它。通常,正结果意味着回调没有错误/警告,并在合理的时间内返回0。这在以下方面得到了证明:
    [ 76.201970] calling 0000:00:02.0+ @ 2298, parent: pci0000:00
    [ 76.217006] call 0000:00:02.0+ returned 0 after 14677 usec
    

    负结果意味着回调要么包含一些错误/警告,要么花费不合理的长时间来完成。

  • no_console_suspend
  • 在内核命令行cmdline中添加no_console_suspend启动选项可以在挂起/休眠期间禁用控制台的挂起。添加此选项后,调试消息可以到达各种控制台,而系统的其他部分则处于休眠状态。这可能不能在所有控制台都可靠地工作,但是众所周知,它可以在串口和VGA控制台上工作。

  • ignore_loglevel
  • 在内核命令行cmdline中添加ignore_loglevel启动选项可以将所有内核消息输出到控制台,无论当前的loglevel是什么,这对于调试非常有用。

  • 动态调试
  • 对于STI/STR,添加dyndbg="file .c +p"引导选项,对于HTD,添加file swap.c +p;file snapshot.c +p;file hibernate.c +p引导选项,用这些选项启用动态调试,这样我们可以在休眠过程中收集更多的信息。

  • serial console
  • serial console输出对于跟踪某些系统挂起问题非常有用,尤其是当VGA控制台宕机或无法记录完整的崩溃日志时。要启用serial console,请将console=ttyS0,115200no_console_suspend添加到内核的命令行cmdline,然后在另一台机器上使用minicomGNU screen等程序将串行端口设置为115200 8-N-1以捕获日志。有关更多细节,请参考https://www.kernel.org/doc/Documentation/serial-console.txt

  • RTC跟踪
  • RTC跟踪是一种特性,用于在挂起/恢复期间以文件+行形式捕获损坏的驱动程序,将其保存到CMOS SRAM中,并在下一次引导期间恢复它。要启用这一点,请确保内核是使用CONFIG_PM_TRACE_RTC=y构建的,并且通过echo 0 > /sys/power/pm_async禁用pm异步。更多信息请参考https://www.kernel.org/doc/Documentation/power/s2ram.txt

    测试结果:从STI/STR/HTD失败重启后的dmesg输出。请注意,重新启动必须在三分钟内发生,否则保存在SRAM中的数据将被破坏。

  • pm_async
  • 现在Linux会同时挂起和恢复所有设备,以节省时间。可以使用echo 0 > /sys/power/pm_async来检查挂起/休眠失败是否由未知的设备依赖问题引起。这也可以用来调优驱动程序挂起/恢复延迟。

    测试结果:在禁用异步暂停/恢复后,检查问题是否仍然存在。

  • pm_test
  • pm_test是一种调试方法,系统可以部分地进行STR/HTD调试,它可以用来缩小导致STI/STR/HTD故障的代码的范围。请参考https://www.kernel.org/doc/Documentation/power/basic-pm-debugging.txt获得更多细节。

    测试结果可以检查哪些测试模式有问题,哪些测试模式没有问题。dmesg输出的失效模式,如果可能的话。

  • ACPI唤醒
  • /proc/acpi/wakeup列出了所有ACPI设备的唤醒能力,以及对可用物理设备的引用。如下例所示,戳这个文件可以启用/禁用设备的唤醒功能。例如,你可以通过以下步骤禁用/启用“启用”/“禁用”设备:

    root@linux-machine# cat /proc/acpi/wakeup
    Device  S-state   Status   Sysfs node
    ...
    EHC1      S4    *enabled   pci:0000:00:1d.0
    ...
    root@linux-machine# echo EHC1 > /proc/acpi/wakeup
    root@linux-machine# cat /proc/acpi/wakeup
    Device  S-state   Status   Sysfs node
    ...
    EHC1      S4    *disabled  pci:0000:00:1d.0
    ...
    

    测试结果可以禁用和启用每个设备的唤醒功能可以解决这个问题。

  • acpidump
  • acpidump是一个可以用来转储BIOS提供的ACPI表的工具。这在调试可能由ACPI引起的挂起/休眠问题时非常有用。要获得该工具,只需转到内核源代码的tools/power/acpi/目录,并运行make

  • rtcwake
  • rtcwake是一个工具,可以用来进入系统休眠状态(挂起/休眠),直到指定的唤醒时间。你可以很容易地使用它来执行挂起/休眠压力测试。例如,你可以使用这个简单的脚本轻松运行1000个STR循环:

    for i in $(seq 1000); do
        rtcwake –m mem –s 30
    done
    

  • analyze_suspend
  • analyze_syspend工具为系统开发人员提供了可视化挂起和恢复之间的活动的功能,允许他们识别效率低下和瓶颈。例如,你可以使用以下命令启动:

    ./analyze_suspend.py -rtcwake 30 -f -m mem
    

    30秒后系统自动恢复并在./suspend-yymmddyy-hhmmss目录下生成3个文件:

    mem_dmesg.txt  mem_ftrace.txt  mem.html
    

    您可以首先用浏览器打开mem.html文件,然后深入mem_ftrace.txt查找数据细节。你可以通过git获得analyze_suspend工具:

    git clone https://github.com/01org/suspendresume.git
    

    更多细节,请访问主页:https://01.org/endresume

    测试结果:mem_dmesg.txt mem_ftrace.txt mem.html

  • 磁盘模式
  • 这只适用于HTD。尝试echo {shutdown/reboot} > /sys/power/disk跳过一些平台特定的代码,这些代码在运行echo disk > /sys/power/state之前可能会有bug。在ACPI平台上,系统实际进入的是S5而不是S4。

    测试结果:当/sys/power/disk等于关机或重启时,是否仍然存在问题。

  • 典型的挂起/休眠问题

  • 回归
  • 回归是指挂起/休眠可以正常工作,但是在升级内核之后会失败。在这种情况下,找到根本原因并解决问题的最快速和最有效的方法是使用git bisect找出是哪个提交导致了问题,然后将问题报告给提交作者/组件所有者。

  • 驱动程序特定问题
  • 驱动损坏可能导致:

    1. 系统无法进入suspend (suspend命令返回错误代码)。
    2. 在挂起/恢复期间挂起的系统。
    3. 系统需要不合理的长时间挂起/恢复。
    4. 暂停/恢复后在dmesg中生成的调试跟踪/错误日志。
    5. 驱动本身无法工作。

    因此,当您遇到与挂起/休眠相关的问题时,通常首先要检查这个问题是否是驱动程序特有的问题。如果在恢复/挂起失败后系统是响应的(可以通过VGA/serial console、ssh等访问系统),尝试使用启动选项initcall_debugno_console_suspend重现问题。如果有,做以下事情:

    1. 将驱动程序构建为模块
    2. 卸载驱动程序
    3. 挂起并恢复机器
    4. 使用以下命令重新加载驱动程序:modprobe -r foo && echo mem > /sys/power/state & modprobe foo
    5. 如果驱动程序是不可加载的,那么根本不要通过Kconfig设置构建驱动程序。
    6. 如果问题不能重现,并且错误/警告在此之后没有重新出现,那么这是一个驱动程序特定的问题。

    如果系统不响应,请执行以下操作:

    1. 启用serial console并使用initcall_debugno_console_suspend引导。
    2. 检查serial console输出是否有驱动程序引起的错误/警告。
    3. 如果发现任何错误/警告,将驱动程序构建为模块。
    4. 卸载驱动程序。
    5. 如果驱动程序无法在运行时卸载,则完全不要通过Kconfig设置构建驱动程序。
    6. 挂起并恢复机器。
    7. 重新加载驱动程序:modprobe -r foo && echo mem > /sys/power/state & modprobe foo
    8. 如果问题不能重现,并且在这些步骤之后错误/警告没有重新出现,那么驱动程序就会导致问题。

    如果系统在恢复后运行良好,但是一些驱动程序不再工作,请执行以下操作:

    1. 将驱动程序构建为模块。
    2. 卸载驱动程序
    3. 挂起并恢复机器
    4. 重新加载驱动程序:modprobe -r foo && echo mem > /sys/power/state & modprobe foo

    如果问题是不可重现的,这是一个驱动特有的问题。

    对于这些问题,直接向驱动程序/组件所有者提交错误报告,因为这些问题表明有问题的驱动程序/组件破坏了Linux 挂起/休眠。

    下面几节将讨论一些典型的驱动程序特有的问题,这些问题可能会导致挂起/休眠中断。

  • 图形问题
  • 当系统在挂起/休眠期间挂起或恢复后监视器不再显示时,请尝试以下步骤:

    1. 禁用您正在使用的内核图形驱动程序。在Intel平台上,设置CONFIG_DRM_I915=n
    2. 注意:不要在命令行中使用nomodeset modprobe.blacklist=i915,因为有时i915驱动程序可能会被其他组件探测。
    3. 尝试再次暂停/恢复。
    4. 如果在恢复后看到黑屏,没有图像,请尝试通过串口控制台或SSH访问系统。如果系统仍在工作,这是一个图形问题。
    5. 如果serial console和SSH都不可用,请按PS2键盘上的Num Lock。如果Num Lock LED在您按下键时亮起,则使用键盘输入reboot。虽然监视器没有显示,但是如果系统重新启动成功,那么很可能系统工作正常,这是一个图形问题。

    对于图形问题,请在https://bugs.freedesktop.org/中提交一个错误。例如,我们从dmesg输出的下面几行代码中得到提示,将挂起/休眠问题的根本原因隔离到图形驱动程序中:

    [  218.176542] ------------[ cut here ]------------
    [  218.176550] WARNING: CPU: 1 PID: 221 at drivers/gpu/drm/i915/intel_lrc.c:1100 gen8_init_rcs_context+0x15d/0x160()
    [  218.176552] WARN_ON(w->count == 0)
    [  218.176553] Modules linked in:
    [  218.176556] CPU: 1 PID: 221 Comm: kworker/u16:3 Tainted: G        W      3.18.0-eywa-dirty #30
    [  218.176558] Hardware name: Intel Corporation Skylake Client platform/Skylake Y LPDDR3 RVP3, BIOS SKLSE2P1.86C.B060.R01.1411140050 11/14/2014
    [  218.176564] Workqueue: events_unbound async_run_entry_fn
    [  218.176567]  0000000000000009 ffff88009a96fad8 ffffffff82431683 0000000000000001
    [  218.176570]  ffff88009a96fb28 ffff88009a96fb18 ffffffff8111e001 00000000fffe6b2d
    [  218.176573]  ffff88009b42f300 0000000000000005 ffff88009b670000 ffff88009b671ce8
    [  218.176574] Call Trace:
    ...
    [  218.176657] ---[ end trace f587fddb962240b1 ]---
    

  • 声音问题
  • 我们还没有看到音频驱动程序暂停/休眠,但音频驱动程序本身可能需要很长时间才能恢复,如下面的例子所示:

    [   61.273112] calling  hdaudioC0D2+ @ 1236, parent: 0000:00:1f.3
    [   64.295757] snd_hda_intel 0000:00:1f.3: azx_get_response timeout, switching to polling mode: last cmd=0x208f8100
    [   65.303916] snd_hda_intel 0000:00:1f.3: No response from codec, disabling MSI: last cmd=0x208f8100
    [   66.312147] snd_hda_intel 0000:00:1f.3: azx_get_response timeout, switching to single_cmd mode: last cmd=0x208f8100
    [   66.312402] azx_single_wait_for_response: 153 callbacks suppressed
    [   66.323406] snd_hda_codec_hdmi hdaudioC0D2: Unable to sync register 0x2f0d00. -5
    [   66.323791] snd_hda_codec_hdmi hdaudioC0D2: HDMI: invalid ELD buf size -1
    [   66.324179] snd_hda_codec_hdmi hdaudioC0D2: HDMI: invalid ELD buf size -1
    [   66.324565] snd_hda_codec_hdmi hdaudioC0D2: HDMI: invalid ELD buf size -1
    [   66.324571] call hdaudioC0D2+ returned 0 after 4932188 usecs
    

    在这种情况下,提交一个音频错误

  • USB问题
  • 通常,USB问题不会挂起系统,但一些USB设备可能会停止工作后,系统恢复。如果一些USB设备(如USB鼠标/键盘/网络)在恢复系统后停止工作,而其他设备(如PS/2键盘)似乎可以正常工作,请检查dmesg/串行日志中的USB警告。如果您发现一个警告,这可能是一个USB问题。例如:

    [ 21.203077] xhci-hcd: probe of xhci-hcd.1.auto failed with error -110
    

    在这种情况下,提交USB错误

  • MMC问题
  • MMC失败会导致挂起操作(特别是挂起磁盘)失败。在挂起期间,检查dmesg和serial console输出中的MMC警告。例如:

    [90.373541] mmc1: Timeout waiting for hardware interrupt.
    [160.493633] mmc1: error -110 during resume (card was removed?)
    

    如果rootfs不在MMC上,请再次尝试使用modprobe.blacklist = mmc_block启动选项。如果rootfs在MMC上,请使用具有相同内核/发行版的USB驱动器,然后再次尝试使用modprobe.blacklist = mmc_block。如果在将MMC驱动程序列入黑名单后问题已经解决,则提交MMC错误

  • i2c问题
  • 我们也曾看到若干个由I2C引发的错误,比如:

    calling i2c_designware.0+ @ 2867, parent: 0000:00:15.0
    ------------[ cut here ]------------
    WARNING: CPU: 1 PID: 2867 at drivers/clk/clk.c:845 __clk_disable+0x5b/0x60()
    [<ffffffff810751fa>] warn_slowpath_null+0x1a/0x20
    [<ffffffff81623a5b>] __clk_disable+0x5b/0x60
    [<ffffffff81624267>] clk_disable+0x37/0x50
    [<ffffffffa03a9029>] dw_i2c_suspend+0x29/0x40
    

    在这种情况下用文件记录I2C错误并提交。

  • 调试挂起/休眠问题

  • State不可用

  • 症状
  • 运行cat /sys/power/state,输出未列举freeze/mem/disk,从而系统不能进入STI/STR/HTD状态。

  • 诊断步骤
    1. 对于STI
      1. 检查内核是否使用CONFIG_SUSPEND构建。如果否就设置它。
      2. 检查内核是否使用参数relative_sleep_states=1引导。如果有就移除它。
    2. 对于STR
      1. 检查内核是否使用CONFIG_SUSPEND构建。如果否就设置它。
      2. 引导后检查dmesg输出,寻找有这样关键字的行ACPI: (supports S0 S3 S4 S5)。如果S3没有列出,它通常意味着平台不支持S3。如果你不确定,保存一个挂起/休眠的缺陷到文件。
    3. 对于HTD
      1. 检查内核是否使用CONFIG_HIBERNATION构建。如果否就用CONFIG_HIBERNATION=y重新构建。
      2. 引导后检查dmesg输出,寻找有这样关键字的行ACPI: (supports S0 S3 S4 S5)。如果S4没有列出,它通常意味着平台不支持S4。如果你不确定,保存一个挂起/休眠的缺陷到文件。
    4. 如果上面的测试后问题仍然存在,保存[platform] {STI, STR, HTD}: state not available,,连同dmesg输出,acpidump输出和你正在使用的内核配置文件为挂起/休眠缺陷并提交。

  • 休眠/挂起失败

  • 症状
  • 运行: echo freeze/mem/disk > /sys/power/state,返回有错误代码。

  • 诊断步骤
    1. 检查这是否是回归。
    2. 如果没有,启用initcall_debugno_console_suspend
    3. 如果您发现一些设备回调返回错误代码而不是0,这意味着设备不能被挂起,这将导致系统挂起/休眠产生。在这种情况下,将错误提交给驱动程序/组件所有者。
    4. 如果不是,文件保存挂起/休眠缺陷到[Platform] {STI, STR, HTD}: failed to suspend,连同挂起失败后dmesg输出和返回的错误代码。

  • 在挂起/休眠或者恢复期间系统宕机

  • 症状
  • 在运行echo freeze/mem/disk > /sys/power/state之后,屏幕变黑,就像挂起/休眠成功一样,但是系统变得没有响应,无法唤醒到工作状态。这可能在挂起或恢复期间发生。或者,系统进入预期的睡眠状态,但是在恢复时没有响应,只显示黑屏或崩溃日志。例如,风扇开始旋转,任何现有的电源按钮背光改变,但系统永远不会回到工作状态。

  • 诊断步骤
    1. 检查这是否是回归。
    2. 如果不是,请检查这是否是图形问题。
    3. 如果不是,检查这是否是驱动程序特定的问题。
    4. 如果问题仍然存在,文件保存挂起/休眠缺陷[Platform] {STI, STR, HTD}: system hangs during suspend/resume,以及问题重现后的串口控制台输出/截图,/var/log/kern.log内容,以及磁盘模式(仅限HTD)、pm_testpm_asyncrtc跟踪的测试结果。

  • 在挂起或者恢复期间系统重启

  • 症状
  • 屏幕变暗,重新启动,用户什么都不做。

  • 诊断步骤
    1. 检查这是否是回归。
    2. 如果不是,请检查这是否是图形问题。
    3. 如果不是,检查这是否是驱动程序特定的问题。
    4. 如果问题仍然存在,文件保存挂起/休眠缺陷{STI, STR, HTD}: system reboot during suspend/resume,以及问题重现后的串口控制台输出/截图,/var/log/kern.log内容,以及磁盘模式(仅限HTD)、pm_testpm_asyncrtc跟踪的测试结果。

  • 恢复后系统错误

  • 症状
  • 系统可以挂起并从STI/STR/HTD状态恢复,但是某些组件或驱动程序可能无法工作,或者在恢复后的dmesg中可能出现错误或警告。

  • 诊断步骤
    1. 检查这是否是回归。
    2. 如果不是,检查这是否是驱动程序特定的问题。
    3. 如果问题仍然存在,文件保存挂起/休眠缺陷[Platform] {STI, STR, HTD}: xxx broken after resume,以及恢复后dmesg输出。

  • 挂起后系统立即唤醒了

  • 症状
  • 挂起后系统很快就会回到工作状态,而用户不做任何事情。dmesg显示系统已经完成了一个完整的挂起/恢复周期。

  • 诊断步骤
    1. 检查这是否是回归。
    2. 检查这是否是一个驱动程序特定的问题,删除任何不必要的外设,如USB驱动器,网线等。

  • 系统无法唤醒

  • 症状
  • 当您按下电源键、使用键盘等时,系统不会自动唤醒。

  • 诊断步骤
    1. 检查这是否是回归。对于STI,检查驱动程序是否有自己的托管中断。如果是,检查enable_irq_wake()是否为驱动程序使用的中断调用。如果不是,将其添加到驱动程序.probe().suspend()回调中。例如:

      static int bu21013_suspend(struct device *dev)
      {
          ...
          if (device_may_wakeup(&client->dev))
              enable_irq_wake(bu21013_data->irq);
          else
              disable_irq(bu21013_data->irq);
          ...
          return 0;
      }
      
    2. 如果问题仍然存在,文件保存挂起/休眠缺陷[Platform] {STI, STR, HTD}: XXX cannot wake up the system,确保包含dmesg、acpidump输出、ACPI唤醒的测试结果以及缺陷报告。

  • 系统挂起/恢复超时

  • 症状
  • 总的/特定的驱动程序挂起/恢复时间比预期/要求的长。

  • 诊断步骤
  • 你已经确定了一个特定的驱动程序,需要花太多的时间暂停/恢复,文件错误的驱动程序所有者。

    如果总体挂起/恢复延迟很高,请执行以下操作:

    1. 使用initcall_debugno_console_suspend启动选项重新启动。
    2. 禁用异步挂起/恢复。
    3. 使用analyze_suspend重做挂起/恢复。
    4. 检查测试结果,找出哪些驱动程序和组件在挂起/恢复期间花费的时间最多,并将问题连同analyze_suspend的测试结果一起报告给驱动程序/组件所有者。

  • 挂起/休眠偶尔失败

  • 症状
  • 症状可以是上面列出的任何症状,但区别是这些问题不是100%可重复的。有时可能需要数十甚至数百个挂起/休眠周期才能进行复制。

  • 诊断步骤
    1. 对于HTD,
      1. 检查用于交换分区的驱动程序。
      2. 如果驱动程序是作为模块构建的,则将驱动程序构建进来并检查问题是否仍然存在。
      3. 如果没有,文件保存[Platform] HTD: hibernate fails when disk driver is built as module
      4. 如果是,请按照以下步骤继续调试。
    2. 使用initcall_debugno_console_suspendignore_loglevel、动态调试和serial console重新启动。
    3. 使用rtcwake进行压力测试。
    4. 按照上面一节中的说明,当挂起/hibernate无法验证和提出问题时,可以很好地描述症状。

  • 提出争论点
  • 如果您已经缩小了问题的范围,或者发现问题的根本原因是驱动程序特定的问题:

    1. 如果您知道在哪里为指定的驱动程序提出问题,则直接在那里提出。例如,对于图形问题,在https://bugs.freedesktop.org/中提交一个错误报告。
    2. 如果您不清楚在哪里提出问题,最好的方法是在检查了http://vger.kernel.org/vger-lists.html之后订阅子系统/驱动程序邮件列表并在那里提出问题,在提出问题时请执行CC [email protected]

    如果不是驱动程序特定的问题,或者在调试之后您不确定问题是什么,可以在https://bugzilla.kernel.org/enter_bug.cgi?product=Power%20Management,将组件设置为“休眠/挂起”,幷包含所需的调试信息和测试结果。

    發表評論
    所有評論
    還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
    相關文章