【问题标题】:How to open and close RPM DB without leaking memory?如何在不泄漏内存的情况下打开和关闭RPM DB?
【发布时间】:2023-01-04 23:27:51
【问题描述】:

我正在使用我自己的使用 librpm library 的代码来模拟 rpm -qa。我这样做是为了对一个更大的程序进行初步实验,该程序将出于安全目的分析已安装的软件。

现在,我只打开 RPM DB 并在不读取任何内容的情况下将其关闭。

当我比较我代码的 valgrind 输出和 rpm -qa 的 valgrind 输出时,结果如下:

$ valgrind ./泄漏
==8201== Memcheck,内存错误检测器
==8201== 版权所有 (C) 2002-2017,以及 Julian Seward 等人的 GNU GPL'd。
==8201== 使用 Valgrind-3.18.1 和 LibVEX;使用 -h 重新运行以获得版权信息
==8201== 命令:./leaky
==8201==
==8201==
==8201== 堆摘要:
==8201== 在退出时使用:2,352 个块中的 104,700 字节
==8201== 堆总使用量:10,430 次分配,8,078 次释放,分配了 2,292,650 字节
==8201==
==8201== 泄漏摘要:
==8201== 绝对丢失:0 个块中的 0 个字节
==8201== 间接丢失:0 个块中的 0 个字节
==8201== 可能丢失:325 个块中的 25,740 个字节
==8201== 仍可访问:2,027 个块中的 78,960 字节
==8201== 抑制:0 个块中的 0 个字节
==8201== 重新运行 --leak-check=full 以查看泄漏内存的详细信息
==8201==
==8201== 对于检测到的和抑制的错误列表,重新运行:-s
==8201== 错误摘要:0 个上下文中的 0 个错误(抑制:0 个中的 0 个)
$ valgrind rpm -qa > /dev/null
==8101== Memcheck,内存错误检测器
==8101== 版权所有 (C) 2002-2017,以及 Julian Seward 等人的 GNU GPL'd。
==8101== 使用 Valgrind-3.18.1 和 LibVEX;使用 -h 重新运行以获得版权信息
==8101== 命令:rpm -qa
==8101==
==8101==
==8101== 堆摘要:
==8101== 在退出时使用:2 个块中的 287 个字节
==8101== 总堆使用量:170,103 allocs,170,101 frees,120,309,981 bytes allocated
==8101==
==8101== 泄漏摘要:
==8101== 绝对丢失:0 个块中的 0 个字节
==8101== 间接丢失:0 个块中的 0 个字节
==8101== 可能丢失:0 个块中的 0 个字节
==8101== 仍然可达:2 个块中的 287 个字节
==8101== 抑制:0 个块中的 0 个字节
==8101== 重新运行 --leak-check=full 以查看泄漏内存的详细信息
==8101==
==8101== 对于检测到的和抑制的错误列表,重新运行:-s
==8101== 错误摘要:0 个上下文中的 0 个错误(抑制:0 个中的 0 个)

如您所见,我的程序可能丢失了 25,740 个字节,而 rpm -qa 丢失了 0 个字节。

这是我的代码:

#include <rpm/rpmdb.h>
#include <rpm/rpmlib.h>
#include <rpm/rpmts.h>

bool openDb(rpmts & ts, rpmdbMatchIterator & mi);
void closeDb(rpmts & ts, rpmdbMatchIterator & mi);

int main()
{
    rpmts ts;
    rpmdbMatchIterator mi;

    if (!openDb(ts, mi)) {
        return 1;
    }

    closeDb(ts, mi);
    return 0;
}

bool openDb(rpmts & ts, rpmdbMatchIterator & mi)
{
    {
        static volatile bool s_bHereBefore = false;
        if (!s_bHereBefore) {
            s_bHereBefore = true;
            rpmReadConfigFiles(NULL, NULL);
        }
    }

    mi = NULL;
    ts = rpmtsCreate();

    if (!ts) {
        printf("RPM open failed\n");
    } else {
        mi = rpmtsInitIterator(ts, (rpmTag)RPMDBI_PACKAGES, NULL, 0);
        if (!mi) {
            printf("RPM iterator failed\n");
            rpmtsFree(ts);
        }
    }

    return mi != NULL;
}

