首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> 更多程式語言 >> 組合語言

經典 CPU 偵測演算法:從 8088 到 Pentium 的組合語言實作

     編輯:組合語言

前言:無 CPUID 時代的處理器辨識術

在 CPUID 指令 (Pentium 才引入) 普及之前,軟體必須靠「副作用差異」來辨識 CPU 型號:旗標暫存器保留位行為、指令預取佇列長度、移位指令計數遮罩、特權指令可用性等。這份由 Richard C. Leinecker 撰寫的經典程式碼 (發表於 Dr. Dobb's Journal) 系統性實作了從 8088/8086 到 80486 的偵測邏輯,並附贈 Pentium 偵測補充 (LISTING TWO) 及記憶體對齊效能測試 (LISTING THREE)。程式碼結構模組化,每個偵測步驟獨立成 PROC,極具教學與參考價值。

偵測決策樹架構

主程式 _Processor 依序呼叫:

  1. IsItAn8088:區分 8088/8086 (回傳 0) 與 286 以上 (回傳 2)
  2. IsItAn8086:若為 8088/8086 組,進一步區分 8088 (0) vs 8086 (1)
  3. IsItA286:若 ≥286,測試旗標 NT/IOPL 位 (bits 12-14) 是否可設定,區分 286 (2) 與 386+ (3→4)
  4. IsItA386:若 ≥386,測試 EFLAGS AC 位 (bit 18) 可否設定,區分 386 (3) 與 486 (4)
  5. Pentium (LISTING TWO):嘗試切換 EFLAGS ID 位 (bit 21),成功則為 Pentium+,再用 CPUID 取 Family 欄位。

關鍵偵測技術深度解析

1. 旗標暫存器保留位測試 (8088/86 vs 286+)

IsItAn8088 利用 8088/86 的 FLAGS 高 4 位 (bits 12-15) 永遠為 1 的特性:

pushf
pop bx
and bx, 00fffh    ; 清除高 4 位
push bx
popf              ; 嘗試寫入 FLAGS
pushf
pop bx
and bx, 0f000h    ; 讀回高 4 位
cmp bx, 0f000h    ; 若仍為 1 → 8088/86
je Not286
mov al, 2         ; 否則 ≥286

286+ 將這些位定義為 NT/IOPL/RF/VM,可被修改;8088/86 硬體強制為 1。

2. 指令預取佇列長度差異 (8088 vs 8086)

IsItAn8086 利用 8088 (4 bytes) vs 8086 (6 bytes) 預取佇列差異:

mov di, OFFSET EndLabel
mov al, 90h       ; NOP 機器碼
mov cx, 3
rep stosb         ; 向後寫入 3 個 NOP (修改 EndLabel 前 5 位元組)
cld
nop               ; 3 個 NOP 填充
nop
nop
dec dx            ; 只有 8088 會執行到這裡 (佇列未刷新)
nop               ; 第 6 位元組
EndLabel: nop

若 CPU 為 8088,預取佇列僅 4 位元組,dec dx 指令已被預取進佇列,自我修改的 NOP 不生效,DX 保持 1;8086 佇列 6 位元組,修改生效,dec dx 執行,DX 變 0。極具巧思的「自我修改程式碼」偵測法。

3. NT/IOPL 位測試 (286 vs 386+)

IsItA286 嘗試將 7000h (bits 12-14) 寫入 FLAGS:

mov ax, 7000h
push ax
popf              ; 載入 FLAGS
pushf
pop ax
and ah, 70h       ; 檢查 bits 12-14 是否保留
jz YesItIsA286    ; 清零 → 286
inc ax            ; 置位 → 386+ (回傳 4)

286 的 FLAGS 只有低 16 位,高位寫入會被忽略;386 擴展為 32 位 EFLAGS,保留這些位。

4. AC 位測試 (386 vs 486)

IsItA386 需 .386 指令集支援,操作 EFLAGS bit 18 (Alignment Check):

.386
pushfd
pop eax
or eax, 40000h    ; 設定 AC 位
push eax
popfd
pushfd
pop eax
test eax, 40000h  ; 若保留 → 486
jz YesItIsA386
add di, 2         ; 回傳 4 (486)

386 無 AC 位,寫入無效;486 實作了對齊檢查機制,該位可讀寫。注意 .8086 切回指令集以相容連結器。

5. Pentium ID 位與 CPUID

LISTING TWO 偵測 Pentium:

pushfd
pop eax
mov ebx, eax
xor eax, 200000h  ; 切換 ID 位 (bit 21)
push eax
popfd
pushfd
pop eax
push ebx
popfd             ; 恢復原旗標
and eax, 200000h
and ebx, 200000h
cmp eax, ebx
je Is486          ; 無法切換 → 486
mov eax, 1
cpuid             ; 取得 Family (bits 8-11)
shr eax, 8
and ax, 0Fh       ; 5 = Pentium

記憶體對齊效能測試 (LISTING THREE)

附贈 C 內嵌組合語言測試:比較 LOOP 指令在 16-byte 對齊 vs 非對齊位址的執行速度。486+ 對齊存取顯著更快,展示指令預取與快取行對齊的效能影響。

完整原始碼結構 (LISTING ONE 核心)

_PTEXT SEGMENT PARA PUBLIC 'CODE'
ASSUME CS:_PTEXT, DS:_PTEXT
public _Processor
_Processor proc far
push bp
mov bp,sp
push ds
push di
mov ax,cs
mov ds,ax
call IsItAn8088
cmp al,2
jge AtLeast286
call IsItAn8086
jmp short ExitProcessor
AtLeast286:
call IsItA286
cmp al,3
jl short ExitProcessor
AtLeast386:
call IsItA386
ExitProcessor:
pop di
pop ds
pop bp
ret
_Processor endp
... (四個偵測 PROC 完整實作) ...
_PTEXT ENDS
END

歷史地位與現代啟示

這套演算法曾廣泛用於:

  • 遊戲/應用程式自動調整程式碼路徑 (如 386+ 使用 32 位元指令)
  • 系統資訊工具 (如 Norton SI, CheckIt)
  • 開機載入器/核心早期偵測硬體能力

現代 x86 皆支援 CPUID,偵測邏輯已標準化 (葉功能 01h 取得 Family/Model/Stepping/Features)。但理解這些「副作用偵測」技巧,對於:

  • 虛擬機/模擬器實作 (需模擬舊 CPU 行為)
  • 惡意程式碼分析 (反偵測/反虛擬化技巧常借鑑此類邏輯)
  • 韌體/BIOS 開發 (早期初始化階段 CPUID 可能不可用)

依然具備核心價值。Leinecker 的程式碼以清晰的模組化、詳細的註解、嚴謹的暫存器保存,堪稱系統程式設計的典範之作。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved