Linux诊断和故障排除系列(七) — 应用程序诊断

1. 什么是库依赖

库依赖是软件开发中一个重要的概念,它涉及到不同软件组件之间的交互和资源共享。在现代软件开发实践中,为了提高代码的复用性、降低开发成本并简化维护工作,开发者们通常会将一些通用的功能封装成库。这些库包含了一系列的函数和资源,用于执行常见的任务,比如文件操作、网络通信、加密解密等。

以nginx和pmlogger为例,这两个应用程序可能都需要使用到加密功能。为了实现这一功能,它们可以依赖于一个名为libcrypto的加密库。libcrypto是一个广泛使用的开源加密库,提供了丰富的加密算法和安全功能,被许多应用程序所采用。
在应用程序启动时,链接器(ld-linker)扮演着至关重要的角色。链接器是一种工具,负责在程序运行时将所需的库动态链接到应用程序的地址空间中。它通过解析应用程序的依赖关系,加载相应的库文件,并将这些库映射到内存中,确保应用程序能够顺利调用库中的函数和资源。
通过使用链接器,nginx和pmlogger能够共享libcrypto库,而无需各自实现相同的加密功能。这样不仅减少了代码的冗余,也使得应用程序更加轻量级,同时还能享受到库更新带来的安全性和性能提升。

此外,库依赖管理也是软件开发中的一个挑战。开发者需要确保所依赖的库是可靠和安全的,避免因为库的漏洞而影响到应用程序的稳定性和安全性。同时,合理管理库的版本,确保应用程序能够在不同的环境中稳定运行,也是开发者需要考虑的问题。
总之,库依赖是现代软件开发不可或缺的一部分,它促进了代码的模块化和重用,提高了开发效率,同时也带来了库管理和安全性方面的挑战。开发者需要在使用库依赖的同时,不断优化和更新,以确保软件的质量和性能。

2. 查询库依赖关系

在Linux系统中,了解应用程序所依赖的库是理解程序行为和进行系统管理的重要一环。库依赖信息不仅有助于开发者进行程序调试和性能优化,还能帮助系统管理员进行软件的维护和升级。
要查询系统中的库,我们可以使用一些实用的命令行工具。ldconfig是一个强大的命令,它能够显示当前系统内存中的库缓存信息。通过执行ldconfig -p,我们可以列出所有已经配置的共享库及其对应的路径,这些信息通常被缓存以加速库的加载过程。

$ ldconfig -p | more
298 libs found in cache `/etc/ld.so.cache`
        p11-kit-trust.so (libc6,x86-64) => /lib64/p11-kit-trust.so
        libz.so.1 (libc6,x86-64) => /lib64/libz.so.1
        libyaml-0.so.2 (libc6,x86-64) => /lib64/libyaml-0.so.2
......

进一步地,ldd命令是显示程序共享库依赖关系的利器。它能够列出一个可执行文件所依赖的所有共享库,以及这些库的确切位置。这对于诊断库加载问题或了解程序的依赖结构非常有用。
例如,如果你想知道python3解释器所依赖的库,可以使用ldd /bin/python3 命令组合,这个命令会显示python3所依赖的所有共享库。

$ ldd /bin/python3
        linux-vdso.so.1 =>  (0x00007ffd177ad000)
        libpython3.6m.so.1.0 => /lib64/libpython3.6m.so.1.0 (0x00007fcb5945e000)
......

这些命令的输出结果可以提供丰富的信息,包括库的名称、版本以及它们在文件系统中的位置。通过分析这些信息,开发者和系统管理员可以更好地理解程序的行为,优化系统配置,甚至解决因缺失或不兼容的库导致的问题。
在实际使用中,了解如何查询和管理库依赖是提高系统稳定性和性能的关键。随着软件复杂度的增加,合理地管理库依赖也变得越来越重要。掌握这些基本的命令和它们的含义,能够帮助用户更深入地了解和控制他们的Linux系统。

3. 内存泄漏检测

内存泄漏检测是软件开发生命周期中的一个关键环节。它帮助开发人员识别和修复那些在程序运行期间未能正确释放的内存资源。尽管内存泄漏不会导致立即的错误,但随着时间的推移,它们可能会消耗大量的系统资源,影响应用程序的性能和响应能力,甚至崩溃。因此,检测内存泄漏并生成相应的报告是确保软件质量和稳定性的重要步骤。

3.1 使用Valgrind检测C和C++程序的内存泄漏

Valgrind是一个强大的Linux工具,它提供了一套用于调试和性能分析的功能,特别是针对C和C++程序。它的核心工具之一是Memcheck,专门用于检测内存问题,包括内存泄漏、内存越界访问、使用后释放等。

$ valgrind /root/memleak_test_app               
==10077== Memcheck, a memory error detector
==10077== Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al.
==10077== Using Valgrind-3.15.0 and LibVEX; rerun with -h for copyright info
==10077== Command: /root/memleak_test_app
==10077== 
==10077== Invalid write of size 4
==10077==    at 0x40053B: f (in /root/memleak_test_app)
==10077==    by 0x40054B: main (in /root/memleak_test_app)
==10077==  Address 0x5205068 is 0 bytes after a block of size 40 alloc'd
==10077==    at 0x4C29F73: malloc (vg_replace_malloc.c:309)
==10077==    by 0x40052E: f (in /root/memleak_test_app)
==10077==    by 0x40054B: main (in /root/memleak_test_app)
==10077== 
==10077== 
==10077== HEAP SUMMARY:
==10077==     in use at exit: 40 bytes in 1 blocks
==10077==   total heap usage: 1 allocs, 0 frees, 40 bytes allocated
==10077== 
==10077== LEAK SUMMARY:
==10077==    definitely lost: 40 bytes in 1 blocks
==10077==    indirectly lost: 0 bytes in 0 blocks
==10077==      possibly lost: 0 bytes in 0 blocks
==10077==    still reachable: 0 bytes in 0 blocks
==10077==         suppressed: 0 bytes in 0 blocks
==10077== Rerun with --leak-check=full to see details of leaked memory      <<<<<<
==10077== 
==10077== For lists of detected and suppressed errors, rerun with: -s
==10077== ERROR SUMMARY: 1 errors from 1 contexts (suppressed: 0 from 0)

3.2 Java

Java:VisualVM是一个强大的Java虚拟机监控和分析工具,它可以帮助开发人员识别Java应用程序中的内存泄漏问题。

3.3 Python

Python:memory_profiler是一个Python库,它可以跟踪Python应用程序的内存使用情况,并帮助识别内存泄漏。

3.4 Go

Go:go-leak是一个Go语言的内存泄漏检测工具,它通过分析Go程序的运行时行为来发现潜在的内存泄漏。

内存泄漏检测是一个持续的过程,需要开发人员在整个软件开发周期中保持警惕。通过使用合适的工具,如Valgrind和其他语言特定的检测工具,开发人员可以更有效地发现和解决内存泄漏问题,从而提高软件的稳定性和性能。这些工具不仅帮助开发人员在开发阶段就识别问题,还为维护和优化现有应用程序提供了有力的支持。

4. 使用标准工具调试应用程序

在软件开发和系统管理中,调试是一个不可或缺的过程,它帮助我们识别和定位应用程序中的问题。调试工具是开发人员和系统管理员的宝贵资源。它们允许用户监视和分析程序的运行时行为,从而发现潜在的错误、性能瓶颈或其他问题。尽管调试工具通常不直接修复这些问题,但它们提供了必要的信息,使得问题解决成为可能。

4.1 使用ltrace追踪库调用

ltrace是一个强大的命令行工具,它可以追踪程序执行期间的库函数调用。这对于理解程序如何与库交互以及识别可能的库调用问题非常有用。

