Skip to content

Commit

Permalink
chore(capi+go): don't enable rules profiling by default.
Browse files Browse the repository at this point in the history
Also improves the profiling-related documentation in the Go library.
  • Loading branch information
plusvic committed Nov 29, 2024
1 parent fef1c63 commit 595a2bd
Show file tree
Hide file tree
Showing 6 changed files with 70 additions and 54 deletions.
4 changes: 2 additions & 2 deletions capi/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ homepage.workspace = true

[features]
# The `capi` feature is required by `cargo-c`.
default = ["capi", "rules-profiling"]
default = ["capi"]
capi = []

# When enabled, the serialization of compiled rules include native code for
Expand All @@ -29,7 +29,7 @@ native-code-serialization = ["yara-x/native-code-serialization"]

# Enables rules profiling.
#
# This feature is enabled by default.
# This feature is disabled by default.
rules-profiling = ["yara-x/rules-profiling"]


Expand Down
5 changes: 4 additions & 1 deletion capi/include/yara_x.h
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,9 @@ typedef enum YRX_RESULT {
SERIALIZATION_ERROR,
// An error returned when a rule doesn't have any metadata.
NO_METADATA,
// An error returned in cases where some API is not supported because the
// library was not built with the required features.
NOT_SUPPORTED,
} YRX_RESULT;

