report for PA1

来源:https://www.cnblogs.com/bllovetx/archive/2019/09/28/11602441.html
-Advertisement-
Play Games

說明:最近特別忙,都沒有時間寫blog,好多遇到的問題都沒能記下來,下麵是PA1的報告主要記錄了nemu debuger一些功能的實現方式和實現中遇到的問題,代替一下blog ...


說明:最近特別忙,都沒有時間寫blog,好多遇到的問題都沒能記下來,下麵是PA1的報告主要記錄了nemu debuger一些功能的實現方式和實現中遇到的問題,代替一下blog

% report for PA1

1.ISA=x86
2.關於x86 register 存在的問題,修改前reg.h文件寄存器設置中32,16,8位寄存器空間採用struct分配,
不共用空間,按照x86要求,改為使用Anonymous Union分配,然而發現修改後發現仍然報assertion fail,
檢查reg.c 中test的code後,發現assert函數通過檢驗之後在同一個struct中聲明的一系列rtlreg(eax,ecx,etc.)是否與對應寄存器位置相同,
所以要求這一系列rtlreg與gpr之間也採用Anonymous Union分配。


%% PA1.1

fun1.si

​ 利用sscanf(source_str,format,&des)按格式讀入參數,註意des參數要用地址表示;
之後根據參數調用相應函數(cpu_exec)即可
​ 完成之後添加了判斷N==0,提示無效(閱讀代碼框架可知N=-1表示最大uint,有效)
fun2.info r
​ 在相應的isa中寫好isa相關的isa_reg_display,後調用即可,寫的時候利用閱讀代碼可知直接利用相應的寫好的巨集定義等(reg_name.reg_b,reg_l,reg_w)即可快速實現
​ 好看起見,查閱了printf函數中列印16進位相關參數,

“%#x”   //表示按格式輸出,
“%nx    //表補齊n位(空格),
”%0nx“  //表示用0補齊n位

​ 利用switch可以比較清楚的處理不同寬度的寄存器
​ 仿照框架使用!(index&0x3)換行,輸出效果如下:

    (nemu) info r
     al:        20H  cl:        f0H  dl:        77H  bl:        52H                       
     ah:        f5H  ch:        39H  dh:        aaH  bh:        c4H                       
     ax:      f520H  cx:      39f0H  dx:      aa77H  bx:      c452H                       
     sp:      66c7H  bp:      524eH  si:      bd82H  di:      3886H                       
     eax: 5f11f520H  ecx: 246d39f0H  edx: 00b0aa77H  ebx: 2e19c452H                       
     esp: 7d0666c7H  ebp: 13e6524eH  esi: 1322bd82H  edi: 68f83886H

fun3.x n info
仍然使用sscanf獲得參數
一開始自己寫了輸出,由於x86是小端,需要轉化成小段,即輸出的每一個四位元組串,要先輸出小地址的位元組
其次,虛擬的地址用數組pmem表示,從0開始(對應0x0),共12810241024(0x8000000)位元組(題目中提到的0x80100000指的是大端的情況)
事實上,這一點在每一次make run是系統都輸出了:

    [src/memory/memory.c,16,register_pmem] Add 'pmem' at [0x00000000, 0x07ffffff]         
    [src/device/io/mmio.c,14,add_mmio_map] Add mmio map 'argsrom' at [0xa2000000, 0xa2000fff]                 

​ 後來閱讀代碼註意到已有框架函數直接輸出記憶體(vaddr_read)故改為直接調用框架函數
​ si前後0x100000附近列印結果如下:

    (nemu) x 20 0x100000                                                                  
    0x00100000:     0x001234b8      0x0027b900      0x01890010      0x0441c766            
    0x00100010:     0x02bb0001      0x66000000      0x009984c7      0x01ffffe0             
    0x00100020:     0x0000b800      0x00d60000      0x00000000      0x00000000           
    0x00100030:     0x00000000      0x00000000      0x00000000      0x00000000            
    0x00100040:     0x00000000      0x00000000      0x00000000      0x00000000            
    (nemu) si 7                                                                           
      100000:   b8 34 12 00 00                        movl $0x1234,%eax                   
      100005:   b9 27 00 10 00                        movl $0x100027,%ecx                 
      10000a:   89 01                                 movl %eax,(%ecx)                     
      10000c:   66 c7 41 04 01 00                     movw $0x1,0x4(%ecx)                 
      100012:   bb 02 00 00 00                        movl $0x2,%ebx                       
      100017:   66 c7 84 99 00 e0 ff ff 01 00         movw $0x1,-0x2000(%ecx,%ebx,4)       
      100021:   b8 00 00 00 00                        movl $0x0,%eax                       
    (nemu) x 20 0x100000                                                                   
    0x00100000:     0x001234b8      0x0027b900      0x01890010      0x0441c766             
    0x00100010:     0x02bb0001      0x66000000      0x009984c7      0x01ffffe0             
    0x00100020:     0x0000b800      0x34d60000      0x01000012      0x00000000             
    0x00100030:     0x00000000      0x00000000      0x00000000      0x00000000
    0x00100040:     0x00000000      0x00000000      0x00000000      0x00000000            

​ 顯然可以看到0x100000附近存儲了內置客戶程式內用,而0x100027出在運行了內置程式後存入了0x1234


%%PA1.2

本節實現算術表達式功能,分為讀入,遞歸計算和生成隨機表達式檢測,實現的算是表達式功能可應用於x,p等功能中。

目前實現的表達式功能包括:()+-**/,hex,dex
這裡特地將hex寫在dex前,是因為匹配正則表達式是如果先匹配10進位,會將0x~~開頭的0匹配掉,從而出現錯誤,所以採取優先匹配16進位的策略,正則表示如下:*

    {" +", TK_NOTYPE},    // spaces                                                       
    {"\\+", '+'},         // plus                                                         
    {"==", TK_EQ},         // equal                                                       
    {"\\*", '*'},         //multiply                                                       
    {"-", '-'},           //sub                                                           
    {"/", '/'},           //div                                                           
    {"\\(", '('},         //bra                                                           
    {"\\)", ')'},         //ket                                                           
    {"0x[0-9,a-f,A-F]+",TK_HEX},  //hex                                                   
    {"[0-9]+",TK_DEX}     //dex         

其中+,*,(,)需要加雙斜杠表示其本意,雙斜杠原因是正則表達式和c語言個需要識別一次

存儲匹配結果時,空格不處理,其餘直接將type記錄到tokens[nr_token].type中,講pmatch.so->pmatch.eo的字元串拷貝到str成員變數中即可
當然每次不為空格都要nr_token++
另外拷貝的字元串是不含\0的,意味著要不每次完成拷貝後認為在結束地址添加\0,要不就要每次使用tokens[]前清空,否則多次調用時,前面的內用會在一些情況下影響後面的調用,出現錯誤!
這裡我才用了人為補\0,直接在substr_len出補即可
其次,剛纔提到所用的type的操作理論上是一樣的,dex和hex都要存類型,複製字元串,補\0,而實際上符號類型雖然只需要存類型,但也可以複製字元串,補\0,之後不使用而已,故而可以不用switch,直接判斷是否是空格然後統一操作即可。
不過考慮到框架代碼使用switch可能考慮到安全性,代碼的可讀性,可修改性等,還是用switch完成了這一步。