void closeDb(rpmts & ts, rpmdbMatchIterator & mi)
{
    mi = rpmdbFreeIterator(mi);
    if (ts) {
        rpmtsFree(ts);
    }
}

我用g++ -Wall -Wextra -Wunused -Og -g try_to_fix_mem_leak.cpp -lrpm -o leaky编译。

我仔细检查了我的程序,但我无法通过手动检查发现任何内存泄漏。

当我运行 valgrind --leak-check=full ./leaky 并在输出中搜索 try_to_fix_mem_leak.cpp 时,所有的命中都针对第 27 行,即 rpmReadConfigFiles(NULL, NULL); 行(从技术上讲,第 13 行也有命中,但这只是因为那是openDbmain 中进行调用)。 (请参阅下面的 pastebin 链接。)但我不知道这一行如何导致任何内存泄漏。 The function's documentation for my version of librpm (4.16.1) 没有提到需要释放任何内存。

如何在不泄漏内存的情况下正确打开和关闭 RPM DB?或者,换句话说,我如何打开和关闭 RPM 数据库,而最坏情况下泄漏的字节数与 rpm -qa 一样多?


编辑

pastebin link,完整输出为valgrind --leak-check=full ./leaky

【问题讨论】:

  • 您可以将其他选项传递给 valgrind,这些选项将准确报告泄漏内存的分配位置,并为您指明正确的方向。查看 valgrind 的文档以获取更多信息。
  • @SamVarshavchik,我尝试将 valgrind 与 --leak-check=full 一起使用,但我看不出它指向的行是如何泄漏的。请查看我更新的问题,特别是有关--leak-check=fullrpmReadConfigFiles 的段落。
  • 如果您只是在程序退出时泄漏内存,那么这很可能并不重要 - 除非这意味着重要的析构函数没有运行,这些析构函数没有运行。一旦可执行文件退出,内核就会清理全部它是内存(包括你泄露的内容)。实际上;通过避免不必要的对象破坏和内存释放,故意泄漏内存可能是允许应用程序快速关闭的有效策略。
  • @JesperJuhl,释放内存后我在我的程序中做的最后一件事是退出(参见我的main 函数的最后两行),所以如果我的程序退出时有任何未释放的内存,那么对我来说这意味着我我正在泄漏内存。
  • @ShaneBishop 您只能在程序运行时泄漏内存。内存泄漏是一个问题,因为内存使用会随着时间的推移不断增加。一旦您的程序退出,内核就会清除它曾经分配的所有内容(即使您没有)。内存泄漏不会在多个应用程序启动/终止时持续存在。

标签: c++ linux memory-leaks rpm


【解决方案1】:

我的一位同事发现,在 rpm 二进制文件本身的代码中,RPM 开发者 call the following two functions 释放了它们的内存:

rpmFreeMacros(NULL);
rpmFreeRpmrc();

要使用 rpmFreeMacros(),请在您的源代码中包含 rpm/rpmmacro.h 系统标头,并链接到 librpmio.so

这是我更新的代码:

#include <rpm/rpmdb.h>
#include <rpm/rpmlib.h>
#include <rpm/rpmts.h>
#include <rpm/rpmmacro.h>

bool openDb(rpmts & ts, rpmdbMatchIterator & mi);
void closeDb(rpmts & ts, rpmdbMatchIterator & mi);

int main()
{
    rpmts ts;
    rpmdbMatchIterator mi;

    if (!openDb(ts, mi)) {
        return 1;
    }

    closeDb(ts, mi);
    return 0;
}

