CALL - 呼叫過程

操作碼

指令

說明

E8 cw

CALL rel16

相對近呼叫,位移量相對於下一條指令

E8 cd

CALL rel32

相對近呼叫,位移量相對於下一條指令

FF /2

CALL r/m16

絕對間接近呼叫,地址由 r/m16 給出

FF /2

CALL r/m32

絕對間接近呼叫,地址由 r/m32 給出

9A cd

CALL ptr16:16

絕對遠呼叫,地址由運算元給出

9A cp

CALL ptr16:32

絕對遠呼叫,地址由運算元給出

FF /3

CALL m16:16

絕對間接遠呼叫,地址由 m16:16 給出

FF /3

CALL m16:32

絕對間接遠呼叫,地址由 m16:32 給出

說明

將過程鏈接資訊儲存到堆疊上,並分支到目標(呼叫目標)運算元指定的過程(被呼叫過程)。目標運算元指定被呼叫過程中第一條指令的地址。此運算元可以是立即數、通用暫存器或記憶體位置。

此指令可用於執行四種不同型別的呼叫:

    近呼叫 - 呼叫目前程式碼段(CS 暫存器目前指向的段)中的過程,有時稱為段內呼叫。

    遠呼叫 - 呼叫目前程式碼段之外的段中的過程,有時稱為段間呼叫。

    特權級別間遠呼叫 - 對特權級別與目前執行程式或過程不同的段中的過程進行的遠呼叫。

    任務切換 - 呼叫不同任務中的過程。

后兩種呼叫型別(特權級別間呼叫與任務切換)只能在保護模式中執行。如需有關近呼叫、遠呼叫及特權級別間呼叫的詳細資訊,請參閱“IA-32 英特爾(R) 體系結構軟件開發人員手冊”第 1 卷第 6 章中標題為“使用 Call 與 RET 呼叫過程”的部分。如需有關使用 CALL 指令執行任務切換的詳細資訊,請參閱“IA-32 英特爾(R) 體系結構軟件開發人員手冊”第 3 卷第 6 章“任務管理”。

近呼叫。執行近呼叫時,處理器將 EIP 暫存器的值(包含 CALL 指令後面的指令的偏移量)壓入堆疊(稍後用作返回指令指針)。然後,處理器分支到目前程式碼段中由目標運算元指定的地址。目標運算元指定程式碼段中的絕對偏移量(即相對於程式碼段基址的偏移量)或相對偏移量(相對於 EIP 暫存器中指令指針的當前值的有符號位移量,此指針指向 CALL 指令後面的指令)。執行近呼叫時,CS 暫存器保持不變。

對於近呼叫,絕對偏移量在通用暫存器或記憶體位置(r/m16r/m32)中間接指定。運算元大小屬性確定目標運算元的大小(16 位或 32 位)。絕對偏移量直接載入到 EIP 暫存器。如果運算元大小屬性是 16,則 EIP 暫存器的兩個高位位元組清除為零,得到大小最大為 16 位的指令指針。(使用堆疊指針 [ESP] 作為基址暫存器來間接訪問絕對偏移量時,使用的基址值是 ESP 在指令執行之前的值)。

在彙編程式碼中,相對偏移量(rel16rel32)通常指定為標籤,但是在機器程式碼級別,它的編碼形式是有符號的 16 位或 32 位立即數。此值會加到 EIP 暫存器中的值上。對於絕對偏移量,運算元大小屬性確定目標運算元的大小(16 位或 32 位)。

實地址模式或虛 8086 模式中的遠呼叫。在實地址模式或虛 8086 模式中執行遠呼叫時,處理器將 CS 與 EIP 暫存器的當前值壓入堆疊,作為返回指令指針使用。然後,處理器執行指向目標運算元指定的程式碼段與偏移量的“遠分支”操作,以便呼叫被呼叫過程。這裡,絕對遠地址由目標運算元使用指針(ptr16:16ptr16:32)直接指定,或是使用記憶體位置(m16:16m16:32)間接指定。使用指針方法時,被呼叫過程的段與偏移量在指令中編碼,編碼時使用 4 位元組(16 位運算元大小)或 6 位元組(32 位運算元大小)遠地址立即數。使用間接方法時,目標運算元指定記憶體位置,它包含 4 位元組(16 位運算元大小)或 6 位元組(32 位運算元大小)遠地址。運算元大小屬性確定遠地址中偏移量的大小(16 位或 32 位)。遠地址直接載入到 CS 與 EIP 暫存器。如果運算元大小屬性為 16,則 EIP 暫存器的兩個高位位元組清除為零。

