我最近在測試一個M0+ MCU的運(yùn)行功耗,測試代碼采用如下最簡單的方式,即main函數(shù)里只跑一個while(1)空循環(huán),測試出來的電流是1.11mA,使用的IDE為KEIL MDK,優(yōu)化等級為0
當(dāng)我在while(1)的前面插入3條NOP指令,測出來的電流卻變成了0.89mA。
這是怎么回事?是測量誤差,還是事實(shí)就是如此?這可是足足差了200多uA啊,為此我又做了如下幾個對比實(shí)驗(yàn)。
測試條件 | 功耗 |
優(yōu)化等級0,while(1)前不加NOP | 1.11mA |
優(yōu)化等級0,while(1)前插入1個NOP | 0.90mA |
優(yōu)化等級0,while(1)前插入2個NOP | 1.11mA |
優(yōu)化等級0,while(1)前插入3個NOP | 0.89mA |
優(yōu)化等級0,while(1)前插入4個NOP | 1.12mA |
優(yōu)化等級0,while(1)前插入5個NOP | 0.91mA |
優(yōu)化等級0,while(1)前插入6個NOP | 1.11mA |
優(yōu)化等級0,while(1)前插入7個NOP | 0.88mA |
優(yōu)化等級0,while(1)前插入8個NOP | 1.11mA |
上述實(shí)驗(yàn)可以看到明顯的規(guī)律,只要while(1)前插入的NOP是奇數(shù)時功耗就相對小一點(diǎn)(差不多都是約0.9mA),while(1)前插入的NOP是偶數(shù)時功耗就大一點(diǎn)(差不多都是約1.11mA)。
說到這里,我們需要來了解一下NOP指令,我之前對NOP指令的理解只停留在它可以用來做軟件延時用,其實(shí)它還有一個重要的作用是實(shí)現(xiàn)指令對齊
在調(diào)試窗口下,我們看一下匯編代碼
C代碼的while(1)被匯編成了2條指令,即NOP和B,跳轉(zhuǎn)指令B前自動插了一個NOP。while(1)實(shí)際上是先執(zhí)行一個NOP指令,再執(zhí)行B指令,B指令跳轉(zhuǎn)的地址就是自身的地址,達(dá)到無限循環(huán)的效果??梢钥吹酱藭rwhile(1)里NOP指令地址是0x00000152(十進(jìn)制338),B指令地址是0x00000154(十進(jìn)制340)。
當(dāng)while(1)前插入奇數(shù)條NOP指令后,while(1)對應(yīng)的指令地址會改變。
指令地址的變化為什么會影響功耗呢?這又得需要提一下CPU執(zhí)行指令的過程。
CPU內(nèi)部一直重復(fù)執(zhí)行著 Fetch(取指令)–> Decode(指令譯碼)–> Execute(執(zhí)行指令)的過程。
CPU在執(zhí)行程序取指令的時候,每次按照Flash 4字節(jié)對齊的方式從Flash一次讀32bit的指令,如果while(1)前插入偶數(shù)(包括0)個NOP指令,那么CPU在執(zhí)行while(1)時,需要從Flash讀取2次32bit內(nèi)容再Decode去執(zhí)行。如果while(1)前插入奇數(shù)個NOP指令,那么CPU在執(zhí)行while(1)時,只需要從Flash讀取1次32bit內(nèi)容即可。就是這個地方的差異會引起功耗的差異,前者要執(zhí)行更多的操作所以功耗更大一點(diǎn)。
此外如果while(1)前不加入NOP,但是把優(yōu)化等級調(diào)到最高,此時while(1)里 B指令前就不會插入一條NOP指令,這時B指令的地址為0x00000152,這時效果和不開優(yōu)化等級、while(1)之前插入奇數(shù)個NOP一樣,功耗也會低一點(diǎn)。道理其實(shí)是一樣的,因?yàn)閣hile(1)的執(zhí)行只需要從0x150地址取一次指。
最后我還做了一個實(shí)驗(yàn),就是把程序放到了RAM里,不管while(1)前加多少NOP,功耗都是一樣,都是0.58mA。程序在RAM里,就不用從Flash里讀程序了,所以功耗更低。
利用功耗的不同去做破解的行為,也是類似的原理。
以上分析僅是猜測,因?yàn)椴涣私釳CU內(nèi)部的運(yùn)行細(xì)節(jié),如果不對之處,歡迎大家指正。