evaluate中,首先p>q直接輸出報錯,assert(0)
p==q直接switch(type)hex和dex使用sscanf返回大小,default assert(0)
檢查括弧使用標識變數ch_p初始化為-1,遇見‘(’++,遇見‘)’--,只要小於0返回false,否則返回true(找主符號時也用了這個框架,小於0表示在括弧外,大於等於0表示在括弧內)同時上述演算法只遍歷了p->q-1,預設表達是合法,考慮到表達式可能不合法的情況,遍歷結束後若沒有返回(即應當返回true),assert(tokens[p].type==')')
最後一種情況要找主符號,首先利用上述框架標記處於括弧內還是括弧外,括弧外+-優先順序高於/,代碼如下:

 int fd_main=-1,m_op=-1;
 for(int i=p;i<=q;i++){
     switch( tokens[i].type ){
         case '(':fd_main++;break;
         case ')':fd_main--;break;
         case '+':if(fd_main<0){m_op=i;};break;
         case '-':if(fd_main<0){m_op=i;};break;
         case '*':if(fd_main<0&&m_op<0){m_op=i;};break;
         case '/':if(fd_main<0&&m_op<0){m_op=i;};break;
         default :break;
     }
 }
 assert(p<m_op&&m_op<q);
 assert(m_op!=-1);
 uint32_t left_main=eval(p,m_op-1),right_main=eval(m_op+1,q);
 //printf("%d    %d\n",left_main,right_main);
 switch( tokens[m_op].type ){
     case '+':return left_main+right_main;break;
     case '-':return left_main-right_main;break;
     case '*':return left_main*right_main;break;
     case '/':
             if( right_main==0 )printf("Unvalid Expression");
             assert(right_main!=0);
             return left_main/right_main;break;
     default :assert(0);break;
 }

在計算時檢查了除法分母不等於0;
m_op初始化為-1可以用於檢驗是否找到主算符,沒有找到說明表達式或代碼出錯,終止程式。

==%ps:關於思考的問題printf為什麼要換行,再一次測試bug中,我在bug前幾行加了printf輸出相關變數檢測bug的原因,但是沒有換行,結果只是報錯了,卻沒有輸出我要的變數,換行後就解決了,可以看出,不換行時printf和後續代碼內容是一起輸出的,所以由於後續代碼中報錯終止,printf也沒有輸出。==

test:
1.choose(n){return rand()%n}
2.gen_num():用choose和switch隨機生成十進位或十六進位
3.gen_op 後用gen_num代替遞歸gen_expr保證不生成/0的情況
4.在代碼框架基礎上新增一個case:生成一個空格在遞歸一次gen_expr()
5.完成後結尾加一個\0
6.輸出input後,main函數用fscanf讀取str時會遇到空格終止,為讀入含空格字元串使用正則表達式:%[^\n]
7.檢測到的bug:見上面的代碼,在處理主運算符時(在沒有遇到+/-的條件下)取第一個遇到的//為主運算符,即對於或/位置越前優先順序越高,但實際邏輯上與之相反,修改後代碼如下:

int fd_main=-1,m_op=-1;                                     
for(int i=p;i<=q;i++){                                    
    switch( tokens[i].type ){                               
        case '(':fd_main++;break;                           
        case ')':fd_main--;break;                           
        case '+':if(fd_main<0){m_op=i;};break;           
        case '-':if(fd_main<0){m_op=i;};break;          
        case '*':if(fd_main<0&&m_op<0){m_op=i;};break;      
        case '/':if(fd_main<0&&m_op<0){m_op=i;};break;      
        default :break;                                     
    }                                                 
}                                                  
assert(p<m_op&&m_op<q);                                     
assert(m_op!=-1);

%%PA1.3

%算術表達式擴展

之前一直採用了switch來處理主算符問題,雖然通過一些標誌性(flag)變數簡化了代碼,但進一步的擴展卻會十分困難,且易出錯。
為了更好地實現表達式擴展,想利用expr.c開頭的枚舉類型中不同類型的順序來表徵優先順序(privilege)
這裡遇到了一個問題
之前一直不理解為什麼要給TK_NOTYPE(space)賦值為256,為此我列印了TK_NOTYPE(=256)和TK_EQ(=257)
與我理解的只有TK_NOTYPE的值受賦值影響有所不同
這樣的話目的顯然是避免和‘+’等的ascii碼重覆
優先順序如下:
同級越往後優先順序越高,即先出現先運算,後遞歸
1.deref
2.*/

3.+-

4.== !=

5.&& ||(\\|\\|)

