Computer >> บทช่วยสอนคอมพิวเตอร์ >  >> ฮาร์ดแวร์ >> CPU

การปลดล็อกความลับของ CPU:ดึงข้อมูลระบบด้วยคำสั่ง CPUID

การปลดล็อกความลับของ CPU:ดึงข้อมูลระบบด้วยคำสั่ง CPUID

เมื่อพัฒนาบูตโหลดเดอร์/เคอร์เนล การทำความเข้าใจสถาปัตยกรรมพื้นฐานเป็นสิ่งสำคัญในการเพิ่มประสิทธิภาพและความเข้ากันได้ระหว่างซอฟต์แวร์และฮาร์ดแวร์

เครื่องมือที่สำคัญอย่างหนึ่งแต่บางครั้งก็ถูกมองข้ามสำหรับวิศวกรในการสืบค้นและเรียกข้อมูลระบบคือคำสั่ง CPUID

คำสั่ง CPUID คืออะไร

คำสั่ง CPUID เป็นคำสั่งระดับต่ำ ซึ่งอยู่ภายในหัวใจของโปรเซสเซอร์ x86 และ x86-64 สมัยใหม่ทุกตัวที่อนุญาตให้ซอฟต์แวร์ค้นหา CPU สำหรับข้อมูลเกี่ยวกับโปรเซสเซอร์และคุณสมบัติที่รองรับ

ด้วยการเรียกใช้คำสั่งนี้ คุณสามารถรวบรวมข้อมูล เช่น รุ่นของโปรเซสเซอร์ ตระกูล ขนาดแคชภายใน และคุณสมบัติที่รองรับ เช่น SIMD หรือการจำลองเสมือนสำหรับฮาร์ดแวร์ สิ่งนี้สามารถช่วยคุณเพิ่มประสิทธิภาพและเปิดหรือปิดใช้งานคุณสมบัติที่รองรับแบบไดนามิกได้

สำหรับบูตโหลดเดอร์หรือนักพัฒนาเคอร์เนล การทำความเข้าใจคุณสมบัติที่โปรเซสเซอร์รองรับ เช่น การจำลองเสมือนสำหรับฮาร์ดแวร์ ขนาดแคช หรือคำสั่ง SIMD สามารถรับประกันได้ว่าระบบจะทำงานได้อย่างมีประสิทธิภาพ และโค้ดที่คุณเขียนนั้นเข้ากันได้กับ CPU ที่แตกต่างกัน ด้วยการใช้คำสั่ง CPUID คุณสามารถปรับพฤติกรรมเคอร์เนลของคุณแบบไดนามิกตามโปรเซสเซอร์เฉพาะที่เคอร์เนลทำงานอยู่

ในบทความนี้ คุณจะได้เรียนรู้วิธีตรวจสอบว่าคำสั่ง CPUID พร้อมใช้งานสำหรับระบบของคุณหรือไม่ รวมถึงวิธีการทำงาน และข้อมูลใดบ้างที่คุณจะได้รับจากการใช้งาน

ข้อกำหนดเบื้องต้น

  • ความรู้บางอย่างเกี่ยวกับภาษาแอสเซมบลี (สำหรับตัวอย่างนี้ ฉันใช้ FASM)

  • ความรู้บางประการเกี่ยวกับระบบปฏิบัติการ/เคอร์เนล

  • สิทธิ์เข้าถึงเครื่องมือแก้ไขข้อบกพร่องระดับต่ำ (เช่น GDB) หรือโปรแกรมจำลองฮาร์ดแวร์ เช่น QEMU เพื่อทดสอบโปรแกรมโหลดบูต/เคอร์เนลบนแพลตฟอร์มต่างๆ

ขั้นตอนที่ 1:ตรวจสอบความพร้อมใช้งานของ CPUID

ก่อนที่จะดำเนินการคำสั่ง CPUID สิ่งสำคัญคือต้องตรวจสอบว่าโปรเซสเซอร์รองรับหรือไม่ เนื่องจาก CPU ไม่รับประกันว่าจะมีฟังก์ชันการทำงานนี้ทั้งหมด รหัสต่อไปนี้ตรวจสอบความพร้อมใช้งานของคำสั่ง CPUID โดยการแก้ไขและทดสอบบิต ID (บิต 21) ในการลงทะเบียน EFLAGS

นี่คือรูปภาพจาก wiki.osdev.org ที่แสดงการลงทะเบียน EFLAGS แต่ละบิต:

การปลดล็อกความลับของ CPU:ดึงข้อมูลระบบด้วยคำสั่ง CPUID

หากโปรเซสเซอร์อนุญาตให้สลับบิตนี้ได้ CPUID ก็ได้รับการสนับสนุน ไม่อย่างนั้นมันก็ไม่ใช่ ต่อไปนี้เป็นวิธีการทำงานของกระบวนการตรวจจับ:

(คนส่วนใหญ่คิดว่าในโหมด Real 32 รีจิสเตอร์ไม่สามารถเข้าถึงได้ นั่นไม่เป็นความจริง รีจิสเตอร์ 32 บิตทั้งหมดสามารถใช้งานได้)

cpuid_check:
 pusha ; save state
 pushfd ; Save EFLAGS
 pushfd ; Store EFLAGS
 xor dword [esp],0x00200000 ; Invert the ID bit in stored EFLAGS
 popfd ; Load stored EFLAGS (with ID bit inverted)
 pushfd ; Store EFLAGS again (ID bit may or may not be inverted)
 pop eax ; eax = modified EFLAGS (ID bit may or may not be inverted)
 xor eax,[esp] ; eax = whichever bits were changed
 popfd ; Restore original EFLAGS
 and eax,0x00200000 ; eax = zero if ID bit can't be changed, else non-zero
 cmp eax,0x00
 je .cpuid_instruction_not_is_available
.cpuid_instruction_is_available:
 ;handle CPUID exists
.cpuid_instruction_not_is_available:
 ;handle CPUID isn't supported
.cpuid_check_end:
 popa ; restore state
 ret

09 :บันทึกการลงทะเบียนวัตถุประสงค์ทั่วไปทั้งหมดเพื่อให้แน่ใจว่าสามารถเรียกคืนสถานะดั้งเดิมได้เมื่อสิ้นสุด

19 :บันทึกการลงทะเบียน EFLAGS ปัจจุบัน

26 :เก็บสำเนาของ EFLAGS

33 :โค้ดจะพลิกบิต ID (21) ของ EFLAGS โดยใช้ตัวดำเนินการ XOR

49 :เรียกคืน EFLAGS ที่แก้ไขโดยกลับบิต ID

53 :ดัน EFLAGS ที่แก้ไขแล้วกลับไปที่สแต็ก

65 :ใส่ EFLAGS ที่แก้ไขแล้ว (บิต ID อาจจะกลับด้านหรือไม่ก็ได้) ในรีจิสเตอร์ EAX

78 :หลังจากการดำเนินการ XOR EAX จะมีบิตที่มีการเปลี่ยนแปลง

85 :คืนค่า EFLAGS ดั้งเดิม

98 :105 การดำเนินการแยกบิตที่ 21 (บิต ID) โดยการปกปิดบิตอื่นๆ ทั้งหมด หลังจากการดำเนินการนี้ รีจิสเตอร์ EAX จะมี 0x00200000 (หากมีการเปลี่ยนแปลง 21 บิต ซึ่งหมายความว่ารองรับ CPUID) หรือ 0×00 (ไม่มีการเปลี่ยนแปลง 21 บิต ไม่รองรับ CPUID)

116 :คำสั่ง CMP จะตรวจสอบผลลัพธ์ของการทำงานก่อนหน้านี้ หาก EAX เท่ากับ 0×00 หมายความว่าบิต ID ไม่สามารถแก้ไขได้ และโปรเซสเซอร์ไม่รองรับคำสั่ง CPUID หากไม่เป็นศูนย์ แสดงว่าบิต ID ถูกพลิกและโปรเซสเซอร์ของคุณรองรับคำสั่ง CPUID