保護模式中的遠呼叫。處理器在保護模式中操作時,CALL 指令可用於執行以下三種類型的遠呼叫:

    相同特權級別遠呼叫。不同特權級別遠呼叫(特權級別間呼叫)。任務切換(遠呼叫另一項任務)。

在保護模式中,處理器總是使用遠地址中的段選擇器部分訪問 GDT 或 LDT 中相應的描述符。描述符型別(程式碼段、呼叫門、任務門或 TSS)與訪問許可權確定要執行的呼叫操作型別。

如果所選描述符是程式碼段的,則執行相同特權級別程式碼段遠呼叫。(如果選擇的程式碼段在另一個特權級別中,並且程式碼段為非相容程式碼段,則產生一般保護性異常)。在保護模式中執行的相同特權級別遠呼叫與在實地址模式或虛 8086 模式中執行的遠呼叫非常相似。絕對遠地址由目標運算元使用指針(ptr16:16ptr16:32)直接指定,或是使用記憶體位置(m16:16m16:32)間接指定。運算元大小屬性確定遠地址中偏移量的大小(16 位或 32 位)。新的程式碼段選擇器及其描述符載入到 CS 暫存器,相對於指令的偏移量載入到 EIP 暫存器。

請注意,呼叫門(在下一段敘述)也可用於執行相同特權級別上程式碼段的遠呼叫。此機制提供了另一層面的間接呼叫,進行 16 位與 32 位程式碼段之間的呼叫時,首選這種方法。

執行特權級別間遠呼叫時,被呼叫過程的程式碼段必須通過呼叫門訪問。目標運算元指定的段選擇器確定呼叫門。同樣地,在這裡,目標運算元可以使用指針(ptr16:16ptr16:32)直接指定呼叫門的段選擇器,或是使用記憶體位置(m16:16m16:32)間接進行指定。處理器從呼叫門描述符中獲取新程式碼段的段選擇器與新的指令指針(偏移量)。(使用呼叫門時,忽略目標運算元的偏移量)。執行特權級別間呼叫時,處理器會切換到被呼叫過程的特權級別的堆疊。新堆疊段的段選擇器在目前執行的任務的 TSS 中指定。執行堆疊切換之後,分支到新的程式碼段。(請注意,使用呼叫門對相同特權級別的段執行遠呼叫時,不會發生堆疊切換)。在新堆疊中,處理器會壓入以下值:呼叫過程堆疊的段選擇器與堆疊指針、呼叫過程堆疊的一組參數(可選),以及呼叫過程程式碼段的段選擇器與指令指針。(呼叫門描述符的值確定要將多少個參數複製到新的堆疊)。最後,處理器分支到新程式碼段中被呼叫過程的地址。

使用 CALL 指令執行任務切換與通過呼叫門執行呼叫存在一定程度的相似。這裡,目標運算元指定要切換到的任務的任務門段選擇器(忽略目標運算元中的偏移量)。任務門則指向任務的 TSS,它包含任務程式碼與堆疊段的段選擇器。TSS 還包含掛起任務之前要執行的下一條指令的 EIP 值。此指令指針值載入到 EIP 暫存器,以便任務從這個下一條指令再次執行。

CALL 指令也可直接指定 TSS 的段選擇器,這樣就不用間接通過任務門。如需有關任務切換機制的詳細資訊,請參閱“IA-32 英特爾® 體系結構軟件開發人員手冊”第 3 卷第 6 章“任務管理”。

請注意,使用 CALL 指令執行任務切換時,會將 EFLAGS 暫存器中的巢狀任務標誌 (NT) 設定為 1,並且會同時載入新 TSS 的前一個任務鏈接欄位與舊任務的 TSS 選擇器。可以預見,程式碼會通過執行 IRET 指令暫停此巢狀任務,由於 NT 標誌已設定為 1,此指令將自動使用前一個任務鏈接返回到呼叫任務。(如需有關巢狀任務的詳細資訊,請參閱“IA-32 英特爾® 體系結構軟件開發人員手冊”第 3 卷第 6 章“任務鏈接”)。使用 CALL 指令切換任務與 JMP 指令在這一點上是不同的,JMP 指令不會將 NT 標誌設定為 1,因此 IRET 指令應該不會暫停任務。

