CHAPTER 13

Writing 32-Bit Applications

This chapter is an introduction to 32-bit programming for the 80386. The guidelines in this chapter also apply to the 80486 processor, which is basically a faster 80386 with the equivalent of a 80387 floating-point processor. Since you are already familiar with 16-bit real-mode programming, this chapter covers the differences between 16-bit programming and 32-bit protected-mode programming.

The 80386 processor (and its successors such as the 80486) can run in real mode, virtual-86 mode, and in protected mode. In real and virtual-86 modes, the 80386 can run 8086/8088 programs. In protected mode, it can run 80286 programs. The 386 also extends the features of protected mode to include 32-bit operations and segments larger than 64K.

The MS-DOS operating system directly supports 8086/8088 programs, which it runs either in real mode or virtual-86 mode. Native 32-bit 80386 programs can be run by using a "DOS extender," by using the WINMEM32.DLL facility of Microsoft Windows 3.x, or by running a native 32-bit operating system, such as Microsoft Windows NT. You can use MASM to generate object code (OMF or COFF) for 32-bit programs. To do this, you will need a software development kit such as the Windows SDK for the target environment. Such kits include the linker and other components specific to your chosen operating environment.

32-Bit Memory Addressing

The 80386 has six segment registers. Four of these are familiar to 8086/8088 programmers: CS (Code Segment), SS (Stack Segment), DS (Data Segment), and ES (Extra Segment). The two additional registers, FS and GS, are used as data segment registers.

Memory addresses on 80x86 machines consist of two parts - a segment and an offset. In real-mode programs, the segment is a 16-bit number and the offset is a 16-bit number. Effective addresses are calculated by multiplying the segment by 16 and adding the offset to it. In protected mode, the segment value is not used directly as a number, but instead is an index to a table of "selectors." Each selector describes a block of memory, including attributes such as the size and location of the block, and the access rights the program has to it (read, write, execute). The effective address is calculated by adding the offset to the base address of the memory block described by the selector.

All segment registers are 16 bits wide. The offset in a 32-bit protected-mode program is itself 32 bits wide, which means that a single segment can address up to 4 gigabytes of memory. Because of this large range, there is little need to use segment registers to extend the range of addresses in 32-bit programs. If all six segment registers are initially set to the same value, then the rest of the program can ignore them and treat the processor as if it used a 32-bit linear address space. This is called 0:32, or flat, addressing. (The full segmented 32-bit addressing mode, in which the segment registers can contain different values, is called 16:32 addressing.) Flat addressing is used by the Windows NT operating system.

Figure 13.34 32-Bit Register Set

MASM Directives for 32-Bit Programming

If you use the simplified segment directives, a 32-bit program is surprisingly similar to a program for MS-DOS. Here are the differences:

Supply the .386 directive, which enables the 32-bit programming features of the 386 and its successors. The .386 directive must precede the .MODEL directive.

For flat-model programming, use the directive

.MODEL flat, stdcall

which tells the assembler to assume flat model (0:32) and to use the Windows NT standard calling convention for subroutine calls.

Precede your data declarations with the .DATA directive.

Precede your instruction codes with the .CODE directive.

At the end of the source file, place an END directive.

 

Sample Program

The following sample is a 32-bit assembly language subroutine, such as might be called from a 32-bit C program written for the Windows NT operating system. The program illustrates the use of a variety of directives to make assembly language easier to read and maintain. Note that with 32-bit flat model programming, there is no longer any need to refer to segment registers, since these are artifacts of segmented addressing.

;* szSearch - An example of 32-bit assembly programming using MASM 6.1

;*

;* Purpose: Search a buffer (rgbSearch) of length cbSearch for the

;* first occurrence of szTok (null terminated string).

;*

;* Method: A variation of the Boyer-Moore method

;* 1. Determine length of szTok (n)

;* 2. Set array of flags (rgfInTok) to TRUE for each character

;* in szTok

;* 3. Set current position of search to rgbSearch (pbCur)

;* 4. Compare current position to szTok by searching backwards

;* from the nth position. When a comparison fails at

;* position (m), check to see if the current character

;* in rgbSearch is in szTok by using rgfInTok. If not,

;* set pbCur to pbCur+(m)+1 and restart compare. If

;* pbCur reached, increment pbCur and restart compare.

;* 5. Reset rgfInTok to all 0 for next instantiation of the

;* routine.

.386

.MODEL flat, stdcall

FALSE EQU 0

TRUE EQU NOT FALSE

.DATA

; Flags buffer - data initialized to FALSE. We will

; set the appropriate flags to TRUE during initialization

; of szSearch and reset them to FALSE before exit.

rgfInTok BYTE 256 DUP (FALSE);

.CODE

PBYTE TYPEDEF PTR BYTE

szSearch PROC PUBLIC USES esi edi,

rgbSearch:PBYTE,

cbSearch:DWORD,

szTok:PBYTE

; Initialize flags buffer. This tells us if a character is in

; the search token - Note how we use EAX as an index

; register. This can be done with all extended registers.

mov esi, szTok

xor eax, eax

.REPEAT

lodsb

mov BYTE PTR rgfInTok[eax], TRUE

.UNTIL (!AL)

; Save count of szTok bytes in EDX

mov edx, esi

sub edx, szTok

dec edx

; ESI will always point to beginning of szTok

mov esi, szTok

; EDI will point to current search position

; and will also contain the return value

mov edi, rgbSearch

; Store pointer to end of rgbSearch in EBX

mov ebx, edi

add ebx, cbSearch

sub ebx, edx

 

; Initialize ECX with length of szTok

mov ecx, edx

.WHILE ( ecx != 0 )

dec ecx ; Move index to current

mov al, [edi+ecx] ; characters to compare

; If the current byte in the buffer doesn't exist in the

; search token, increment buffer pointer to current position

; +1 and start over. This can skip up to 'EDX'

; bytes and reduce search time.

.IF !(rgfInTok[eax])

add edi, ecx

inc edi ; Initialize ECX with

mov ecx, edx ; length of szTok

; Otherwise, if the characters match, continue on as if

; we have a matching token

.ELSEIF (al == [esi+ecx])

.CONTINUE

; Finally, if we have searched all szTok characters,

; and land here, we have a mismatch and we increment

; our pointer into rgbSearch by one and start over.

.ELSEIF (!ecx)

inc edi

mov ecx, edx

.ENDIF

; Verify that we haven't searched beyond the buffer.

.IF (edi > ebx)

mov edi, 0 ; Error value

.BREAK

.ENDIF

.ENDW

; Restore flags in rgfInTok to 0 (for next time).

mov esi, szTok

xor eax, eax

.REPEAT

lodsb

mov BYTE PTR rgfInTok[eax], FALSE

.UNTIL !AL

; Put return value in eax

mov eax, edi

ret

szSearch ENDP

end

 

Interested: