wiki主页:https://sourceware.org/glibc/wiki/Debugging/Development_Debugging
有几种调试加载器的场景:
直接 GDB 调试加载器,然后直接在调试中的加载器加载应用程序。如:
bash-4.2$ gdb /home/carlos/build/glibc/elf/ld.so GNU gdb (GDB) Fedora 7.6.1-42.fc19 Copyright (C) 2013 Free Software Foundation, Inc. License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html> This is free software: you are free to change and redistribute it. There is NO WARRANTY, to the extent permitted by law. Type "show copying" and "show warranty" for details. This GDB was configured as "x86_64-redhat-linux-gnu". For bug reporting instructions, please see: <http://www.gnu.org/software/gdb/bugs/>... Reading symbols from /home/carlos/build/glibc/elf/ld.so...done. (gdb) set exec-wrapper env LD_LIBRARY_PATH=/home/carlos/build/glibc:/home/carlos/build/glibc/elf:/home/carlos/build/glibc/math (gdb) break _start Breakpoint 1 at 0x11e0 (gdb) run /home/carlos/test Starting program: /home/carlos/build/glibc/elf/ld.so /home/carlos/test Breakpoint 1, 0x00005555555551e0 in _start () (gdb)
需要注意的一件重要事情是,不能使用set env来设置环境变量,否则影响动态加载器。这个原因是因为 gdb 默认在子 shell 中运行下级(inferior),这样做有几个原因,最重要的是参数扩展。如果为动态加载器设置了环境变量,它们也会影响子 shell,并可能导致子 shell 无法启动下级。
加载器的某些部分可以被单步执行(比如运行时解析器)。这些部分更容易调试,因为您可以简单地从您的应用程序进入它们。如:
bash-4.2$ gdb /home/carlos/test GNU gdb (GDB) Fedora 7.6.1-42.fc19 Copyright (C) 2013 Free Software Foundation, Inc. License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html> This is free software: you are free to change and redistribute it. There is NO WARRANTY, to the extent permitted by law. Type "show copying" and "show warranty" for details. This GDB was configured as "x86_64-redhat-linux-gnu". For bug reporting instructions, please see: <http://www.gnu.org/software/gdb/bugs/>... Reading symbols from /home/carlos/test...done. (gdb) break main Breakpoint 1 at 0x400534: file test.c, line 8. (gdb) r Starting program: /home/carlos/test Breakpoint 1, main () at test.c:8 8 keys[512] = 42; (gdb) n 9 printf ("%d\n", keys[512]); (gdb) si 0x0000000000400544 9 printf ("%d\n", keys[512]); (gdb) 0x0000000000400546 9 printf ("%d\n", keys[512]); (gdb) 0x000000000040054b 9 printf ("%d\n", keys[512]); (gdb) 0x0000000000400550 9 printf ("%d\n", keys[512]); (gdb) 0x0000000000400410 in printf@plt () (gdb) 0x0000000000400416 in printf@plt () (gdb) 0x000000000040041b in printf@plt () (gdb) 0x0000000000400400 in ?? () (gdb) 0x0000000000400406 in ?? () (gdb) _dl_runtime_resolve () at ../sysdeps/x86_64/dl-trampoline.S:34 34 subq $56,%rsp (gdb)
在示例中,可以看到 x86_64 上的调试器单步执行过程链接表(3 条指令),然后是另外 3 条指令(始终跳转到解析器中的 PLT 条目,并且所有惰性 PLT 条目在启动时都绑定到该条目)跳转到运行时解析器例程。glibc 中此例程的目的是定位您需要的符号,将 PLT 条目绑定到该符号,并使用您的参数调用该符号。这仅在您有惰性符号解析时发生,如果启用了 LD_BIND_NOW,您将看到从 PLT 直接跳转到 printf。
通常,在开发过程中,GLIBC 将安装到独立环境中。为了在此环境中测试应用程序,应用程序将使用安装在独立环境中的加载器启动,而不是系统加载器。这创建了一个独特的调试环境,为了调试应用程序或库的准确的执行环境,用户必须启动加载器的调试,然后进入应用程序或库。
这给 GNU 调试器gdb在查找有关应用程序和库的符号信息方面带来了一些挑战。当 GDB 调试加载器时,它只是为加载器单独加载符号信息。您需要执行一些特殊操作来支持 GDB 找到应用程序和库的符号和地址。
按照标题为“使用备用加载程序调试”部分下的加载器调试页面中描述的技术进行操作。
很多时候,人们会处理一些代码(例如 memcpy),这些代码将被 GLIBC 的基本组件(如加载器)拾取和使用。如果这段代码有问题,那么第一次使用加载器时,它可能会使加载器崩溃(即segmentation violation段违规),这将阻止 make check 运行任何测试。
我们将调查发生这种情况的设想,并介绍一些调试分段违规的方法。
- 为架构开发了一个新版本的 memcpy,它可能有一些问题。
- 在 /home/user/glibc/good-build/ 中有一个干净的不带memcpy 补丁的构建
- 在 /home/user/glibc/bad-build/ 中有一个带有 memcpy 补丁的构建
- GLIBC 为修改过的特定代码段提供了一个测试用例,例如 test-memcpy。
- 这个测试用例永远不会运行,因为加载器在 test-memcpy 运行之前在另一个测试中崩溃,这可能是由于 memcpy 中的错误导致的。
以下调试选项提供了几种调试此问题的技术。我们将对第三个选项最感兴趣。
这些示例中的 GLIBC 构建是使用cross-compiling=yes构建的,因此在构建过程中不会运行加载器。
在这种情况下,只要没有语法错误,GLIBC make阶段就会成功完成。通常,在首次使用新构建的 GLIBC 之前,不会出现基本的运行时错误。
要执行的 GLIBC 的第一个片段是在 GLIBC 的make check 中运行的加载器。
如果cross-compiling=no(这是默认值),那么加载器将在构建期间运行,可能会在 make 阶段遇到构建失败。
保存make和make check阶段输出以供以后搜素的好形式,例如:
> make -j4 2>&1 | tee _make64_1 > make check 2>&1 | tee _check64_1
查找失败的地方是从_check64_1输出开始。
/bin/sh: line 1: 10667 Segmentation fault GCONV_PATH=/home/user/glibc/bad-build/glibc64/iconvdata LC_ALL=C MALLOC_TRACE=/home/user/glibc/bad-build/glibc64/nptl/tst-stack3.mtrace /home/user/glibc/bad-build/glibc64/elf/ld64.so.1 --library-path /home/user/glibc/bad-build/glibc64:/home/user/glibc/bad-build/glibc64/math:/home/user/glibc/bad-build/glibc64/elf:/home/user/glibc/bad-build/glibc64/dlfcn:/home/user/glibc/bad-build/glibc64/nss:/home/user/glibc/bad-build/glibc64/nis:/home/user/glibc/bad-build/glibc64/rt:/home/user/glibc/bad-build/glibc64/resolv:/home/user/glibc/bad-build/glibc64/crypt:/home/user/glibc/bad-build/glibc64/nptl /home/user/glibc/bad-build/glibc64/nptl/tst-stack3 >/home/user/glibc/bad-build/glibc64/nptl/tst-stack3.out
make[2]: *** [/home/user/glibc/bad-build/glibc64/nptl/tst-stack3.out] Error 139
加载器中发生段错误的可能性很大。让我们检查:
> /home/user/glibc/bad-build/glibc64/elf/ld64.so.1 Segmentation fault
由于我们的 memcpy 版本有问题,加载器中肯定存在问题。我们将使用调试选项三中的技术来进行调试。
搜索错误构建的make输出_make64_1并确定 memcpy.o 的生成位置。
/opt/toolchain/bin/gcc -m64 …/sysdeps/powerpc/powerpc64/cell/memcpy.S -c -I…/include -I/home/user/glibc/bad-build/glibc64/string -I/home/user/glibc/bad-build/glibc64 -I…/sysdeps/powerpc/powerpc64/elf -I…/sysdeps/powerpc/elf -I…/sysdeps/unix/sysv/linux/powerpc/powerpc64/cell/fpu -I…/sysdeps/unix/sysv/linux/powerpc/powerpc64/cell -I…/sysdeps/unix/sysv/linux/powerpc/powerpc64/fpu -I…/sysdeps/powerpc/powerpc64/fpu -I…/nptl/sysdeps/unix/sysv/linux/powerpc/powerpc64 -I…/sysdeps/unix/sysv/linux/powerpc/powerpc64 -I…/sysdeps/unix/sysv/linux/wordsize-64 -I…/nptl/sysdeps/unix/sysv/linux/powerpc -I…/sysdeps/unix/sysv/linux/powerpc -I…/sysdeps/ieee754/ldbl-128ibm -I…/sysdeps/ieee754/ldbl-opt -I…/nptl/sysdeps/unix/sysv/linux -I…/nptl/sysdeps/pthread -I…/sysdeps/pthread -I…/sysdeps/unix/sysv/linux -I…/sysdeps/gnu -I…/sysdeps/unix/common -I…/sysdeps/unix/mman -I…/sysdeps/unix/inet -I…/nptl/sysdeps/unix/sysv -I…/sysdeps/unix/sysv -I…/sysdeps/unix/powerpc -I…/nptl/sysdeps/unix -I…/sysdeps/unix -I…/sysdeps/posix -I…/sysdeps/powerpc/powerpc64/cell -I…/sysdeps/powerpc/powerpc64 -I…/sysdeps/wordsize-64 -I…/sysdeps/powerpc/fpu -I…/nptl/sysdeps/powerpc -I…/sysdeps/powerpc -I…/sysdeps/ieee754/dbl-64 -I…/sysdeps/ieee754/flt-32 -I…/sysdeps/ieee754 -I…/sysdeps/generic/elf -I…/sysdeps/generic -I…/nptl -I… -I…/libio -I. -nostdinc -isystem /opt/toolchain/lib/gcc/powerpc64-linux/4.3.0/include -isystem /opt/toolchain/lib/gcc/powerpc64-linux/4.3.0/include-fixed -isystem /opt/toolchain/include -D_LIBC_REENTRANT -include …/include/libc-symbols.h -DASSEMBLER -o /home/user/glibc/bad-build/glibc64/string/memcpy.o -MD -MP -MF /home/user/glibc/bad-build/glibc64/string/memcpy.o.dt -MT /home/user/glibc/bad-build/glibc64/string/memcpy.o
在这一点上,我们可以简单地保存有问题的memcpy.o文件供以后使用。相反,我们希望使用这个片段来创建一个脚本,以便我们可以随意重新生成有问题的 memcpy.o 并将输出放置到一个 stage 目录中。
创建stage目录
> mkdir /home/user/stage/
从_make64_1中识别出memcpy.S的点开始,向后搜索术语“Entering”。每个 GLIBC 编译片段都使用源码的本地路径,因此您需要知道构建此特定文件时 shell 所在的源码目录。
make[2]: Entering directory `/home/user/glibc/bad-build/libc/string'
将此文件和之前确定的memcpy.o编译片段复制到stage 目录中名为memcpy.sh的文件中。您需要搜索和替换一些内容才能使其有用
- 将-o目标更改为stage 目录,即-o /home/user/stage/memcpy.o。
- 删除-M*标志。他们不需要了。
#!/bin/bash
cd /home/user/glibc/bad-build/libc/string
/opt/toolchain/bin/gcc -m64 …/sysdeps/powerpc/powerpc64/cell/memcpy.S -c -I…/include -I/home/user/glibc/bad-build/glibc64/string -I/home/user/glibc/bad-build/glibc64 -I…/sysdeps/powerpc/powerpc64/elf -I…/sysdeps/powerpc/elf -I…/sysdeps/unix/sysv/linux/powerpc/powerpc64/cell/fpu -I…/sysdeps/unix/sysv/linux/powerpc/powerpc64/cell -I…/sysdeps/unix/sysv/linux/powerpc/powerpc64/fpu -I…/sysdeps/powerpc/powerpc64/fpu -I…/nptl/sysdeps/unix/sysv/linux/powerpc/powerpc64 -I…/sysdeps/unix/sysv/linux/powerpc/powerpc64 -I…/sysdeps/unix/sysv/linux/wordsize-64 -I…/nptl/sysdeps/unix/sysv/linux/powerpc -I…/sysdeps/unix/sysv/linux/powerpc -I…/sysdeps/ieee754/ldbl-128ibm -I…/sysdeps/ieee754/ldbl-opt -I…/nptl/sysdeps/unix/sysv/linux -I…/nptl/sysdeps/pthread -I…/sysdeps/pthread -I…/sysdeps/unix/sysv/linux -I…/sysdeps/gnu -I…/sysdeps/unix/common -I…/sysdeps/unix/mman -I…/sysdeps/unix/inet -I…/nptl/sysdeps/unix/sysv -I…/sysdeps/unix/sysv -I…/sysdeps/unix/powerpc -I…/nptl/sysdeps/unix -I…/sysdeps/unix -I…/sysdeps/posix -I…/sysdeps/powerpc/powerpc64/cell -I…/sysdeps/powerpc/powerpc64 -I…/sysdeps/wordsize-64 -I…/sysdeps/powerpc/fpu -I…/nptl/sysdeps/powerpc -I…/sysdeps/powerpc -I…/sysdeps/ieee754/dbl-64 -I…/sysdeps/ieee754/flt-32 -I…/sysdeps/ieee754 -I…/sysdeps/generic/elf -I…/sysdeps/generic -I…/nptl -I… -I…/libio -I. -nostdinc -isystem /opt/toolchain/lib/gcc/powerpc64-linux/4.3.0/include -isystem /opt/toolchain/lib/gcc/powerpc64-linux/4.3.0/include-fixed -isystem /opt/toolchain/include -D_LIBC_REENTRANT -include …/include/libc-symbols.h -DASSEMBLER -o /home/user/stage/memcpy.o
从 stage 目录执行这个脚本,你应该有一个生成的文件/home/user/stage/memcpy.o:
> cd /home/user/stage/ > sh memcpy.sh
将来如果您需要重新编写 memcpy.S并将其重新编译为memcpy.o只需:
> vim /home/user/glibc/bad-build/libc/sysdeps/powerpc/powerpc64/cell/memcpy.S <make modifications> > sh /home/user/stage/memcpy.sh
接下来的步骤将使用干净构建的make 检查日志_check64_1来查找并提取脚本片段,以便将干净构建的 test-memcpy 与有问题的 memcpy.o 重新链接,以便新的 test-memcpy 可以使用干净构建的加载器,但是使用坏的 memcpy 功能。这应该有助于我们确定 memcpy 中的错误所在的位置。
搜索测试程序test-memcpy.c的编译
/opt/toolchain/bin/gcc -m64 test-memcpy.c -c -std=gnu99 -O2 -Wall -Winline -Wwrite-strings -fmerge-all-constants -g -mlong-double-128 -mnew-mnemonics -Wstrict-prototypes -mlong-double-128 -I…/include -I/home/user/glibc/good-build/glibc64/string -I/home/user/glibc/good-build/glibc64 -I…/sysdeps/powerpc/powerpc64/elf -I…/sysdeps/powerpc/elf -I…/sysdeps/unix/sysv/linux/powerpc/powerpc64/fpu -I…/sysdeps/powerpc/powerpc64/fpu -I…/nptl/sysdeps/unix/sysv/linux/powerpc/powerpc64 -I…/sysdeps/unix/sysv/linux/powerpc/powerpc64 -I…/sysdeps/unix/sysv/linux/wordsize-64 -I…/nptl/sysdeps/unix/sysv/linux/powerpc -I…/sysdeps/unix/sysv/linux/powerpc -I…/sysdeps/ieee754/ldbl-128ibm -I…/sysdeps/ieee754/ldbl-opt -I…/nptl/sysdeps/unix/sysv/linux -I…/nptl/sysdeps/pthread -I…/sysdeps/pthread -I…/sysdeps/unix/sysv/linux -I…/sysdeps/gnu -I…/sysdeps/unix/common -I…/sysdeps/unix/mman -I…/sysdeps/unix/inet -I…/nptl/sysdeps/unix/sysv -I…/sysdeps/unix/sysv -I…/sysdeps/unix/powerpc -I…/nptl/sysdeps/unix -I…/sysdeps/unix -I…/sysdeps/posix -I…/sysdeps/powerpc/powerpc64 -I…/sysdeps/wordsize-64 -I…/sysdeps/powerpc/fpu -I…/nptl/sysdeps/powerpc -I…/sysdeps/powerpc -I…/sysdeps/ieee754/dbl-64 -I…/sysdeps/ieee754/flt-32 -I…/sysdeps/ieee754 -I…/sysdeps/generic/elf -I…/sysdeps/generic -I…/nptl -I… -I…/libio -I. -nostdinc -isystem /opt/toolchain/lib/gcc/powerpc64-suse-linux/4.1.2/include -isystem /opt/toolchain/include -D_LIBC_REENTRANT -include …/include/libc-symbols.h -DPIC -DNOT_IN_libc=1 -o /home/user/glibc/good-build/glibc64/string/test-memcpy.o -MD -MP -MF /home/user/glibc/good-build/glibc64/string/test-memcpy.o.dt -MT /home/user/glibc/good-build/glibc64/string/test-memcpy.o
将test-memcpy.o复制到您的stage目录
> cp /home/user/glibc/good-build/glibc64/string/test-memcpy.o /home/user/stage
识别_check_64_1中的片段,该片段将 test-memcpy.o 与其余库链接并生成了test-memcpy可执行文件。
/opt/toolchain/bin/gcc -m64 -nostdlib -nostartfiles -o /home/user/glibc/good-build/glibc64/string/test-memcpy -Wl,-dynamic-linker=/lib64/ld64.so.1 -Wl,-z,combreloc -Wl,-z,relro /home/user/glibc/good-build/glibc64/csu/crt1.o /home/user/glibc/good-build/glibc64/csu/crti.o
/opt/toolchain/bin/gcc -m64 --print-file-name=crtbegin.o
/home/user/glibc/good-build/glibc64/string/test-memcpy.o -Wl,-rpath-link=/home/user/glibc/good-build/glibc64:/home/user/glibc/good-build/glibc64/math:/home/user/glibc/good-build/glibc64/elf:/home/user/glibc/good-build/glibc64/dlfcn:/home/user/glibc/good-build/glibc64/nss:/home/user/glibc/good-build/glibc64/nis:/home/user/glibc/good-build/glibc64/rt:/home/user/glibc/good-build/glibc64/resolv:/home/user/glibc/good-build/glibc64/crypt:/home/user/glibc/good-build/glibc64/nptl /home/user/glibc/good-build/glibc64/libc.so.6 /home/user/glibc/good-build/glibc64/libc_nonshared.a -lgcc -Wl,–as-needed -lgcc_s -Wl,–no-as-needed/opt/toolchain/bin/gcc -m64 --print-file-name=crtend.o
/home/user/glibc/good-build/glibc64/csu/crtn.o
将此片段作为test-memcpy.sh保存到stage目录中。
在这一点上,我们想要将这个我们保存为test-memcpy.sh 的动态链接示例转换为一个脚本,该脚本静态链接到我们有问题的测试用例中。我们需要做几件事:
#!/bin/bash
/opt/toolchain/bin/gcc -m64 -nostdlib -nostartfiles -o /home/user/stage/test-memcpy -Wl,-dynamic-linker=/home/user/glibc/good-build/glibc64/elf/ld64.so.1 -Wl,-z,combreloc -Wl,-z,relro /home/user/glibc/good-build/glibc64/csu/crt1.o /home/user/glibc/good-build/glibc64/csu/crti.o/opt/toolchain/bin/gcc -m64 --print-file-name=crtbegin.o
/home/user/stage/test-memcpy.o /home/user/stage/memcpy.o
-Wl,-rpath=/home/user/glibc/good-build/glibc64: /home/user/glibc/good-build/glibc64/math:/home/user/glibc/good-build/glibc64/elf:
/home/user/glibc/good-build/glibc64/dlfcn:/home/user/glibc/good-build/glibc64/nss:
/home/user/glibc/good-build/glibc64/nis:/home/user/glibc/good-build/glibc64/rt:
/home/user/glibc/good-build/glibc64/resolv:/home/user/glibc/good-build/glibc64/crypt:
/home/user/glibc/good-build/glibc64/nptl
/home/user/glibc/good-build/glibc64/libc.so /home/user/glibc/good-build/glibc64/libc_nonshared.a -lgcc -Wl,–as-needed -lgcc_s -Wl,–no-as-needed/opt/toolchain/bin/gcc -m64 --print-file-name=crtend.o
/home/user/glibc/good-build/glibc64/csu/crtn.o
这应该已经创建了文件**/home/user/stage/test-memcpy**。
此时,您可以验证test-memcpy是否正在使用来自良好构建的加载器:
> readelf -l /home/user/stage/test-memcpy | grep INTERP -A 2 INTERP 0x0000000000000238 0x0000000010000238 0x0000000010000238 0x0000000000000045 0x0000000000000045 R 1 [Requesting program interpreter: /home/user/glibc/good-build/glibc64/elf/ld64]
如果您执行test-memcpy,您现在应该会看到段违规:
> ./test-memcpy simple_memcpy builtin_memcpy memcpy Length 1, alignment 0/ 0: 1 3 1 Length 1, alignment 0/ 0: 1 2 2 Length 1, alignment 0/ 0: 1 3 1 Length 1, alignment 0/ 0: 1 3 1 ... Length 496, alignment 31/ 0: 249 48 47 Length 496, alignment 0/31: 249 51 49 Length 496, alignment 31/31: 249 29 29 Length 4096, alignment 0/ 0: 2052 134 134 Didn't expect signal from child: got `Segmentation fault'
现在运行调试器并确保使用–direct标志运行。调试器会将–direct标志传递给 GLIBC测试框架,该框架将以标志为方向(direction)直接运行测试而不是立即fork函数。
> gdb64 test-memcpy ... (gdb) run --direct Starting program: /home/user/stage/test-memcpy --direct warning: Breakpoint address adjusted from 0x100a1dd0 to 0x10000200. simple_memcpy builtin_memcpy memcpy Length 1, alignment 0/ 0: 2 4 2 Length 1, alignment 0/ 0: 2 4 2 Length 1, alignment 0/ 0: 2 4 2 ... Length 4096, alignment 0/ 0: 2052 135 137 Program received signal SIGSEGV, Segmentation fault. 0x000000001000188c in .memcpy () (gdb) bt #0 0x000000001000188c in .memcpy () #1 0x0000000010000ce8 in test_main () at test-memcpy.c:193 #2 0x0000000010001490 in main (argc=2, argv=0xffffff04fb8) at ../test-skeleton.c:274
现在您可以调试有问题的代码,只需修改/home/user/stage/bad-build/libc/sysdeps/powerpc/powerpc64/cell/memcpy.S并重新运行 memcpy.sh 以及test-memcpy.sh。然后简单地重新运行 test-memcpy; 冲洗(rinse); 重复。
如果您无法让动态链接器正确链接memcpy.o和test-memcpy.o,您可能需要静态链接 .o 文件,尽管这是一个骇人听闻的解决方案。-rpath和-dynamic-linker方法是优选的。
#!/bin/bash
/opt/toolchain/bin/gcc -m64 -static -nostdlib -nostartfiles -o /home/user/stage/test-memcpy -Wl,-dynamic-linker=/home/user/glibc/good-build/glibc64/elf/ld64.so.1 -Wl,-z,combreloc -Wl,-z,relro /home/user/glibc/good-build/glibc64/csu/crt1.o /home/user/glibc/good-build/glibc64/csu/crti.o/opt/toolchain/bin/gcc -m64 --print-file-name=crtbegin.o
/home/user/stage/test-memcpy.o /home/user/stage/memcpy.o
-Wl,-rpath-link=/home/user/glibc/good-build/glibc64: /home/user/glibc/good-build/glibc64/math:/home/user/glibc/good-build/glibc64/elf:
/home/user/glibc/good-build/glibc64/dlfcn:/home/user/glibc/good-build/glibc64/nss:
/home/user/glibc/good-build/glibc64/nis:/home/user/glibc/good-build/glibc64/rt:
/home/user/glibc/good-build/glibc64/resolv:/home/user/glibc/good-build/glibc64/crypt:
/home/user/glibc/good-build/glibc64/nptl
/home/user/glibc/good-build/glibc64/libc.a /home/user/glibc/good-build/glibc64/libc_nonshared.a /home/user/glibc/good-build/glibc64/elf/dl-iteratephdr.o -lgcc -Wl,–as-needed -lgcc_eh -Wl,–no-as-needed/opt/toolchain/bin/gcc -m64 --print-file-name=crtend.o
home/user/glibc/good-build/glibc64/csu/crtn.o
注意:在前面的调用中,整个 -rpath-link 都在一行上,没有换行符。
GLIBC 源文件和头文件搜索顺序保存在配置输出中。这首先由操作系统和平台决定,其次由depth决定,第三由 Implies 文件决定,第四由 Subdirs 文件决定。
使用 -dD -E 或 --save-temps