NASM - The Netwide Assembler
NASM Forum => Programming with NASM => Topic started by: debs3759 on November 09, 2016, 11:12:13 PM
-
It's been so long since I did much coding that I am a little confused at how to use condition codes. I'm trying to simplify some of the code in my rewritten CPUID code.
pushf ; save flags
push byte -1
popf ; set flags [15-0]
pushf
pop ax ; read flags back
popf ; restore original flags
test ah,0F0h ; are bits 12-15 clear?
jz is16bit ; all clear: it's not a 386 (it's 286)
je is16bit
test ah,080h ; is bit 15=0?
jz is32bit ; yes, it's a 32-bit CPU
It's the first TEST instruction and the following conditional jumps that I am confused with. Logically, if the high nibble of AH is all clear, the zero flag will be set. If the high nibble is all set, it makes sense that the Equals flag should be said, but I know JE and JZ are the same.
What would be the correct way to code the tests (or ANDs, just as good) and jumps?
For reference, the code should implement the following:
; 808x and 8018x: flags[12-15] are always set. +
; 286: flags[12-15] are always clear in real-address mode. +
; 32-bit processors: real-mode: +
; bit 15 is always clear; +
; bits 12-14 have the last value loaded into them. +
; 32-bit processors: protected mode: +
; bit 15 is always clear; +
; bit 14 has the last value loaded into it; +
; and the IOPL bits depends on the (CPL). +
; The IOPL field can be changed only if the CPL is 0. +
I am currently only looking at running the code in real mode or V86 mode. This section won't be needed when I rewrite it for Windows.
-
I'm considering the following:
test ah,0F0h ; are bits 12-15 clear?
jz is16bit ; all clear: it's not a 386 (it's 286)
js is16bit
test ah,080h ; is bit 15=0?
jz is32bit ; yes, it's a 32-bit CPU
For 8086 the high (sign) bit is set, for 286 in real mode the result will always be zero.
Then the second test is just the high bit, which should always be clear on a 386+. I relise the last two lines of code can be replaced by a simple jump, but want to be sure I understand the condition codes first.
I have forgotten so much during a decade of not coding :)
-
Hi Debs,
Isn't it a drag when your memory goes out the window? I'm having a lot of trouble with that lately. At the moment, I'm fuzzy about the pushf/popf effect(s). I remember how "test" works, I think. If the entire high nibble is clear, the zero flag will be set. If any of the four high bits are set, the zero flag is cleared. That is, we will jump on "nz" (or "ne" but I think "nz" is clearer - exact same opcode). We do not get any information about which one(s) or if more than one bit is set. I'm not sure if "test" sets the sign flag or not - I'd have to test it, and you can do that (or do you need help?). I think you're better off using "test" with one bit at a time, or "and" and "cmp" with a specific value for multiple bits. "all bits clear" will work, though.
Does that help any? I can try to get back to this if I have to, but I'm feeling pretty fuzzy right now...
Best,
Frank
-
I need to set up a DOS machine to test it (or maybe create a small test file that I can load from my boot sector, that shouldn't be too hard if my working system can boot from USB floppy). Win 10 64-bit won't run my DOS 16-bit com files, and I never got as far as coding a Win32 console app (and Linux is way ahead of me at the moment). :)
-
I am having trouble understanding your code debs, but what you probably need is a SHR or AND instruction against AX if you are interested in knowing the state of bits 15 to bit 12 regardless of other bits
xor ax,ax
pushf
pop ax
shr ax,12
;and ax,1111000000000000b ;(mask) bit15 - bit12 (this equals 0xF000)
test ax,ax ;this actually not needed, but for clarity purpose...
jz is286
I don't know if this helps.
-
For testing bit 15, you could use BT (carry) or TEST instruction (sign)
bt ax,15
jc is386
or
test ah,ah
js is386
I'm trying to sum it up...
pushf
pop ax
mov bx,ax
shr ax,12
jz .isbit16
test bh,bh
js .isbit32
Is this the effect you are expecting?
-
For testing bit 15, you could use BT (carry) or TEST instruction (sign)
bt ax,15
jc is386
or
test ah,ah
js is386
I'm trying to sum it up...
pushf
pop ax
mov bx,ax
shr ax,12
jz .isbit16
test bh,bh
js .isbit32
Is this the effect you are expecting?
It's roughly what I'm looking for, but it has shown me what to do. BT is the solution, along with shr. My final code is:
pushf ; save flags
push byte -1
popf ; set flags [15-0]
pushf
pop ax ; read flags back
popf ; restore original flags
bt ah,0Fh ; is bit15 set?
jc is16bit ; yes, it's an 8018x or earlier
shr ax,0Ch ; shr AX 12.
jnz is32bit ; if not 0, it's 386+
is16bit
<error code, 16-bit processors to be added later>
is32bit
Thanks for pointing me in the right direction.
-
good to hear that. But as a pointer, the flag register is an extremely volatile register, so you'd probably want to pop it off much later. It does you no good POPFing it earlier since both SHR and BT modify it anyway.
-
If I understand it (possibly not)...
push -1
popf
sets only some bits, depending on CPU.
pushf
pop ax
puts the bits we're interested in in ax. From then on, it does no harm to alter flags.
This is not looking like one of my better days, so maybe I should shut up.
Best,
Frank
-
If I understand it (possibly not)...
push -1
popf
sets only some bits, depending on CPU.
pushf
pop ax
puts the bits we're interested in in ax. From then on, it does no harm to alter flags.
This is not looking like one of my better days, so maybe I should shut up.
Best,
Frank
I can't find any documentation that describes PUSH BYTE imm8, so I may stick with pushing a word (just in case it doesn't sign extend the imm8)
I restore the original flags where I do in case I ever place the code snippet inside some code the relies on NT, IF or IOPL having the original values. It's only very minimal code and instruction timing overhead..
-
Hi Debs,
I can't find any documentation that describes PUSH BYTE imm8, so I may stick with pushing a word (just in case it doesn't sign extend the imm8)
Just:
push -1
will assemble the signed byte form, and of course it will be sign extended. If you really want the word form, use:
push strict word -1
There's no point in using the long form, though. I can't find where this is documented at the moment, but I'm quite sure it's true.
FWIW, Linux is "easy". "isbit16" isn't going to be happening, of course. True of Windows as well...
Best,
Frank
-
FWIW, Linux is "easy". "isbit16" isn't going to be happening, of course. True of Windows as well...
I ultimately aim to write the DOS code to run on all systems from 808x up. Higher end tests with CPUID will be tested from a floppy (USB where possible), then reused for my Windows code (I have to learn the basics of Windows coding) which will only test CPUs with CPUID. I may even dual boot into Linus on some older test systems and learn to code for that as well. It won't be quick, I'm still researching all the CPUID codes from 2001 on, but it may help me to find some programming work when I know the basic API calls and how to implement them in asm and C/C++.
Naturally the 16 bit tests won't be needed for systems with CPUID, and I doubt I'll write Windows or Linux code for a 486 or earlier.
-
My final code for this portion of the code is
push byte -1
popf ; set flags [15-0]
pushf
pop ax ; read flags back
bt ah,0F0h ; is bit 15 set?
jc is16bit ; yes, it's an 80(1)86
bt ah,0Eh ; is bit 14 set?
jc is32bit ; yes? it's a 386+
80286: ; we get here if it is a 286
is16bit:
is32bit:
This looks like the optimal code, and is easy to modify to add 8-bit and 16-bit chips identification. Not sure yet whether to save/restore original flags.
-
I finished the 8/16 bit detection code (this is what was missing from my original code, I'll rewrite the 32-bit non CPUID code while I finish my CPUID database. The code I am posting in this thread does not include macro definitions, inc files, etc - you'll see all that packaged together when I finish the ground up rewrite (unless you want to see it, then I'll zip it and post it here).
bits 16
%include "boot.inc"
%include "cpuid.inc"
%include "i386.inc"
;-----------------------------------------------------------------------+
; Set order and alignment of sections +
;-----------------------------------------------------------------------+
SECTION .text
SECTION .code follows=.text align=16
SECTION .data align=16
SECTION .bss align=16
;-----------------------------------------------------------------------+
; To build a version that will work properly under DOS, use ORG 100h. +
;-----------------------------------------------------------------------+
org 100h
SECTION .text
;-----------------------------------------------------------------------+
; Clear screen. +
; Set up segments and stack. +
;-----------------------------------------------------------------------+
.start:
mov ax,3 ; clear screen and set text
int 10h ; mode 3.
mov dx,cs
mov ds,dx
mov es,dx
mov dx,stackseg ; initialise the stack, using the same
mov ss,dx ; values used in the boot sector.
mov sp,stacksize ; Don't assume that the boot sector
; had cleaned it up first.
xor dx,dx
;-----------------------------------------------------------------------+
; Now we've set DS and ES to the same as CS, lets check what we can do +
; with the flags (this will detect a 32-bit processor). +
;-----------------------------------------------------------------------+
mov si,Str_Out_Array16 ; this has to be preserved for pre 386
push byte -1
popf ; clear flags [15-0]
pushf
pop ax ; read flags back
cld
bt ax,0Fh ; is bit 15 set?
jc is16bit ; yes, it's an 80(1)86
bt ax,0Eh ; is bit 14 set?
jc is32bit ; yes, it's a 386+
;-----------------------------------------------------------------------+
; If we got here, we have a 286. +
;-----------------------------------------------------------------------+
i80286:
MkClass16 Str_286
MkFamily16 Str_286
MkModel16 Str_286
mov dx,8
jmp i16.end
[section .data]
align 8
Str_Out_Array16:
SOA_Class16 equ $-Str_Out_Array ; Compatability Class
.Class dw Str_CPU_Class,24
dw Str_Blank,1 ; 3,4
SOA_Family16 equ $-Str_Out_Array ; Processor Family
.Family dw Str_CPU_Family,24
dw Str_Blank,1 ;
SOA_Model16 equ $-Str_Out_Array ; CPU Model
.Model dw Str_CPU_Model,24
dw Str_Blank,1 ; Cx...
Str_Out16_Sz equ ($-Str_Out_Array16)>>3 ; Number of elements in array
__SECT__
;-----------------------------------------------------------------------+
; Is it an 8018x or 808x processor? +
;-----------------------------------------------------------------------+
is16bit:
mov ax,-1 ; Set all bits in ax
shr ax,020h ; attempt to shift right 32
or ax,ax ; did it clear ax?
jz i808x ; yes, it's 808x or Vx0
;-----------------------------------------------------------------------+
; It's an 8018x. +
;-----------------------------------------------------------------------+
i8018x:
MkClass16 Str_18x
MkFamily16 Str_18x
call test8bit ; is it 8-bit?
jz i80188 ; yes? It's 80188
MkModel16 Str_186 ; it's a 186
mov dx,7
jmp i16.end
i80188:
MkModel16 Str_188 ; it's a 188
mov dx,6
jmp i16.end
;-----------------------------------------------------------------------+
; It's an 808x, 80C8x or NEC V20/30. +
;-----------------------------------------------------------------------+
i808x:
MkClass16 Str_808x
mov ax,sp ; copy sp
pusha ; executes as 2-byte nop on 808x
nop
cmp ax,sp ; has SP changed?
jne Vx0 ; yes, it's an NEC Vx0
mov ax,0FFFh
push si
mov di,si
MkFamily16 Str_808x
.1
mov cx,0FFFFh
rep es:movsb ; copy 2^32 bits from ES:DI to DS:SI
; these are set to the same places
or cx,cx ; is CX 0?
jnz .3 ; no, it's an 808x
dec ax ; dec dx
jnz .1 ; outer loop
;-----------------------------------------------------------------------+
; It's an 80C8x. +
;-----------------------------------------------------------------------+
call test8bit ; is it 8-bit?
jz .2 ; yes? It's a V20
MkModel16 Str_80C86 ; it's an 8086
mov dx,3
jmp i16.end
.2
MkModel16 Str_80C88 ; it's an 8088
mov dx,2
jmp i16.end
;-----------------------------------------------------------------------+
; It's an 808x. +
;-----------------------------------------------------------------------+
.3
pop si ; restore SI
call test8bit ; is it 8-bit?
jz i8088 ; yes? It's a V20
MkModel16 Str_8086 ; it's an 8086
mov dx,1
jmp i16.end
i8088:
MkModel16 Str_8088 ; it's an 8088
mov dx,0
jmp i16.end
;-----------------------------------------------------------------------+
; It's an NEC V20 or V30. +
;-----------------------------------------------------------------------+
Vx0:
popa
MkFamily16 Str_Vx0
call test8bit ; is it 8-bit?
jz V20 ; yes? It's a V20
MkModel16 Str_V30 ; it's a V30
mov dx,5
jmp i16.end
V20:
MkModel16 Str_V20 ; it's a V20
mov dx,4
jmp i16.end
;-----------------------------------------------------------------------+
; test8bit routine tests whether we have an 8 or 16-bit data bus. +
;-----------------------------------------------------------------------+
[section .code]
test8bit:
mov ax,-1
mov [.1], byte 040h ; try to change code at .1 to inc
nop
nop
nop
nop
.1 nop ; if executes as inc, it's 8-bit
nop ; and ZF is set.
ret
__SECT__
;-----------------------------------------------------------------------+
; Some strings to define the CPU class/family/model for 8/16 bit CPUs. +
;-----------------------------------------------------------------------+
[section .data]
Str_Vx0 db "NEC Vx0"
Str_Vx0_Sz equ $-Str_Vx0
Str_V20 db "NEC V20"
Str_V20_Sz equ $-Str_V20
Str_V30 db "NEC V30"
Str_V30_Sz equ $-Str_V30
Str_808x db "808x"
Str_808x_Sz equ $-Str_808x
Str_8086 db "8086"
Str_8086_Sz equ $-Str_8086
Str_8088 db "8088"
Str_8088_Sz equ $-Str_8088
Str_80C86 db "80C86"
Str_80C86_Sz equ $-Str_80C86
Str_80C88 db "80C88"
Str_80C88_Sz equ $-Str_80C88
Str_18x db "8018x"
Str_18x_Sz equ $-Str_18x
Str_186 db "80186"
Str_186_Sz equ $-Str_186
Str_188 db "80188"
Str_188_Sz equ $-Str_188
Str_286 db "286"
Str_286_Sz equ $-Str_286
__SECT__
;-----------------------------------------------------------------------+
; Display error message if not a 32-bit processor. +
;-----------------------------------------------------------------------+
i16:
.end:
hlt
jmp $-1 ; make sure we halt again if we are
; resumed by an interrupt.
;-------------------------------------------------------------------------------+
; Now for the 32-bit processor detection code... +
; +
; If we got here, we know we have a 386 or later processor. +
;-------------------------------------------------------------------------------+
is32bit:
IntStart ; setup interrupt vectors
call Int_Setup ; first call installs our new interrupts
mov si,Str_Out_Array ; this has to be preserved throughout...
;-------------------------------------------------------------------------------+
; Int06_New: Invalid opcode handler +
; +
; input: BX = 0 +
; +
; output: BX = 0FFFFh +
; +
; Sets BX to 0ffffh (as an invalid opcode semaphore), and then increments the +
; return IP by 3 before returning control to the original code. +
;-------------------------------------------------------------------------------+
[section .code]
Int06_New:
push bp
mov bp,sp
dec bx
add word [SS:BP],3
pop bp
iret
__SECT__
;-------------------------------------------------------------------------------+
; Int0D_New: General Protection Exception handler +
; +
; input: BX = 0 +
; +
; output: BX = 01h +
; +
; Sets BX to 01h (as a #GP semaphore), and then increments the return IP by 3 +
; before returning control to the original code. +
;-------------------------------------------------------------------------------+
[section .code]
Int0D_New:
push bp
mov bp,sp
inc bx
add word [SS:BP + 2],3
pop bp
iret
__SECT__
;-------------------------------------------------------------------------------+
; Int_Setup: Set up Int handlers +
; +
; input [bp-4] = new int 6 offset +
; [bp-2] = new int 6 segment +
; +
; output: none +
; +
; This routine simply exchanges the old Int6 address and the new one, thus +
; saving the original vector on the first call and restoring it on the +
; second. +
;-------------------------------------------------------------------------------+
[section .text]
Int_Setup:
push dx
push ds
push byte 0
pop ds ; interrupt vector table...
mov edx,[IVT_int06]
xchg edx,[es:new_int06]
mov [IVT_int06],edx
mov edx,[IVT_int07]
xchg edx,[es:new_int07]
mov [IVT_int07],edx
mov edx,[IVT_int0D]
xchg edx,[es:new_int0D]
mov [IVT_int0D],edx
pop ds
pop dx
ret
__SECT__
;-------------------------------------------------------------------------------+
; prints: print string +
; +
; input: ES:BP pointer to string +
; CX string length in bytes +
; +
; output: none +
;-------------------------------------------------------------------------------+
[section .code]
prints:
push dx
push ax
push bx
push cx
mov ah,03h
xor bh,bh
int 10h
pop cx
mov ax,01301h
mov bx,7
int 10h
pop bx
pop ax
pop dx
ret
__SECT__
[section .data]
new_int06 dd 0 ; store int 06 vector
new_int07 dd 0 ; store int 06 vector
new_int0D dd 0 ; store int 0D vector
__SECT__
;-----------------------------------------------------------------------+
; This array is used to hold pointers to the various data elements +
; which describe the processor. +
;-----------------------------------------------------------------------+
[section .data]
align 8
Str_Out_Array:
SOA_Class equ $-Str_Out_Array ; Compatability Class
.Class dw Str_CPU_Class,24
dw Str_Blank,1 ; 3,4
SOA_Type equ $-Str_Out_Array ; Processor Type
.Type dw Str_CPU_Type,24 ; Single, Overdrive, Dual
dw Str_Blank,1 ;
SOA_Family equ $-Str_Out_Array ; Processor Family
.Family dw Str_CPU_Family,24
dw Str_Blank,1 ;
SOA_Model equ $-Str_Out_Array ; CPU Model
.Model dw Str_CPU_Model,24
dw Str_Blank,1 ; Cx...
SOA_Mask equ $-Str_Out_Array ; Stepping, revision...
.Mask dw Str_CPU_Mask,24
dw Str_Blank,1 ;
SOA_FPU equ $-Str_Out_Array ; FPU Type
.FPU dw Str_FPU_Type,24
dw Str_Blank,1 ;
SOA_IDType equ $-Str_Out_Array ; Method used for ID
.IDType dw Str_CPUID_Type,24
dw Str_Feat,Str_Feat_Sz ; All
SOA_VID equ $-Str_Out_Array ; Vendor ID String
.VID dw Str_CPU_V_ID,24
dw Str_VendorID,12 ; Completed (CPUID Only)
SOA_Vendor equ $-Str_Out_Array ; Vendor name (or generic...)
.Vendor dw Str_Vendor,24
dw Str_Blank,1 ; All 386, 486
SOA_ID equ $-Str_Out_Array ; CPU ID
.ID dw Str_CPUID_Val,24
dw Str_IDVal,4 ; All 486+
SOA_Clock equ $-Str_Out_Array ; Clock multiplier
.Clock dw Str_CPU_Clock,24
dw Str_Blank,1 ;
Str_Out_Sz equ ($-Str_Out_Array)>>3 ; Number of elements in array
;-----------------------------------------------------------------------+
; The first few data items are part of the output used to display the +
; information returned by this code. +
;-----------------------------------------------------------------------+
Str_CPU_Class db "Compatibility : " ; 386, 486, Y
; Pentium
Str_CPU_Type db "Processor type : " ; Overdrive, Dual, etc
Str_CPU_Family db "Family : " ; 386/486/Pentium, etc
Str_CPU_Model db "Model : " ; 486DX, K6-III, etc
; Cx486.... Y
Str_CPU_Mask db "Stepping/Revision : " ; If known...
Str_FPU_Type db "FPU type : " ; 287 - 487, built-in
Str_CPUID_Type db "Identification method : " ;CPUID, BIOS, reset, Y
; feature test,
; Cyrix DIRs,
; IBM MSRs
Str_CPU_V_ID db "Vendor ID : " ; CPUID Vendor ID
Str_Vendor db "CPU Vendor : " ; Intel or compatible,
; Cyrix or compatible,
; NexGen, IBM, etc
Str_CPUID_Val db "Processor ID : " ; From CPUID, DIRs, etc
Str_FSB_Clock db "FSB Clock : " ; If known...
Str_CPU_Clock db "Core Clock : " ; If known...
Str_Clk_Ratio db "Core/Bus Clock Ratio : " ; If known...
Str_Blank db " "
Str_Blank_Sz equ $-Str_Blank
;-----------------------------------------------------------------------+
; Some strings to define the CPU class/family/model. +
;-----------------------------------------------------------------------+
Str_386 db "386"
Str_386_Sz equ $-Str_386
Str_486 db "486"
Str_486_Sz equ $-Str_486
Str_P6 db "P6"
Str_P6_Sz equ $-Str_P6
;-----------------------------------------------------------------------+
; Array of CPUID methods strings +
;-----------------------------------------------------------------------+
ID_Arr:
istruc ID_Array
at ID_Array.Feat, dd Str_Feat + (Str_Feat_Sz<<10h)
at ID_Array.CPUID, dd Str_CPUID + (Str_CPUID_Sz<<10h)
at ID_Array.Reset, dd Str_Reset + (Str_Reset_Sz<<10h)
at ID_Array.IBM, dd Str_IBM_MSR + (Str_IBM_MSR_Sz<<10h)
at ID_Array.BIOS, dd Str_BIOS + (Str_BIOS_Sz<<10h)
at ID_Array.CxDIRs, dd Str_Cx_DIR + (Str_Cx_DIR_Sz<<10h)
at ID_Array.CxFeat, dd Str_Cx_Feat + (Str_Cx_Feat_Sz<<10h)
at ID_Array.Unknown, dd Str_Unknown + (7 << 10h)
iend
Str_Feat db "CPU Features"
Str_Feat_Sz equ $-Str_Feat
Str_CPUID db "CPUID"
Str_CPUID_Sz equ $-Str_CPUID
Str_Reset db "Reset"
Str_Reset_Sz equ $-Str_Reset
Str_IBM_MSR db "IBM MSRs"
Str_IBM_MSR_Sz equ $-Str_IBM_MSR
Str_BIOS db "BIOS"
Str_BIOS_Sz equ $-Str_BIOS
Str_Cx_DIR db "Cyrix DIRs"
Str_Cx_DIR_Sz equ $-Str_Cx_DIR
Str_Cx_Feat db "Cyrix features"
Str_Cx_Feat_Sz equ $-Str_Cx_Feat
Str_VendorID db " "
;-----------------------------------------------------------------------+
; Other strings... +
;-----------------------------------------------------------------------+
Str_IDVal db " "
CPUID_Val dd 0
CPUID_Arr:
.Type db 0
.Family db 0,0
.Model db 0
.Mask db 0
CPUID_Arr_Sz equ $-CPUID_Arr
__SECT__
-
All macros like MkClass16, MkFamily16, etc are filling the arrays I will use for setting up the output.