Skip to content

Commit 0bc9c08

Browse files
committed
Using the standard deviation to identify outliers
1 parent 134a9cd commit 0bc9c08

File tree

1 file changed

+63
-7
lines changed

1 file changed

+63
-7
lines changed

chipsec/modules/tools/smm/smm_ptr.py

Lines changed: 63 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,7 @@
8383
import struct
8484
import sys
8585
import time
86+
import math
8687

8788
from chipsec.module_common import BaseModule
8889
from chipsec.library.returncode import ModuleResult
@@ -147,6 +148,8 @@
147148
# Defines the time percentage increase at which the SMI call is considered to
148149
# be long-running
149150
OUTLIER_THRESHOLD = 10
151+
OUTLIER_STD_DEV = 2
152+
SCAN_CALIB_SAMPLES = 50
150153

151154
# Scan mode delay before SMI calls
152155
SCAN_MODE_DELAY = 0.01
@@ -205,6 +208,15 @@ def __init__(self):
205208
self.outliers_hist = 0
206209
self.records = {'deltas': [], 'times': []}
207210
self.msr_count = self.get_msr_count()
211+
self.stdev = 0
212+
self.stdev_hist = 0
213+
self.m2 = 0
214+
self.m2_hist = 0
215+
self.difference = 0
216+
self.difference_hist = 0
217+
self.needs_calibration = True
218+
self.calib_samples = 0
219+
self.first_measurement = True
208220

209221
def get_msr_count(self):
210222
cpu = 0
@@ -214,6 +226,12 @@ def get_msr_count(self):
214226
os.close(fd)
215227
return count
216228

229+
def is_first_measurement(self):
230+
is_first = self.first_measurement
231+
if self.first_measurement:
232+
self.first_measurement = False
233+
return is_first
234+
217235
def check_inc_msr(self):
218236
valid = False
219237
count = self.get_msr_count()
@@ -243,6 +261,12 @@ def clear(self):
243261
self.code = None
244262
self.confirmed = False
245263
self.records = {'deltas': [], 'times': []}
264+
self.stdev = 0
265+
self.m2 = 0
266+
self.difference = 0
267+
self.needs_calibration = True
268+
self.calib_samples = 0
269+
self.first_measurement = True
246270

247271
def add(self, duration, time, code, data, gprs, confirmed=False):
248272
if not self.code:
@@ -252,6 +276,7 @@ def add(self, duration, time, code, data, gprs, confirmed=False):
252276
self.records['times'].append(time)
253277
self.acc_smi_duration += duration
254278
self.acc_smi_num += 1
279+
self.update_stdev(duration)
255280
if not outlier:
256281
if duration > self.max.duration:
257282
self.max.update(duration, code, data, gprs.copy())
@@ -272,24 +297,49 @@ def avg(self):
272297
self.acc_smi_duration = 0
273298
self.acc_smi_num = 0
274299

300+
def update_stdev(self, value):
301+
self.difference = value - self.avg_smi_duration
302+
self.difference_hist = value - self.hist_smi_duration
303+
self.avg()
304+
self.m2 += self.difference * (value - self.avg_smi_duration)
305+
self.m2_hist += self.difference_hist * (value - self.hist_smi_duration)
306+
variance = self.m2 / self.avg_smi_num
307+
variance_hist = self.m2_hist / self.hist_smi_num
308+
self.stdev = math.sqrt(variance)
309+
self.stdev_hist = math.sqrt(variance_hist)
310+
311+
def update_calibration(self, duration):
312+
if not self.needs_calibration:
313+
return
314+
self.acc_smi_duration += duration
315+
self.acc_smi_num += 1
316+
self.update_stdev(duration)
317+
self.calib_samples += 1
318+
if self.calib_samples >= SCAN_CALIB_SAMPLES:
319+
self.needs_calibration = False
320+
print(f"Calibration done. stdev: {self.stdev}, mean: {self.avg_smi_duration}, samples: {self.calib_samples}")
321+
322+
275323
def is_slow_outlier(self, value):
276324
ret = False
277-
if self.avg_smi_duration and value > self.avg_smi_duration * (1 + OUTLIER_THRESHOLD / 100):
325+
if value > self.avg_smi_duration + OUTLIER_STD_DEV * self.stdev:
278326
ret = True
279-
if self.hist_smi_duration and value > self.hist_smi_duration * (1 + OUTLIER_THRESHOLD / 100):
327+
if value > self.hist_smi_duration + OUTLIER_STD_DEV * self.stdev_hist:
280328
ret = True
281329
return ret
282330

283331
def is_fast_outlier(self, value):
284332
ret = False
285-
if self.avg_smi_duration and value < self.avg_smi_duration * (1 - OUTLIER_THRESHOLD / 100):
333+
if value < self.avg_smi_duration - OUTLIER_STD_DEV * self.stdev:
286334
ret = True
287-
if self.hist_smi_duration and value < self.hist_smi_duration * (1 - OUTLIER_THRESHOLD / 100):
335+
if value < self.hist_smi_duration - OUTLIER_STD_DEV * self.stdev_hist:
288336
ret = True
289337
return ret
290338

291339
def is_outlier(self, value):
292-
self.avg()
340+
if self.needs_calibration:
341+
return False
342+
293343
ret = False
294344
if self.is_slow_outlier(value):
295345
ret = True
@@ -308,7 +358,6 @@ def get_total_outliers(self):
308358
return self.outliers_hist
309359

310360
def get_info(self):
311-
self.avg()
312361
avg = self.avg_smi_duration or self.hist_smi_duration
313362
info = f"average {round(avg)} checked {self.avg_smi_num + self.outliers}"
314363
if self.outliers:
@@ -496,7 +545,14 @@ def smi_fuzz_iter(self, thread_id, _addr, _smi_desc, fill_contents=True, restore
496545
while True:
497546
#time.sleep(SCAN_MODE_DELAY)
498547
_, duration, start = self.send_smi_timed(thread_id, _smi_desc.smi_code, _smi_desc.smi_data, _smi_desc.name, _smi_desc.desc, _rax, _rbx, _rcx, _rdx, _rsi, _rdi)
499-
if scan.check_inc_msr():
548+
if scan.is_first_measurement():
549+
continue
550+
if not scan.check_inc_msr():
551+
continue
552+
if scan.needs_calibration:
553+
scan.update_calibration(duration)
554+
continue
555+
else:
500556
break
501557
#
502558
# Re-do the call if it was identified as an outlier, due to periodic SMI delays

0 commit comments

Comments
 (0)