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 allows any function to switch modes (from Real/Virtual 86 Mode to Protected Mode, or vice versa). The possible need for this was determined important enough to support such uses, although no current users are known. 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.
section_of
may be used even when the specified function
does not exist, for example when it is not included
due to the current build options.
A label specified with the section_of_function
macro,
however, must correspond to an earlier use of section_of
.
This example is based on
the initial revision
that introduced the dual code sections support.
This is what the bu_relocated
function would look like
as a normal, near-callable function
within the single lDEBUG_CODE
section:
bu_relocated: lframe near lpar word, sign lenter mov ax, word [bp + ?sign] mov di, msg.bu_relocated.sign call hexword mov dx, msg.bu_relocated call putsz lleave lret
Note that this hardcodes a near stack frame. And this is how it would be called:
mov ax, 2642h push ax call bu_relocated
Next, here's how to change it to a dual-callable function
which goes into the lDEBUG_CODE2
section
if that is used:
%if _DUALCODE usesection lDEBUG_CODE2 %endif section_of bu_relocated dualfunction bu_relocated: lframe dualdistance lpar word, sign lenter mov ax, word [bp + ?sign] mov di, msg.bu_relocated.sign nearcall hexword mov dx, msg.bu_relocated nearcall putsz lleave dualreturn lret
And this is a caller:
mov ax, 2642h push ax dualcall bu_relocated
Note the uses of dualdistance
and dualreturn
.
When this source is compiled with dual code segments enabled,
the distance of the stack frame will be far instead.
If the build is DPMI-capable then
the dualcall
helper function
will push a far-like return address,
which the dualreturn
helper will then
convert into a segmented far return address.
The lret
will expand to a retf 2
in this case.
Converting function calls to use the nearcall
method
only involves replacing plain call
instructions
by the nearcall
macro invocation,
as well as specifying the correct sections
with section_of
.
As mentioned, a function must not use lpar
parameters
on its stack in order to call it using nearcall
.
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
.