日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

PUSHA/PUSHAD POPA/POPAD 指令详解

發布時間:2025/3/15 编程问答 28 豆豆
生活随笔 收集整理的這篇文章主要介紹了 PUSHA/PUSHAD POPA/POPAD 指令详解 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

PUSHA/PUSHAD POPA/POPAD 指令詳解

官方文檔的解釋

《Intel Architecture Software Developer’s Manual Volume 2:Instruction Set Reference》中說明了PUSHA/PUSHAD,POPA/POPAD指令的用法。

PUSHA/PUSHAD

他們的指令碼是一樣的。

當操作數的大小是32位時:
這兩個指令的作用是把通用寄存器壓棧。寄存器的入棧順序依次是:EAX,ECX,EDX,EBX,ESP(初始值),EBP,ESI,EDI.
當操作數的大小是16位時:
這兩個指令的作用是把通用寄存器壓棧。寄存器的入棧順序依次是:AX,CX,DX,BX,SP(初始值),BP,SI,DI.

注意:指令執行后,對標志寄存器無影響。
如果你還不理解上面的文字,那么官方的偽代碼更能說明問題:

Operation

IF OperandSize = 32 ( PUSHAD instruction )
THEN
Temp ← (ESP);
Push(EAX);
Push(ECX);
Push(EDX);
Push(EBX);
Push(Temp);
Push(EBP);
Push(ESI);
Push(EDI);
ELSE ( OperandSize = 16, PUSHA instruction )
Temp ← (SP);
Push(AX);
Push(CX);
Push(DX);
Push(BX);
Push(Temp);
Push(BP);
Push(SI);
Push(DI);
FI;

但是,但是,有段話要注意:

The PUSHA (push all) and PUSHAD (push all double) mnemonics reference the same opcode. The PUSHA instruction is intended for use when the operand-size attribute is 16 and the PUSHAD instruction for when the operand-size attribute is 32. Some assemblers may force the operand size to 16 when PUSHA is used and to 32 when PUSHAD is used. Others may treat these mnemonics as synonyms (PUSHA/PUSHAD) and use the current setting of the operand-size attribute to determine the size of values to be pushed from the stack, regardless of the
mnemonic used.

我把這段話翻譯一下:
PUSHA (push all) and PUSHAD (push all double)這兩個指令助記符對應的是同一個指令碼。當操作數大小是16位的時候用PUSHA,當操作數大小是32位的時候用PUSHAD。對于某些編譯器,當使用PUSHA的時候會強制操作數為16位,當使用PUSHAD的時候,會強制操作數為32位(在后面的實驗中,我發現用NASM編譯,在16位模式下就是這樣);對于另外一些編譯器,會把PUSHA/PUSHAD當成是相同的指令助記符,根據當前操作數的大小來決定壓入一個字還是一個雙字(用NASM編譯器,在32位保護模式下,PUSHA/PUSHAD都是壓入雙字)。
由此可見,NASM真是一個優秀且奇葩的編譯器 :smiley:

POPA/POPAD

這兩個指令的指令碼也是一樣的。

這兩個指令用于把棧中的值彈出到通用寄存器。其實是執行和PUSHA/PUSHAD相反的操作。
當操作數的大小是32位時:
出棧順序依次是:EDI,ESI,EBP,EBX,EDX,ECX,EAX;
當操作數的大小是16位時:
出棧順序依次是:DI,SI,BP,BX,DX,CX,AX;

The value on the stack for the ESP or SP register is ignored. Instead, the ESP or SP register is incremented after each register is loaded.

需要注意的是,棧中的ESP或者SP的值是被忽略的,ESP或SP的值在每出棧一個寄存器后會增加4或者增加2;
同樣,指令執行后,對標志寄存器無影響。
官方的偽代碼是:

Operation
IF OperandSize = 32 (instruction = POPAD)
THEN
EDI ← Pop();
ESI ← Pop();
EBP ← Pop();
increment ESP by 4 (skip next 4 bytes of stack)
EBX ← Pop();
EDX ← Pop();
ECX ← Pop();
EAX ← Pop();
ELSE (OperandSize = 16, instruction = POPA)
DI ← Pop();
SI ← Pop();
BP ← Pop();
increment ESP by 2 (skip next 2 bytes of stack)
BX ← Pop();
DX ← Pop();
CX ← Pop();
AX ← Pop();
FI;

同樣,也有句話需要注意:

The POPA (pop all) and POPAD (pop all double) mnemonics reference the same opcode. The POPA instruction is intended for use when the operand-size attribute is 16 and the POPAD instruction for when the operand-size attribute is 32. Some assemblers may force the operand size to 16 when POPA is used and to 32 when POPAD is used (using the operand-size override prefix [66H] if necessary). Others may treat these mnemonics as synonyms (POPA/POPAD) and use the current setting of the operand-size attribute to determine the size of values to be popped from the stack, regardless of the mnemonic used. (The D flag in the current code segment’s segment descriptor determines the operand-size attribute.)

