PA1.3

寫在前面的話

如果您對該系列感興趣的話,推薦您先看一下南京大學的計算機組成原理實驗(也就是PA)的講義,然後再來看這篇文章可能有更多地收穫。如果您是要完成該作業的學生,我推薦你先看講義,或者好好聽老師的講課,然後自己獨立完成這個作業,但是如果你沒有聽懂,或者你無論如何也無法理解講義上面的字,又或者說對講義上面的某點知識某個問題不瞭解而又覺得太簡單不好意思問老師,那麼您可能會從這篇文章裏面獲得一些你需要的信息。本篇文章將會包括筆者自己做PA的所有經過,希望你並不將該文章當成抄襲的根源,而是成爲你思考的源泉。
從PA1開始我們就開始真正進行試驗了,之前的PA0只是搭配環境,說白了就是輸入幾個指令,但是從現在開始就是真正運用起來我們自己的智慧的時刻,我們要自己動手寫代碼,用自己的腦洞來創造一個模擬器,所以相比於PA0,從今往後你對於每個任務都要仔細認真地思考並且儘量獨自完成。

PA系列傳送門

PA0:https://blog.csdn.net/qq_41983842/article/details/88921427
PA1.1:https://blog.csdn.net/qq_41983842/article/details/88934779
PA1.2:https://blog.csdn.net/qq_41983842/article/details/89714479
PA1.3:https://blog.csdn.net/qq_41983842/article/details/89714689
PA2.1:https://blog.csdn.net/qq_41983842/article/details/95232055
PA2.2&2.3:https://blog.csdn.net/qq_41983842/article/details/101164495
PA3.1:https://blog.csdn.net/qq_41983842/article/details/103094859
PA3.2:https://blog.csdn.net/qq_41983842/article/details/103843093
PA4:https://blog.csdn.net/qq_41983842/article/details/104667951

思考題

  1. 體驗監視點

    測試代碼:

    #include<stdio.h>
    int i = 0,j = 0;
    int main()
    {
        for(i = 0;i < 10;i++)
     	   j += 5;
        return 0;
    }
    
    

    玩GDB:
    在這裏插入圖片描述
    在這裏插入圖片描述

  2. 此處可否使用 free 來作爲頭指針的名字呢?

    不能,c語言中free直接就把空間給釋放了,所以編譯的時候符號名就會重複,就像我給一個變量取名叫new一樣,這些都是在c語言中有其他意義的符號。

  3. static 在此處的含義是什麼?爲什麼要在此處使用它?

    這裏定義了一個靜態全局變量,如果不加static的話那麼wp_pool[NR_WP],*head, *free_;對整個工程可見,加上以後僅僅對本文件可見,這就保證了其他文件不可訪問,其他文件可以定義與其同名的變量,兩者互不影響,避免不同文件同名變量的衝突,且不會誤使用。

  4. 在 my-x86 中,文章中的斷點機制還可以正常工作嗎?爲什麼?

    int3指令長度是一個字節,換成兩個字節之後就不能正常工作了。這種形式是有價值的 , 因爲它可以被用於代替第一指令字節中的任何一個的斷點 , 包括另一字節指令 , 而無需重寫其它代碼。

  5. 把斷點設置在指令的非首字節(中間或末尾),會發生什麼?

    GDB沒有辦法將斷點設置在指令的中間或者末尾,會出錯。 在這裏插入圖片描述

  6. 模擬器 (Emulator) 和調試器 (Debugger) 有什麼不同?更具體地,和 NEMU 相比,GDB 到底是如何調試程序的?

    調試器可以開始一些過程和系統進行調試 , 或者自己附加到現有進程。它可以單一步驟通過代碼 , 設置斷點和運行 ,檢查變量值和堆棧跟蹤。調試器有許多高級功能 ,比如執行表達式和函數的調用 debbugged 進程的地址空間 ,並且即使改變的過程的代碼在實時觀看的效果。模擬器跟調試器不同的地方在於環境的區別吧,實現斷點的方式不是軟硬件斷點是模擬出來的。我們所說的模擬器都是利用軟件模擬相關的硬件,相當於用程序去模擬,而在硬件上運行的是你運行這套模擬系統的程序,而虛擬機或者說調試器是把系統的指令送到機器裏,用硬件來執行指令的,所以效率高並且還會快很多。

  7. 假設你現在需要了解一個叫selector的概念, 請通過i386手冊的目錄確定你需要閱讀手冊中的哪些地方.

    通過搜索定位到第五章5.1.3。手冊的第96頁。

  8. 你將會在調試上花費多少時間?簡易調試器可以幫助你節省多少調試的時間?

    500×0.9×30×20=270000s=75h,500×0.9×10×20=25h,可以節省50h時間。

  9. EFLAGS寄存器中的CF位是什麼意思?ModR/M字節是什麼?mov指令的具體格式是怎麼樣的?

    CF位是進位標誌位(2.3.4.1,手冊第34頁標誌位),ModR/M長度爲1個字節(17.2,手冊第240頁),ModR/MMod決定操作數,R/M表示Register (or/and) Mermory,跟mod一起確定源操作數。

    mov指令格式:(手冊414頁的表)

    1.MOV 寄存器, 寄存器/內存單元/段寄存器/立即數

    2.MOV 內存單元 , 寄存器/段寄存器/立即數

    3.MOV 段寄存器 , 寄存器/內存單元

  10. shell命令

    find ics2017/nemu/ -name "*[.h|.c]" |xargs cat|wc -l命令統計有4032行代碼

    find ics2017/nemu/ -name "*[.h|.c]" |xargs cat|grep -v ^$|wc -l命令統計去除空格後有3340行代碼

    返回到master分支以後,統計代碼有3526行,差不多編寫了不到500行吧。

  11. -Wall-Werror有什麼作用? 爲什麼要使用-Wall-Werror?

    -Wall 是打開gcc的所有警告,-Werror要求gcc將所有的警告當成錯誤進行處理。-Wall作用很明顯,編譯肯定要有警告嘛,如果沒有警告萬一影響了程序運行不就尷尬了。位-Werror可以防止函數定義未使用,當定義未使用時,會報錯,而不是警告,保證了程序的正確運行。他將程序中所有的warning都指示成爲error,防止程序因爲warning造成程序的不穩定性。