#define p_token(pos) privilege(tokens[pos].type)                                          
#define p_t(type) privilege(type)+1                                                       
int privilege(int type){                                                                  
     switch(type){                                                                         
             case DEREF:return 1;                             
             case '*':case '/':return p_t(DEREF);             
             case '+':case '-':return p_t('*');               
             case TK_EQ:case TK_NEQ:return p_t('+');          
             case TK_AND:case TK_OR:return p_t(TK_EQ);        
             default:return 0;                                
         }                                                    
}  

識別成功後的存儲部分與之前類似;
調用eval前識別出所有解引用,這裡題目中提示考察前一個tokens的類型,顯然很多類型都可以
不過考慮到這些類型顯然是優先順序相關的,所以可以借用privilege表,實現一表雙用:

  if( tokens[i].type=='*' && (i==0||p_token(i-1)>0) ){tokens[i].type=DEREF;} 

eval p=q調用isa相關函數,for迴圈strcmp對比,找到則輸出,同時為方便實用,實現了大寫寄存器名字的識別
在找主符號前增加處理解引用的else if,找主符號時直接利用privilege表即可

%%監視點

% [x] 1. cpu_exe:遍歷所有監視點,發生改變則更改state,同時輸出變化的監視點信息,更新old_val
時間(O(n))
一開始直接在cpu-exec中寫遍歷,但是要解決很多變數聲明的問題,所以直接改成在watchpoint中寫好相關函數,返回bool值,根據結果改變nemustate即可
同樣的道理info w也直接在watchpoint.c中寫好相關函數直接調用
檢查w變化函數:
整體上沒有什麼問題,遍歷之後列印監視點變化信息並返回bool即可,細節有三點:
i.關於多個wp同時改變問題,採取遍歷結束在返回bool值的策略,即會將所有改變列印出來,顯然,程式中斷時我們關心的所有變數都應當列印出來,以判斷變化原因
ii.關於列印內容,對變化的wp列印了no,expr,以及改變前後的值,但是debuger實際並不知道使用者需要dex進位還是hex進位,所以這裡我們都處理成同時都列印
iii.為了模仿GDB實現下文提到的enable/unable功能,我們在wp結構內額外加入bool wp_Enb變數表徵該監視點是否使用,
    所謂enable/unable是指一些時候可能暫時不需要使用/不關心某個監視點,但一段時間後有需要再次啟用,為簡便期間暫時性unable
    但是很重要的一點,unable狀態下,成員變數old_value仍然要更新(或者在enable時更新)否則一旦enable立馬會stop程式,顯然不符合要求
    考慮到雖然我們暫時可能不關心這個wp,但將他的變化實時打出來只會利於debug,所以採用實時更新變數,併在更新時輸出更新信息但不暫停程式的做法。
% [x] 2. ui.c(b expr):設置斷點功能,存儲expr,並計算存儲old_val(初始化enb)
時間(O(1))new_wp將節點插入在head後面
調用new_wp並初始化各變數即可(包括將以要求外額外添加的兩個bool初始化為true)
% [x] 3. ui.c(d N):調用free_
時間(O(1))
調用free_即可,不過從這裡開始遇到一些變數聲明相關的問題
如果通過在watchpoint.c中寫函數實現當然沒問題,但很不方便,況且這裡額外寫一個函數本身意義實在不大
先說一下問題是什麼
比如d N,調用free_時參數顯然為wp_pool[N],但是wp_pool在該文件中未聲明
而聲明又有很大困難,extern static編譯器認為兩個修飾衝突,只有extern,編譯器不能識別,只有static不知道為什麼視為新定義一個變數。
最終處理為刪去watchpoint.c中定義時static,同時在watchpoint.h中申明外部變數(extern)從而解決這一問題(但不知道會不會影響後續操作“
(已解決)->static 表示只在文件內可見!可以避免函數衝突
% [x] 4. ui.c(info w):按照池順序輸出watchpoint信息//按順序
時間(O(n))
同樣是在w..p.c文件中寫好相關函數直接引用,列印內容包括
序號,enb(y/n是否早使用),oldvalue(hex/dex),newvalue(hex/dex),表達式
這裡選擇用遍歷池而非遍歷鏈表,是為了直接編號順序輸出
當然也可以
    1.遍歷鏈表後排序輸出:遍歷與排序不同時,很麻煩,不簡潔(kiss)
    2.插入時(new_wp)排序:新建wp時要O(lg(n))甚至O(n)時間
% [x] 5. ui.c(enable/disable)
時間(O(n))
都很容易實現,不過有一些函數聲明相關的問題,前面已敘述相關解決

==記錄一下最近添加的配置或應用之類的,加了很多,基本都忘記了,只記得幾個這兩天加的
1.首先是神之編輯器emacs配置了好久仍然不能輸中文,更不會導出含中文的pdf,不過學習了一下基本操作
2.在圖形界面交換了escape和caps建的位置,這樣使用vim就不那麼彆扭了,不過感覺交換ctrl與caps也很誘人,沒有什麼好的解決方法,畢竟主要用vim
實現上在開機啟動項里增加了命令:setxkbmap -option '' -option 'caps:swapescape'(1st option:取消之前有的option)ctrl交換的命令應該是ctrl:swapcaps
3.剛好前幾天看到ctags可以加強vim中C-p,C-n的提示輸出,今天jyy又推薦了ctags的C-]功能(C-t/o返回),可以跳轉到函數定義所以裝了一下ctags
生成tags文件命令為ctags -R (R:遞歸,所有文件)
另外可以在根目錄.vimrc中set:tags=(path)設置路徑,也可以set tags=tags;set autochdir自動切換(沒試過)==

