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,注意此時原先棧頂的內容是不變的(雖然棧頂指針已經不指向它了)。
結束!