16 位與 32 位混合呼叫。在 16 位與 32 位程式碼段之間執行遠呼叫時,應該通過呼叫門進行。如果是從 32 位程式碼段到 16 位程式碼段的遠呼叫,則應該從 32 位程式碼段的頭 64 KB 執行呼叫。這是因為指令的運算元大小屬性設定為 16,所以只能儲存 16 位返回地址偏移量。另外,應該使用 16 位呼叫門執行呼叫,以便將 16 位值壓入堆疊。如需有關在 16 位與 32 位程式碼段之間執行呼叫的詳細資訊,請參閱“IA-32 英特爾(R) 體系結構軟件開發人員手冊”第 3 卷第 16 章“16 位與 32 位混合程式碼”。

操作

IF near call
THEN IF near relative call
IF the instruction pointer is not within code segment limit THEN #GP(0); FI;
THEN IF OperandSize 32
THEN
IF stack not large enough for a 4-byte return address THEN #SS(0); FI;
Push(EIP);
EIP EIP + DEST; (* DEST is rel32 *)
ELSE (* OperandSize 16 *)
IF stack not large enough for a 2-byte return address THEN #SS(0); FI;
Push(IP);
EIP (EIP + DEST) AND 0000FFFFH; (* DEST is rel16 *)
FI;
FI;
ELSE (* near absolute call *)
IF the instruction pointer is not within code segment limit THEN #GP(0); FI;
IF OperandSize 32
THEN
IF stack not large enough for a 4-byte return address THEN #SS(0); FI;
Push(EIP);
EIP DEST; (* DEST is r/m32 *)
ELSE (* OperandSize 16 *)
IF stack not large enough for a 2-byte return address THEN #SS(0); FI;
Push(IP);
EIP DEST AND 0000FFFFH; (* DEST is r/m16 *)
FI;
FI:
FI;

IF far call AND (PE 0 OR (PE 1 AND VM 1)) (* real-address or virtual-8086 mode *)
THEN
IF OperandSize 32
THEN
IF stack not large enough for a 6-byte return address THEN #SS(0); FI;
IF the instruction pointer is not within code segment limit THEN #GP(0); FI;
Push(CS); (* padded with 16 high-order bits *)
Push(EIP);
CS DEST[47:32]; (* DEST is ptr16:32 or [m16:32] *)
EIP DEST[31:0]; (* DEST is ptr16:32 or [m16:32] *)
ELSE (* OperandSize 16 *)
IF stack not large enough for a 4-byte return address THEN #SS(0); FI;
IF the instruction pointer is not within code segment limit THEN #GP(0); FI;
Push(CS);
Push(IP);
CS DEST[31:16]; (* DEST is ptr16:16 or [m16:16] *)
EIP DEST[15:0]; (* DEST is ptr16:16 or [m16:16] *)
EIP EIP AND 0000FFFFH; (* clear upper 16 bits *)
FI;
FI;

IF far call AND (PE 1 AND VM 0) (* Protected mode, not virtual-8086 mode *)
THEN
IF segment selector in target operand null THEN #GP(0); FI;
IF segment selector index not within descriptor table limits
THEN #GP(new code segment selector);
FI;
Read type and access rights of selected segment descriptor;
IF segment type is not a conforming or nonconforming code segment, call gate,
task gate, or TSS THEN #GP(segment selector); FI;
Depending on type and access rights
GO TO CONFORMING-CODE-SEGMENT;
GO TO NONCONFORMING-CODE-SEGMENT;
GO TO CALL-GATE;
GO TO TASK-GATE;
GO TO TASK-STATE-SEGMENT;
FI;

CONFORMING-CODE-SEGMENT:
IF DPL > CPL THEN #GP(new code segment selector); FI;
IF segment not present THEN #NP(new code segment selector); FI;
IF OperandSize 32
THEN
IF stack not large enough for a 6-byte return address THEN #SS(0); FI;
IF the instruction pointer is not within code segment limit THEN #GP(0); FI;
Push(CS); (* padded with 16 high-order bits *)
Push(EIP);
CS DEST[NewCodeSegmentSelector);
(* segment descriptor information also loaded *)
CS(RPL) CPL
EIP DEST[offset);
ELSE (* OperandSize 16 *)
IF stack not large enough for a 4-byte return address THEN #SS(0); FI;
IF the instruction pointer is not within code segment limit THEN #GP(0); FI;
Push(CS);
Push(IP);
CS DEST[NewCodeSegmentSelector);
(* segment descriptor information also loaded *)
CS(RPL) CPL
EIP DEST[offset) AND 0000FFFFH; (* clear upper 16 bits *)
FI;
END;

NONCONFORMING-CODE-SEGMENT:
IF (RPL > CPL) OR (DPL CPL) THEN #GP(new code segment selector); FI;
IF segment not present THEN #NP(new code segment selector); FI;
IF stack not large enough for return address THEN #SS(0); FI;
tempEIP DEST[offset)
IF OperandSize=16
THEN
tempEIP tempEIP AND 0000FFFFH; (* clear upper 16 bits *)
FI;
IF tempEIP outside code segment limit THEN #GP(0); FI;
IF OperandSize 32
THEN
Push(CS); (* padded with 16 high-order bits *)
Push(EIP);
CS DEST[NewCodeSegmentSelector);
(* segment descriptor information also loaded *)
CS(RPL) CPL;
EIP tempEIP;
ELSE (* OperandSize 16 *)
Push(CS);
Push(IP);
CS DEST[NewCodeSegmentSelector);
(* segment descriptor information also loaded *)
CS(RPL) CPL;
EIP tempEIP;
FI;
END;

CALL-GATE:
IF call gate DPL < CPL or RPL THEN #GP(call gate selector); FI;
IF call gate not present THEN #NP(call gate selector); FI;
IF call gate code-segment selector is null THEN #GP(0); FI;
IF call gate code-segment selector index is outside descriptor table limits
THEN #GP(code segment selector); FI;
Read code segment descriptor;
IF code-segment segment descriptor does not indicate a code segment
OR code-segment segment descriptor DPL > CPL
THEN #GP(code segment selector); FI;
IF code segment not present THEN #NP(new code segment selector); FI;
IF code segment is non-conforming AND DPL < CPL
THEN go to MORE-PRIVILEGE;
ELSE go to SAME-PRIVILEGE;
FI;
END;

MORE-PRIVILEGE:
IF current TSS is 32-bit TSS
THEN
TSSstackAddress new code segment (DPL * 8) + 4
IF (TSSstackAddress + 7) > TSS limit
THEN #TS(current TSS selector); FI;
newSS TSSstackAddress + 4;
newESP stack address;
ELSE (* TSS is 16-bit *)
TSSstackAddress new code segment (DPL * 4) + 2
IF (TSSstackAddress + 4) > TSS limit
THEN #TS(current TSS selector); FI;
newESP TSSstackAddress;
newSS TSSstackAddress + 2;
FI;
IF stack segment selector is null THEN #TS(stack segment selector); FI;
IF stack segment selector index is not within its descriptor table limits
THEN #TS(SS selector); FI
Read code segment descriptor;
IF stack segment selector's RPL DPL of code segment
OR stack segment DPL DPL of code segment
OR stack segment is not a writable data segment
THEN #TS(SS selector); FI
IF stack segment not present THEN #SS(SS selector); FI;
IF CallGateSize 32
THEN
IF stack does not have room for parameters plus 16 bytes
THEN #SS(SS selector); FI;
IF CallGate(InstructionPointer) not within code segment limit THEN #GP(0); FI;
SS newSS;
(* segment descriptor information also loaded *)
ESP newESP;
CS:EIP CallGate(CS:InstructionPointer);
(* segment descriptor information also loaded *)
Push(oldSS:oldESP); (* from calling procedure *)
temp parameter count from call gate, masked to 5 bits;
Push(parameters from calling procedure's stack, temp)
Push(oldCS:oldEIP); (* return address to calling procedure *)
ELSE (* CallGateSize 16 *)
IF stack does not have room for parameters plus 8 bytes
THEN #SS(SS selector); FI;
IF (CallGate(InstructionPointer) AND FFFFH) not within code segment limit
THEN #GP(0); FI;
SS newSS;
(* segment descriptor information also loaded *)
ESP newESP;
CS:IP CallGate(CS:InstructionPointer);
(* segment descriptor information also loaded *)
Push(oldSS:oldESP); (* from calling procedure *)
temp parameter count from call gate, masked to 5 bits;
Push(parameters from calling procedure's stack, temp)
Push(oldCS:oldEIP); (* return address to calling procedure *)
FI;
CPL CodeSegment(DPL)
CS(RPL) CPL
END;

SAME-PRIVILEGE:
IF CallGateSize 32
THEN
IF stack does not have room for 8 bytes
THEN #SS(0); FI;
IF EIP not within code segment limit then #GP(0); FI;
CS:EIP CallGate(CS:EIP) (* segment descriptor information also loaded *)
Push(oldCS:oldEIP); (* return address to calling procedure *)
ELSE (* CallGateSize 16 *)
IF stack does not have room for parameters plus 4 bytes
THEN #SS(0); FI;
IF IP not within code segment limit THEN #GP(0); FI;
CS:IP CallGate(CS:instruction pointer)
(* segment descriptor information also loaded *)
Push(oldCS:oldIP); (* return address to calling procedure *)
FI;
CS(RPL) CPL
END;
TASK-GATE:
IF task gate DPL < CPL or RPL
THEN #GP(task gate selector);
FI;
IF task gate not present
THEN #NP(task gate selector);
FI;
Read the TSS segment selector in the task-gate descriptor;
IF TSS segment selector local/global bit is set to local
OR index not within GDT limits
THEN #GP(TSS selector);
FI;
Access TSS descriptor in GDT;

IF TSS descriptor specifies that the TSS is busy (low-order 5 bits set to 00001)
THEN #GP(TSS selector);
FI;
IF TSS not present
THEN #NP(TSS selector);
FI;
SWITCH-TASKS (with nesting) to TSS;
IF EIP not within code segment limit
THEN #GP(0);
FI;
END;

TASK-STATE-SEGMENT:
IF TSS DPL < CPL or RPL
OR TSS descriptor indicates TSS not available
THEN #GP(TSS selector);
FI;
IF TSS is not present
THEN #NP(TSS selector);
FI;
SWITCH-TASKS (with nesting) to TSS
IF EIP not within code segment limit
THEN #GP(0);
FI;
END;

影響的標誌

發生任務切換時影響所有的標誌;未發生任務切換時則不影響任何標誌。

保護模式異常

#GP(0) - 如果目標運算元中的目標偏移量超出新程式碼段的限制。如果目標運算元中的段選擇器為空。如果門中的程式碼段選擇器為空。如果記憶體運算元有效地址超出 CS、DS、ES、FS 或 GS 段限制。如果 DS、ES、FS、或 GS 暫存器用於訪問記憶體,並且它包含空的段選擇器。

#GP(選擇器) - 如果程式碼段選擇器、門選擇器或 TSS 選擇器的索引超出描述符表格的限制。如果目標運算元中的段選擇器指向的不是相容程式碼段、非相容程式碼段、呼叫門、任務門或任務狀態段的段描述符。如果非相容程式碼段的 DPL 不等於 CPL,或是該段的段選擇器的 RPL 大於 CPL。如果相容程式碼段的 DPL 大於 CPL。如果呼叫門、任務門或 TSS 段描述符的 DPL 小於 CPL,或者小於呼叫門、任務門或 TSS 的段選擇器的 RPL。如果呼叫門提供的段選擇器對應的段描述符沒有指出它是程式碼段。如果呼叫門提供的段選擇器超出描述符表格的限制。如果從呼叫門獲取的程式碼段的 DPL 大於 CPL。如果 TSS 的段選擇器將自己的區域性/全域性位設定為區域性。如果 TSS 段描述符指出 TSS 忙或不可用。

#SS(0) - 如果將返回地址、參數或堆疊段指針壓入堆疊時超出堆疊段的邊界,而沒有進行堆疊切換。如果記憶體運算元有效地址超出 SS 段限制。

#SS(選擇器) - 如果將返回地址、參數或堆疊段指針壓入堆疊時超出堆疊段的邊界,而沒有進行堆疊切換。如果在執行堆疊切換的過程中載入 SS 暫存器,但指向的段標記為不存在。如果發生堆疊切換時,堆疊段沒有空間可以儲存返回地址、參數或堆疊段指針。

#NP(選擇器) - 如果程式碼段、數據段、堆疊段、呼叫門、任務門或 TSS 不存在。

#TS(選擇器) - 如果新的堆疊段選擇器與 ESP 超出 TSS 的末尾。如果新的堆疊段選擇器為空。如果新堆疊段選擇器在 TSS 中的 RPL 與訪問的程式碼段的 DPL 不相等。如果新堆疊段的堆疊段描述符的 DPL 與程式碼段描述符的 DPL 不相等。如果新的堆疊段是不可寫數據段。如果堆疊段的段選擇器索引超出描述符表格限制。

#PF(錯誤程式碼) - 如果發生頁錯誤。

#AC(0) - 如果在 CPL 為 3 且啟用對齊檢查的情況下發生未對齊的記憶體訪問。

實地址模式異常

#GP - 如果記憶體運算元有效地址超出 CS、DS、ES、FS 或 GS 段限制。如果目標偏移量超出程式碼段的限制。

虛 8086 模式異常

#GP(0) - 如果記憶體運算元有效地址超出 CS、DS、ES、FS 或 GS 段限制。如果目標偏移量超出程式碼段的限制。

#PF(錯誤程式碼) - 如果發生頁錯誤。

#AC(0) - 如果在啟用對齊檢查的情況下發生未對齊的記憶體訪問。