簡體   English   中英

Valgrind 條件跳轉...讀取文件時 PCRE2 JIT 出錯

[英]Valgrind conditional jump ... error with PCRE2 JIT when reading from file

我有一個非常有趣的問題。

我想使用 PCRE2 及其 JIT function。任務很簡單:從文件中讀取行,並找到模式。

這是示例代碼:

#include <stdio.h>
#include <string.h>

#define PCRE2_CODE_UNIT_WIDTH 8
#include <pcre2.h>

int search(pcre2_code *re, unsigned char * subject) {

    pcre2_match_data *match_data_real = pcre2_match_data_create_from_pattern(re, NULL);

    size_t len_subject = strlen((const char *)subject);

    int rc = pcre2_match(
        re,
        (PCRE2_SPTR)subject,
        len_subject,
        0,
        0,
        match_data_real,
        NULL
    );

    pcre2_match_data_free(match_data_real);
    return rc;
}

int main(int argc, char ** argv) {

    unsigned char subject[][100]     = {
        "this is a foobar",
        "this is a barfoo",
        "this is a barbar",
        "this is a foofoo"
    };

    pcre2_code *re;
    PCRE2_SPTR  pattern = (unsigned char *)"foo";
    int         errornumber;
    PCRE2_SIZE  erroroffset;

    re = pcre2_compile(
        pattern,
        PCRE2_ZERO_TERMINATED,
        0,
        &errornumber,
        &erroroffset,
        NULL
    );
    pcre2_jit_compile(re, PCRE2_JIT_COMPLETE);

    FILE *fp;

    int s = 0;
    while(s < 2) {
        search(re, subject[s++]);
    }

    if (argc >= 2) {
        fp = fopen(argv[1], "r");
        if (fp != NULL) {
            char tline[2048];
            while(fgets(tline, 2048, fp) != NULL) {
                search(re, (unsigned char *)tline);
            }
            fclose(fp);
        }
    }

    pcre2_code_free(re);

    return 0;
}

編譯代碼:

gcc -Wall -O2 -g pcretest.c -o pcretest -lpcre2-8

如您所見,在第 58 行我檢查是否給定了參數,代碼嘗試將其作為文件打開。

另外,正如您在第 49 行中看到的,我想使用 PCRE2 的 JIT。

該代碼也能正常工作,但我用 Valgrind 對其進行了檢查,發現了一個有趣的行為:

  • 如果我添加一個文件作為參數,則 Valgrind 報告Conditional jump or move depends on uninitialised value(s) and Uninitialised value was created by a stack allocation ,但它指向main() 命令: valgrind --tool=memcheck --leak-check=full --show-leak-kinds=all --track-origins=yes -s./pcretest myfile.txt
  • 沒有爭論,就沒有任何 Valgrind 報告。 命令: valgrind --tool=memcheck --leak-check=full --show-leak-kinds=all --track-origins=yes -s./pcretest
  • 如果我注釋掉pcre2_jit_compile((*re), PCRE2_JIT_COMPLETE); 在第 55 行,然后一切正常,沒有任何 Valgrind 報告。 命令: valgrind --tool=memcheck --leak-check=full --show-leak-kinds=all --track-origins=yes -s./pcretest myfile.txt

Valgrind的相關output:

==31385== Conditional jump or move depends on uninitialised value(s)
==31385==    at 0x4EECD1A: ???
==31385==    by 0x1FFEFFFC1F: ???
==31385==  Uninitialised value was created by a stack allocation
==31385==    at 0x1090FA: main (pcretest.c:27)
...
==31385== HEAP SUMMARY:
==31385==     in use at exit: 0 bytes in 0 blocks
==31385==   total heap usage: 12 allocs, 12 frees, 13,486 bytes allocated
==31385== 
==31385== All heap blocks were freed -- no leaks are possible
==31385== 
==31385== ERROR SUMMARY: 1 errors from 1 contexts (suppressed: 0 from 0)
==31385== 
==31385== 1 errors in context 1 of 1:
==31385== Conditional jump or move depends on uninitialised value(s)
==31385==    at 0x4EECD1A: ???
==31385==    by 0x1FFEFFFC1F: ???
==31385==  Uninitialised value was created by a stack allocation
==31385==    at 0x1090FA: main (pcretest.c:27)
==31385== 
==31385== ERROR SUMMARY: 1 errors from 1 contexts (suppressed: 0 from 0)

在第 27 行有int main(...)

我想念什么?

觀察

  • Valgrind 報告告訴您正在訪問的未初始化數據位於對main()的初始調用的堆棧幀中。 然而,

  • 即使您使用調試信息進行編譯,Valgrind 報告也不會暗示特定變量。 還,

  • 該報告的錯誤堆棧跟蹤不提供函數名稱,也不會追溯到main() 而且當然,

  • 當您禁用模式的 JIT 編譯時,不會報告錯誤。

顯然,該錯誤與 PCRE2 的 JIT 編譯器生成的機器碼有關 如果您不執行 JIT 編譯,那么您將通過普通的匹配路徑獲得正確的操作。 如果您確實執行了 JIT 編譯,那么 JIT 生成的代碼就會被使用,並且該代碼會觸發 Valgrind 錯誤。 盡管如此,您可能會得到正確的匹配,但我不會依賴它來觸發觀察到的 Valgrind 錯誤的代碼。

我嘗試了您代碼的變體,發現該錯誤與函數search()中對pcre2_match_data_create_from_pattern()pcre2_match()的調用特別相關。 任何一個都會導致 Valgrind 報告錯誤。 為什么錯誤只發生在對search()的某些調用中

這似乎是因為 JIT 編譯在main()的棧幀中設置了數據結構,這些數據結構被執行if (argc > 2)語句的主體破壞了。 這與我能夠通過在該塊中為變量tline添加初始化程序來避免錯誤的事實是一致的:

            char tline[2048] = {0};

我可以想象為什么這會產生影響的各種場景,所有這些都與 JIT 生成的代碼和編譯器生成的代碼如何操作堆棧指針有關。

就個人而言,發現這樣的問題可能會說服我遠離 PCRE 的 JIT 編譯器。 至少在我有證據表明模式匹配是我程序的性能熱點之前,我肯定會這樣做。 但是,如果您必須使用 JIT,那么這里有一些可能(或可能不會)幫助您避免麻煩的建議:

  1. 牢記“及時”:盡可能接近實際使用模式時執行 JIT。

  2. 不要假設 JIT 代碼是長期可行的。 特別是,在調用 JIT 編譯器的函數返回后使用它可能是不安全的,但即使那么久也可能不好。

  3. 在運行 JIT 編譯器的同一函數中(僅)使用 JIT 編譯的正則表達式。

  4. 使該功能盡可能簡單。

  5. 在開始時使用初始值設定項聲明該函數的所有局部變量。

  6. 徹底測試。

這似乎比解決您的特定示例代碼的問題所必需的要多,但它更普遍地旨在減少違反 JIT 所做假設的已編譯程序的橫截面。

這確實是高效使用SSE2造成的。 CPU-s 使用 1K 或更大的頁面到 map memory,因此與有效緩沖區相交的 16 字節對齊的 16 字節讀取始終有效。 然而,16 字節數據的某些部分可能永遠不會被初始化,但算法會忽略這些字節,因此隨機字節對任何計算都沒有影響。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM