Article View: comp.os.msdos.programmer
Article #1652Re: Detecting an 80486
From: Andrew Rossmann
Date: Mon, 23 Jul 1990 22:31
Date: Mon, 23 Jul 1990 22:31
637 lines
19276 bytes
19276 bytes
In article <26a858b9@ralf> Ralf.Brown@B.GP.CS.CMU.EDU writes:
>In article <1990Jul20.230335.22816@ddsw1.MCS.COM>, andyross@ddsw1.MCS.COM (Andrew Rossmann) wrote:
>}In article <315@bally.Bally.COM> pete@bally.UUCP (exilied in my own office) writes:
>}>In article <1990Jul19.025150.6150@looking.on.ca> brad@looking.on.ca (Brad Templeton) writes:
>} In my Infoplus program, the 286/386/486 are detected by trapping the
>Unfortunately, your program will fail miserably on my system (and about a
>million others), because I am running QEMM-386. QEMM runs in protected mode
>with DOS in virtual-86 mode. Any invalid opcode exception that your
>program generated invokes the QEMM exception handler, NOT YOURS. And the
>QEMM exception handler gives the choice of terminating or rebooting. I
>expect similar behavior from 386^Max, CEMM, AllCharge386, MICEMM, ....
>--
Since my program is grabbing the interrupt, there is no way QEMM (or
whatever) can get it. I've run Infoplus under QEMM 5 and MICEMM and
EMM386.SYS, and it works fine. What QEMM and other DON'T like is doing a
386 protected instruction!! (Which I found out the hard way.)
The only time it doesn't seem to work right is running under Windows 3
enhanced mode.
Several people have asked to see the code I use. Here it is. It is
written for use under Turbo Pascal, but should be adaptable. I'll even
throw in the other two routines used by Infoplus. Someone might find them
useful, too.
page 75, 110
;--------------------------------------------------------------------
;
; INFOPLUS.ASM
;
; Version 1.10
;
; Three subprograms used by INFOPLUS.PAS:
;
; CPUID - identifies host CPU and NDP (if
; any)
; DISKREAD - reads absolute sectors from disk
; LONGCALL - calls a routine using a CALL FAR
;
; Originally by:
; Steve Grant
; Long Beach, CA
; January 13, 1989
;
; mods by Andrew Rossmann (7/20/90)
; 286/386/486 detection adapted from code by Robert Collins.
;--------------------------------------------------------------------
.286P
.8087
public CPUID, DISKREAD, LONGCALL
;-----------------------------------------------------------------------------
; 80486 instruction macro -- because MASM 5.1 doesn't support the 80486!
;-----------------------------------------------------------------------------
;uncomment next 3 lines if not using TASM 2.0
;XADD macro
; db 0fh,0C0h,0D2h ; 80486 instruction macro
;ENDM
CODE segment byte
; Conditional jumps are all coded with the SHORT qualifier in
; order to minimize the size of the .OBJ file output of Turbo
; Assembler.
;--------------------------------------------------------------------
CPUID proc near
assume cs:CODE, ds:DATA, es:nothing, ss:nothing
; On entry:
;
; BP
; SP => near return address
; offset of a cpu_info_t record
; segment " " " "
;
; On exit, the cpu_info_t record has been filled in as follows:
;
; byte = CPU type
; word = Machine Status Word
; 6 bytes = Global Descriptor Table
; 6 bytes = Interrupt Descriptor Table
; boolean = segment register change/interrupt flag
; byte = NDP type
; word = NDP control word
mCPU equ byte ptr [bx]
mMSW equ word ptr [bx + 1]
mGDT equ [bx + 3]
mIDT equ [bx + 9]
mchkint equ byte ptr [bx + 15]
mNDP equ byte ptr [bx + 16]
mNDPCW equ word ptr [bx + 17]
f8088 equ 0
f8086 equ 1
fV20 equ 2
fV30 equ 3
f80188 equ 4
f80186 equ 5
f80286 equ 6
f80386 equ 7
f80486 equ 8
funk = 0FFH
false equ 0
true equ 1
push bp
mov bp,sp
push ds
lds bx,[bp + 4]
call cpu
call chkint
call ndp
pop ds
pop bp
ret 4
;--------------------------------------------------------------------
cpu:
; interrupt of multi-prefix string instruction
mov mCPU,funk ;set CPU type to unknown
sti
mov cx,0FFFFH
rep lods byte ptr es:[si]
jcxz short cpu_02
call piq
cmp dx,4
jg short cpu_01
mov mCPU,f8088
ret
cpu_01:
cmp dx,6
jne short cpu_01a
mov mCPU,f8086
cpu_01a:
ret
cpu_02:
; number of bits in displacement register used by shift
mov al,0FFH
mov cl,20H
shl al,cl
or al,al
jnz short cpu_04
call piq
cmp dx,4
jg short cpu_03
mov mCPU,fV20
ret
cpu_03:
cmp dx,6
jne short CPUID_done
mov mCPU,fV30
ret
cpu_04:
; order of write/decrement by PUSH SP
push sp
pop ax
cmp ax,sp
je short cpu_06
call piq
cmp dx,4
jg short cpu_05
mov mCPU,f80188
ret
cpu_05:
cmp dx,6
jne short CPUID_done
mov mCPU,f80186
ret
; The following code is adapted from a program by Robert Collins
;since we probably have a 286, 386 or 486, we'll trap the invalid opcode
;handler, and try to determine the cpu type by using various opcodes.
;First, though, grab some tables
cpu_06:
smsw mMSW
sgdt mGDT
sidt mIDT
;-----------------------------------------------------------------------------
; Setup INT6 handler
;-----------------------------------------------------------------------------
push bx ;save bx
mov ax,3506h ;get INT 6 pointer
int 21h
mov old_int06_seg,es ;save
mov old_int06_ofs,bx
push ds ;save DS
mov dx,seg INT6_handler ;point to new handler
mov ds,dx
mov dx,offset INT6_handler
mov ax,2506h ;put into effect
int 21h
pop ds ;restore DS
pop bx
mov mCPU,f80486 ; initialize CPU flag
xor cx,cx ; initialize semaphore
;-----------------------------------------------------------------------------
; Now, try and determine which CPU we are by executing invalid opcodes.
; The instructions I chose to invoke invalid opcodes, are themselves rather
; benign. In each case, the chosen instruction modifies the DX register,
; and nothing else. No system parameters are changed, e.g. protected mode,
; or other CPU dependant features.
;-----------------------------------------------------------------------------
; The 80486 instruction 'XADD' xchanges the registers, then adds them.
;-----------------------------------------------------------------------------
;if not using TASM 2.0, comment out next 2 lines, and uncoment 3rd.
.486
XADD DX,DX ; 80486
; XADD ;DX,DX
.286
jcxz CPU_exit
mov mCPU,f80386 ; set 80386 semaphore
xor cx,cx ; CX=0
;-----------------------------------------------------------------------------
; For a description on the effects of the following instructions, look in
; the Intel Programmers Reference Manual's for the 80186, 80286, or 80386.
;-----------------------------------------------------------------------------
.386
mov edx,edx ; 80386
.286P
jcxz CPU_exit
mov mCPU,f80286 ; set 80286 semaphore
xor cx,cx ; CX=0
smsw dx ; 80286
jcxz CPU_exit
mov mCPU,funk ; set unknown CPU
CPU_exit:
push ds ;save DS
lds dx,old_int06 ;get old pointer
mov ax,2506h ;restore it
int 21h
pop ds
;!!!!!!!
;!!! Original 286/386 detection code. Commented out but left in in
;!!! case of problems.
;!!!!!!!
; try to alter flag register bits 15-12
;
; pushf
; pop ax
; mov cx,ax
; xor cx,0F000H
; push cx
; popf
; pushf
; pop cx
; cmp ax,cx
; jne short cpu_07
; mov mCPU,f80286
; ret
;cpu_07:
; mov mCPU,f80386
; ret
;cpu_08:
; mov mCPU,funk
CPUID_done:
ret
;--------------------------------------------------------------------
piq:
; On exit:
;
; DX = length of prefetch instruction queue
;
; This subroutine uses self-modifying code, but can
; nevertheless be run repeatedly in the course of the calling
; program.
count = 7
opincdx equ 42H ; inc dx opcode
opnop equ 90H ; nop opcode
mov al,opincdx
mov cx,count
push cx
push cs
pop es
mov di,offset piq_01 - 1
push di
std
rep stosb
mov al,opnop
pop di
pop cx
xor dx,dx
cli
rep stosb
rept count
inc dx
endm
piq_01:
sti
ret
;--------------------------------------------------------------------
chkint:
; save old INT 01H vector
push bx
mov ax,3501H
int 21H
mov old_int01_ofs,bx
mov old_int01_seg,es
pop bx
; redirect INT 01H vector
push ds
mov ax,2501H
mov dx,seg new_int01
mov ds,dx
mov dx,offset new_int01
int 21H
pop ds
; set TF and change SS -- did we trap on following instruction?
pushf
pop ax
or ah,01H ; set TF
push ax
popf
push ss ; CPU may wait one
; instruction before
; recognizing single step
; interrupt
pop ss
chkint_01: ; shouldn't ever trap here
; restore old INT 01H vector
push ds
mov ax,2501H
lds dx,old_int01
int 21H
pop ds
ret
;--------------------------------------------------------------------
new_int01:
; INT 01H handler (single step)
;
; On entry:
;
; SP => IP
; CS
; flags
sti
pop ax ; IP
cmp ax,offset chkint_01
jb short new_int01_03
je short new_int01_01
mov mchkint,false
jmp short new_int01_02
new_int01_01:
mov mchkint,true
new_int01_02:
pop cx ; CS
pop dx ; flags
and dh,0FEH ; turn off TF
push dx ; flags
push cx ; CS
new_int01_03:
push ax ; IP
iret
;-----------------------------------------------------------------------------
; INT6 handler sets a semaphore (CX=FFFF) and adjusts the return address to
; point past the invalid opcode.
;-----------------------------------------------------------------------------
INT6_handler:
enter 0,0 ; create new stack frame
dec cx ; make CX=FFFF
add word ptr ss:[bp][2],3 ; point past invalid opcode
leave
iret
;--------------------------------------------------------------------
ndp:
fnone equ 0
f8087 equ 1
f80287 equ 2
f80387 equ 3
funk = 0FFH
mov word ptr ndp_cw,0000H
cli
; The next three 80x87 instructions cannot carry the WAIT prefix,
; because there may not be an 80x87 for which to wait. The WAIT is
; therefore emulated with a MOV CX,<value>! LOOP $ combination.
; CPU NDP
fnsave ndp_save ; 14 221
mov cx,(221-6-1)/17+1 ; 4
loop $ ; 17*CX-12
; 17*CX+6
fninit ; 8 8
mov cx,(8-0-1)/17+1 ; 4
loop $ ; 17*CX-12
; 17*CX
fnstcw ndp_cw ; 14 24
mov cx,(24-2-1)/17+1 ; 4
loop $ ; 17*CX-12
; 17*CX+2
sti
mov ax,ndp_cw
cmp ax,0000H
jne short ndp_01
mov mNDP,fnone
ret
ndp_01:
cmp ax,03FFH
jne short ndp_02
mov mNDP,f8087
jmp short ndp_04
ndp_02:
.287
cmp ax,037FH
jne short ndp_05
fld1
fldz
fdiv
fld1
fchs
fldz
fdiv
fcom
fstsw ndp_sw
mov ax,ndp_sw
and ah,41H ; C3, C0
cmp ah,40H ; ST(0) = ST(1)
jne short ndp_03
mov mNDP,f80287
jmp short ndp_04
ndp_03:
cmp ah,01H ; ST(0) < ST(1)
jne short ndp_05
mov mNDP,f80387
ndp_04:
.8087
frstor ndp_save
fstcw mNDPCW
ret
ndp_05:
mov mNDP,funk
ret
CPUID endp
;--------------------------------------------------------------------
DISKREAD proc near
assume cs:CODE, ds:DATA, es:nothing
; On entry:
;
; BP
; SP => near return address
; offset of disk buffer
; segment " " "
; number of sectors to read
; starting logical sector number
; drive number (0=A, 1=B, etc.)
;
; On exit:
;
; AX = function result
; 00 - function successful
; 01..FF - DOS INT 25H error result
drive equ [bp + 12]
starting_sector equ [bp + 10]
number_of_sectors equ [bp + 8]
buffer equ [bp + 4]
push bp
mov bp,sp
mov ax,3000h ;get DOS version
int 21h
cmp al,4 ;DOS 4?
jb read3 ;Read assuming DOS 3.x
mov al,drive
mov bx,starting_sector ;copy info into parameter block
mov extd_starting_sector_lo,bx
mov extd_starting_sector_hi,0 ;We're only using lower part
mov bx,number_of_sectors
mov extd_number_of_sectors,bx
les bx,buffer ;get seg:ofs of buffer in ES:BX
mov extd_bufofs,bx ;put into block
mov extd_bufseg,es
mov bx,offset dos4_block ;DS:BX points to block
mov cx,-1 ;-1 means extended read
push ds ;save DS (not really needed, but lets
;me share code with DOS 3 read.)
jmp short readit
read3: mov al,drive
mov dx,starting_sector
mov cx,number_of_sectors
push ds
lds bx,buffer ;get seg:ofs of buffer in DS:BX
readit: int 25H
inc sp ; fix broken stack
inc sp
pop ds
jc short diskread_01
xor ax,ax
diskread_01:
pop bp
ret 10
DISKREAD endp
;
;LONGCALL will call a routine using a CALL FAR. This is used by XMS
;
;Pascal format: procedure longcall(AXi: word; var addr: longint;
; var AXo, BXo, DXo: word); external;
;
longcall proc near
assume cs:CODE, ds:DATA, es:nothing
AXi equ [bp+20]
addr equ [bp+16]
AXo equ [bp+12]
BXo equ [bp+8]
DXo equ [bp+4]
push bp
mov bp,sp
les di,addr
mov ax,es:[di]
mov word ptr address,ax
mov ax,es:[di+2]
mov word ptr address+2,ax
mov ax,AXi
call dword ptr address
les di,DXo
mov [es:di],dx
les di,BXo
mov [es:di],bx
les di,AXo
mov [es:di],ax
pop bp
ret 18
longcall endp
code ends
;--------------------------------------------------------------------
DATA segment byte
; storage for CPUID
; redirected INT 01H vector
old_int01 label dword
old_int01_ofs dw ?
old_int01_seg dw ?
; redirected INT 6 vector
old_int06 label dword
old_int06_ofs dw ?
old_int06_seg dw ?
; storage for NDPID
; 80x87 control word after initialization, status word after divide by zero
ndp_cw dw ?
ndp_save db 94 dup (?)
ndp_sw dw ?
; storage for DISKREAD
; DOS 4.0 extended read parameter block
dos4_block label byte
extd_starting_sector_lo dw ?
extd_starting_sector_hi dw ?
extd_number_of_sectors dw ?
extd_bufofs dw ?
extd_bufseg dw ?
; storage for LONGCALL
address dd ?
DATA ends
end
Andrew Rossmann
andyross@ddsw1.MCS.COM
Technical Details
Message-ID:
<1990Jul23.223141.1776@ddsw1.MCS.COM>Path:
arc1-ca.pugleaf.net!utzoo!utgpu!news-server.csri.toronto.edu!cs.utexas.edu!tut.cis.ohio-state.edu!rutgers!mcdchg!ddsw1!andyrossReferences:
<1990Jul20.230335.22816@ddsw1.MCS.COM> <26a858b9@ralf>Show Full Headers
Complete Article Headers
Path: arc1-ca.pugleaf.net!utzoo!utgpu!news-server.csri.toronto.edu!cs.utexas.edu!tut.cis.ohio-state.edu!rutgers!mcdchg!ddsw1!andyross From: andyross@ddsw1.MCS.COM (Andrew Rossmann) Newsgroups: comp.os.msdos.programmer Subject: Re: Detecting an 80486 Message-ID: <1990Jul23.223141.1776@ddsw1.MCS.COM> Date: 23 Jul 90 22:31:41 GMT References: <1990Jul20.230335.22816@ddsw1.MCS.COM> <26a858b9@ralf> Reply-To: andyross@ddsw1.MCS.COM (Andrew Rossmann) Organization: ddsw1.MCS.COM Contributor, Wheeling, IL Lines: 637