up:
Chapter 16 -- Mixing 16-Bit and 32 Bit Code
prev:
16.3 Sharing Data Segments Among Mixed Code Segments
next:
Chapter 17 -- 80386 Instruction Set
16.4 Transferring Control Among Mixed Code Segments
When transferring control among procedures in USE16 and USE32 code
segments, programmers must be aware of three points:
- Addressing limitations imposed by pointers with 16-bit offsets.
- Matching of operand-size attribute in effect for the CALL/RET pair and
theInterrupt/IRET pair so as to manage the stack correctly.
- Translation of parameters, especially pointer parameters.
Clearly, 16-bit effective addresses cannot be used to address data or code
located beyond 64K in a 32-bit segment, nor can large 32-bit parameters be
squeezed into a 16-bit word; however, except for these obvious limits, most
interfacing problems between 16-bit and 32-bit modules can be solved. Some
solutions involve inserting interface procedures between the procedures in
question.
16.4.1 Size of Code-Segment Pointer
For control-transfer instructions that use a pointer to identify the next
instruction (i.e., those that do not use gates), the size of the offset
portion of the pointer is determined by the operand-size attribute. The
implications of the use of two different sizes of code-segment pointer are:
- JMP, CALL, or RET from 32-bit segment to 16-bit segment is always
possible using a 32-bit operand size.
- JMP, CALL, or RET from 16-bit segment using a 16-bit operand size
cannot address the target in a 32-bit segment if the address of the
target is greater than 64K.
An interface procedure can enable transfers from USE16 segments to 32-bit
addresses beyond 64K without requiring modifications any more extensive than
relinking or rebinding the old programs. The requirements for such an
interface procedure are discussed later in this chapter.
16.4.2 Stack Management for Control Transfers
Because stack management is different for 16-bit CALL/RET than for 32-bit
CALL/RET, the operand size of RET must match that of
CALL. (Refer to
Figure 16-1
.) A 16-bit CALL pushes the 16-bit IP and
(for calls between privilege levels)
the 16-bit SP register. The corresponding RET must also use a 16-bit
operand size to POP these 16-bit values from the stack into the 16-bit
registers. A 32-bit CALL pushes the 32-bit EIP and (for interlevel calls)
the 32-bit ESP register. The corresponding RET must also use a 32-bit
operand size to POP these 32-bit values from the stack into the 32-bit
registers. If the two halves of a CALL/RET pair do not have matching operand
sizes, the stack will not be managed correctly and the values of the
instruction pointer and stack pointer will not be restored to correct
values.
When the CALL and its corresponding RET are in segments that have D-bits
with the same values (i.e., both have 32-bit defaults or both have 16-bit
defaults), there is no problem. When the CALL and its corresponding RET are
in segments that have different D-bit values, however, programmers (or
program development software) must ensure that the CALL and RET match.
There are three ways to cause a 16-bit procedure to execute a 32-bit call:
- Use a 16-bit call to a 32-bit interface procedure that then uses a
32-bit call to invoke the intended target.
- Bind the 16-bit call to a 32-bit call gate.
- Modify the 16-bit procedure, inserting an operand-size prefix before
the call, thereby changing it to a 32-bit call.
Likewise, there are three ways to cause a 32-bit procedure to execute a
16-bit call:
- Use a 32-bit call to a 32-bit interface procedure that then uses a
16-bit call to invoke the intended target.
- Bind the 32-bit call to a 16-bit call gate.
- Modify the 32-bit procedure, inserting an operand-size prefix before
the call, thereby changing it to a 16-bit call. (Be certain that the
return offset does not exceed 64K.)
Programmers can utilize any of the preceding methods to make a CALL in a
USE16 segment match the corresponding RET in a USE32 segment, or to make a
CALL in a USE32 segment match the corresponding RET in a USE16 segment.
16.4.2.1 Controlling the Operand-Size for a Call
When the selector of the pointer referenced by a CALL instruction selects a
segment descriptor, the operand-size attribute in effect for the CALL
instruction is determined by the D-bit in the segment descriptor and by any
operand-size instruction prefix.
When the selector of the pointer referenced by a CALL instruction selects a
gate descriptor, the type of call is determined by the type of call gate. A
call via an 80286 call gate (descriptor type 4) always has a 16-bit
operand-size attribute; a call via an 80386 call gate (descriptor type 12)
always has a 32-bit operand-size attribute. The offset of the target
procedure is taken from the gate descriptor; therefore, even a 16-bit
procedure can call a procedure that is located more than 64 kilobytes from
the base of a 32-bit segment, because a 32-bit call gate contains a 32-bit
target offset.
An unmodified 16-bit code segment that has run successfully on an 8086 or
real-mode 80286 will always have a D-bit of zero and will not use
operand-size override prefixes; therefore, it will always execute 16-bit
versions of CALL. The only modification needed to make a16-bit procedure
effect a 32-bit call is to relink the call to an 80386 call gate.
16.4.2.2 Changing Size of Call
When adding 32-bit gates to 16-bit procedures, it is important to consider
the number of parameters. The count field of the gate descriptor specifies
the size of the parameter string to copy from the current stack to the stack
of the more privileged procedure. The count field of a 16-bit gate specifies
the number of words to be copied, whereas the count field of a 32-bit gate
specifies the number of doublewords to be copied; therefore, the 16-bit
procedure must use an even number of words as parameters.
16.4.3 Interrupt Control Transfers
With a control transfer due to an interrupt or exception, a gate is always
involved. The operand-size attribute for the interrupt is determined by the
type of IDT gate.
A 386 interrupt or trap gate (descriptor type 14 or 15) to a 32-bit
interrupt procedure can be used to interrupt either 32-bit or 16-bit
procedures. However, it is not generally feasible to permit an interrupt or
exception to invoke a 16-bit handler procedure when 32-bit code is
executing, because a 16-bit interrupt procedure has a return offset of only
16-bits on its stack. If the 32-bit procedure is executing at an address
greater than 64K, the 16-bit interrupt procedure cannot return correctly.
16.4.4 Parameter Translation
When segment offsets or pointers (which contain segment offsets) are passed
as parameters between 16-bit and 32-bit procedures, some translation is
required. Clearly, if a 32-bit procedure passes a pointer to data located
beyond 64K to a 16-bit procedure, the 16-bit procedure cannot utilize it.
Beyond this natural limitation, an interface procedure can perform any
format conversion between 32-bit and 16-bit pointers that may be needed.
Parameters passed by value between 32-bit and 16-bit code may also require
translation between 32-bit and 16-bit formats. Such translation requirements
are application dependent. Systems designers should take care to limit the
range of values passed so that such translations are possible.
16.4.5 The Interface Procedure
Interposing an interface procedure between 32-bit and 16-bit procedures can
be the solution to any of several interface requirements:
- Allowing procedures in 16-bit segments to transfer control to
instructions located beyond 64K in 32-bit segments.
- Matching of operand size for CALL/RET.
- Parameter translation.
Interface procedures between USE32 and USE16 segments can be constructed
with these properties:
- The procedures reside in a code segment whose D-bit is set, indicating
a default operand size of 32-bits.
- All entry points that may be called by 16-bit procedures have offsets
that are actually less than 64K.
- All points to which called 16-bit procedures may return also lie
within 64K.
The interface procedures do little more than call corresponding procedures
in other segments. There may be two kinds of procedures:
- Those that are called by 16-bit procedures and call 32-bit procedures.
These interface procedures are called by 16-bit CALLs and use the
operand-size prefix before RET instructions to cause a 16-bit RET.
CALLs to 32-bit segments are 32-bit calls (by default, because the
D-bit is set), and the 32-bit code returns with 32-bit RET
instructions.
- Those that are called by 32-bit procedures and call 16-bit procedures.
These interface procedures are called by 32-bit CALL instructions, and
return with 32-bit RET instructions (by default, because the D-bit is
set). CALLs to 16-bit procedures use the operand-size prefix;
procedures in the 16-bit code return with 16-bit RET instructions.
up:
Chapter 16 -- Mixing 16-Bit and 32 Bit Code
prev:
16.3 Sharing Data Segments Among Mixed Code Segments
next:
Chapter 17 -- 80386 Instruction Set