CSAPP閱讀筆記-匯編語言初探(數據傳送類指令)-來自第三章3.2-3.3的筆記-P115-P128


1.如何由機器代碼生成匯編代碼?

objdump -d再加上文件名即可直接在終端看到由反匯編器恢復的匯編代碼。注意,文件名並不一定得是.o文件,任何可執行文件都可以。

結果如下:

僅列舉了反匯編test.o的結果,其它的也測試過,不放圖了。

 

2. 32位和64位的基本數據類型大小對比:

32位:

char:1字節,char*:4字節,short int:2字節,int:4字節,unsigned int:4字節,float:4字節,double:8字節,long:4字節,long long:8字節,unsigned long:4字節

64位:

char:1字節,char*:8字節,short int:2字節,int:4字節,unsigned int:4字節,float:4字節,double:8字節,long:8字節,long long:8字節,unsigned long:8字節

紅字標識的是有變化的數據類型。

 

3.幾個術語:

字節:8位,后綴:b

字:16位,后綴:w

雙字:32位,后綴:l

四字:64位,后綴:q

MOV類指令也有movb,movw,movl,movq,分別對應這幾種大小的操作數。

 

4.x86-64的寄存器:

看下面這張圖:

 

x86-64有16個64位通用寄存器,如最左側所示,可以對每個寄存器的低字節操作,比如要訪問%rax的低8位,則可以訪問%al。

同理,也有%ax對應16位,%eax對應32位,%rax對應64位。

有一條特殊規則:當對某個寄存器的低4字節操作時,如%eax,會自動把高4個字節全置為0。

 

5.尋址方式:

  1: $+立即數,則取得的操作數就是立即數

  2:立即數,則取得的操作數就是以立即數為地址,對應取出的操作數

  3:寄存器,則取得的操作數就是以寄存器的值

  4:(寄存器),則取得的操作數就是以寄存器的值為地址,對應取出的操作數

  5:立即數1(寄存器1,寄存器2,立即數2),則取得的操作數就是以立即數1的值+寄存器1的值+寄存器2的值*立即數2為地址,對應取出的操作數

  6.有了最通用的第5條,其他變種都能寫出,比如(,寄存器2,立即數2),則取得的操作數就是以寄存器2的值*立即數2為地址,對應取出的操作數

習題練一下:

  

答案:

%rax對應0x100,  0x104對應0xAB,  $0x108對應0x108,  (%rax)對應0xFF,  4(%rax)對應0xAB,  9(%rax,%rdx)對應0x11

260(%rcx,%rdx)對應0x13,  0xFC(,%rcx,4)對應0xFF,  (%rax,%rdx,4)對應0x11

講解一下260(%rcx,%rdx),因為260=0x104,所以操作數是0x104+0x1+0x3=0x108地址對應的值,是0x13

 

6.mov指令

  1.mov指令的順序是從左到右,如mov a,b,則把a的值復制給b

  2.除了之前提到的movb,movw,movl,movq,還有movabsq,代表傳送絕對的四字,movq雖可傳四字,但一旦要傳立即數,則只能傳32位補碼表示的立即數,隨后把它符號拓展到64位。而movabsq可以直接傳64位的立即數,但是它只能以寄存器作為目的地。

  3.所有mov指令都不支持從一個內存地址直接傳到另一個內存地址,如movw (%rax),4(%rsp)是不行的。

  4.決定mov使用哪個后綴的是寄存器的大小,當兩邊操作的都是寄存器時,若大小不同,必須用第5條中的小數據復制到大目的地的類型的mov指令,當兩邊操作的是立即數和內存時,可以以立即數大小為准,

  例子:movl $0x4050,%eax  0x4050雖然是2字節,但%eax是4字節,所以movl

     movw %bp,%sp

     movb (%rdi,%rcx),%al

     movb $17,(%rsp)  立即數->內存

     movq %rax,-12(%rbp)

  5.當想將小的數據復制到大的目的地時,可以用movz或movs,前者代表用0填充高字節,后者代表用符號填充高字節,后面還要加上兩種轉換數據的大小,

  比如movzbw(字節->字,0填充),movswq(字->四字,符號填充),還有一種cltq指令,特指%eax->%rax的符號拓展轉換,等價於movslq %eax,%rax

  注意movs和movz都是以寄存器為目的地的。

  根據以上信息,可以知道,之前的第4點中的特殊規則其實相當於是說movl可以實現movzlq的功能

  

  6.當作強制類型轉換時,若既涉及大小的變化又涉及符號變化,則操作時應先改變大小。

  例子:有兩個指針,sp和dp,分別保存在%rdi和%rsi中,指向的數據類型分別是src_t和dest_t,中間寄存器為%rax,可用其任意子部分

  當src_t為long,dest_t為long時,寫出轉換的指令:

    movq (%rdi),%rax

    movq %rax,(%rsi)

  當src_t為char,dest_t為int時,寫出轉換的指令:

    movsbl (%rdi),%eax           //因為char是有符號的,先用movsbl轉成4字節

    movl %eax,(%rsi)     //以寄存器大小決定后綴用l

  當src_t為char,dest_t為unsigned時,寫出轉換的指令:

    movsbl (%rdi),%eax          

    movl %eax,(%rsi)     //轉換后兩者大小相等,無需作零拓展

  當src_t為unsigned char,dest_t為long時,寫出轉換的指令:

    movzbq (%rdi),%rax          //答案是movzbl (%rdi),%eax,答案疑似錯了

    movq %rax,(%rsi)    

  當src_t為int,dest_t為char時,寫出轉換的指令:

    movl (%rdi),%eax           //先改變大小指的是源比目的小時先改變大小

    movb %al,(%rsi)

  當src_t為unsigned,dest_t為unsigned char時,寫出轉換的指令:

    movl (%rdi),%ax         

    movb %al,(%rsi) 

  當src_t為char,dest_t為short時,寫出轉換的指令:

    movsbw (%rdi),%ax      

    movw %ax,(%rsi) 

 

7.push與pop指令

  1.棧是向下增長的,因此棧頂元素的地址是所有棧中元素地址中最低的。

  2.%rsp保存着棧頂元素的地址。

  3.push和pop同樣有pushq,pushl,pushw,pushb等操作,以下都以pushq為例來討論。

  4.pushq時,先將棧頂指針減8,再將值寫到新棧頂地址,如:

   pushq %rbp

   等價於:

   subq $8,%rsp (下一章講述這個指令)

   movq %rbp,(%rsp)

   這兩種操作的區別是機器代碼中pushq指令編碼占1字節,而上面兩條指令加起來需要8字節。

   驗證:隨便找個.c文件,先生成.s匯編代碼,然后在.s里面改成需要的匯編指令,生成.o文件,再用objdump -d反匯編查看,結果如下:

   

   

   

   

    

    

    

    

    此時可以看到pushq占1個字節,再改成等價的指令來看下:

       

     

    可以看到,變成了8個字節,驗證完畢。

    對了,有個小知識點,如果匯編語言中想注釋怎么辦?

    答案是和c,c++等一樣,//和#都可以實現,可以加上注釋再反匯編一下看有沒有反匯編到注釋的句子來驗證,具體就不演示了。

   5.類似地,popq會把棧頂的值讀出數據,然后再將棧指針+8,注意此時原先棧頂的內容是不變的(雖然棧頂指針已經不指向它了)。

  結束!


免責聲明!

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



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