This is an old revision of the document!
The macros for dual code segment support contain two different macros for differently handling inter-segment calls.
The easier to use is the nearcall
macro.
If calling within the same segment it simply expands
to a near immediate call (opcode 0E8h
).
Otherwise it calls a helper function, with a target offset to call
stored in the code behind the helper function call opcode.
This helper thunk will rearrange the stack so that
it branches far to the desired target,
with two return addresses on the stack.
First is a near return address.
This will return to a small helper in the same code segment
as the target.
This helper will use the second return address, which is far,
to return to the original caller in the other segment.
(The second return address is actually "far-like". Details to follow
in the lDebugX dual call description.)
The nearcall
mechanism only works if the called target function does
not access parameters on its stack.
If such access is desired a dualcall
must be used instead.
The following first describes 86-Mode-only dual calls, which
cannot be used by DPMI-capable (lDebugX) builds.
The dualcall
macro will expand to a push cs
then call near rel16
sequence if calling into the same code segment.
This sequence constructs a stack frame matching that of a far call.
When calling into the other code segment,
the macro expands into a call far imm16:imm16
instruction,
with zero used as the segment.
The segment word of this instruction is entered into
a relocation patch table,
which is used by init to fill in the appropriate target segment.
We use our own patch table rather than emitting DOS MZ .EXE relocations for three reasons:
In the DPMI-capable lDebugX, the dualcall
macro will instead always call
a helper function. Each code segment has two such helpers:
One to call a function in the same segment, and one to do an actual inter-segment function call.
For both, the target offset is stored behind the (near) call instruction
in the caller's code.
The helper will construct a stack frame so that it can branch to the
desired target function, either near or far.
For true inter-segment calls this far branch always uses an actual
segment or selector, chosen by the helper thunk to match the current mode.
The target function will receive a stack frame consisting of a far-like return address,
as well as the stack as set up by the original caller.
A dual function must return with a call to the dualreturn
helper, then
a retf
or retf imm16
instruction.
Further, dual functions have to use far call stack frames.
To simplify this, the macros define dualdistance
to be far
if dual code segments are in use and near
if not.
Dual functions generally should use lframe dualdistance
to set up their stack frames.
Then any number of lpar
and a use of lpar_return
are permitted.
The dual function can directly access parameters on its stack then.
The nature of far-like return addresses is that they take up 32 bits, just like actual far return addresses. However, the segment/selector value in the high word is instead used to store an index. The dual return helper examines the index and replaces it by the desired segment or selector to which to return. This complication serves the possible need for calls to be made which switch modes in the debugger's control flow. The index supports both modes. An actual segment or selector value is inserted to replace the index only in the dual return helper, which must be immediately followed by the far return instruction that will use this far return address.
Finally, the section_of
and section_of_function
macros
are used to tell the macros which section a target function belongs to.
section_of
for a specific symbol currently must be used
before any nearcall
or dualcall
macro that uses that symbol.
The section to note down is the current active section as selected
by the addsection
or usesection
macros (from lmacros3.mac
).
The section_of_function
macro is used at a function's definition
to verify that the correct section was specified.
Discussion
I just added a new option:
_DUALCODENEARDUAL
. When enabled, and_DUALCODE=1 _PM=0
, then uses of thenearcall
macro which emit inter-segment calls will simply invoke thedualcall
macro. The target of thatdualcall
is a helper in the target segment, which calls the target function (near) and then returns far. This helper is written once for each function referenced by anearcall
from the other segment. The normalnearcall
helper functions (one in each segment) are not used then and consequently omitted from the binary.This option only takes effect if not building with DPMI support. This is because
dualcall
requires a helper function call regardless for the DPMI builds, so there's little to be saved by replacing anearcall
by adualcall
.