實驗內容

實現監視點結構體

添加三個變量
在這裏插入圖片描述

實現監視點池的管理

這個任務主要就是鏈表的操作,一共兩個鏈表,一個head一個free_WP* new_wp()函數就是把free_鏈表裏面的空閒結點拿出來放到head裏面,而void free_wp(WP *wp)就正好相反,所以每個函數最主要的核心功能就是刪除鏈表元素和添加鏈表元素,根據講義上面的指示,很快就可以寫出來兩個函數,但是這時候就出現了一個問題,什麼時候調用結構裏面寫好的init_wp_pool()函數呢?我就又加設了一個全局變量init,初值位0,當他位0的時候就會初始化監視點池,然後每增加一個監視點,init就加1,反之減一。而在free_wp函數中就需要判斷刪除的位置:頭結點、中間結點、沒有監視點,三種情況分別判斷就好了,我將函數返回類型改成了int,並且將參數改成了需要刪除的編號(爲了更好在delete_watchpoint中調用)
在這裏插入圖片描述

將監視點加入調試器功能

在寫完這個基礎的函數之後,就要開始實現真正的功能了,任務三和任務四是連在一起的,所以就一起實現了,實現這個任務要在ui.c裏面寫三個相應的功能,cmd_w cmd_dcmd_info裏面的w子命令,分別對應添加監視點,刪除監視點和顯示所有監視點的功能。而他們分別對應着任務4的函數,所以基本上直接調用就行了。
cmd_w函數只需要將參數直接傳到set_watchpoint(args)函數裏面就好了,不需要分割,因爲他本來就是一個表達式。
cmd_d函數需要將得到的字符串分割,並且轉換成數字,即編號,這樣就可以傳入delete_watchpoint()函數處理了。

在這裏插入圖片描述
cmd_info裏面的w子命令,就是調用list_watchpoint()就行了,所以任務三沒什麼難度,主要內容都是在任務4裏面搞的。
在這裏插入圖片描述

實現監視點

實現4個函數,

int set_watchpoint(char *e);    //給予一個表達式e,構造以該表達式爲監視目標的監視點,並返回編號
bool delete_watchpoint(int NO); //給予一個監視點編號,從已使用的監視點中歸還該監視點到池中
void list_watchpoint(void);     //顯示當前在使用狀態中的監視點列表
WP* scan_watchpoint(void);      //掃描所有使用中的監視點,返回觸發的監視點指針,若無觸發返回NULL