==4.安裝了typora和haroopad,本實驗報告就是使用typoora寫的,不過移動游標相比vim,emacs真的太不方便了,嘗試著更改.json文件但不知道為什麼沒有用附查到的相關代碼==

{ "keys": ["alt+a"], "command": "move_to", "args": {"to": "bol", "extend": false} },
{ "keys": ["alt+f"], "command": "move_to", "args": {"to": "eol", "extend": false} },
{ "keys": ["alt+j"], "command": "move", "args": {"by": "characters", "forward": false} },
{ "keys": ["alt+l"], "command": "move", "args": {"by": "characters", "forward": true} },
{ "keys": ["alt+i"], "command": "move", "args": {"by": "lines", "forward": false} },
{ "keys": ["alt+k"], "command": "move", "args": {"by": "lines", "forward": true} },

%%pa1.3思考題:
1.如果是兩個位元組就無法替換誤操作數的指令了
2.關於將斷點設在命令中間或結果的測試如下(利用測試結果算出了int 3 的opcode)
測試1:

    0x555555555137 <main+18>                mov    -0x8(%rbp),%eax
    (gdb) info b
    Num     Type           Disp Enb Address            What
    1       breakpoint     keep y   0x0000555555555129 <main+4>
            breakpoint already hit 1 time
    2       breakpoint     keep y   0x0000555555555139 <main+20>
    3       breakpoint     keep y   0x0000555555555137 <main+18>
    (gdb) c
    Continuing.
 Breakpoint 3, 0x0000555555555137 in main ()        
(gdb) c                                                                                    Continuing.     
[Inferior 1 (process 9368) exited normally]     

==可以看到開頭處的端點有效,中間的無效(刪去b 3,仍然不會觸發b 2)== 測試2:

(gdb) info b
Num     Type           Disp Enb Address            What 
5       breakpoint     keep y   0x0000555555555179 <__libc_csu_init+41> 
6       breakpoint     keep y   0x0000555555555138 <main+19> 
7       breakpoint     keep y   0x0000555555555139 <main+20> 
(gdb) disable 5   
(gdb) run test_gdbw                  
The program being debugged has been started already. 
Start it from the beginning? (y or n) y    
Starting program: /home/bllovetx/Test/test_gdbw test_gdbw                                 
Breakpoint 7, 0x0000555555555139 in main ()   
(gdb) si    
0x000055555555513a in main ()    

==可以看到雖然中間的b沒有生效,但結尾的b生效了==

