contributed by < paul90317
>
Urbaner
目前分數 100/100
Image Not Showing Possible ReasonsLearn More →
- The image file may be corrupted
- The server hosting the image is unavailable
- The image path is incorrect
- The image format is not supported
container_of
是 某個東西的容器,只要把指標丟進去,便可以反向定位出容器的指標。
ptr
為容器中成員的指標, type
為容器的類別, member
為成員的名稱。
舉例:container_of(node, element_t, list)
,其中 node
的類別是 struct list_head
head
:head of queue,不是第一個元素entry
:類別是 element_t
,容器node
:類別是 struct list_head
INIT_LIST_HEAD(head)
:將指標 struct list_head
初始化成一個大小為 0 的 queue。
既然此處的鏈結串列為雙向,那「順向」到底是哪個方向?需要精準用語和定義。
list_for_each(node, head)
:單純的向後 (向 next
) 走訪,當對 node
進行修改會發生未定義行為,不適合用於這裡。list_for_each_safe(node, safe, head)
:多一個 safe
預先儲存下一個存取的節點,便可以在每次對 node
修改。list_for_each_entry_safe(entry, safe, head, member)
:由於存取每個節點都要取得 node
的容器,並且將 value
釋放,不如直接拿到容器 entry
會更漂亮。strncpy(s1, s2, n)
重點如下
If the array pointed to by s2 is a string that is shorter than n characters,
–-> null characters are appended to the copy in the array pointed to by s1,
–-> until n characters in all have been written.
如果 s2
比 n
短,會在 s1
中的副本的結尾接上空字元,否則只會把 s2
前 n
個字元複製到 s1 中,所以為了防止後續對 s1
進行超出記憶體的讀寫 (讀不到空字元),都會在使用該函式後固定把緩存 s1
最後變成空字元。
當 fast
繞了一圈後,slow
會剛好在中間。
走訪 list,存取每一個 entry
,如果當前元素 (entry
) 的下一個元素 (next
) 與 entry
有相同的值時,會執行以下步驟
next
,直到停在第一個不同於 entry
的值,並釋放所經元素的記憶體。entry
前一元素與 next
串連,釋放 entry
。變數說明如下
prev
:當前節點的上一節點node
:當前節點next
:當前節點的下一節點nextnext
:當前節點的下下節點while
說明如下
init
:初始化變數condition
:當剩下兩個以下的節點則結束迭代statement
:交換 node
與 next
,並與 prev
和 nextnext
進行連接iteration
:迭代 node
兩次,並給予其他變數新的值使用 list_for_each_safe
可以讀寫 node
,原理請見 q_free
使用 stack,每 k 個元素作為一組將其推入 stack 中,然後再依序取出來,就會得到相反順序的元素序列
改進你的漢語表達。
已透過 Chat GPT 改進
先來分析題目,每個元素 entry
會不會被刪掉取決在右側是否有一個比自己大的元素。
所以只要由右而左走訪,紀錄最大值 best
,就可以在存取每個元素時只需要比較一次。
使用 merge sort 進行排序
l_merge
將 left
與 right
合併到 head
的尾端。
q_sort
使用快慢指標找中點 slow
,在用 list_cut_position
將其一分為二成 left
和 right
,分別做排序,再用 l_merge
合併。
list_cut_position(left, right, mid)
:將 right
以元素 mid
進行切割,左邊放到 left
(包含 mid
),右邊保留在 right
。持續將 queue 兩兩合併,直到只剩 1 個 queue。
for
:每次迭代會將兩個 queue 合併成一個並放到 slow
指向的 queue 裡 (原本是空的),當只剩一個 queue 時,在 for
結束時會直接放到 slow
指向的 queue 裡。while
:若當前有 n
個 queue,經過迭代後會剩 floor[(n + 1) / 2]
個 (因為會兩兩合併),最後剩下一個就是題目要的解。初看論文 Dude, is my code constant time? 之後,發現 dudect 測試工具其實是對某個函式進行大量測試,得到對不同 cycle 的機率分佈,從而推論該函式是否為 constant time。
所以首先,我們先觀測目前的機率分佈,看看為何我的程式碼不是 constant time。
可以看出 q_insert_head
在 cycle 為 350 附近有不明原因的高峰,經過排查,這是 malloc
造成的,原因有待查證。
如果 dudect 是從機率分佈判斷是否為 constant time,解法就很簡單了,讓 cycle 為 150 的樣本跑完後等到 cycle 350 的樣本結束再離開,該方法用到 busy loop,以下是程式碼。
為了解釋該巨集的用法,我舉一個例子,當有一個函式 float f(int a,char *b)
,而函式呼叫 f(a, b)
套用巨集變成 at_least_cycle(500, float, f, a, b)
,第一個參數是至少要跑多少 cycle,第二個參數是函式回傳值,第三個參數是原本的函式的指標,剩下的參數就是原本 f
的參數。
機率分佈如下
最後將程式碼上傳,順利看到皮卡丘。
Action
TODO
理解論文 Dude, is my code constant time? 想要做的事,改進 simulation 程式。而非用 busy loop 假解,FB 討論區
qtest
實作的記憶體錯誤輸入命令 valgrind ./qtest < traces/trace-01-ops.cmd
可得以下輸出。
第 1 行與第 9 行是因為 queues 仍然可以存取,我最一開始的作法是在 main 函式最後放置以下程式碼
然而在執行 trace-14
陷入無窮迴圈,當時使用的指令是 make valgrind
。本以為是排序演算法太慢,除錯後發現是因為節點太多釋放且釋放每個節點的時間過長。
解決方法是加 set_cautious_mode(false)
在上面提到的程式碼的前面,可降低每個節點釋放時間 。
也可以直接移除 q_quit
第一行的 return true;
,因為 q_quit
本來就是釋放 queues 的函式。
第 19 行以後是在 line_history_add
第 1263 與 1275 行所分配的記憶體沒有釋放。
為了要在程式結束前將 history
釋放,在 linenoise.c
增加以下函式
在 if (!history) {
區塊中插入 add_quit_helper(history_free);
以註冊釋放事件。
以 add_quit_helper
註冊的函式會在 main 函式中所執行的 finish_cmd
執行。
接著執行命令 make valgrind
,沒有錯誤訊息。
commit
論文中,使用兩種資料集 (fix 和 random) 作為輸入,產出兩個 distribution,對他們進行 Welch’s t-test,試圖推翻兩者相同的 null hypothesis,推翻代表程式非常數時間,否則有可能是常數時間。
在 lab0 中,random 資料集的每次測試,是先產生隨機大小的 queue,接著執行欲測試函式並紀錄其時間,fix 則都是大小為 0 (測試 remove 大小為 1) 的 queue。
以下是用於統計計算的結構。
取自論文原始碼 dudect.h
引用論文
Cropping: Typical timing distributions are positively
skewed towards larger execution times. This may be caused by
measurement artifacts, the main process being interrupted by
the OS or other extraneous activities. In this case, it may be
convenient to crop the measurements (or discard measurements
that are larger than a fixed, class-independent threshold).
引用論文
Pre-processing: We pre-process measurements prior to
statistical processing. We discard measurements that are larger
than the p percentile, for various2 values of p. The rationale
here is to test a restricted distribution range, especially the
lower cycle count tail. The upper tail may be more influenced
by data-independent noise. This (heuristic) process gives good
empirical results (makes detection faster); nevertheless we also
process measurements without pre-processing.
前兩個引文取自論文,是在說為了排除排外在因素,像是測量遺器或是作業系統中斷事件等等,論文使用百分點裁切來進行前處理。以下幾個論點是我閱讀論文原始馬後,覺得跟前兩段引文最相關的部份。
dudect_main
update_statistics
t_compute
進行計算得到一個統計量 t-value。代表每個受限分佈都有一個統計量。prepare_percentiles
max_test
、report
report
圖一
這是一張柱狀圖,可以看到時間大部分的時間都在 10000 以下,但在 60000 確有幾筆資料,可能是與測資獨立的外在因素導致。
lab0 中的實作因為有自己的考量,有些部份似乎與論文原始碼不同,條列以下幾點
lab0 | dudect.h | |
---|---|---|
percentile | 有 | 無 |
每次測試持續時間 | 到有足夠資料就回報並停止 | 直到發現 leakage |
判定常數時間的方法 | 10次 leakage 機會,有一次常數時間就是常數時間 | 程式不停止,只有 leakage found 和 constant time at this moment |
lab0 為了跑自動測試,將每次測試的持續時間改成有上限。為了讓程式更容易通過自動測試,將 判定常數時間的方法 改成有 9 次 leakage 的機會。
N_THRESHOLDS
: 有多少受限分佈first
: 是否是第一次呼叫 dudect_main
restricted_distributions
: 受限分佈,restricted_distributions[N_THRESHOLDS]
存原始分佈percentiles
: 每個受限分佈的閾值。回報的狀態
LEAKAGE_FOUND
: 發現 leakageNO_LEAKAGE_EVIDENCE_YET
: 常數時間NOT_ENOUGH_MEASURE
: 沒有足夠的樣本ERROR_IMPLEMENT
: 錯誤的實作將 doit
改成 dudect_main
,第一次會呼叫函式 prepare_percentiles
而非 update_statistics
。
美化 test_const
這是我自己添加,取最大值時忽略後面 20 個受限分佈,因為它們可能無法有效排除外在因素,但要注意剩下的受限分佈可能會錯誤地排除正常樣本,從而導致本來會發現 leakage 的情況變成常數時間,在論文中這會造成資安漏洞。
此外,我無法辨別 malloc
是否是造成外在因素的原因 ? 若是,malloc
所造成的外在因素是否也是一種 leakage ?