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 sposób wykrywania pod Linuksem jest jeden (nie wliczając żadnych dodatkowych funkcji jądra czy też czytania z /proc/cpuinfo): poprzez rejestr E-FLAG.
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.
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
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:
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.
Tutaj możliwości są tylko 4: brak koprocesora, 8087, 80287, 80387. No to do roboty.
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:
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
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.
; 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
Koprocesor ten nie odróżnia minus nieskończoności od plus nieskończoności. Kod na sprawdzenie tego wygląda tak:
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)=nieskończoność fchs ; st(0)= minus nieskończoność ; 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
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.
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
.
EAX = informacje o wersji:
sprawdź rozszerzone informacje o rodzinie)
EDX = cechy procesora (tutaj akurat z procesorów Intela; najpierw numery bitów):
CMPXCHG8B
SYSENTER
i SYSEXIT
CMOV*
CLFLUSH
FXSAVE
i FXRSTOR
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).