รับคุณสมบัติ CPU

คำสั่ง CPUID จะส่งคืนข้อมูลที่แตกต่างกันด้วยค่าที่แตกต่างกันในการลงทะเบียน EAX

mov eax, 0x1
cpuid

เมื่อ EAX ตั้งค่าเป็น 1 CPUID จะส่งกลับบิตฟิลด์ใน EDX ซึ่งจะมีค่าต่อไปนี้ แบรนด์ต่างๆ อาจให้ความหมายที่แตกต่างกันไป (ที่มา https://wiki.osdev.org/CPUID)

enum {
 CPUID_FEAT_ECX_SSE3 = 1 << 0,
 CPUID_FEAT_ECX_PCLMUL = 1 << 1,
 CPUID_FEAT_ECX_DTES64 = 1 << 2,
 CPUID_FEAT_ECX_MONITOR = 1 << 3,
 CPUID_FEAT_ECX_DS_CPL = 1 << 4,
 CPUID_FEAT_ECX_VMX = 1 << 5,
 CPUID_FEAT_ECX_SMX = 1 << 6,
 CPUID_FEAT_ECX_EST = 1 << 7,
 CPUID_FEAT_ECX_TM2 = 1 << 8,
 CPUID_FEAT_ECX_SSSE3 = 1 << 9,
 CPUID_FEAT_ECX_CID = 1 << 10,
 CPUID_FEAT_ECX_SDBG = 1 << 11,
 CPUID_FEAT_ECX_FMA = 1 << 12,
 CPUID_FEAT_ECX_CX16 = 1 << 13,
 CPUID_FEAT_ECX_XTPR = 1 << 14,
 CPUID_FEAT_ECX_PDCM = 1 << 15,
 CPUID_FEAT_ECX_PCID = 1 << 17,
 CPUID_FEAT_ECX_DCA = 1 << 18,
 CPUID_FEAT_ECX_SSE4_1 = 1 << 19,
 CPUID_FEAT_ECX_SSE4_2 = 1 << 20,
 CPUID_FEAT_ECX_X2APIC = 1 << 21,
 CPUID_FEAT_ECX_MOVBE = 1 << 22,
 CPUID_FEAT_ECX_POPCNT = 1 << 23,
 CPUID_FEAT_ECX_TSC = 1 << 24,
 CPUID_FEAT_ECX_AES = 1 << 25,
 CPUID_FEAT_ECX_XSAVE = 1 << 26,
 CPUID_FEAT_ECX_OSXSAVE = 1 << 27,
 CPUID_FEAT_ECX_AVX = 1 << 28,
 CPUID_FEAT_ECX_F16C = 1 << 29,
 CPUID_FEAT_ECX_RDRAND = 1 << 30,
 CPUID_FEAT_ECX_HYPERVISOR = 1 << 31,
 CPUID_FEAT_EDX_FPU = 1 << 0,
 CPUID_FEAT_EDX_VME = 1 << 1,
 CPUID_FEAT_EDX_DE = 1 << 2,
 CPUID_FEAT_EDX_PSE = 1 << 3,
 CPUID_FEAT_EDX_TSC = 1 << 4,
 CPUID_FEAT_EDX_MSR = 1 << 5,
 CPUID_FEAT_EDX_PAE = 1 << 6,
 CPUID_FEAT_EDX_MCE = 1 << 7,
 CPUID_FEAT_EDX_CX8 = 1 << 8,
 CPUID_FEAT_EDX_APIC = 1 << 9,
 CPUID_FEAT_EDX_SEP = 1 << 11,
 CPUID_FEAT_EDX_MTRR = 1 << 12,
 CPUID_FEAT_EDX_PGE = 1 << 13,
 CPUID_FEAT_EDX_MCA = 1 << 14,
 CPUID_FEAT_EDX_CMOV = 1 << 15,
 CPUID_FEAT_EDX_PAT = 1 << 16,
 CPUID_FEAT_EDX_PSE36 = 1 << 17,
 CPUID_FEAT_EDX_PSN = 1 << 18,
 CPUID_FEAT_EDX_CLFLUSH = 1 << 19,
 CPUID_FEAT_EDX_DS = 1 << 21,
 CPUID_FEAT_EDX_ACPI = 1 << 22,
 CPUID_FEAT_EDX_MMX = 1 << 23,
 CPUID_FEAT_EDX_FXSR = 1 << 24,
 CPUID_FEAT_EDX_SSE = 1 << 25,
 CPUID_FEAT_EDX_SSE2 = 1 << 26,
 CPUID_FEAT_EDX_SS = 1 << 27,
 CPUID_FEAT_EDX_HTT = 1 << 28,
 CPUID_FEAT_EDX_TM = 1 << 29,
 CPUID_FEAT_EDX_IA64 = 1 << 30,
 CPUID_FEAT_EDX_PBE = 1 << 31
};

คำอธิบายโดยย่อเกี่ยวกับคุณสมบัติของ CPU ด้านบน:

  • 121 :ชุดคำสั่งการเข้ารหัสเพื่อการเข้ารหัสและถอดรหัสที่รวดเร็ว

  • 139 :การสนับสนุนการจำลองเสมือนสำหรับการรันเครื่องเสมือน

  • 148 :ชุดคำสั่ง SIMD เพื่อการประมวลผลมัลติมีเดีย คณิตศาสตร์ และเวกเตอร์ที่รวดเร็วยิ่งขึ้น

  • 157 :Fused Multiply-Add ปรับปรุงประสิทธิภาพในการคำนวณจุดลอยตัว

  • 161 :เครื่องกำเนิดตัวเลขสุ่ม

  • 170 :การจัดการการขัดจังหวะขั้นสูงในระบบมัลติโปรเซสเซอร์

  • 184 :ปรับการจัดการหน่วยความจำให้เหมาะสมในระหว่างการสลับบริบท

  • 199 :หน่วยจุดลอยตัวของฮาร์ดแวร์เพื่อการดำเนินการทางคณิตศาสตร์ที่รวดเร็วยิ่งขึ้น

  • 203 :ส่วนขยายที่อยู่ทางกายภาพ อนุญาตให้ระบุที่อยู่หน่วยความจำมากกว่า 4 GB

  • 214 :อนุญาตให้แกน CPU ตัวเดียวจัดการหลายเธรดได้

  • 224 :คุณสมบัติการจัดการหน่วยความจำสำหรับการควบคุมแคชและการแมปหน้า

  • 236 :ชุดคำสั่ง SIMD รุ่นเก่าสำหรับการประมวลผลมัลติมีเดีย

รับสตริงผู้จำหน่าย CPU

หากคุณต้องการรับสตริงผู้จำหน่าย CPU EAX ควรตั้งค่าเป็น 0×0 ก่อนที่จะเรียกใช้คำสั่ง CPUID

mov eax, 0x0
cpuid

สตริงผู้จำหน่ายคือตัวระบุเฉพาะที่ผู้จำหน่าย CPU เช่น AMD และ Intel ใช้ ตัวอย่างได้แก่:GenuineIntel (สำหรับโปรเซสเซอร์ Intel) หรือ AuthenticAMD (สำหรับโปรเซสเซอร์ AMD) โดยพื้นฐานแล้วจะระบุผู้ผลิต CPU

สตริงผู้ขายอนุญาตให้เคอร์เนลระบุผู้ผลิต CPU ซึ่งมีประโยชน์มากเนื่องจากผู้ผลิตแต่ละรายใช้คุณสมบัติบางอย่างแตกต่างกัน นอกจากนี้ ซอฟต์แวร์หรือไดรเวอร์สามารถโต้ตอบที่แตกต่างกันไปตามผู้ผลิต CPU เพื่อให้มั่นใจถึงความเข้ากันได้

เมื่อใช้เช่นนี้ สตริงรหัสผู้ขายจะถูกส่งกลับในการลงทะเบียน EBX, EDX, ECX คุณสามารถเขียนลงในบัฟเฟอร์และรับสตริงอักขระเต็ม 12 ตัว

รหัสตัวอย่าง:

ขั้นตอนที่ 1:บัฟเฟอร์

สร้างบัฟเฟอร์ที่สามารถเก็บได้ 12 ไบต์:

buffer: db 12 dup(0), 0xA, 0xD, 0

ขั้นตอนที่ 2:พิมพ์บัฟเฟอร์

เราเริ่มต้นด้วยการสร้างฟังก์ชันการพิมพ์สตริง

รหัสแอสเซมบลีนี้จะอ่านอักขระสตริงทีละอักขระและพิมพ์ไปที่หน้าจอโดยใช้ BIOS ขัดจังหวะ 0x10 247 ฟังก์ชันวนซ้ำสตริงและใช้ 259 คำแนะนำในการโหลดอักขระแต่ละตัวใน 263 ลงทะเบียน

จากนั้น 274 ฟังก์ชั่นใช้อินเทอร์รัปต์ 0×10 เพื่อพิมพ์บนหน้าจอ เมื่อโค้ดถึงจุดสิ้นสุดของสตริง (ตัวยุติค่า null) การวนซ้ำจะสิ้นสุด

print_string:
 call print
 ret
print:
.loop: 
 lodsb ;read character to al and then increment
 cmp al ,0 ;check if we reached the end
 je .done ;we reached null terminator, finish
 call print_char ;print character
 jmp .loop ;jump back into the loop
.done:
 ret
print_char:
 mov ah, 0eh
 int 0x10
 ret

ขั้นตอนที่ 3:เติมบัฟเฟอร์แล้วพิมพ์

ที่นี่ หลังจากบันทึกสถานะปัจจุบันโดยใช้ 286 คำแนะนำและการโทร 297 เมื่อผ่าน 0×0 ในการลงทะเบียน EAX เราสามารถจัดเก็บเนื้อหาของ 306 , 312 , 325 ไปที่บัฟเฟอร์ จากนั้นเราโทรไปที่ 339 เพื่อพิมพ์

get_cpu_vendor:
 pusha
 mov eax, 0x0
 cpuid
 mov [buffer], ebx
 mov [buffer + 4], edx
 mov [buffer + 8], ecx
 mov si, buffer 
 call print_string
 popa
 ret

วิดีโอจากช่อง YouTube ของฉันที่ฉันใช้งานและอธิบายโค้ดด้านบนโดยละเอียด

ข้อมูลเพิ่มเติมเกี่ยวกับข้อมูลที่คำสั่ง CPUID สามารถให้คุณตามค่าที่ส่งในการลงทะเบียน EAX สามารถพบได้ที่นี่:https://gitlab.com/x86-cpuid.org/x86-cpuid-db

บทส่งท้าย

ด้วยการทำความเข้าใจและการใช้คำสั่ง CPUID คุณสามารถทำให้บูตโหลดเดอร์/เคอร์เนลของคุณสามารถปรับให้เข้ากับโปรเซสเซอร์ที่หลากหลายได้มากขึ้น การรู้วิธีตรวจจับความพร้อมใช้งานของคำสั่งและดึงข้อมูลระบบที่สำคัญ เช่น คุณลักษณะของ CPU ขนาดแคช และเทคโนโลยีที่รองรับ สามารถเพิ่มประสิทธิภาพและความเข้ากันได้ได้อย่างมาก

หลังจากอ่านบทความนี้ คุณควรมีเครื่องมือและความรู้เพื่อเริ่มสำรวจคำสั่ง CPUID และวิธีใช้คำสั่งดังกล่าวในโปรเจ็กต์ของคุณเอง!

ขอให้สนุกกับการเขียนโค้ด!

เรียนรู้การเขียนโค้ดฟรี หลักสูตรโอเพ่นซอร์สของ freeCodeCamp ช่วยให้ผู้คนมากกว่า 40,000 คนได้งานในตำแหน่งนักพัฒนา เริ่มต้น


No