測試3: 不複製代碼了,直接說結果: 一開始沒有發現,後來因為輸錯端點碰巧在某一個callq函數的中間位置設置了端點,造成了段錯誤 但是無論如何列印(p/x *addr)代碼的二進位內容都與加端點之前沒有區別, 為此我進行了單步調試 原始代碼如下

0x555555555178 <__libc_csu_init+40>     callq  0x555555555000 <_init>   
0x55555555517d <__libc_csu_init+45>     sar    $0x3,%rbp        

本應跳轉到0x555555555000,當我在0x555555555179加入端點後,跳轉到了0x555555555049 disable該斷點,在0x55555555517a設端點,顯示:無法跳轉到0x555555551e00 顯然跳轉地址由於int 3操作發生了改變 這樣看來這所以p/x命令不能列印出變化很可能是gdb在遇到int 3指令時自動替換為原指令再輸出,以避免影響調試者判斷 但是由於指令終端的int 3 指令無法被執行,自然gdb也無法在該指令被調用時提前複原,所以造成了錯誤 為了確定是否p/x結果不發生改變確實是gdb的優化,以及弄清具體int3 指令是如何改變返回地址的 我查閱許多相關資料網站,並把我測試的可執行文件用objdump(-d)反彙編 最終發現二進位代碼使用了偏移定址,下麵我用我反彙編的一段代碼來說明:

1174:   48 83 ec 08             sub    $0x8,%rsp    
1178:   e8 83 fe ff ff          callq  1000 <_init>   
117d:   48 c1 fd 03             sar    $0x3,%rbp   
(0x1178對應gdb時0x555555555178,p/x *結果為0xfffffe83e8--小端)  

首先通過觀察多個callq,0x1178處的一個位元組0xe8顯然是callq指令之後四個位元組顯然是一個int 其實際意義時跳轉地址相對下一條命令首地址的偏移量,這裡跳轉相對地址為0x1000,下一條指令首地址為0x117d 0x1000-0x117d=0xfffffe83

計算:

顯然利用上述結果可以算出int 3指令的16進位碼(單位元組)

addr-start=0x55555555517d

breakpoint code cal(hex) addr
0x555555555178 0xfffffe83(e8-callq) 5000-517d=fffffe83 0x555555555000
0x555555555179 0xfffffe(int 3)(e8-callq) 5049-517d=fffffecc 0x555555555049
0x55555555517a 0xffff(int 3)83(e8-callq) 1e00-517d=ffffcc83 0x555555551e00

==從上表顯然可以看出int 3的指令碼就是0xcc==

