The MASM Forum

Miscellaneous => 16 bit DOS Programming => Topic started by: fredrikhr on March 12, 2025, 06:43:40 AM

Title: Storing and restoring segment registers
Post by: fredrikhr on March 12, 2025, 06:43:40 AM
Following the open-sourcing of the Command&Conquer Game series by Electronic Arts earlier this month, I have started looking at the codebase for Red Alert;
( https://github.com/electronicarts/CnC_Red_Alert (https://github.com/electronicarts/CnC_Red_Alert) ) out of pure curiosity.
The code is dated 1995 and contains some Assembly code, mostly written for the Borland Turbo Assembler TASM. I am looking at "modernizing" the code to make it compilable with the modern version of MASM just as way to learn the differences between old TASM code and MASM.

Anyway, Red Alert had both a 32-bit and a 16-bit launcher application (as well as the actual RA95.exe executable). The 16-bit DOS Launcher lives in the LAUNCH.ASM file

It declares a 16-bit CODE segment in ( https://github.com/electronicarts/CnC_Red_Alert/blob/main/LAUNCH/LAUNCH.ASM#L111 (https://github.com/electronicarts/CnC_Red_Alert/blob/main/LAUNCH/LAUNCH.ASM#L111) ) and later also a 16-bit DATA segment ( Line 550 ).

TITLE    "Red Alert launcher"
.386
; ...

_code segment para public use16 'code'
; ...
_code ends

_data segment dword public use16 'data'
; ...
_data ends

end

In the application entry point it starts setting up the DS segment register:
mov ax,_data
mov ds,ax
(ref. https://github.com/electronicarts/CnC_Red_Alert/blob/main/LAUNCH/LAUNCH.ASM#L136 (https://github.com/electronicarts/CnC_Red_Alert/blob/main/LAUNCH/LAUNCH.ASM#L136) )

However, when assembling this MASM returns the following error message:
LAUNCH/LAUNCH.ASM(136): error A2004: symbol type conflict

When converting some of the other TASM Assembly to MASM I ran into similar issues (e.g. in the Keyboard and Timer interrupt handlers) where MASM is decidedly unhappy with assigning the _DATA (or even @DATA or SEG @DATA or SEG _DATA) values to the ax register (in order to subsequently assign that to DS, ES, etc.

Why? And what declaration on the segments need to be done in order for this to work?

Using Microsoft (R) Macro Assembler Version 14.43.34808.0 and invoking it thusly:
> ml.exe /c LAUNCH\LAUNCH.ASM
Microsoft (R) Macro Assembler Version 14.43.34808.0
Copyright (C) Microsoft Corporation.  All rights reserved.

 Assembling: LAUNCH\LAUNCH.ASM
LAUNCH\LAUNCH.ASM(136) : error A2004:symbol type conflict
LAUNCH\LAUNCH.ASM(282) : error A2004:symbol type conflict
Title: Re: Storing and restoring segment registers
Post by: zedd151 on March 12, 2025, 06:46:30 AM
Maybe use an older, legacy version of ml.exe? Like what is already in the Masm32 SDK...
Of course there may be a different reason for your issues. I never code in 16 bit, or any other that uses segment registers explicitly, so maybe I'm not the best to give advice here.
Title: Re: Storing and restoring segment registers
Post by: fredrikhr on March 12, 2025, 06:57:13 AM
Sure, I could use an older assembler, I tried the MASM /Zm flag, but that only gives "Enable MASM 5.10 compatibility" (with the same error message). However, using an old assembler defeats the purpose of "modernizing" the code. I'd much rather understand what the issue is here, but I do not understand the error message.

I get that 32-bit flat layout largely makes segments redundant, but this is 16-bit realmode, right?
Title: Re: Storing and restoring segment registers
Post by: zedd151 on March 12, 2025, 06:59:39 AM
Quote from: fredrikhr on March 12, 2025, 06:57:13 AMHowever, using an old assembler defeats the purpose of "modernizing" the code. I'd much rather understand what the issue is here, but I do not understand the error message.
ok. My bad. I thought that something like "Enable MASM 5.10 compatibility", or similar compatibility option is what you might need - and possibly not available on more modern versions of ml.

Confirms what I said above "maybe I'm not the best to give advice here". Sorry about that.
Title: Re: Storing and restoring segment registers
Post by: sinsi on March 12, 2025, 07:50:07 AM
Later versions of ML need the /omf switch.
C:\asm\CnC_Red_Alert-main\LAUNCH>ml/c LAUNCH.ASM
Microsoft (R) Macro Assembler Version 14.43.34809.0
Copyright (C) Microsoft Corporation.  All rights reserved.

 Assembling: LAUNCH.ASM
LAUNCH.ASM(136) : error A2004:symbol type conflict
LAUNCH.ASM(282) : error A2004:symbol type conflict
LAUNCH.ASM(594) : warning A4023:with /coff switch, leading underscore required for start address : Start

C:\asm\CnC_Red_Alert-main\LAUNCH>ml/c /omf LAUNCH.ASM
Microsoft (R) Macro Assembler Version 14.43.34809.0
Copyright (C) Microsoft Corporation.  All rights reserved.

 Assembling: LAUNCH.ASM

C:\asm\CnC_Red_Alert-main\LAUNCH>

Thanks for the link to the source, I (many moons ago) wrote a no-cd launcher for this game :biggrin:)
Title: Re: Storing and restoring segment registers
Post by: NoCforMe on March 12, 2025, 07:53:11 AM
Something else: I took a look back at my old 16-bit code.
You might try simplifying your segment directives.
I found mine to be more like this:
Code SEGMENT WORD
ASSUME CS:Code, DS:Code
which just uses default attributes which worked for me.
Not sure if you need all those qualifiers, some of which may be scrambling MASM's little brains ...

(not sure if you need the ASSUME statement; I just included it for a possible example)
Title: Re: Storing and restoring segment registers
Post by: sinsi on March 12, 2025, 08:05:41 AM
Quote from: NoCforMe on March 12, 2025, 07:53:11 AMSomething else: I took a look back at my old 16-bit code.
You might try simplifying your segment directives.
I found mine to be more like this:
Code    SEGMENT WORD
    ASSUME    CS:Code, DS:Code
which just uses default attributes which worked for me.
Not sure if you need all those qualifiers, some of which may be scrambling MASM's little brains ...

(not sure if you need the ASSUME statement; I just included it for a possible example)

ASSUME is there in the source.
Also this lol
QuoteSimplified segment models are for wimmin

One reason for defining segments - more control over segment attributes
QuoteWe want 32 bit instructions in a 16 bit segment

Title: Re: Storing and restoring segment registers
Post by: NoCforMe on March 12, 2025, 08:14:49 AM
OK. What threw me was
QuoteIt declares a 16-bit CODE segment in ( https://github.com/electronicarts/CnC_Red_Alert/blob/main/LAUNCH/LAUNCH.ASM#L111 (https://github.com/electronicarts/CnC_Red_Alert/blob/main/LAUNCH/LAUNCH.ASM#L111) )

which I assumed meant 16-bit code in that segment.
I've never actually seen that mixture of 16- and 32-bit code in the same program before.
Didn't even know you could do that. (Well, I guess .386 makes that possible.)

So OP, did that MASM option (/omf) fix your problem?

(Sure it isn't actually /omfg?) haha
Title: Re: Storing and restoring segment registers
Post by: fredrikhr on March 12, 2025, 09:18:03 AM
Quote from: NoCforMe on March 12, 2025, 08:14:49 AMSo OP, did that MASM option (/omf) fix your problem?
Yes, I can confirm that using the /omf option to ml.exe works.

For reference:
> ml /?
Microsoft (R) Macro Assembler Version 14.43.34808.0
Copyright (C) Microsoft Corporation.  All rights reserved.

/omf generate OMF format object file
About OMF (see https://en.wikipedia.org/wiki/Object_Module_Format_(Intel)#Use (https://en.wikipedia.org/wiki/Object_Module_Format_(Intel)#Use) )

QuoteThe file format is the most important object file format under DOS, 16-bit Windows, and 16-bit and 32-bit OS/2.

Few toolchains use the 32-bit version of the OMF format. For example, the Watcom C toolchain allows generating code for targets that use 32-bit segmented memory layouts.
On the difference between COFF and OMF (ref. https://stackoverflow.com/a/966689/2226662 (https://stackoverflow.com/a/966689/2226662) )

QuoteBorland, for example, still uses OMF object files and libraries, while Microsoft's 32-bit compilers produce COFF format files. Watcom C/C++ v11.0 seems to prefer COFF when compiling and linking Windows applications, but generates OMF object files for use with their DOS4GW 32-bit protected-mode DOS-extender.
The Red Alert source code here targets the Borland C/C++ Compiler and the Borland Turbo Assembler while it shows in some places to be compatible with the Watcom C/C++ compiler. The source code also shows references to DOS 32-bit protected-mode extension.

So yes, all the above clearly indicate that this code base needs to assemble using OMF format (where necessary). Since modern MASM and the modern Linker support OMF, this simply means that I will append the OMF-flag for the few assembly files where it becomes necessary. I think I'll use COFF everywhere else still.

Thanks for the help in figuring this out :)
Title: Re: Storing and restoring segment registers
Post by: sinsi on March 12, 2025, 09:27:45 AM
Quote from: fredrikhr on March 12, 2025, 09:18:03 AMSince modern MASM and the modern Linker support OMF
MASM/ML support outputting OMF but later MS linkers will probably choke, since they attempt to convert OMF to COFF.

Also, later MS linkers won't output a 16-bit EXE, only 32-bit PE EXEs.
The MASM32 SDK includes LINK.EXE (renamed to LINK16.EXE)
Microsoft (R) Segmented Executable Linker  Version 5.60.339 Dec  5 1994
Title: Re: Storing and restoring segment registers
Post by: zedd151 on March 12, 2025, 10:54:46 AM
I had thought in the beginning that it might have been some sort of compatibility issue with the later version of ml. (14.xxxx for the OP here)

I was partially right (it didn't appear to be an issue with the code itself), but also wrong at the same time, (not what I was thinking).

Glad your all sorted, frederikhr
Title: Re: Storing and restoring segment registers
Post by: FORTRANS on March 12, 2025, 10:50:34 PM
Hi,

Quote from: NoCforMe on March 12, 2025, 08:14:49 AMwhich I assumed meant 16-bit code in that segment.
I've never actually seen that mixture of 16- and 32-bit code in the same program before.
Didn't even know you could do that. (Well, I guess .386 makes that possible.)

   You can use 32-bit opcodes/instructions in 16-bit code.  I have done that and it
is fairly easy to do.  However  mixing code that works in a 16-bit CPU mode with code
using a 32-bit code requires you change the CPU mode in the program.  This is not
something I have looked at for any practical usage.  One boots in a real 16-bit mode
and I have seen code that then switches to a different mode for the program proper.
Probably in code to just do that to see how it works out.  Have fun as needed.

   Oh, and the use of 32-bit DOS extenders of course.  But that uses prebuilt code from others.
Cheers,

Steve N.
Title: Re: Storing and restoring segment registers
Post by: NoCforMe on March 13, 2025, 09:25:28 AM
Quote from: FORTRANS on March 12, 2025, 10:50:34 PMYou can use 32-bit opcodes/instructions in 16-bit code.  I have done that and it
is fairly easy to do.  However  mixing code that works in a 16-bit CPU mode with code
using a 32-bit code requires you change the CPU mode in the program.
That's interesting: I went back and looked at the OP's posted code; I didn't find any such mode-change instructions before the first use of 32-bit instructions.
; dos get free disc space returns with the following
; ax - sectors per cluster
; bx - number of available clusters
; cx - bytes per sector
; dx - cluster on the drive

mul bx ;clusters avail*sectors per cluster
shl edx,16
mov dx,ax
mov eax,edx
and ecx,65535
mul ecx ;*bytes per sector
cmp eax,INIT_FREE_DISK_SPACE
blo Disc_Error
Unless I missed something there ...

So do you really need to do this (change the CPU mode) or not?
Title: Re: Storing and restoring segment registers
Post by: FORTRANS on March 13, 2025, 09:57:02 AM
Quote from: NoCforMe on March 13, 2025, 09:25:28 AM
Quote from: FORTRANS on March 12, 2025, 10:50:34 PMYou can use 32-bit opcodes/instructions in 16-bit code.  I have done that and it
is fairly easy to do.  However  mixing code that works in a 16-bit CPU mode with code
using a 32-bit code requires you change the CPU mode in the program.
That's interesting: I went back and looked at the OP's posted code; I didn't find any such mode-change instructions before the first use of 32-bit instructions.

Unless I missed something there ...

So do you really need to do this (change the CPU mode) or not?

   No mode change is necessary to run 32-bit instructions in a 16-bit mode.
No mode change is required to run 16-bit instructions in a 32-bit mode.
But code designed to run in a 32-bit mode will not run (for the most part)
in a 16-bit CPU mode.

   You can write code, and using the USE16 and USE32 directives, you can
see that MASM produces rather different opcodes for your code depending on
it being written for a 32-bit mode or a 16-bit mode.  If you write a program,
you (normally) cannot run mixed 16-bit and 32-bit code without either changing
modes, or expecting weird results (crashes/exceptions mostly).

   Amusingly, you can use the 32-bit addressing modes in a 16-bit mode.  You
do have to limit the addressing range to the 16-bit segment limits.  Except
that you can reprogram the segment limits to create "Unreal Mode" or a memory
manager.  DPMI and the DOS extenders used by Watcom compilers come to mind.

Regards,

Steve N.
Title: Re: Storing and restoring segment registers
Post by: NoCforMe on March 13, 2025, 10:03:38 AM
Still confused by your answer:
What's the difference between 16- and 32-bit modes and 16- and 32-bit instructions?

What perplexes me is your statement
QuoteBut code designed to run in a 32-bit mode will not run (for the most part) in a 16-bit CPU mode.
Title: Re: Storing and restoring segment registers
Post by: sinsi on March 13, 2025, 10:52:45 AM
Quote from: NoCforMe on March 13, 2025, 10:03:38 AMStill confused by your answer:
What's the difference between 16- and 32-bit modes and 16- and 32-bit instructions?

What perplexes me is your statement
QuoteBut code designed to run in a 32-bit mode will not run (for the most part) in a 16-bit CPU mode.
You know what 32-bit instructions are, 32-bit mode is a mode of the CPU.
When a computer boots it is in 16-bit mode (real mode). You then switch the CPU to 32-bit mode (protected mode).
With either mode you can use 32-bit instructions, with a few limitations. For example
    mov al,[edx]Legal in real mode, but due to segmentation EDX must be less than 65536 (0000FFFF maximum).
There are instructions which only work in protected mode and will cause the CPU to fault (#GP is common).

Windows versions up to Windows ME were basically a real mode EXE that had a built-in DOS extender to allow it to switch to protected mode. The biggest advantages were a flat memory model (using 64KB sectors was a real PITA) and hardware multitasking (although Windows used software multitasking).

Nowadays, protected mode is called compatibility mode and is a subset of 64-bit (long) mode.
Title: Re: Storing and restoring segment registers
Post by: FORTRANS on March 14, 2025, 01:44:36 AM
Hi,

Quote from: NoCforMe on March 13, 2025, 10:03:38 AMStill confused by your answer:
What's the difference between 16- and 32-bit modes and 16- and 32-bit instructions?

What perplexes me is your statement
QuoteBut code designed to run in a 32-bit mode will not run (for the most part) in a 16-bit CPU mode.

   Well, it's complicated.  An overly simplified explanation
follows.  Maybe, sort of.

   16-bit code is normally real mode (without protection) using a
segment:offset addressing scheme.  32-bit code is then protected
mode with selector based addressing.

   Current Intel (or other's) documentation should be used for a
good description of the operating characteristics of a 32-bit X86
processor.  The best I could scavenge is quoted below.  Best
meaning what I got before quitting.

QuoteINTEL 80386 PROGRAMMER'S REFERENCE MANUAL 1986

PART II SYSTEMS PROGRAMMING

Chapter 4 Systems Architecture
----------------------------------------------------------------------------

Many of the architectural features of the 80386 are used only by systems
programmers. This chapter presents an overview of these aspects of the
architecture.

The systems-level features of the 80386 architecture include:

  Memory Management
  Protection
  Multitasking
  Input/Output
  Exceptions and Interrupts
  Initialization
  Coprocessing and Multiprocessing
  Debugging

These features are implemented by registers and instructions, all of which
are introduced in the following sections. The purpose of this chapter is not
to explain each feature in detail, but rather to place the remaining
chapters of Part II in perspective. Each mention in this chapter of a
register or instruction is either accompanied by an explanation or a
reference to a following chapter where detailed information can be obtained.

(...)

Chapter 6 Protection

(...)

6.2 Overview of 80386 Protection Mechanisms

Protection in the 80386 has five aspects:

  1. Type checking
  2. Limit checking
  3. Restriction of addressable domain
  4. Restriction of procedure entry points
  5. Restriction of instruction set

(...)

Chapter 16 Mixing 16-Bit and 32 Bit Code

(...)

The 80386 functions most efficiently when it is possible to distinguish
between pure 16-bit modules and pure 32-bit modules. A pure 16-bit module
has these characteristics:

 . All segments occupy 64 Kilobytes or less.
 . Data items are either 8 bits or 16 bits wide.
 . Pointers to code and data have 16-bit offsets.
 . Control is transferred only among 16-bit segments.

A pure 32-bit module has these characteristics:

. Segments may occupy more than 64 Kilobytes (zero bytes to 4 gigabytes).
. Data items are either 8 bits or 32 bits wide.
. Pointers to code and data have 32-bit offsets.
. Control is transferred only among 32-bit segments.

HTH,

Steve N.
Title: Re: Storing and restoring segment registers
Post by: daydreamer on March 14, 2025, 02:39:45 AM
Quote from: sinsi on March 13, 2025, 10:52:45 AMYou know what 32-bit instructions are, 32-bit mode is a mode of the CPU.
When a computer boots it is in 16-bit mode (real mode). You then switch the CPU to 32-bit mode (protected mode).
With either mode you can use 32-bit instructions, with a few limitations. For example
    mov al,[edx]Legal in real mode, but due to segmentation EDX must be less than 65536 (0000FFFF maximum).
There are instructions which only work in protected mode and will cause the CPU to fault (#GP is common).
to fill or zero memory area using dword = 4 byte wide is faster than word and bytes = use 32bit register with non adressing way

there is a special load instruction that helps you use bigger adress range than 0-65535 indirectly
it helps you translate an adress in 1mb memory range to split between segment register and 16 bit register
but with games using many megabytes you need a dos extender

Title: Re: Storing and restoring segment registers
Post by: sinsi on March 14, 2025, 02:43:48 AM
Quote from: daydreamer on March 14, 2025, 02:39:45 AM
Quote from: sinsi on March 13, 2025, 10:52:45 AMYou know what 32-bit instructions are, 32-bit mode is a mode of the CPU.
When a computer boots it is in 16-bit mode (real mode). You then switch the CPU to 32-bit mode (protected mode).
With either mode you can use 32-bit instructions, with a few limitations. For example
    mov al,[edx]Legal in real mode, but due to segmentation EDX must be less than 65536 (0000FFFF maximum).
There are instructions which only work in protected mode and will cause the CPU to fault (#GP is common).
to fill or zero memory area using dword = 4 byte wide is faster than word and bytes = use 32bit register with non adressing way

there is a special load instruction that helps you use bigger adress range than 0-65535 indirectly
it helps you translate an adress in 1mb memory range to split between segment register and 16 bit register
but with games using many megabytes you need a dos extender


It was an attempt to show a simple example.
I didn't want to gfet into unreal mode/flat real mode, way to complicated.