PA2 附加關卡


完成了聲卡的實現,可以流暢播放If I Could Tell Her~
但是玩litenes就只有23幀,用fceux就卡得聲音都變成了兒童鞋墊

收貨

講幾個主要的

假設需要同時定義很多個東西,同時這些東西又有着相同的模式,並且我們希望能夠方便地修改、添加這些東西(只需要維護一份唯一的列表),那么就可以這么做:

#define LIST(F) F(item1) F(item2) ...
#define FUNC(X) //blablabla

LIST(FUNC)

這樣非常像fp的寫法,並且我們只需要維護一份item表,套用不同的模式生成函數就可以得到不同模式的列表了(這句話有點繞),其實就是一個X-macro

一系列trace能夠使得debug非常簡便,但是最強的還是difftest,通常只需要找到第一處diff就能查出錯誤了。

然后還想講一個匪夷所思、困擾了我一整周的bug。NEMU默認通過一個get_time()函數獲取宿主機時間,並且提供了兩個選項:gettimeofday()clock_gettime()。STFW之后可以發現,gettimeofday()是通過vdso實現的,即並沒有真的系統調用,因此性能很高。並且使用的是tsc時鍾源,所以精度也很高,是獲取時間的首選方法。然而在我的筆記本上(機械革命Code01)同時安裝了windows10和ubuntu21.04,在win10休眠后啟動ubuntu,就會出現tsc時鍾源無法訪問的情況,這時候再調用gettimeofday(),最終仍然會通過clock_gettime()獲取時間,從而花費更多時間,導致NEMU性能下降。STFW之后發現速度大約能差17倍,這也和我跑分400->23的變化趨勢大致相符合。YZH的建議是讓我看看為什么會頻繁調用gettimeofday,但是我在callgrind之后發現,二者的區別僅僅在於調用所消耗時間,而調用次數則一模一樣
解決方案就更那啥了,只需要重啟進入win10,點擊關機,然后再進入ubuntu即可。通過命令cat /sys/devices/system/clocksource/clocksource0/available_clocksource 可以觀察是否有tsc

思考題

為什么不需要rtl_muls_lo

對於有符號乘法的低32位,結果來源於兩個操作數的低16位,此時指令的行為和無符號整數的乘法行為一致

為什么執行了未實現指令會出現上述報錯信息

special.h中定義了inv指令,所有的指令模式匹配失敗后最后會返回inv表示invalid opcode,在inv的輔助執行函數中就打印了錯誤信息

指令名對照

可以以關鍵詞"expand"、"expansion"、"pseudo"來搜索偽指令,或者直接通過偽指令的二進制序列來搜索真正對應的指令。之所以能這么做是因為偽指令是軟件層面的約定,偽指令最終對應什么指令由它的二進制序列決定

stdarg是如何實現的?

我能想到的一個實現方式是傳入一個鏈表的表頭作為參數,這樣通過更改鏈表的長度就可以實現任意數量的參數傳參了。而取參數的操作則是通過遍歷鏈表完成的

消失的符號

宏在預處理時就被展開了,因此不會出現在符號表中

局部變量在鏈接時是不可見的,因此也不會出現在符號表中

一個符號要可以被跨文件使用,因此不能是局部變量。


update:

上完課之后理解更深了一點

所謂符號,必須是有確定地址的實體,這就是為什么macro和局部變量不是符號——macro不存在地址,直接被展開;局部變量分配在棧上,甚至連地址都不是確定的。

冗余的符號表

之所以會出現這樣的情況,是因為ELF文件在運行時,並不需要符號表中的信息,只需要映射對應的段到虛擬內存的不同地址中即可。

而對於可重定位文件,符號表則是必不可少的——否則鏈接將無法進行,鏈接器對符號的查找也將無從下手。

尋找"Hello World!"

.rodata段,因為.strtab雖然叫字符串表,但儲存的是符號的名字的字符串,而不是程序中的字符串。程序中的字符串是以數據的形式儲存着的

不匹配的函數調用和返回

f0f1是尾遞歸,可以用一次ret代替連續的一整段ret

通過vscode的查找,可以發現f2f3的call和ret是匹配的,而所有的f1只有在遞歸的base case處有ret,f2也是類似的。通過查找可以發現call的數量與check的數字一致,因此call一定不會少,少的就只能是ret了。再繼續查找所有對f函數的call,剛好能對應上recursion.c最后的check中的常數,這就有力地證明了我生成的ftrace是正確的實現(至少對所有的call都有了正確的處理)

如何生成native的可執行文件

關鍵在於CFLAGS中的-D選項,根據傳入ARCH參數的不同,Makefile會通過gcc的參數傳入不同的宏定義,以此來產生不同平台上的編譯產物

這是如何實現的?

在定義了__NATIVE_USE_KLIB__之后,klib的庫函數將會有函數體,因此成為強符號,在鏈接時所有對庫函數的調用都將指向klib中的庫函數

實現DiffTest

實際上我並不是RTFSC找到的這個順序,而是直接翻閱了Spike的官方手冊得到了這一順序...

Spike本身就是按照ABI的順序來的,因此我的代碼不需要改變就可以按順序比對寄存器

捕捉死循環(有點難度)

我沒有實現,但我猜可以通過記錄程序的狀態,然后如果多次經過一個位置就比對此時寄存器狀態和上一次經過時的寄存器狀態,如果相同則很有可能陷入了死循環

不過這樣好像會漏掉一些死循環的情況....

理解volatile關鍵字

感覺這里的優化過於激進了

關鍵在於這里的_endextern變量,因此p指向的內存可能被其它函數/程序修改,從而導致

  1. 僅從函數func()來看,*p是定值
  2. 但是考慮了其它情況后,*p不是定值

這里顯式地寫出,是為了不讓編譯器做過度優化,從而保證可執行文件的行為與程序語言的描述一致

理解mainargs

am-kernels/kernels/hello/目錄下的Makefile會include $AM_HOME/目錄下的Makefile

$AM_HOME/目錄下的Makefile則會include對應架構的($ARCH).mk文件,然后分別include硬件架構和軟件平台的兩份.mk文件

nemu

只需要觀察abstract-machine/scripts/platform/nemu.mk中的run規則

這里會通過-DMAINARGS=\"$(mainargs)\"傳入一個宏MAINARGS="xxx",這里雙引號內的字符串將會在abstract-machine/am/src/platform/nemu/trm.c中被作為static const char mainargs[]的值初始化,然后這個字符串會被傳入main中,也就是我們的hello.c中的main函數

native

這個和上面比起來就要簡單一些

使用-nB命令就可以發現,最終的hello-nativehello.o一個am-native.a文件鏈接而成,尋找這個am-native.a就可以發現它源於platform.o這個文件,觀察platform.o就可以發現在函數init_platform()中有const char *args=getenv("mainargs")這樣一行代碼

STFW即可發現,getenv(const char *)用於找到特定名稱的環境變量的值,在這里就是我們在編譯時寫下的mainargs=xxx

神奇的調色板

只需要改變調色板的亮度,就可以不改變顯示而實現漸暗效果了


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM