【hyperscan】示例解讀 simplegrep

示例位置: <hyperscan source>/examples/simplegrep.c
參考:http://01org.github.io/hyperscan/dev-reference/api_files.htmlhtml

 

1. 概述

此示例實現一個grep的簡化版本:指定一個正則表達式和文件,執行後依次輸出匹配位置。c++

但這個簡單示例並不支持從stdin讀取數據,也不支持grep那豐富的命令行參數。git

simplegrep演示瞭如下hyperscan概念:github

  1. 單個模式的編譯
    使用最簡單的hs_compile接口,僅支持一個正則表達式。支持多個表達式同時編譯的API是hs_compile_multi
  2. Block方式的模式匹配
    在單個數據塊上進行搜索匹配;更復雜的是在流(stream)上進行匹配,它能夠跨數據塊進行模式匹配
  3. 臨時數據(scratch)的分配與使用
    hyperscan在匹配時須要一塊臨時數據(記爲D),調用者須要保證在同一時刻只有一個hs_scan接口使用同一D,但並不要求連續的hs_can調用必須使用同一個D。因爲D的分配代價昂貴,爲了性能考慮,用戶最好在運行前就分配好D並在運行時重用它。

2. 源碼解讀

這個示例很是簡單,這裏只解讀表達式編譯和匹配兩部分的代碼,讀取數據文件等代碼忽略。正則表達式

2.1 編譯正則表達式(compile)

進行匹配以前,首先須要編譯正則表達式,生成hs_database_t。express

    hs_database_t *database;
    hs_compile_error_t *compile_err;
    if (hs_compile(pattern, HS_FLAG_DOTALL, HS_MODE_BLOCK, NULL, &database, &compile_err) != HS_SUCCESS) {
        fprintf(stderr, "ERROR: Unable to compile pattern \"%s\": %s\n",
                pattern, compile_err->message);
        hs_free_compile_error(compile_err);
        return -1;
    }

hs_compile的原型是api

hs_error_t hs_compile(const char * expression, 
unsigned int flags,
unsigned int mode,
const hs_platform_info_t * platform,
hs_database_t ** db,
hs_compile_error_t ** error)

其中,expression是正則表達式字符串;flags用來控制正則的行爲,好比忽略大小寫,使.包含換行等;mode肯定了生成database的格式,主要有BLOCK,STREAM和VECTOR三種,每一種模式的database只能由相應的scan接口使用;platform用來指定此database的目標平臺(主要是一些CPU特性),爲NULL表示目標平臺與當前平臺一致;db用來保存編譯後的database;error接收錯誤信息。函數

2.2 進行匹配(scan)

首先分配好每次匹配須要用的臨時數據(scratch)。性能

hs_scratch_t *scratch = NULL;
    if (hs_alloc_scratch(database, &scratch) != HS_SUCCESS) {
        fprintf(stderr, "ERROR: Unable to allocate scratch space. Exiting.\n");
        free(inputData);
        hs_free_database(database);
        return -1;
    }

接下來進行匹配(scan)。spa

if (hs_scan(database, inputData, length, 0, scratch, eventHandler, pattern) != HS_SUCCESS) {
        fprintf(stderr, "ERROR: Unable to scan input buffer. Exiting.\n");
        hs_free_scratch(scratch);
        free(inputData);
        hs_free_database(database);
        return -1;
    }

hs_scan的原型是

hs_error_t hs_scan(const hs_database_t * db, 
const char * data,
unsigned int length,
unsigned int flags,
hs_scratch_t * scratch,
match_event_handler onEvent,
void * context)

其中,db就是上一步編譯的databas;data和length分別是要匹配的數據和數據長度;flags用來在將來版本中控制函數行爲,目前未使用;scratch是匹配時要用的臨時數據,以前已經分配好;onEvent很是關鍵,即匹配時調用的回調函數,由用戶指定;context是用戶自定義指針。

匹配回調函數的原型是

typedef (* match_event_handler)(unsigned int id, 
unsigned long long from,
unsigned long long to,
unsigned int flags,
void *context)

其中,id是命中的正則表達式的ID,對於使用hs_compile編譯的惟一表達式來講,此值爲0;若是在編譯時指定了相關模式選項(hs_compile中的mode參數),則此值將會設爲匹配特徵的起始位置,不然會設爲0;to是命中數據的下一個字節的偏移;flags目前未用;context是用戶自定義指針。

返回值爲非0表示中止匹配,不然繼續;在匹配的過程當中,每次命中時都將同步調用匹配回調函數,直到匹配結束。

本例中的回調函數是

static int eventHandler(unsigned int id, unsigned long long from,
                        unsigned long long to, unsigned int flags, void *ctx) {
    printf("Match for pattern \"%s\" at offset %llu\n", (char *)ctx, to);
    return 0;
}

輸出了正則表達式和其匹配的位置(命中數據的下一個字節在數據中的偏移值)。

2.3 清理資源

程序結束後,應清理相關數據,釋放內存。

 hs_free_scratch(scratch);
    free(inputData);
    hs_free_database(database);

3. 編譯運行

編譯以前,我已經經過make install將hyperscan頭文件和靜態庫安裝在了/usr/local相關目錄中。

gcc -o simplegrep simplegrep.c -lhs -lstdc++ -lm

注意連接stdc++和math庫 (lstdc++ -lm)。若是是連接動態庫,不須要加-lstdc++ -lm。

運行,在另外一示例代碼pcapscan.cc中匹配/[f|F]ile/:

./simplegrep '[f|F]ile' pcapscan.cc   
Scanning 22859 bytes with Hyperscan
Match for pattern "[f|F]ile" at offset 1692
.....(略,共45次匹配)

用grep命令驗證結果

grep -o '[f|F]ile' pcapscan.cc | wc -l
45

OK,也是45次。

相關文章
相關標籤/搜索