1. Core Concepts: 16-bit Segments vs. 32-bit Segments
In the x86 architecture, the length and characteristics of a “segment” depend on the operating mode of the processor.
-
16-bit Segment:
- Maximum Length:64KB (
<span>0FFFFh</span>). - Typical Modes:Real Mode and Virtual 8086 Mode. This is the only option for 8086/8088 and is a requirement for 80386+ in real mode.
- Offset Pointers: Using 16-bit registers (such as
<span>IP</span>,<span>SP</span>,<span>SI</span>,<span>DI</span>). For example,<span>CS:IP</span>together determine the address of the next instruction. - Default Operand/Address Size: Instructions default to operating on 16-bit data, using 16-bit addressing modes (such as
<span>[BX+SI]</span>). -
32-bit Segment:
- Maximum Length:4GB (
<span>0FFFFFFFFh</span>). - Typical Mode:Protected Mode. This is the primary operating mode for 80386 and later processors in protected mode.
- Offset Pointers: Using 32-bit registers (such as
<span>EIP</span>,<span>ESP</span>,<span>ESI</span>,<span>EDI</span>). For example,<span>CS:EIP</span>. - Default Operand/Address Size: Instructions default to operating on 32-bit data, using 32-bit addressing modes (such as
<span>[EAX*4 + EBX]</span>). -
Compatibility: To maintain compatibility in protected mode (for example, to run old real mode programs), 80386+ allows the creation and use of 16-bit segments in protected mode. The CPU knows whether a segment is 16-bit or 32-bit through the attribute bits in the segment descriptor.
2. Segment Definition and Attribute Types (<span>USE16</span>/ <span>USE32</span>)
In a complete segment definition, the <span>USE16</span> or <span>USE32</span> attributes can be used to explicitly specify the type of the segment, which is particularly crucial in protected mode as it informs the CPU how to execute the code within the segment.
Complete Segment Definition Format:
SegmentName SEGMENT [Location Type] [Combine Type] ['Category] [Attribute Type]
; ... Segment content ...
SegmentName ENDS
Where <span>Attribute Type</span> is either <span>USE16</span> or <span>USE32</span>.
<span>.386</span> Pseudo-Instruction Impact: The assembler needs to know the target CPU to determine the default segment attributes.
- Not using
<span>.386</span>(or higher) pseudo-instruction: The assembler defaults to targeting 8086, and the segment attributes are **defaulted to<span>USE16</span>**. - Using
<span>.386</span>pseudo-instruction: The assembler knows the target supports 32-bit, and the segment attributes are **defaulted to<span>USE32</span>**.
Therefore, explicitly specifying <span>USE16</span> or <span>USE32</span> in protected mode programming is a very good practice to avoid confusion and errors.
3. Mandatory Rules in Real Mode
In real mode, there are two unbreakable hardware rules:
- Only 16-bit segments can be used. Any attempt to define or use a 32-bit segment is invalid. Even if
<span>USE32</span>is written in the code, the CPU will treat it as a 16-bit segment in real mode. - The stack is always 16-bit. This means:
- The stack segment (SS) must be a 16-bit segment.
- The stack pointer must use the 16-bit
<span>SP</span>register, not the 32-bit<span>ESP</span>.<span>PUSH</span>/<span>POP</span>instructions default to operating on 16-bit data.
Code Examples
Example 1: Standard Real Mode Program (16-bit Segment)
This example demonstrates the structure of a standard real mode program, where all segments must be 16-bit.
; Not specifying .386, defaults all segments to USE16
.MODEL SMALL
STACK_SEG SEGMENT STACK ; Stack segment, must be 16-bit
DW 100h DUP(?)
STACK_SEG ENDS
DATA_SEG SEGMENT ; Data segment, 16-bit
Msg DB 'Hello, Real Mode!', '$'
DATA_SEG ENDS
CODE_SEG SEGMENT ; Code segment, 16-bit
ASSUME CS:CODE_SEG, DS:DATA_SEG, SS:STACK_SEG
Start:
MOV AX, DATA_SEG
MOV DS, AX ; Initialize DS
; Display string (DOS function call)
MOV AH, 09h
LEA DX, Msg
INT 21h
; Exit program
MOV AX, 4C00h
INT 21h
CODE_SEG ENDS
END Start
Example 2: Using <span>.386</span> but Still in Real Mode (Explicitly Using <span>USE16</span>)
This example shows how to use 32-bit registers and instructions of the 80386 in real mode, while still keeping the segments as 16-bit.
; Informing the assembler we are using the 80386 instruction set
.386
; Explicitly declare all subsequent segments' default attributes as 16-bit!
.MODEL SMALL, USE16
STACK_SEG SEGMENT STACK USE16 ; Explicitly declare 16-bit stack segment
DW 100h DUP(?)
TopOfStack LABEL WORD ; Mark the top of the stack
STACK_SEG ENDS
DATA_SEG SEGMENT USE16 ; Explicitly declare 16-bit data segment
; A 32-bit variable, but its address offset is still within 64K segment
BigNumber DD 12345678h
DATA_SEG ENDS
CODE_SEG SEGMENT USE16 ; Explicitly declare 16-bit code segment
ASSUME CS:CODE_SEG, DS:DATA_SEG, SS:STACK_SEG
Start:
; Initialize segment registers
MOV AX, DATA_SEG
MOV DS, AX
MOV AX, STACK_SEG
MOV SS, AX
MOV SP, OFFSET TopOfStack ; Initialize 16-bit stack pointer SP
; Use 32-bit registers and instructions within a 16-bit segment
MOV EAX, [BigNumber] ; Load 32-bit variable into EAX
ADD EAX, 1000h ; Perform 32-bit operation
MOV [BigNumber], EAX ; Store back
; Use 386's 32-bit addressing mode, but ensure offset does not exceed bounds
; Assume the value of ESI is controlled by us and is definitely less than 0FFFFh
MOVZX ESI, SI ; Safely zero-extend 16-bit SI to 32-bit ESI
MOV AL, [ESI + BX] ; Use 32-bit indexed register
; Exit
MOV AX, 4C00h
INT 21h
CODE_SEG ENDS
END Start
Key Point: Even within a 16-bit segment, as long as <span>.386</span> is used, 32-bit registers and new instructions such as <span>EAX</span>, <span>ESI</span> can be utilized in the instructions. However, the memory access offset (the final address calculated by any addressing mode) **must never exceed <span>0FFFFh</span>**.
Example 3: Segment Definition in Protected Mode (Conceptual)
The following is a simplified outline of a protected mode code snippet, demonstrating how to mix the definition of 16-bit and 32-bit segments.
.386P ; Allow the use of privileged instructions
; ... (GDT definitions would be included here) ...
; 32-bit code segment in protected mode
CODE32_SEG SEGMENT PARA USE32 PUBLIC 'CODE32'
ASSUME CS:CODE32_SEG
PM_Entry:
; Here is the 32-bit protected mode code
; Default operates on 32-bit data, using 32-bit offsets
MOV EAX, [EBX] ; Load from 32-bit data segment
JMP FAR PTR SEL_CODE16:PM_TO_RM ; Jump to 16-bit segment
CODE32_SEG ENDS
; 16-bit code segment in protected mode (for switching back to real mode, etc.)
CODE16_SEG SEGMENT PARA USE16 PUBLIC 'CODE16'
ASSUME CS:CODE16_SEG
PM_TO_RM:
; Here is the 16-bit protected mode code
; Default operates on 16-bit data, using 16-bit offsets
MOV AX, SEL_DATA16
MOV DS, AX
; ... Code to switch back to real mode ...
CODE16_SEG ENDS
; 32-bit data segment in protected mode
DATA32_SEG SEGMENT PARA USE32 PUBLIC 'DATA32'
PM_Data DD ?
DATA32_SEG ENDS
; 16-bit data segment in protected mode
DATA16_SEG SEGMENT PARA USE16 PUBLIC 'DATA16'
RM_Data DW ?
DATA16_SEG ENDS
; ... GDT table would contain descriptors for the above segments and set the correct limits and attributes ...
Conclusion
<span>USE16</span>/<span>USE32</span>are segment attributes primarily used for protected mode, defining the default operation and addressing size of the segments.- In real mode, only 16-bit segments (
<span>USE16</span>) can be used. The stack must be 16-bit, and<span>SP</span>must be used as the pointer. - Using
<span>.386</span>pseudo-instruction allows the use of 32-bit registers and instructions in the code, but this does not change the nature of the segments in real mode (still 64KB). - When writing programs that may involve mode switching or require explicit segment properties, explicitly using
<span>USE16</span>or<span>USE32</span>is a very good practice to clarify code intent and avoid unexpected errors due to the assembler using default values.