PA1總結(查閱手冊&必答題)

  1. ISA:x86

  2. 理解基礎設施:
    $$
    450200.5=4500(min)=75(h)
    $$

  3. 查閱手冊:

  • CF:CARRY FLAG進位
  • modR/M位元組跟在一些操作碼之後,用於指示操作對象信息(如reg or mem)主要包括三部分,2bit的mod field,3bit的reg/opcode field,和3bit的R/M field(手冊說是最不重要的不知道為什麼)。其中mod field和R/M field一起指示8個寄存器和24個記憶體((1+3)×8),reg/opcode 由opcode決定,存儲寄存器序號或這額外的opcode信息
  • mov R/M R/M不能同時是M
  1. 使用find和wc-l/grep -c '\|'直接就能統計行數,為了去除空行,採用grep的參數-Ev(E表示使用正則表達式,v表示反向搜索:

    ➜  nemu git:(pa1) find . -name "*.c" -or -name "*.h" | xargs grep -Ev "^$" | wc -l
    4406
    ➜  nemu git:(pa1) git checkout pa0
    Switched to branch 'pa0'
    ➜  nemu git:(pa0) find . -name "*.c" -or -name "*.h" | xargs grep -Ev "^$" | wc -l
    4007

    即pa1增加了399行

    接下來實現在makefile中增加自動輸出行數功能,首先在打開nemu中的makefile,找到clean,gdb等指令的位置,模仿加入count指令,發現指令中的$(正則表達式)會被錯誤識別為shell指令,查閱資料,make會將所有$去掉再交給shell,所以使用$$替換$即可,好看起見,可以用:=先定義變數,然後使用@echo輸出

    另外,我試圖實現在輸出總代碼的同時輸出除了框架代碼以外增加代碼數,即要進行減法運算,但是makefile並不支持代數運算,於是調用shell中的expr功能,數字運算符之間要用‘ ’隔開,代碼如下:

     68 # Command for count                                    
     69 COUNT_L := $(shell  find . -name "*.h" -or -name "*.c" | xargs grep -Ev "^$$" | wc -l)  
     70 COUNT_ADD := $(shell expr $(COUNT_L) - 4007)  
    
     92 count:                                  
     93     @echo Totally $(COUNT_L) lines of code in nemu of this branch except empty line                                         
     94     @echo Totally $(COUNT_ADD) lines added into the frame code     
    

    然而仍然很醜,因為每次輸出前都會輸出多餘的信息: Building x86-nemu

    註意到make clean時並不會輸出該信息,閱讀代碼,發現框架代碼通過ifneq為clean排除check操作:

    ifneq ($(MAKECMDGOALS),clean) # ignore check for make clean 

    只要在ifneq內實現或運算加入count也排除掉check即可,採用make的findstring函數:

    ifneq ($(findstring$(MAKECMDGOALS),clean,count),) # ignore check for make clean 

    然而這又出現了新的問題,如果make後沒有指令(空指令也會抑制之後的行為check)這樣make run,make submit就會出問題,需要額外加上ISA=x86才能成功,為了不用每次輸出x86,ifneq套ifneq及判斷兩次。

    在pa1中的makefile添加同樣功能:

    ➜  nemu git:(pa1) ✗ make count      
    Totally 4406 lines of code in nemu of this branch
    Totally 399 lines added to the frame code
  2. 表示將所有warning視為error


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 前面已經分析過了Intel的記憶體映射和linux的基本使用情況,已知head_32.S僅是建立臨時頁表,內核還是要建立內核頁表,做到全面映射的。下麵就基於RAM大於896MB,而小於4GB ,切CONFIG_HIGHMEM配置了高端記憶體的環境情況進行分析。 建立內核頁表前奏,瞭解兩個很關鍵的變數: ...
  • 網路編程基礎瞭解 socket套接字 socket是一種通訊機制,它包含一整套的調用介面和數據結構的定義,他給應用程式提供了使用如TCP/UDP等網路通訊的手段。 linux中的網路編程通過socket介面實現,socket既是一種特殊的IO,提供對應的文件描述符。一個完整的socket都有一個相關 ...
  • Centos7 ...
  •      標準IO和管道實驗練習 【例1】把/etc/fstab文件內容重定向到/tmp目錄下文件名為fstab.out 寫法: 【例2】把hello world追加到/tmp/fstab.out文件尾部 寫法: 【例3】禁止覆蓋重定向和強制重定向 ...
  • 1. 物理CPU的個數: 2. 每個物理CPU的核心數量: 3. 邏輯CPU個數: ...
  • 創建密鑰並使用密鑰ssh登錄linux 使用密鑰對登錄ssh簡介 通過ssh_keygen勝場公鑰和私鑰,公鑰放在要登錄的目標的機器上,私鑰放登錄發起的機器上。 生成密鑰 我是在ubuntu上生成的密鑰,首先在用戶目錄新建一個.ssh文件夾 進入.ssh目錄生成密鑰 生成後目錄下會多出兩個文件 id ...
  • Github_link_from:https://github.com/lawlite19/MachineLearning_Python 機器學習演算法Python實現 目錄 機器學習演算法Python實現 一、線性回歸 1、代價函數 2、梯度下降演算法 3、均值歸一化 4、最終運行結果 5、使用scik ...
  • 1) SIGHUP 本信號在用戶終端連接(正常或非正常)結束時發出, 通常是在終端的控制進程結束時, 通知同一session內的各個作業, 這時它們與控制終端不再關聯.2) SIGINT 程式終止(interrupt)信號, 在用戶鍵入INTR字元(通常是Ctrl+C)時發出3) SIGQUIT 和 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...