這段話和上面那段話類似,我翻譯一下:
The POPA (pop all) and POPAD (pop all double)這兩個指令助記符對應的是同一個指令碼。當操作數大小是16位的時候用POPA,當操作數大小是32位的時候用POPAD。對于某些編譯器,當使用POPA的時候會強制操作數為16位,當使用POPAD的時候,會強制操作數為32位(如果有必要的話,會使用指令前綴0x66,以反轉默認操作數的大小);對于另外一些編譯器,會把POPA/POPAD當成是相同的指令助記符,根據當前操作數的大小來決定壓入一個字還是一個雙字(當前代碼段描述符的D標志位決定了操作數的大小)。
好了,官方文檔就讀到這里。下面我們做實驗。

用NASM來測試

我決定在三種模式下分別驗證pusha/pushad和popa/popad的行為。
這三種模式分別是:
1. 實模式
2. 16位保護模式
3. 32位保護模式

如果你對這三種模式有疑惑的話,可以參考我的另外一篇博文:
http://blog.csdn.net/longintchar/article/details/50602851

1.實模式

1.1實驗過程

;文件說明:實模式下測試 pusha/pushad, popa/popad;創建日期:2016-3-10;設置堆棧段和棧指針 mov ax,cs mov ss,axmov sp,0x7c00mov ax,0x01mov bx,0x02mov cx,0x03mov dx,0x04mov bp,0x05mov si,0x06mov di,0x07pushapopapushadpopada: jmp a times 510-($-$$) db 0db 0x55,0xaa

在Bochs模擬器環境下,我們看看調試截圖。

1.1.1.pusha

可以看到,寄存器的入棧順序依次是:AX,CX,DX,BX,SP(初始值0x7c00),BP,SI,DI.與官方文檔相符。

1.1.2.popa


圖片說明了一切,我就不多說了。:smiley:

1.1.3.pushad


可以看到,寄存器的入棧順序依次是:EAX, ECX,EDX,EBX,ESP(初始值0x7c00),EBP,ESI,EDI.也就是說,強制壓入32位寄存器的值。

1.1.4.popad


可以看到,依次彈出雙字的值到32位通用寄存器。

1.2.實驗結論

上面的實驗表明,如果采用NASM編譯器,那么在實模式下,pusha和pushad的效果是不一樣的,前者是入棧16位的通用寄存器,后者是入棧32位的通用寄存器。popa和popad也是這個道理,此處略去若干字。
如果觀察編譯后的指令碼,你會發現pushad和popad是添加了指令前綴0x66的,表示強制操作數大小為32位。

2.16位保護模式

2.1.實驗過程

;test pusha/pushad,popa/popad (16位保護模式下);設置堆棧段和棧指針 mov ax,cs mov ss,axmov sp,0x7c00;計算GDT所在的邏輯段地址 mov ax,[cs:gdt_base+0x7c00] ;低16位 mov dx,[cs:gdt_base+0x7c00+0x02] ;高16位 mov bx,16 div bx mov ds,ax ;令DS指向該段以進行操作mov bx,dx ;段內起始偏移地址 ;創建0#描述符,它是空描述符,這是處理器的要求mov dword [bx+0x00],0x00mov dword [bx+0x04],0x00 ;創建#1描述符,保護模式下的代碼段描述符mov dword [bx+0x08],0x7c0001ff mov dword [bx+0x0c],0x00009800 ;創建#2描述符,保護模式下的數據段描述符(文本模式下的顯示緩沖區) mov dword [bx+0x10],0x8000ffff mov dword [bx+0x14],0x0000920b ;創建#3描述符,保護模式下的堆棧段描述符mov dword [bx+0x18],0x00007a00mov dword [bx+0x1c],0x00009600;初始化描述符表寄存器GDTRmov word [cs: gdt_size+0x7c00],31 ;描述符表的界限(總字節數減一) lgdt [cs: gdt_size+0x7c00]in al,0x92 ;南橋芯片內的端口 or al,0000_0010Bout 0x92,al ;打開A20cli ;保護模式下中斷機制尚未建立,應 ;禁止中斷 mov eax,cr0or eax,1mov cr0,eax ;設置PE位;以下進入保護模式... ...jmp 0x0008:flush ;描述符選擇子:16位偏移;清流水線并串行化處理器 flush:mov cx,0x10 ;加載數據段選擇子(0x10)mov ds,cxmov cx,0x18 ;加載堆棧段選擇子mov ss,cxmov sp,0x7c00mov eax,0x01mov ebx,0x02mov ecx,0x03mov edx,0x04mov ebp,0x05mov esi,0x06mov edi,0x07pushapopapushadpopadhlt ;已經禁止中斷,將不會被喚醒 ;-------------------------------------------------------------------------------gdt_size dw 0gdt_base dd 0x00007e00 ;GDT的物理地址 times 510-($-$$) db 0db 0x55,0xaa