$ ltrace $(which cat) empty                                                 
__libc_start_main(0x401a20, 2, 0x7ffd7267e818, 0x408ca0 <unfinished ...>   
getpagesize()                                                              = 4096
strrchr("/bin/cat", '/')                                                   = "/cat"
setlocale(LC_ALL, "")                                                      = "en_US.UTF-8"
bindtextdomain("coreutils", "/usr/share/locale")                           = "/usr/share/locale"
textdomain("coreutils")                                                    = "coreutils"
__cxa_atexit(0x4029f0, 0, 0, 0x736c6974756572)                             = 0
getopt_long(2, 0x7ffd7267e818, "benstuvAET", 0x60bc60, nil)                = -1
__fxstat(1, 1, 0x7ffd7267e660)                                             = 0
open("empty", 0, 037777600000)                                             = 3                      <<<打开文件
__fxstat(1, 3, 0x7ffd7267e660)                                             = 0
posix_fadvise(3, 0, 0, 2)                                                  = 0
malloc(69631)                                                              = 0x76e030               <<<分配内存
read(3, "", 65536)                                                         = 0                      <<<读取文件
free(0x76e030)                                                             = <void>
close(3)                                                                   = 0                      <<<关闭文件
exit(0 <unfinished ...>                                                    
__fpending(0x7ff0582ee400, 0, 64, 0x7ff0582eeeb0)                          = 0
......
+++ exited (status 0) +++

4.2 使用strace追踪系统调用

与ltrace类似,strace也是一个命令行工具,但它专注于追踪程序执行期间的系统调用。系统调用是程序与操作系统内核交互的接口,因此strace对于理解程序的系统级行为至关重要。

$ strace $(which cat) empty  
execve("/bin/cat", ["/bin/cat", "empty"], 0x7ffdf6856388 /* 24 vars */) = 0
brk(NULL)                               = 0x13de000
mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f8972c84000
access("/etc/ld.so.preload", R_OK)      = -1 ENOENT (No such file or directory)
open("/etc/ld.so.cache", O_RDONLY|O_CLOEXEC) = 3
fstat(3, {st_mode=S_IFREG|0644, st_size=22608, ...}) = 0
mmap(NULL, 22608, PROT_READ, MAP_PRIVATE, 3, 0) = 0x7f8972c7e000
close(3)                                = 0
open("/lib64/libc.so.6", O_RDONLY|O_CLOEXEC) = 3
read(3, "\177ELF\2\1\1\3\0\0\0\0\0\0\0\0\3\0>\0\1\0\0\0`&\2\0\0\0\0\0"..., 832) = 832
fstat(3, {st_mode=S_IFREG|0755, st_size=2156592, ...}) = 0
mmap(NULL, 3985920, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x7f8972696000
mprotect(0x7f897285a000, 2093056, PROT_NONE) = 0
mmap(0x7f8972a59000, 24576, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x1c3000) = 0x7f8972a59000
mmap(0x7f8972a5f000, 16896, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0x7f8972a5f000
close(3)                                = 0
mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f8972c7d000
mmap(NULL, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f8972c7b000
arch_prctl(ARCH_SET_FS, 0x7f8972c7b740) = 0
access("/etc/sysconfig/strcasecmp-nonascii", F_OK) = -1 ENOENT (No such file or directory)
access("/etc/sysconfig/strcasecmp-nonascii", F_OK) = -1 ENOENT (No such file or directory)
mprotect(0x7f8972a59000, 16384, PROT_READ) = 0
mprotect(0x60b000, 4096, PROT_READ)     = 0
mprotect(0x7f8972c85000, 4096, PROT_READ) = 0
munmap(0x7f8972c7e000, 22608)           = 0
brk(NULL)                               = 0x13de000
brk(0x13ff000)                          = 0x13ff000
brk(NULL)                               = 0x13ff000
open("/usr/lib/locale/locale-archive", O_RDONLY|O_CLOEXEC) = 3
fstat(3, {st_mode=S_IFREG|0644, st_size=106176928, ...}) = 0
mmap(NULL, 106176928, PROT_READ, MAP_PRIVATE, 3, 0) = 0x7f896c153000
close(3)                                = 0
fstat(1, {st_mode=S_IFCHR|0620, st_rdev=makedev(136, 0), ...}) = 0
open("empty", O_RDONLY)                 = 3
fstat(3, {st_mode=S_IFREG|0644, st_size=6, ...}) = 0
fadvise64(3, 0, 0, POSIX_FADV_SEQUENTIAL) = 0
read(3, "hello\n", 65536)               = 6
write(1, "hello\n", 6hello
)                  = 6
read(3, "", 65536)                      = 0
close(3)                                = 0
close(1)                                = 0
close(2)                                = 0
exit_group(0)                           = ?
+++ exited with 0 +++

ltrace和strace是两个互补的工具,它们提供了不同层面的洞察力,帮助用户从不同角度分析和理解程序的行为。通过这些工具,用户可以识别出程序中的异常行为,如不寻常的系统调用模式或库调用错误。虽然这些工具可能不会直接解决问题,但它们为开发人员提供了解决问题的基础和方向。

更多内容请参见本系列其他文章

<<Linux诊断和故障排除系列(一) -- 修复启动分区>>
<<Linux诊断和故障排除系列(二) -- 修复内核服务>>
<<Linux诊断和故障排除系列(三) -- 重置root密码>>
<<Linux诊断和故障排除系列(四) -- 修复文件系统>>
<<Linux诊断和故障排除系列(五) -- 修复iSCSI>>
<<Linux诊断和故障排除系列(六) -- 修复软件包及管理器>>
<<Linux诊断和故障排除系列(七) -- 应用程序诊断>>
<<Linux诊断和故障排除系列(八) -- 网络问题诊断>>
<<Linux诊断和故障排除系列(九) -- 身份验证和授权问题诊断>>
<<Linux诊断和故障排除系列(十) -- 硬件问题日志>>
<<Linux诊断和故障排除系列(十一) -- dump设置和分析>>
<<Linux诊断和故障排除系列(十二) -- 日志持久化和转发>>
<<Linux诊断和故障排除系列(十三) -- 官方支持数据sos_report及其分析可视化软件>>

本文内容为原创,如需转载,请务必注明原文出处。
更多相关内容,欢迎访问我的个人网站:hongxu.wang。
我们还提供免费的技术支持,欢迎与我们联系。

Index
滚动至顶部