其實說白了全部都是鏈表的操作,鏈表的插入,刪除,遍歷等,講義裏面也給了思路,直接實現就好。

set_watchpoint(char *e)函數,按照講義上面的參考輸出,首先從監視點池裏面拿一個新的監視點,然後對這個監視點的各個變量進行賦值,表達式直接存進去就好,舊值通過調用表達式求值函數來實現,最後打印出來舊值就行了。

實現後的set_watchpoint(char *e)函數:
在這裏插入圖片描述
delete_watchpoint(int NO)這個函數相當的簡單,直接調用free_wp函數就行了。
在這裏插入圖片描述
list_watchpoint(void)函數這個也很簡單,就是遍歷鏈表嘛,不多說了。
在這裏插入圖片描述

scan_watchpoint(void)這個函數裏面主要就是針對每個監視點判斷他的值有沒有發生變化,如果變化了就停止,如果不變化就繼續運行,實現也非常的簡單,就是鏈表的操作,打印地址我用的cpu.eip,一開始我還把這斷點和監視點兩種搞混了,直接實現的斷點的功能,後來發現監視點其實是不需要讓程序暫停,而斷點纔會讓程序暫停,然後就把任務5從這個函數裏面搬到了cpu-exec()函數裏面。
在這裏插入圖片描述

使用模擬斷點

就像上面說的一樣,在cpu-exec()函數裏面有一個提示你寫代碼的地方,所以就決定寫在這裏了,直接調用scan_watchpoint()函數,看他的返回值,如果不是空,就說明觸發了監視點事件,那麼就直接暫停就好。關於模擬斷點和具體監視點實現的不同,我覺得講義上面寫的不清楚,或者沒講清楚他面兩個的區別,後來問了助教,我才知道模擬斷點不會輸出old_val new_val等在監視點纔會輸出的信息,直接中斷就行了。

但是爲了可以用WP這個結構體,必須給他加個頭文件


#include "monitor/watchpoint.h"//加入watchpoint

在這裏插入圖片描述

可以實現所有的功能了:
在這裏插入圖片描述

set_watchpoint函數修改如下,主要就是將斷點和監視點區分,利用strncmp

int set_watchpoint(char *e) {
  	WP *p;
  	p = new_wp();//拿一個新監視點
  	bool watchpoints = false;
  	strcpy(head->expr,e);//存入表達式
  	if (!strncmp(p->expr,"$eip == ",8)) {//是斷點
		printf("Set break point $eip == ADDR\n");
	}
	else {//是監視點
		watchpoints= true;
		printf("Set watchpoint #%d\n", p->NO);
  		printf("expr = %s\n", p->expr);
	}
  	bool success = true;
  	p->old_val = expr(p->expr,&success);//存入舊值
  	if (!success) {
  		printf("Fail to eval\n");
  		return 0;
  	}
  	else if(watchpoints == true){
  		printf("Old value = %#x\n", p->old_val);
  	}
  	return 1;
}

同樣,將scan_watchpoint修改如下,同樣是根據表達式來區分斷點和監視點:

WP* scan_watchpoint(void) {
  WP *p = head;
  bool success = true;
  if(p == NULL) {
    printf("No Watchpoint\n");
    return false;
  }
  else {
    while (p) {
      p->new_val = expr(p->expr,&success);//計算新值
      if (!success)
          printf("Fail to eval new_val in watchpoint %d\n",p->NO);
        else {
        if (p->new_val != p->old_val) {//值發生變化
        	if (!strncmp(p->expr,"$eip == ",8)) {//是斷點表達式
				printf("Hit break point,program paused\n");
				return p;
			}
			else {
				printf("Hit watchpoint %d at address %#8x\n", p->NO,cpu.eip);
          		printf("expr      = %s\n", p->expr);
          		printf("old value = %#x\nnew value = %#x\n", p->old_val,p->new_val);
          		p->old_val = p->new_val;//舊值更新
          		printf("program paused\n");
          		return p;
			}
        }
      }
    }
  }
  return NULL;
}

最終實現效果如下:
在這裏插入圖片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章