Rozpoznawanie typu procesora


(przeskocz wykrywanie procesora)

Jak zapewne wiecie, wiele programów (systemy operacyjne, gry, ...) potrafi jakoś dowiedzieć się, na jakim procesorze zostały uruchomione. Rozpoznanie typu procesora umożliwia na przykład uruchomienie dodatkowych optymalizacji w programie lub odmowę dalszego działania, jeśli program musi korzystać z instrukcji niedostępnych na danym procesorze.

Wykrywanie rodzaju CPU i FPU nie jest trudne i pokażę teraz, jak po kolei sprawdzać typ procesora (nie można przecież zacząć sprawdzania od najwyższych).

Informacje, które tutaj podam, są oczywiście słuszne dla wszystkich procesorów rodziny x86 (AMD, Cyrix, ...), a nie tylko Intela.

Generalnie sposoby wykrywania są dwa: poprzez rejestr FLAG lub poprzez zakodowanie w kodzie instrukcji, które wykonają się tylko na danym modelu (i późniejszych).

Drugi sposób jest trochę trudniejszy: należy przejąć przerwanie INT6 (nieprawidłowa instrukcja) i sprawdzać, kiedy zostało wywołane.

  1. odróżnienie 8088 od reszty
    (przeskocz 8088)

    Procesor 8088 od pozostałych odróżnia to, że zmniejsza on rejestr SP przed umieszczeniem go na stosie. Reszta robi to po umieszczeniu SP na stosie. Kod wyglądałby więc na przykład tak:

    (przeskocz kod dla 8088)
    		mov	ax, sp
    		push	sp
    		pop	cx
    		xor	ax, cx		; lub cmp ax, cx
    		jz	nie_8088
  2. 8086
    (przeskocz 8086)

    Na tym procesorze w rejestrze flag bity 12-15 zawsze mają wartość 1.

    (przeskocz kod dla 8086)
    		pushf			; flagi na stos
    		pop	ax		; AX = flagi
    		and	ax, 0fffh	; czyścimy bity 12-15
    		push	ax		; AX na stos
    		popf			; flagi = AX
    		pushf			; z powrotem na stos
    		pop	ax		; AX = flagi
    		and	ax, 0f000h	; zerujemy bity poza bitami 12-15
    		cmp	ax, 0f000h	; jeśli ustawione, to 8086
    		jz	jest_8086
  3. 80186
    (przeskocz 80186)

    Test polega na próbie wykonania instrukcji smsw dx, nieprawidłowej na procesorach wcześniejszych niż 80286. Przerwanie nieprawidłowej instrukcji przejmujemy tak:

    (przeskocz kod dla 80186)
    		xor	ax, ax
    		mov	es, ax
    
    		les	bx, [es:6 << 2]	; FASM: les bx, [es:(6 shl 2)]
    
    		mov	[_stare06+2], es
    		mov	[_stare06], bx
    
    		mov	es, ax
    		mov	word [es:(6 << 2)], moje06
    			; FASM: mov word [es:(6 shl 2)], moje06
    		mov	word [es:(6 << 2) + 2], seg moje06
    			; FASM: mov word [es:(6 shl 2)], seg moje06

    Sama procedura obsługi przerwania wyglądać będzie tak:

    		moje06:
    			pop	ax
    			add	ax, 3
    			push	ax
    			xor	ax, ax
    			iret

    Proste: zwiększamy adres powrotny o 3 (długość instrukcji smsw dx) i zerujemy AX (potem w kodzie sprawdzimy jego wartość). Sam kod sprawdzający wygląda tak:

    		mov	ax, 1
    		db	0fh, 1, 0e2h		; smsw dx
    		or	ax, ax
    		jz	jest_286

    Przywrócenie oryginalnej procedury wygląda tak:

    		xor	ax, ax
    		les	cx, [_stare06]
    		mov	ds, ax
    		mov	[ds:(6 << 2)], cx
    			; FASM: mov [ds:(6 shl 2)], cx
    		mov	[ds:(6 << 2) + 2], es
    			; FASM: mov [ds:(6 shl 2) + 2], es
  4. 80286
    (przeskocz 80286)

    Na tym procesorze bity 12-15 flag zawsze mają wartość 0. Przykładowy kod wygląda więc tak:

    (przeskocz kod dla 80286)
    		pushf				; flagi na stos
    		pop	ax			; AX = flagi
    		or	ax, 0f000h		; ustawiamy bity 12-15
    		push	ax			; AX na stos
    		popf				; flagi = AX
    		pushf				; flagi na stos
    		pop	ax			; AX = flagi
    		and	ax, 0f000h	; jeśli wyczyszczone, to 286
    		jnz	nie_286
  5. 80386
    (przeskocz 80386)

    Na tym procesorze nie można zmienić bitu numer 18 we flagach (wiemy, że rejestr flag ma 32 bity). Bit ten odpowiada za Alignment Check i spowoduje przerwanie m.in wtedy, gdy SP nie będzie podzielne przez 4. Dlatego, zanim będziemy testować ten bit, musimy zachować SP i wyzerować jego najmłodsze 2 bity.

    (przeskocz kod dla 80386)
    		mov	dx, sp
    		and	sp, ~3		; aby uniknąć AC fault.
    					; FASM: and sp, not 3
    		pushfd			; flagi na stos
    		pop	eax		; EAX = E-flagi
    		mov	ecx, eax	; zachowanie EAX
    		xor	eax, 40000h	; zmiana bitu 18
    		push	eax		; EAX na stos
    		popfd			; E-flagi = EAX
    		pushfd			; flagi na stos
    		pop	eax		; EAX = flagi
    		xor	eax, ecx  ; czy takie same? jeśli tak, to 386
    		mov	sp, dx		; przywrócenie SP
    		jz	jest_386
  6. 80486
    (przeskocz 80486)

    Na tym procesorze nie można zmienić bitu 21 we flagach. Jeśli ten bit można zmienić, to procesor obsługuje instrukcję CPUID, której będziemy używać do dalszego rozpoznania. Kod:

    (przeskocz kod dla 80486)
    		pushfd			; flagi na stos
    		pop	eax		; EAX = E-flagi
    		mov	ecx, eax	; zachowanie EAX
    		xor	eax, 200000h	; zmiana bitu 21
    		push	eax		; EAX na stos
    		popfd			; E-flagi = EAX
    		pushfd			; flagi na stos
    		pop	eax		; EAX = flagi
    		xor	eax, ecx  ; czy takie same? jeśli tak, to 486
    		jz	jest_486
    		jmp	jest_586

Zanim omówię sposób korzystania z instrukcji CPUID, zajmijmy się sposobem rozpoznania typu koprocesora.



Koprocesor


(przeskocz wykrywanie koprocesora)

Tutaj możliwości są tylko cztery: brak koprocesora, 8087, 80287, 80387. No to do roboty.

  1. czy w ogóle jest jakiś koprocesor?
    (przeskocz test na istnienie FPU)

    To sprawdzamy bardzo łatwo. Jeśli nie ma koprocesora, to w chwili wykonania instrukcji FPU może wystąpić przerwanie INT6 (nieprawidłowa instrukcja), ale nie o tym sposobie chciałem powiedzieć. Koprocesor można wykryć, jeśli słowo stanu zostanie zapisane prawidłowo. Oto kod:

    (przeskocz test na istnienie FPU)
    		fninit			; inicjalizacja zeruje rejestry
    
      		; wpisujemy jakąś niezerowa wartość:
      		mov	word [_fpu_status], 5a5ah
    
    		; zapisz słowo statusowe do pamięci:
    		fnstsw	[_fpu_status]
    		mov	ax, [_fpu_status]
    		or	al, al	; jeśli zapisało dobrze (zera oznaczają
    				; puste rejestry), to jest FPU
    
    		jz	jest_FPU
  2. 8087
    (przeskocz 8087)

    Sztuczka polega na wykorzystaniu instrukcji FDISI (wyłączenie przerwań), która rzeczywiście coś robi tylko na 8087. Po wyłączeniu przerwań w słowie kontrolnym zostaje włączony bit numer 7.

    (przeskocz kod dla 8087)
    		; zachowaj słowo kontrolne do pamięci:
    		fnstcw	[_fpu_status]
    
    		; wyłączamy wszystkie
    		; przerwania (poprzez słowo kontrolne):
    		and	word [_fpu_status], 0ff7fh
    
    		; załaduj słowo kontrolne z pamięci:
    		fldcw	[_fpu_status]
    
    		fdisi		; wyłączamy wszystkie przerwania
    				; (jako instrukcja)
    
    		; zachowaj słowo kontrolne do pamięci:
    		fstcw	[_fpu_status]
    		test	byte [_fpu_status], 80h	; bit 7 ustawiony?
    
    		jz	nie_8087	; jeśli nie, to nie jest to 8087
  3. 80287
    (przeskocz 80287)

    Koprocesor ten nie odróżnia minus nieskończoności od plus nieskończoności. Kod na sprawdzenie tego wygląda tak:

    (przeskocz kod dla 80287)
    		finit
    
    		fld1		; st(0)=1
    		fldz		; st(0)=0,st(1)=1
    		fdivp	st1	; tworzymy nieskończoność,
    				; dzieląc przez 0
    		fld	st0	; st(1):=st(0)=niesk.
    		fchs		; st(0)= -niesk.
    
    				; porównanie st0 z st1 i
    				; zdjęcie obu ze stosu
    		fcompp		; 8087/287: -niesk. = +niesk.,
    				; 387: -niesk. != +niesk.
    
    		fstsw	[_fpu_status]	; zapisz status do pamięci
    		mov	ax, [_fpu_status]	; AX = status
    
    		sahf		; zapisz AH we flagach. tak sie składa,
    				; że tutaj również flaga ZF wskazuje na
    				; równość argumentów.
    		jz	jest_287
    		jmp	jest_387


Dalsze informacje o procesorze - instrukcja CPUID

Od procesorów 586 (choć niektóre 486 też podobno ją obsługiwały), Intel i inni wprowadzili instrukcję CPUID. Pozwala ona odczytać wiele różnych informacji o procesorze (konkretny typ, rozmiary pamięci podręcznych, dodatkowe rozszerzenia, ...).
Korzystanie z tej instrukcji jest bardzo proste: do EAX wpisujemy numer (0-3) i wywołujemy instrukcję, na przykład

	mov	eax, 1
	cpuid

Teraz omówię, co można dostać przy różnych wartościach EAX.

  1. EAX=0
    (przeskocz EAX=0)

    EAX = maksymalny numer funkcji dla CPUID.
    EBX:EDX:ECX = marka procesora (12 znaków ASCII).
    Intel - GenuineIntel
    AMD - AuthenticAMD
    NexGen - NexGenDriven
    Cyrix, VIA - CyrixInstead
    RISE - RiseRiseRise,
    Centaur Technology/IDT - CentaurHauls (programowalne, może być inne)
    United Microelectronics Corporation - UMC UMC UMC
    Transmeta Corporation - GenuineTMx86
    SiS - SiS SiS SiS
    National Semiconductor - Geode by NSC.

  2. EAX=1
    (przeskocz EAX=1)

    EAX = informacje o wersji:

    EDX = cechy procesora (tutaj akurat z procesorów Intela; najpierw numery bitów):


  3. EAX=2

    EBX, ECX, EDX = informacje o pamięci podręcznej cache i TLB

Nawet te informacje, które tu przedstawiłem są już bardzo szczegółowe i z pewnością nie będą takie same na wszystkich procesorach. To jest tylko wstęp. Dalsze informacje można znaleźć na stronach producentów procesorów, na przykład AMD, Intel, ale także tutaj: Sandpile, Lista przerwań Ralfa Brown'a (plik opcodes.lst).



Spis treści off-line (Alt+1)
Spis treści on-line (Alt+2)
Ułatwienia dla niepełnosprawnych (Alt+0)