以上的代碼,是我偷懶從上面那個鏈接博文的代碼改過來的。其實沒有必要定義那么多的段描述符,不管了,我們開始試驗吧。

2.1.1.pusha


可以看到,壓入的是16位的寄存器值。

2.1.2.popa

依次按照字出棧,我就不截圖了。

2.1.3.pushad


可以看到,壓入的是32位的寄存器值。

2.1.4.popad

依次按照雙字出棧到32位通用寄存器。截圖略。

2.2.實驗結論

我們再看看編譯后的指令碼。

上面的實驗表明,如果采用NASM編譯器,那么在16位保護模式下,pusha和pushad的效果是不一樣的,前者是入棧16位的通用寄存器,后者是入棧32位的通用寄存器。這與實模式下的表現相同。

3.32位保護模式

3.1.實驗過程

;文件說明:32位保護模式下,測試pusha/pushad,popa/popad;創建日期:2016-3-7;設置堆棧段和棧指針 mov eax,cs mov ss,eaxmov sp,0x7c00;計算GDT所在的邏輯段地址mov eax,[cs:pgdt+0x7c00+0x02] ;GDT的32位線性基地址 xor edx,edxmov ebx,16div ebx ;分解成16位邏輯地址 mov ds,eax ;令DS指向該段以進行操作mov ebx,edx ;段內起始偏移地址 ;跳過0#號描述符的槽位 ;創建1#描述符,這是一個數據段,對應0~4GB的線性地址空間mov dword [ebx+0x08],0x0000ffff ;基地址為0,段界限為0xFFFFFmov dword [ebx+0x0c],0x00cf9200 ;粒度為4KB,存儲器段描述符 ;2#創建保護模式下初始代碼段描述符mov dword [ebx+0x10],0x7c0001ff ;基地址為0x00007c00,界限0x1FF mov dword [ebx+0x14],0x00409800 ;粒度為1個字節,代碼段描述符 ;3#建立保護模式下的堆棧段描述符 ;基地址為0x00007C00,界限0xFFFFE mov dword [ebx+0x18],0x7c00fffe ;粒度為4KB mov dword [ebx+0x1c],0x00cf9600;初始化描述符表寄存器GDTRmov word [cs: pgdt+0x7c00],31 ;描述符表的界限 lgdt [cs: pgdt+0x7c00]in al,0x92 ;南橋芯片內的端口 or al,0000_0010Bout 0x92,al ;打開A20cli ;中斷機制尚未工作mov eax,cr0or eax,1mov cr0,eax ;設置PE位;以下進入保護模式... ...jmp dword 0x0010:flush ;16位的描述符選擇子:32位偏移[bits 32] flush: mov eax,0x0008 ;加載數據段(0..4GB)選擇子; ds,es,fs,gs指向了(0..4G)mov ds,eax mov es,eaxmov fs,eaxmov gs,eaxmov eax,0x0018 ;加載棧段選擇子mov ss,eaxxor esp,esp ;ESP <- 0 mov eax,0x01mov ebx,0x02mov ecx,0x03mov edx,0x04mov ebp,0x05mov esi,0x06mov edi,0x07pushapopapushadpopadhlt ;-------------------------------------------------------------------------------pgdt dw 0dd 0x00007e00 ;GDT的物理地址times 510-($-$$) db 0db 0x55,0xaa
3.1.1.pusha


雖然用的是pusha,但是壓入的還是32位寄存器。這個是可以理解的,因為代碼段描述符的D位=1;

3.1.2.popa


依次出棧雙字。

3.1.3.pushad

同pusha,圖略。

3.1.4.popad

同popa,圖略。

3.2.實驗結論

如果采用NASM編譯器,在32位保護模式下,pusha就是pushad,他們是一樣的,編譯后指令碼也一樣。同理,popa就是popad,他們的行為也一樣。

總結

實驗環境:Bochs模擬器(版本號2.6.8)
編譯工具:NASM(版本號2.11.08)

在16位模式下(包括實模式和16位保護模式),pusha依次入棧AX,CX,DX,BX,SP(初始值),BP,SI,DI;pushad依次入棧EAX,ECX,EDX,EBX,ESP(初始值),EBP,ESI,EDI.

在32位模式下(就是32位保護模式),pusha和pushad的行為一樣,依次入棧EAX,ECX,EDX,EBX,ESP(初始值),EBP,ESI,EDI.

(完)

總結

以上是生活随笔為你收集整理的PUSHA/PUSHAD POPA/POPAD 指令详解的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。