User Tools

Site Tools


blog:pushbx:2022:0826_dual_code_segments_mechanisms

This is an old revision of the document!


Dual code segments mechanisms

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:

  1. Allowing boot loaded mode where no MZ loader is used.
  2. Allowing the debugger to be compressed without parsing relocations into the depacker.
  3. 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 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

C. MaslochC. Masloch, 2022-09-04 16:10:45 +0200 Sep Sun

I just added a new option: _DUALCODENEARDUAL. When enabled, and _DUALCODE=1 _PM=0, then uses of the nearcall macro which emit inter-segment calls will simply invoke the dualcall macro. The target of that dualcall 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 a nearcall from the other segment. The normal nearcall 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 a nearcall by a dualcall.

You could leave a comment if you were logged in.
blog/pushbx/2022/0826_dual_code_segments_mechanisms.1661469663.txt.gz · Last modified: 2022-08-26 01:21:03 +0200 Aug Fri by ecm