在 Windows 上获取运行中的 PostgreSQL 后端的堆栈跟踪
开始之前
有一些事情你需要知道才能获得有用的堆栈跟踪
- 要获得有用的堆栈跟踪,您的符号路径必须配置为包含要调试的程序的调试符号(.pdb)文件。请参阅下面有关符号路径的部分。
- 似乎在 64 位 Windows 上调试 32 位 PostgreSQL 会产生无用的结果,至少在使用 64 位调试工具时是这样的。
有关更多信息,请参阅下面有关如何验证您是否拥有有用堆栈跟踪的部分。
获取堆栈跟踪的方法
在 Windows 上,有几种不同的方法可以获取在执行操作时崩溃的后端的堆栈跟踪,并且有几种不同的工具可以实现这一点。
方法:
- 获取运行中的后端的进程 ID,将调试器附加到它,并触发崩溃;
- 将调试器设置为事后调试器并触发崩溃;或者
- 启用崩溃转储,触发崩溃,并调试生成的崩溃转储。
直接附加可能更容易,但可能干扰生产操作。它也不总是可行,特别是在崩溃随机且难以重现时。如果进程卡住了(100% CPU 占用,或者只是没有响应并且没有进展),您不能使用崩溃转储。您必须直接附加调试器。
设置事后调试器可以很好地工作,但它是一种侵入性的系统范围更改。(使用 drwatson.exe 恢复到默认的崩溃处理程序)。并非所有 Visual Studio 版本都支持用作事后调试器,并且如果 PostgreSQL 作为独立用户帐户中的服务运行,它将无法正常工作。
崩溃转储(小型转储)对于间歇性、难以重现的崩溃非常有效。如果您需要将可调试的崩溃转储发送给其他人进行检查,也可以使用崩溃转储。与直接附加不同,崩溃转储受 Visual Studio 的免费 Express 版本支持。不要只是将崩溃转储发送给某人并假设他们可以做些什么;您通常应该提取并发送堆栈跟踪。只有在被要求时才发送崩溃转储。
工具:
- Microsoft Visual Studio Professional - 支持崩溃转储和直接附加,非常容易,但收费
- Microsoft Visual Studio Express 2010 - 在启用“专家设置”后支持崩溃转储和直接附加
- Microsoft Visual Studio Express 2012 for Windows Desktop - 支持崩溃转储和直接附加,非常容易
Process Explorer
- 仅用于获取后端的状态而不中断它,用于跟踪明显的无限循环等。- 来自 Windows 调试工具的
windbg
- 支持崩溃转储和直接附加,但使用起来不如用户友好
如果您有 Microsoft Visual Studio,请使用它。这是最简单、最好的选择。
如果您不能使用 Visual Studio,请使用 Process Explorer 获取运行中进程的堆栈跟踪。对于所有其他工作,请安装 Visual Studio。windbg 不是适合普通人类使用的工具。
安装 Windows 调试工具和 Process Explorer
Microsoft Windows 没有内置的调试工具。您需要安装 Windows 调试工具。这些不再作为独立软件包提供,您必须使用Windows SDK 安装程序安装它们。如果您愿意,可以禁用除了调试工具之外的所有内容的安装。另请参阅此处。
安装 Windows 调试工具后,您需要进行一些设置,以便获得有用的堆栈跟踪。
注意:下面的说明假设您的 Windows 副本安装在 c:
上,因此如果情况并非如此,您可能需要调整它们。
您可能还希望从 Sysinternal 套件中获取Process Explorer。Sysinternals 很有用,Process Explorer 尤其如此。Process Explorer 可用于获取堆栈跟踪,显示正在运行的 PostgreSQL 进程状态的快照,以便在跟踪明显的“卡住”查询时使用。
配置符号路径
首先,您需要设置符号路径。首先,您必须弄清楚它是什么,因为它取决于您安装的 PostgreSQL 版本、您的 Windows 版本的语言以及 PostgreSQL 安装的位置。您需要找到的是 PostgreSQL 安装目录中的 symbols
文件夹的位置。如果您使用的是 PostgreSQL 9.4 的 EnterpriseDB 一键安装程序,并且您使用的是英文版本的 Windows,那么它将是
C:\Program Files\PostgreSQL\9.4\symbols
在以下说明中,我将假设情况如此,因此如果需要,请调整显示的路径以匹配您的系统。
要设置符号路径,您需要使用系统控制面板。您可以通过按住 Windows 键并按下 BREAK 键(通常位于暂停键下方)来快速方便地访问所有版本的 Windows。您也可以在控制面板中找到它;您可能需要搜索“系统”。如果您找不到它,请打开开始菜单并单击“运行”,然后输入“sysdm.cpl”并按 Enter 键。
在系统控制面板中,单击高级选项卡,然后单击“环境变量”。将出现“环境变量”对话框。在该对话框中,在“用户变量 for <username>”标签下,您会看到一个列表。您需要做的是单击该列表下的“新建”按钮。
在出现的对话框中,将变量名准确地输入为
_NT_SYMBOL_PATH
请注意前导下划线(“_”)以及变量名全部大写并用下划线分隔单词的事实。
该值分为两部分。第一部分取决于 PostgreSQL 的安装位置,如上所述。第二部分告诉调试工具使用 Microsoft 服务器(符号服务器)来了解 Windows 自身的各个部分。
将值设置为如下,将 PostgreSQL symbols
目录的路径更改为匹配您的 PostgreSQL 安装位置和版本。如有疑问,请查看 Program Files 并检查。如果您设置错误,不会出现错误,您只会得到无用的回溯。
示例值,根据您的系统进行调整
C:\Program Files\PostgreSQL\9.4\symbols;SRV*c:\localsymbols*http://msdl.microsoft.com/download/symbols;
如果您没有活动的互联网连接,您可以下载您版本的 Windows 的 Windows 符号包并安装它,但请注意这将是一个很大的下载。然后您需要调整上面显示的符号路径以指向它。
获取堆栈跟踪
呼。您现在已经配置好一切,可以开始收集信息了。
重要提示:不要浪费时间生成无用的堆栈跟踪。在花费大量时间收集信息或发布到邮件列表之前,请阅读“如何确保堆栈跟踪有用”。您的堆栈跟踪需要包含“!SymbolicNames”以及“+offsets”,以便发挥很大作用。
如果您正在收集有关似乎处于无限循环或使用过高 CPU 的 postgresql 后端的信息,您需要获取多个堆栈跟踪。您应该尝试在短时间内获取很多堆栈跟踪,以便开发人员能够了解它在哪里花费了时间。仅一个堆栈跟踪并不能真正告诉您它在总体上做了什么。
获取 PostgreSQL 后端的进程 ID
识别您感兴趣的 PostgreSQL 进程的进程 ID 并不总是容易。如果您对要查看的后端有活动连接,则可以运行 select pg_backend_pid();
来获取它的进程 ID。有时很明显 - 它是 Process Explorer 中显示的始终使用大量 CPU 的那个,例如。但有时,您需要使用 psql 或 PgAdmin III 查看 pg_catalog.pg_stat_activity
或 pg_catalog.pg_locks
表/视图来找出您感兴趣的进程的进程 ID(“pid”或“procpid”)。
使用 Microsoft Visual Studio 或 VS Express 进行调试
Windbg 是免费的,快速且易于安装,支持远程调试,并且非常难以使用。如果您想做的不仅仅是获取堆栈跟踪,您会想要使用更人性化的工具 - 并且对于某些问题,您确实需要不止一个堆栈跟踪才能知道哪里出了问题。如果您打算逐步执行 PostgreSQL 代码并检查变量的值,使用 Microsoft Visual C++ 会更容易。
Microsoft Visual C++ 拥有一个非常好的调试器,我强烈建议您使用它而不是 windbg.exe - 如果您有的话。
免费的 Visual Studio 2008 Express 版可用于许多调试任务;它缺少远程调试支持和即时事后调试(完整付费版拥有),但除此之外,它作为一个用户友好的调试器做得很好。
Visual Studio Express 2010 默认情况下隐藏了关键功能,例如附加到正在运行的进程的能力。选择工具 -> 设置 -> 专家设置,以使该功能和其他功能在通常的菜单中可见。 Visual Studio Express 2012 for Windows Desktop 不需要这种配置更改。
附加
安装并配置 VC++ 后,启动它,并使用工具菜单中的“附加到进程”连接到要调试的 PostgreSQL 后端。确保选中“显示所有用户的进程”,选择要调试的 postgres.exe
进程,然后单击“附加”。您将收到一个安全警告,您应该在接受之前阅读并理解它。
VC++ 将为 PostgreSQL 加载符号,如果之前没有使用 VC++ 调试 PostgreSQL,这可能需要几分钟(它将不得不从符号服务器获取符号)。一旦 VC++ 完成符号加载,Pg 后端的执行将恢复,您的数据库系统将正常工作。
中断执行
您现在可以使用工具栏上的断点按钮中断 postgres.exe
后端的执行。它看起来像一个暂停按钮。如果您正在调试一个失控的后端,您现在需要中断它的执行。或者,如果您正在调试崩溃,您现在应该触发崩溃。
如果 VC++ 检测到崩溃,它将显示一个类似于此的对话框,让您有机会中断进程的执行
如果您在进程在内核中运行时中断其执行,您将看到类似于此的对话框
... 这对于 PostgreSQL 来说很常见,因为它通常会等待网络或磁盘活动。您通常可以忽略它。
开始调试
您将收到一个对话框,告诉您“当前位置没有可用的源代码”。单击确定。现在,在右下角(默认情况下),您将看到一组名为“调用堆栈”、“断点”和“输出”的选项卡。单击“调用堆栈”。
您可以看到在您中断 postgresql 时,Pg 主线程正在进行的函数调用堆栈。(如果您想或需要更改线程,请使用左下角的“线程”选项卡 - 双击要调试的线程 - 尽管您通常需要默认情况下处于活动状态的主线程)。
在调用堆栈中,您可以双击一个条目以查看函数调用在可执行文件中的位置。如果您没有源代码,它将提供显示反汇编的选项,但是您有 PostgreSQL 的源代码,所以您可以做得更好。(如果您没有 Pg 的源代码,请立即下载并解压缩您版本的源代码。如果您无法打开 .tar.gz 文件,请安装 [www.7-zip.org/|7-zip])。双击堆栈跟踪中最上面的以“postgres.exe”开头的行,将出现一个对话框,要求您提供一个 .c 文件
它将在对话框的顶部标记文件的原始位置。在我的情况下,它需要 src\backend\port\win32\socket.c
中的 socket.c
,所以我在 postgresql 源代码(在我的机器上:c:\developer\postgresql-8.4.0
)中找到它,然后打开 src
,然后 backend
,然后 port
,然后 win32
。Visual Studio 在目录中注意到“win32.c”并自动打开它。从现在开始,在这个调试会话中,VS 应该也会找到所有其他 postgresql 源文件。
调试技巧
Visual Studio 调试器功能强大,无法在此处完全描述。我只给出一些提示 - 并建议您使用文档。
现在您已经显示了源代码,您可以逐行单步执行代码,运行到函数结束,单步进入函数,并使用工具栏上的按钮详细控制 PostgreSQL 进程的执行。您还可以使用左侧的“局部变量”选项卡检查您所在的堆栈帧的局部变量,使用右侧的“断点”选项卡在函数中设置断点,等等。
例如,如果我想在下一次 PostgreSQL 尝试从网络接收数据时暂停执行,我可以设置一个断点到 pgwin32_recv
函数,并恢复执行
如果我在要调试的后端上发出查询,Visual Studio 很快就会在 pgwin32_recv
函数开始处中断回调试器。我现在可以单步执行函数的执行,以查看调用了哪些函数,变量取了什么值,等等。我可以通过单击源文件显示左侧边距中的位置来添加和删除断点。
请注意,您必须小心不要更改任何变量的值,或者使用“设置下一条语句”选项导致执行跳过行。如果您不小心,您可能会使服务器崩溃,或者可能严重损坏您的数据库。
使用 windbg
使用崩溃转储调试随机和不可预测的后端崩溃
如果崩溃似乎是随机的,并且您不知道如何触发它们,则很难在 postgres.exe
崩溃之前将其调试器连接到问题。
将您的调试器设置为 JIT(即时)或事后调试器不会帮助您,因为 PostgreSQL 通常作为不同用户帐户下的服务运行,该帐户无法与桌面交互。您可以始终 initdb
新集群在您的普通用户帐户下,并使用 pg_ctl
手动启动 postmaster 与该集群,以便您可以在您自己的用户帐户下进行 JIT 调试,在该帐户下 Pg 可以与桌面交互。但是,这并不适合生产使用,您可能无法以这种方式重现问题。
在 PostgreSQL 9.0 及更高版本中,PostgreSQL 中包含一个 崩溃转储处理程序。要使用它
- 在 PostgreSQL 数据目录(如
psql
中的SHOW data_directory;
所示)中创建一个名为crashdumps
(全部小写)的目录。 - 在文件夹属性的安全选项卡中,为 PostgreSQL 用户(默认情况下为
postgres
)授予“完全控制”权限。 - 运行有问题的代码。您无需重新启动 Pg 或更改任何设置。
- 当后端崩溃时,Windows 小型转储将被创建在
crashdumps
目录中。
您可以使用 Microsoft Visual Studio 或 windbg 调试此文件,使用与上面解释的附加到进程 ID 时相同的步骤。不幸的是,最近的 Visual Studio Express 版无法调试小型转储。
小型转储也可以发送给其他人进行调试。为了使这能够工作,您必须告诉对方您正在使用的 *确切* PostgreSQL 版本,因为调试符号会因次要版本而异。您无法使用从 9.1.1 安装中获取的调试符号来调试 9.1.0 小型转储。通常最简单的方法是将您的安装中的 .pdb
文件与小型转储一起发送给他们。
调试 postmaster 启动期间的崩溃
使用 Process Explorer 查看正在运行的后端正在做什么
Process Explorer 仅用于从正在运行的后端获取堆栈跟踪。它不适用于调试崩溃。
如果您打算使用 Process Explorer 获取正在运行(未崩溃)后端的基本堆栈跟踪,您需要先应用一些设置。如果您要使用 Visual Studio(建议您拥有它)或 windbg
来自 Windows 调试工具,您可以跳过本节。
初始 Process Explorer 配置
从开始菜单打开 Process Exploer,在开始 -> 运行对话框中使用“procexp.exe”。如果出现提示,请阅读并接受许可协议。
现在,在“选项”菜单中选择“配置符号”。首先,注意“符号路径”条目,它应该与您在系统控制面板中为 _NT_SYMBOL_PATH 输入的值相同。如果不是,您在系统控制面板中设置符号路径的环境变量时就犯了一个错误。
“配置符号”对话框中还有一个名为“dbghelp.dll 路径”的条目。如果它以“c:\windows”开头,您需要将其更改为 Windows 调试工具提供的替代路径。确切的位置因您的调试工具版本而异。使用 "..." 按钮,在“文件名”字段中键入 %ProgramFiles%
并按 Enter。现在找到 Windows 调试工具文件夹,打开它,并找到 dbghelp.dll。双击它。dbghelp.dll 路径应该已经更改为类似于
C:\Program Files\Debugging Tools for Windows (x86)\dbghelp.dll
您现在可以单击“确定”。
检查您的 Process Explorer 设置
您现在应该能够查看在 Windows 系统上预安装的常见进程的堆栈跟踪。我建议启动记事本(开始 -> 运行 -> notepad.exe),然后启动 Process Explorer,双击进程列表中的“notepad.exe”,切换到“线程”选项卡,然后双击列表中显示的唯一条目。显示的堆栈跟踪应类似于此(至少在 Windows XP SP3 上)
ntkrnlpa.exe!KiSwapThread+0x46 ntkrnlpa.exe!KeWaitForSingleObject+0x1c2 win32k.sys!xxxSleepThread+0x192 win32k.sys!xxxRealInternalGetMessage+0x418 win32k.sys!NtUserGetMessage+0x27 ntkrnlpa.exe!KiFastCallEntry+0xf8 ntdll.dll!KiFastSystemCallRet USER32.dll!NtUserGetMessage+0xc notepad.exe!WinMain+0xe5 notepad.exe!WinMainCRTStartup+0x174 kernel32.dll!BaseProcessStart+0x23
如果它看起来像这样(很多行只有“模块名+偏移量”而不是“模块名!符号名+偏移量”)
ntkrnlpa.exe+0x6a822 ntkrnlpa.exe!ZwYieldExecution+0x1938 ntkrnlpa.exe!ZwYieldExecution+0x19a4 win32k.sys+0x2f35 win32k.sys+0x1b0d win32k.sys!EngQueryPerformanceCounter+0x5af ntkrnlpa.exe!KeReleaseInStackQueuedSpinLockFromDpcLevel+0xb14 ntdll.dll!KiFastSystemCallRet notepad.exe+0x2a1b notepad.exe+0x7511 kernel32.dll!RegisterWaitForInputIdle+0x49
... 那么您的符号路径设置错误,或者某些东西干扰了对符号服务器的访问。见 #没有符号名?为什么?.
您也可能会看到类似这样的回溯
00000000`0013e6b8 00000000`73d5282c wow64cpu!CpupSyscallStub+0x9 00000000`0013e6c0 00000000`73dcd07e wow64cpu!WaitForMultipleObjects32+0x32 00000000`0013e780 00000000`73dcc549 wow64!RunCpuSimulation+0xa 00000000`0013e7d0 00000000`779884c8 wow64!Wow64LdrpInitialize+0x429 00000000`0013ed20 00000000`77987623 ntdll!LdrpInitializeProcess+0x17e2 00000000`0013f220 00000000`7797308e ntdll! ?? ::FNODOBFM::`string'+0x2bea0 00000000`0013f290 00000000`00000000 ntdll!LdrInitializeThunk+0xe
带有关于 wow64 的行。此回溯对调试毫无用处。您正在使用 64 位调试工具在 64 位 Windows 上调试 32 位二进制文件。您可能能够通过安装和使用 32 位调试工具来获得有用的跟踪,但我没有验证过这一点。
使用 Process Explorer 获取堆栈跟踪
从开始菜单启动 Process Explorer,或通过运行“procexp.exe”。
您需要找到您感兴趣的 postgres.exe
实例。它可能是一个始终显示高 CPU 使用率的实例,但如果不是,请参阅上面的说明,通过进程 ID 找到它。
一旦您知道要检查哪个 postgres.exe
后端,请双击 Process Explorer 列表中的它。在出现的对话框中打开“线程”选项卡。您现在将看到一个线程列表。在大多数情况下,您感兴趣的是列表中的第一个线程。通过单击它来选择它,然后单击“堆栈”按钮。短暂延迟后,将出现一个新对话框中的列表,看起来像这样
ntkrnlpa.exe!KiSwapContext+0x2f ntkrnlpa.exe!KiSwapThread+0x8a ntkrnlpa.exe!KeWaitForSingleObject+0x1c2 ntkrnlpa.exe!KiSuspendThread+0x18 ntkrnlpa.exe!KiDeliverApc+0x124 ntkrnlpa.exe!KiSwapThread+0xa8 ntkrnlpa.exe!KeWaitForMultipleObjects+0x284 ntkrnlpa.exe!NtWaitForMultipleObjects+0x297 ntkrnlpa.exe!KiFastCallEntry+0xfc ntdll.dll!KiFastSystemCallRet ntdll.dll!ZwWaitForMultipleObjects+0xc kernel32.dll!WaitForMultipleObjectsEx+0x12c postgres.exe!pgwin32_waitforsinglesocket+0x1f0 postgres.exe!pgwin32_recv+0x90 postgres.exe!secure_read+0x17d postgres.exe!pq_recvbuf+0x71 postgres.exe!pq_getbyte+0x15 postgres.exe!SocketBackend+0x6 postgres.exe!PostgresMain+0xbe8 postgres.exe!BackendRun+0x204 postgres.exe!SubPostmasterMain+0x224 postgres.exe!main+0x177 postgres.exe!__tmainCRTStartup+0x10f kernel32.dll!BaseProcessStart+0x23
请注意感叹号后面的每行都有不同的文本?如果不是这种情况,您的符号路径可能设置错误,Process Explorer 无法弄清楚程序正在做什么。没有感叹号后面的文本,堆栈跟踪基本上是无用的,因此您需要收集这些信息。
您需要复制整个列表。单击第一个条目,按住 shift 键,然后按“end”键(或滚动到底部并单击最后一个条目),然后单击“复制”按钮。
现在,您可以将堆栈跟踪粘贴到您的电子邮件程序中,并将其发送到 PostgreSQL 邮件列表。它本身并不有用,因此您 **必须** 还提供通常的调试信息——您的操作系统版本、PostgreSQL 版本、出现的问题、出现问题时您正在做什么,等等。参见 报告问题的指南。
如何确保堆栈跟踪有用
堆栈跟踪并不总是可靠或有用的。尤其是在 Windows 上,程序通常不包含调试信息,如果没有调试信息,就无法可靠地创建正确的堆栈跟踪。
如果您要在错误报告或帮助请求中发布堆栈跟踪,那么它 **非常重要** 它包含关于调用路径的符号信息,例如上面发布的示例。如果它只包含模块名称和模块中的数字偏移量,那么它对读者没有用,除非他们 **完全** 了解您拥有的模块版本。即使那样,它也很难阅读且很麻烦。因此,请确保您的堆栈跟踪包含显示 模块名!符号名称+偏移量
的行,像这样
ntkrnlpa.exe!KiSwapThread+0x8a
不要只是 模块名+偏移量 这样:
ntkrnlpa.exe+0x8dafe
以这个跟踪为例。
ntkrnlpa.exe+0x8dafe ntkrnlpa.exe+0x29a82 ntkrnlpa.exe+0x33198 hal.dll+0x6199 hal.dll+0x63d9 hal.dll+0x6577 hal.dll+0x3902 postgres.exe!process_implied_equality+0x18d50e
来自 backend/optimizer/plan/initsplan.c
的 C 函数 process_implied_equality(...)
位于查询规划器的深处,并且(根据注释)“目前仅在发现等价类包含伪常数时使用”。它不会调用 hal.dll
- 至少在它和 hal.dll
之间没有相当长的调用链的情况下是不会的。因此,我们无法信任 postgres.exe
在调用 hal.dll 时是否真的位于 process_implied_equality
函数中,即使它在那里,我们也不知道它是怎么到那里的。调用跟踪基本上是无用的。
将其与本文前面显示的示例堆栈跟踪进行比较,我们可以清楚地看到 PostgreSQL 通过其来自 pg_getbyte 函数的 win32 包装层调用了 WaitForMultipleObjectsEx
(以等待数据传递到套接字)... 等等。
... kernel32.dll!WaitForMultipleObjectsEx+0x12c postgres.exe!pgwin32_waitforsinglesocket+0x1f0 postgres.exe!pgwin32_recv+0x90 postgres.exe!secure_read+0x17d postgres.exe!pq_recvbuf+0x71 postgres.exe!pq_getbyte+0x15 postgres.exe!SocketBackend+0x6 postgres.exe!PostgresMain+0xbe8 ...
没有符号名称?为什么?
您的堆栈跟踪没有显示 !符号名称,您不知道为什么。以下是一些可能性。
没有模块的符号
您可能根本没有办法获得您正在调试的模块的符号。如果您发现大部分堆栈跟踪(至少是系统 DLL 和 NT 内核中的部分)都显示了符号名称,但其他部分没有,很可能是您只缺少那些部分的调试信息 (pdb 文件)。
如果您已将符号路径设置为指向您版本的 PostgreSQL 以及 Microsoft 符号服务器的符号,那么 PostgreSQL 应该是这样做的。
_NT_SYMBOL_PATH 未设置
您确定已设置 _NT_SYMBOL_PATH 环境变量了吗?
在开始菜单中选择运行,然后输入“cmd”并按回车键。
在弹出的窗口中,精确输入:
set | findstr symbol
您应该得到以下结果,允许 PostgreSQL 版本、驱动器号等有所不同:
_NT_SYMBOL_PATH=c:\program files\postgresql\8.4\symbols;SRV*c:\localsymbols*http://msdl.microsoft.com/download/symbols;
请注意 _NT_SYMBOL_PATH
是大写的,单词之间有下划线,并且以一个下划线开头。
在 Process Explorer 中,您应该在选项菜单的配置符号下看到相同的字符串。
附加软件防火墙
您是否安装了附加的软件防火墙?内置于 Windows 的防火墙不会造成问题,因为它只影响侦听套接字和传入连接,但第三方防火墙通常也会阻止传出连接。这可能会阻止从符号服务器访问工作。
确保您的软件防火墙允许访问端口 80 (http) 上的 msdl.microsoft.com
,或者为 procexp.exe
和 windbg.exe
添加豁免。
最初由 Craig Ringer 编写 - Ringerc 2009 年 7 月 29 日 10:06 (UTC)
关于远程调试、软件防火墙、排除故障堆栈跟踪、识别有用堆栈跟踪和使用 Visual Studio 调试的部分由 Ringerc 2009 年 8 月 22 日 06:25 (UTC) 添加