Skip to content

Commit 7402447

Browse files
committed
Implement SMI detection using the standard deviation
1 parent 528e48a commit 7402447

File tree

1 file changed

+53
-12
lines changed

1 file changed

+53
-12
lines changed

chipsec/modules/tools/smm/smm_ptr.py

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

8788
from chipsec.module_common import BaseModule
8889
from chipsec.library.returncode import ModuleResult
@@ -145,12 +146,12 @@
145146
# very obscure option, don't even try to understand
146147
GPR_2ADDR = False
147148

148-
# Defines the time percentage increase at which the SMI call is considered to
149-
# be long-running
150-
OUTLIER_THRESHOLD = 33.3
149+
# Defines the threshold in standard deviations at which the SMI call is
150+
# considered long-running
151+
OUTLIER_STD_DEV = 2
151152

152-
# Scan mode delay before retry
153-
SCAN_MODE_RETRY_DELAY = 0.01
153+
# Number of samples used for initial calibration
154+
SCAN_CALIB_SAMPLES = 50
154155

155156
# SMI count MSR
156157
MSR_SMI_COUNT = 0x00000034
@@ -207,6 +208,12 @@ def __init__(self):
207208
self.helper = OsHelper().get_default_helper()
208209
self.helper.init()
209210
self.smi_count = self.get_smi_count()
211+
self.needs_calibration = True
212+
self.calib_samples = 0
213+
self.stdev = 0
214+
self.m2 = 0
215+
self.stdev_hist = 0
216+
self.m2_hist = 0
210217

211218
def __del__(self):
212219
self.helper.close()
@@ -254,6 +261,10 @@ def clear(self):
254261
self.outliers = 0
255262
self.code = None
256263
self.confirmed = False
264+
self.needs_calibration = True
265+
self.calib_samples = 0
266+
self.stdev = 0
267+
self.m2 = 0
257268

258269
def add(self, duration, code, data, gprs, confirmed=False):
259270
if not self.code:
@@ -262,6 +273,7 @@ def add(self, duration, code, data, gprs, confirmed=False):
262273
if not outlier:
263274
self.acc_smi_duration += duration
264275
self.acc_smi_num += 1
276+
self.update_stdev(duration)
265277
if duration > self.max.duration:
266278
self.max.update(duration, code, data, gprs.copy())
267279
elif duration < self.min.duration:
@@ -281,24 +293,49 @@ def avg(self):
281293
self.acc_smi_duration = 0
282294
self.acc_smi_num = 0
283295

296+
#
297+
# Computes the standard deviation using the Welford's online algorithm
298+
#
299+
def update_stdev(self, value):
300+
difference = value - self.avg_smi_duration
301+
self.difference_hist = value - self.hist_smi_duration
302+
self.avg()
303+
self.m2 += difference * (value - self.avg_smi_duration)
304+
self.m2_hist += self.difference_hist * (value - self.hist_smi_duration)
305+
variance = self.m2 / self.avg_smi_num
306+
variance_hist = self.m2_hist / self.hist_smi_num
307+
self.stdev = math.sqrt(variance)
308+
self.stdev_hist = math.sqrt(variance_hist)
309+
310+
def update_calibration(self, duration):
311+
if not self.needs_calibration:
312+
return
313+
self.acc_smi_duration += duration
314+
self.acc_smi_num += 1
315+
self.update_stdev(duration)
316+
self.calib_samples += 1
317+
if self.calib_samples >= SCAN_CALIB_SAMPLES:
318+
self.needs_calibration = False
319+
284320
def is_slow_outlier(self, value):
285321
ret = False
286-
if self.avg_smi_duration and value > self.avg_smi_duration * (1 + OUTLIER_THRESHOLD / 100):
322+
if value > self.avg_smi_duration + OUTLIER_STD_DEV * self.stdev:
287323
ret = True
288-
if self.hist_smi_duration and value > self.hist_smi_duration * (1 + OUTLIER_THRESHOLD / 100):
324+
if value > self.hist_smi_duration + OUTLIER_STD_DEV * self.stdev_hist:
289325
ret = True
290326
return ret
291327

292328
def is_fast_outlier(self, value):
293329
ret = False
294-
if self.avg_smi_duration and value < self.avg_smi_duration * (1 - OUTLIER_THRESHOLD / 100):
330+
if value < self.avg_smi_duration - OUTLIER_STD_DEV * self.stdev:
295331
ret = True
296-
if self.hist_smi_duration and value < self.hist_smi_duration * (1 - OUTLIER_THRESHOLD / 100):
332+
if value < self.hist_smi_duration - OUTLIER_STD_DEV * self.stdev_hist:
297333
ret = True
298334
return ret
299335

300336
def is_outlier(self, value):
301-
self.avg()
337+
if self.needs_calibration:
338+
return False
302339
ret = False
303340
if self.is_slow_outlier(value):
304341
ret = True
@@ -501,13 +538,17 @@ def smi_fuzz_iter(self, thread_id, _addr, _smi_desc, fill_contents=True, restore
501538
else:
502539
while True:
503540
_, duration = 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)
504-
if scan.valid_smi_count():
541+
if not scan.valid_smi_count():
542+
continue
543+
if scan.needs_calibration:
544+
scan.update_calibration(duration)
545+
continue
546+
else:
505547
break
506548
#
507549
# Re-do the call if it was identified as an outlier, due to periodic SMI delays
508550
#
509551
if scan.is_outlier(duration):
510-
time.sleep(SCAN_MODE_RETRY_DELAY)
511552
while True:
512553
_, duration = 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)
513554
if scan.valid_smi_count():

0 commit comments

Comments
 (0)