bool openDb(rpmts & ts, rpmdbMatchIterator & mi)
{
    {
        static volatile bool s_bHereBefore = false;
        if (!s_bHereBefore) {
            s_bHereBefore = true;
            rpmReadConfigFiles(NULL, NULL);
        }
    }

    mi = NULL;
    ts = rpmtsCreate();

    if (!ts) {
        printf("RPM open failed
");
    } else {
        mi = rpmtsInitIterator(ts, (rpmTag)RPMDBI_PACKAGES, NULL, 0);
        if (!mi) {
            printf("RPM iterator failed
");
            rpmtsFree(ts);
        }
    }

    return mi != NULL;
}

void closeDb(rpmts & ts, rpmdbMatchIterator & mi)
{
    mi = rpmdbFreeIterator(mi);
    if (ts) {
        rpmtsFree(ts);
    }
    rpmFreeMacros(NULL);
    rpmFreeRpmrc();
}

这是我的新代码 (leak_fixed.cpp) 和我的旧代码 (try_to_fix_mem_leak.cpp) 之间的区别:

$ diff -u try_to_fix_mem_leak.cpp leak_fixed.cpp 
--- try_to_fix_mem_leak.cpp 2023-01-04 10:19:28.084587731 -0500
+++ leak_fixed.cpp  2023-01-04 10:19:57.484483431 -0500
@@ -1,6 +1,7 @@
 #include <rpm/rpmdb.h>
 #include <rpm/rpmlib.h>
 #include <rpm/rpmts.h>
+#include <rpm/rpmmacro.h>
 
 bool openDb(rpmts & ts, rpmdbMatchIterator & mi);
 void closeDb(rpmts & ts, rpmdbMatchIterator & mi);
@@ -50,4 +51,6 @@
     if (ts) {
         rpmtsFree(ts);
     }
+    rpmFreeMacros(NULL);
+    rpmFreeRpmrc();
 }

我用g++ -Wall -Wextra -Wunused -Og -g leak_fixed.cpp -lrpm -lrpmio -o rpm_open_close编译。请注意,我需要添加 -lrpmio 才能使用 rpmFreeMacros() 函数。

对于我的二进制文件和系统 rpm 二进制文件,我从 valgrind 获得了相同的泄漏摘要输出:

$ valgrind ./rpm_open_close 
==3470== Memcheck, a memory error detector
==3470== Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al.
==3470== Using Valgrind-3.18.1 and LibVEX; rerun with -h for copyright info
==3470== Command: ./rpm_open_close
==3470== 
==3470== 
==3470== HEAP SUMMARY:
==3470==     in use at exit: 287 bytes in 2 blocks
==3470==   total heap usage: 10,495 allocs, 10,493 frees, 2,317,132 bytes allocated
==3470== 
==3470== LEAK SUMMARY:
==3470==    definitely lost: 0 bytes in 0 blocks
==3470==    indirectly lost: 0 bytes in 0 blocks
==3470==      possibly lost: 0 bytes in 0 blocks
==3470==    still reachable: 287 bytes in 2 blocks
==3470==         suppressed: 0 bytes in 0 blocks
==3470== Rerun with --leak-check=full to see details of leaked memory
==3470== 
==3470== For lists of detected and suppressed errors, rerun with: -s
==3470== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)
$ valgrind rpm -qa > /dev/null
==3483== Memcheck, a memory error detector
==3483== Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al.
==3483== Using Valgrind-3.18.1 and LibVEX; rerun with -h for copyright info
==3483== Command: rpm -qa
==3483== 
==3483== 
==3483== HEAP SUMMARY:
==3483==     in use at exit: 287 bytes in 2 blocks
==3483==   total heap usage: 172,604 allocs, 172,602 frees, 121,827,169 bytes allocated
==3483== 
==3483== LEAK SUMMARY:
==3483==    definitely lost: 0 bytes in 0 blocks
==3483==    indirectly lost: 0 bytes in 0 blocks
==3483==      possibly lost: 0 bytes in 0 blocks
==3483==    still reachable: 287 bytes in 2 blocks
==3483==         suppressed: 0 bytes in 0 blocks
==3483== Rerun with --leak-check=full to see details of leaked memory
==3483== 
==3483== For lists of detected and suppressed errors, rerun with: -s
==3483== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2011-08-26
    • 1970-01-01
    • 2023-03-15
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-05-17
    相关资源
    最近更新 更多