// A compiler that takes YARA source code and produces compiled rules.
Expand Down Expand Up @@ -708,7 +711,7 @@ enum YRX_RESULT yrx_scanner_set_global_float(struct YRX_SCANNER *scanner,
// Iterates over the top N most expensive rules, calling the callback for
// each rule.
//
// Requires the `rules-profiling` feature.
// Requires the `rules-profiling` feature, otherwise the
//
// See [`YRX_MOST_EXPENSIVE_RULES_CALLBACK`] for more details.
enum YRX_RESULT yrx_scanner_iter_most_expensive_rules(struct YRX_SCANNER *scanner,
Expand Down
3 changes: 3 additions & 0 deletions capi/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,9 @@ pub enum YRX_RESULT {
SERIALIZATION_ERROR,
/// An error returned when a rule doesn't have any metadata.
NO_METADATA,
/// An error returned in cases where some API is not supported because the
/// library was not built with the required features.
NOT_SUPPORTED,
}

/// Returns the error message for the most recent function in this API
Expand Down
77 changes: 40 additions & 37 deletions capi/src/scanner.rs
Original file line number Diff line number Diff line change
Expand Up @@ -61,11 +61,11 @@ pub unsafe extern "C" fn yrx_scanner_set_timeout(
scanner: *mut YRX_SCANNER,
timeout: u64,
) -> YRX_RESULT {
if scanner.is_null() {
return YRX_RESULT::INVALID_ARGUMENT;
}
let scanner = match scanner.as_mut() {
Some(s) => s,
None => return YRX_RESULT::INVALID_ARGUMENT,
};

let scanner = scanner.as_mut().unwrap();
scanner.inner.set_timeout(Duration::from_secs(timeout));

YRX_RESULT::SUCCESS
Expand All @@ -84,16 +84,16 @@ pub unsafe extern "C" fn yrx_scanner_scan(
) -> YRX_RESULT {
_yrx_set_last_error::<ScanError>(None);

if scanner.is_null() {
return YRX_RESULT::INVALID_ARGUMENT;
}
let scanner = match scanner.as_mut() {
Some(s) => s,
None => return YRX_RESULT::INVALID_ARGUMENT,
};

let data = match slice_from_ptr_and_len(data, len) {
Some(data) => data,
None => return YRX_RESULT::INVALID_ARGUMENT,
};

let scanner = scanner.as_mut().unwrap();
let scan_results = scanner.inner.scan(data);

if let Err(err) = scan_results {
Expand Down Expand Up @@ -178,9 +178,10 @@ pub unsafe extern "C" fn yrx_scanner_set_module_output(
data: *const u8,
len: usize,
) -> YRX_RESULT {
if scanner.is_null() {
return YRX_RESULT::INVALID_ARGUMENT;
}
let scanner = match scanner.as_mut() {
Some(s) => s,
None => return YRX_RESULT::INVALID_ARGUMENT,
};

let module_name = match CStr::from_ptr(name).to_str() {
Ok(name) => name,
Expand All @@ -195,8 +196,6 @@ pub unsafe extern "C" fn yrx_scanner_set_module_output(
None => return YRX_RESULT::INVALID_ARGUMENT,
};

let scanner = scanner.as_mut().unwrap();

match scanner.inner.set_module_output_raw(module_name, data) {
Ok(_) => {
_yrx_set_last_error::<ScanError>(None);
Expand All @@ -216,9 +215,10 @@ unsafe extern "C" fn yrx_scanner_set_global<
ident: *const c_char,
value: T,
) -> YRX_RESULT {
if scanner.is_null() {
return YRX_RESULT::INVALID_ARGUMENT;
}
let scanner = match scanner.as_mut() {
Some(s) => s,
None => return YRX_RESULT::INVALID_ARGUMENT,
};

let ident = match CStr::from_ptr(ident).to_str() {
Ok(ident) => ident,
Expand All @@ -228,8 +228,6 @@ unsafe extern "C" fn yrx_scanner_set_global<
}
};

let scanner = scanner.as_mut().unwrap();

match scanner.inner.set_global(ident, value) {
Ok(_) => {
_yrx_set_last_error::<ScanError>(None);
Expand Down Expand Up @@ -327,35 +325,40 @@ pub type YRX_MOST_EXPENSIVE_RULES_CALLBACK = extern "C" fn(
/// Iterates over the top N most expensive rules, calling the callback for
/// each rule.
///
/// Requires the `rules-profiling` feature.
/// Requires the `rules-profiling` feature, otherwise the
///
/// See [`YRX_MOST_EXPENSIVE_RULES_CALLBACK`] for more details.
#[cfg(feature = "rules-profiling")]
#[no_mangle]
#[allow(unused_variables)]
pub unsafe extern "C" fn yrx_scanner_iter_most_expensive_rules(
scanner: *mut YRX_SCANNER,
n: usize,
callback: YRX_MOST_EXPENSIVE_RULES_CALLBACK,
user_data: *mut c_void,
) -> YRX_RESULT {
if scanner.is_null() {
return YRX_RESULT::INVALID_ARGUMENT;
}

let scanner = scanner.as_ref().unwrap();
#[cfg(not(feature = "rules-profiling"))]
return YRX_RESULT::NOT_SUPPORTED;

#[cfg(feature = "rules-profiling")]
{
let scanner = match scanner.as_ref() {
Some(s) => s,
None => return YRX_RESULT::INVALID_ARGUMENT,
};

for profiling_info in scanner.inner.most_expensive_rules(n) {
let namespace = CString::new(profiling_info.namespace).unwrap();
let rule = CString::new(profiling_info.rule).unwrap();
for profiling_info in scanner.inner.most_expensive_rules(n) {
let namespace = CString::new(profiling_info.namespace).unwrap();
let rule = CString::new(profiling_info.rule).unwrap();

callback(
namespace.as_ptr(),
rule.as_ptr(),
profiling_info.pattern_matching_time.as_secs_f64(),
profiling_info.condition_exec_time.as_secs_f64(),
user_data,
);
}

callback(
namespace.as_ptr(),
rule.as_ptr(),
profiling_info.pattern_matching_time.as_secs_f64(),
profiling_info.condition_exec_time.as_secs_f64(),
user_data,
);
YRX_RESULT::SUCCESS
}

YRX_RESULT::SUCCESS
}
23 changes: 21 additions & 2 deletions go/scanner.go
Original file line number Diff line number Diff line change
Expand Up @@ -247,6 +247,13 @@ func (s *Scanner) Scan(buf []byte) (*ScanResults, error) {
return scanResults, err
}

// ProfilingInfo contains profiling information about a YARA rule.
//
// For each rule it contains: the rule's namespace, the rule's name,
// the time spent in matching patterns declared by the rule, and the time
// spent evaluating the rule's condition.
//
// See [Scanner.MostExpensiveRules].
type ProfilingInfo struct {
Namespace string
Rule string
Expand Down Expand Up @@ -276,16 +283,28 @@ func mostExpensiveRulesCallback(
})
}

// MostExpensiveRules returns information about the slowest rules and how much
// time they spent matching patterns and executing their conditions.
//
// In order to use this function the YARA-X C library must be built with
// support for rules profiling, which is done by enabling the `rules-profiling`
// feature. Otherwise, calling this function will cause a panic.
func (s *Scanner) MostExpensiveRules(n int) []ProfilingInfo {
profilingInfo := make([]ProfilingInfo, 0)
mostExpensiveRules := cgo.NewHandle(&profilingInfo)
defer mostExpensiveRules.Delete()

if C._yrx_scanner_iter_most_expensive_rules(
result := C._yrx_scanner_iter_most_expensive_rules(
s.cScanner,
C.size_t(n),
C.YRX_MOST_EXPENSIVE_RULES_CALLBACK(C.mostExpensiveRulesCallback),
C.uintptr_t(mostExpensiveRules)) != C.SUCCESS {
C.uintptr_t(mostExpensiveRules))

if result == C.NOT_SUPPORTED {
panic("MostExpensiveRules requires that the YARA-X C library is built with the `rules-profiling` feature")
}

if result != C.SUCCESS {
panic("yrx_scanner_iter_most_expensive_rules failed")
}

Expand Down
12 changes: 0 additions & 12 deletions go/scanner_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -91,18 +91,6 @@ func TestScannerTimeout(t *testing.T) {
assert.ErrorIs(t, err, ErrTimeout)
}

func TestScannerMostExpensiveRules(t *testing.T) {
r, _ := Compile("rule t { strings: $a = /a(.*)*a/ condition: $a }")
s := NewScanner(r)
_, err := s.Scan(bytes.Repeat([]byte("a"), 5000))
assert.NoError(t, err)
profilingInfo := s.MostExpensiveRules(1)
assert.Equal(t, "t", profilingInfo[0].Rule)
assert.Equal(t, "default", profilingInfo[0].Namespace)
assert.Greater(t, profilingInfo[0].PatternMatchingTime, time.Duration(0))
assert.Greater(t, profilingInfo[0].ConditionExecTime, time.Duration(0))
}

func TestScannerMetadata(t *testing.T) {
r, _ := Compile(`rule t {
meta:
Expand Down

0 comments on commit 595a2bd

Please sign in to comment.