====== 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~~