====== Dual code segments mechanisms ======
[[https://hg.pushbx.org/ecm/ldebug/file/7016dd710698/source/debug.mac#l988|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:
- Allowing boot-loaded mode where no MZ loader is used.
- Allowing the debugger to be compressed without parsing relocations into the depacker.
- Allowing to dynamically determine the layout and placement of the code segments at run time instead of leaving them fixed relative to the PSP.
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''.
===== Examples =====
This example is based on
[[https://hg.pushbx.org/ecm/ldebug/rev/e60574d47874#l1.7|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''.
{{tag>ldebug segmentation}}
~~DISCUSSION~~