x86 รหัสเครื่อง 16/32/64 บิต: 11 ไบต์, คะแนน = 3.66
ฟังก์ชันนี้จะคืนค่าโหมดปัจจุบัน (ขนาดตัวถูกดำเนินการเริ่มต้น) เป็นจำนวนเต็มใน AL โทรจาก C พร้อมลายเซ็นuint8_t modedetect(void);
รายการรหัสเครื่องจักร + แหล่งที่มาของ NASM (แสดงวิธีการทำงานในโหมด 16 บิตเนื่องจากBITS 16
บอกให้ NASM รวบรวมข้อมูลช่วยจำแหล่งที่มาสำหรับโหมด 16 บิต)
1 machine global modedetect
2 code modedetect:
3 addr hex BITS 16
5 00000000 B040 mov al, 64
6 00000002 B90000 mov cx, 0 ; 3B in 16-bit. 5B in 32/64, consuming 2 more bytes as the immediate
7 00000005 FEC1 inc cl ; always 2 bytes. The 2B encoding of inc cx would work, too.
8
9 ; want: 16-bit cl=1. 32-bit: cl=0
10 00000007 41 inc cx ; 64-bit: REX prefix
11 00000008 D2E8 shr al, cl ; 64-bit: shr r8b, cl doesn't affect AL at all. 32-bit cl=1. 16-bit cl=2
12 0000000A C3 ret
# end-of-function address is 0xB, length = 0xB = 11
การให้เหตุผล :
รหัสเครื่อง x86 ไม่มีหมายเลขรุ่นเป็นทางการ แต่ฉันคิดว่านี่เป็นไปตามเจตนาของคำถามโดยต้องสร้างตัวเลขเฉพาะแทนที่จะเลือกสิ่งที่สะดวกที่สุด (ใช้เวลาเพียง 7 ไบต์ดูด้านล่าง)
ซีพียู x86 ดั้งเดิมของ Intel 8086 รองรับเฉพาะรหัสเครื่อง 16 บิต 80386 แนะนำรหัสเครื่อง 32 บิต (ใช้งานได้ในโหมดป้องกันแบบ 32 บิตและรุ่นที่ใหม่กว่าในโหมดที่เข้ากันได้ภายใต้ระบบปฏิบัติการ 64 บิต) AMD แนะนำรหัสเครื่อง 64 บิตซึ่งสามารถใช้งานได้ในโหมด Long เหล่านี้เป็นรุ่นของภาษาเครื่อง x86 ในลักษณะเดียวกับที่ Python2 และ Python3 เป็นรุ่นภาษาที่แตกต่างกัน พวกเขาส่วนใหญ่เข้ากันได้ แต่มีการเปลี่ยนแปลงโดยเจตนา คุณสามารถรันไฟล์ปฏิบัติการ 32 หรือ 64- บิตโดยตรงภายใต้เคอร์เนลระบบปฏิบัติการ 64 บิตในลักษณะเดียวกับที่คุณสามารถรันโปรแกรม Python2 และ Python3
มันทำงานอย่างไร:
al=64
เริ่มต้นด้วย เลื่อนไปทางขวา 1 (โหมด 32 บิต) หรือ 2 (โหมด 16 บิต)
16/32 กับ 64- บิต: 1 ไบต์inc
/ การdec
เข้ารหัสเป็นส่วนนำหน้า REX ใน 64- บิต ( http://wiki.osdev.org/X86-64_Instruction_Encoding#REX_prefix ) REX.W ไม่ได้ส่งผลกระทบต่อคำแนะนำบางอย่างที่ทุกคน (เช่นjmp
หรือjcc
) แต่ในกรณีนี้จะได้รับ 16/32/64 ผมอยากจะ inc หรือธันวาคมมากกว่าecx
eax
ที่ยังกำหนดREX.B
ซึ่งเปลี่ยนการลงทะเบียนปลายทาง แต่โชคดีที่เราสามารถทำให้การทำงานนั้น แต่การตั้งค่าสิ่งขึ้นดังนั้น 64 al
บิตไม่จำเป็นต้องเปลี่ยน
คำสั่งที่ทำงานเฉพาะในโหมด 16 บิตอาจรวมถึง a ret
แต่ฉันไม่พบว่าจำเป็นหรือมีประโยชน์ (และจะทำให้ไม่สามารถอินไลน์เป็นส่วนย่อยของโค้ดในกรณีที่คุณต้องการทำเช่นนั้น) มันอาจเป็นjmp
ฟังก์ชั่นภายใน
16 บิตเทียบกับ 32/64: เป็น 16 บิตทันทีแทนที่จะเป็น 32 บิต โหมดการเปลี่ยนสามารถเปลี่ยนความยาวของคำสั่งได้ดังนั้นโหมด 32/64 บิตจะถอดรหัสสองไบต์ถัดไปซึ่งเป็นส่วนหนึ่งของทันทีแทนที่จะเป็นคำสั่งแยกต่างหาก ฉันทำสิ่งต่าง ๆ ได้ง่าย ๆ โดยใช้คำสั่ง 2 ไบต์ที่นี่แทนการถอดรหัสออกจากการซิงค์ดังนั้นโหมด 16 บิตจะถอดรหัสจากขอบเขตการสอนที่แตกต่างกันกว่า 32/64
ที่เกี่ยวข้อง: คำนำหน้าขนาดตัวถูกดำเนินการเปลี่ยนความยาวของทันที (เว้นแต่ว่ามันเป็นสัญญาณขยาย 8 บิตทันที) เช่นเดียวกับความแตกต่างระหว่างโหมด 16 บิตและ 32/64 บิต ทำให้การถอดรหัสความยาวคำสั่งทำได้ยากในแบบคู่ขนาน ซีพียู Intel มี LCP แผงลอยถอดรหัส
แบบแผนการโทรส่วนใหญ่ (รวมถึง x86-32 และ x86-64 System V psABIs) อนุญาตให้ค่าส่งคืนแคบลงเพื่อให้มีขยะในบิตสูงของการลงทะเบียน พวกเขายังอนุญาตให้ clobbering CX / ECX / RCX (และ R8 สำหรับ 64- บิต) IDK ถ้าเป็นเรื่องปกติในการประชุมแบบ 16 บิต แต่นี่คือรหัสกอล์ฟดังนั้นฉันสามารถบอกได้เลยว่ามันเป็นแบบแผนการโทรที่กำหนดเองอยู่แล้ว
การถอดแบบ 32 บิต :
08048070 <modedetect>:
8048070: b0 40 mov al,0x40
8048072: b9 00 00 fe c1 mov ecx,0xc1fe0000 # fe c1 is the inc cl
8048077: 41 inc ecx # cl=1
8048078: d2 e8 shr al,cl
804807a: c3 ret
การถอดชิ้นส่วน 64 บิต ( ลองออนไลน์! ):
0000000000400090 <modedetect>:
400090: b0 40 mov al,0x40
400092: b9 00 00 fe c1 mov ecx,0xc1fe0000
400097: 41 d2 e8 shr r8b,cl # cl=0, and doesn't affect al anyway!
40009a: c3 ret
ที่เกี่ยวข้อง: โพลีกลอต x86-32 / x86-64ของฉัน- รหัส Q&A บนดังนั้น
ความแตกต่างระหว่าง 16 บิตและ 32/64 ก็คือโหมดการกำหนดแอดเดรสจะถูกเข้ารหัสแตกต่างกัน เช่นlea eax, [rax+2]
( 8D 40 02
) ถอดรหัสเช่นเดียวกับlea ax, [bx+si+0x2]
ในโหมด 16 บิต นี้คือการใช้รหัสกอล์ฟยากอย่างเห็นได้ชัดโดยเฉพาะอย่างยิ่งตั้งแต่e/rbx
และe/rsi
ถูกสงวนไว้ในการประชุมทางโทรศัพท์หลายครั้ง
ฉันยังพิจารณาใช้ 10 ไบต์mov r64, imm64
ซึ่งเป็น REX mov r32,imm32
+ แต่เนื่องจากฉันมีวิธีแก้ปัญหา 11 ไบต์อยู่แล้วนี่จึงเป็นวิธีที่ดีที่สุดเท่ากับ (10 ไบต์ +1 สำหรับret
)
ทดสอบรหัสสำหรับโหมด 32 และ 64 บิต (ฉันไม่ได้ดำเนินการจริงในโหมด 16 บิต แต่การถอดแยกชิ้นส่วนจะบอกคุณว่ามันจะถอดรหัสอย่างไรฉันไม่ได้ติดตั้งตัวจำลอง 16 บิต)
; CPU p6 ; YASM directive to make the ALIGN padding tidier
global _start
_start:
call modedetect
movzx ebx, al
mov eax, 1
int 0x80 ; sys_exit(modedetect());
align 16
modedetect:
BITS 16
mov al, 64
mov cx, 0 ; 3B in 16-bit. 5B in 32/64, consuming 2 more bytes as the immediate
inc cl ; always 2 bytes. The 2B encoding of inc cx would work, too.
; want: 16-bit cl=1. 32-bit: cl=0
inc cx ; 64-bit: REX prefix
shr al, cl ; 64-bit: shr r8b, cl doesn't affect AL at all. 32-bit cl=1. 16-bit cl=2
ret
โปรแกรมลินุกซ์นี้มีออกทางออกสถานะ = เพื่อเรียกใช้เป็นmodedetect()
./a.out; echo $?
รวบรวมและเชื่อมโยงมันเข้ากับไบนารีคงที่เช่น
$ asm-link -m32 x86-modedetect-polyglot.asm && ./x86-modedetect-polyglot; echo $?
+ yasm -felf32 -Worphan-labels -gdwarf2 x86-modedetect-polyglot.asm
+ ld -melf_i386 -o x86-modedetect-polyglot x86-modedetect-polyglot.o
32
$ asm-link -m64 x86-modedetect-polyglot.asm && ./x86-modedetect-polyglot; echo $?
+ yasm -felf64 -Worphan-labels -gdwarf2 x86-modedetect-polyglot.asm
+ ld -o x86-modedetect-polyglot x86-modedetect-polyglot.o
64
## maybe test 16-bit with BOCHS somehow if you really want to.
7 ไบต์ (คะแนน = 2.33) ถ้าฉันสามารถหมายเลขรุ่น 1, 2, 3
ไม่มีหมายเลขรุ่นเป็นทางการสำหรับโหมด x86 ที่แตกต่างกัน ฉันชอบเขียนคำตอบ asm ฉันคิดว่ามันจะเป็นการละเมิดเจตนาของคำถามถ้าฉันเพิ่งเรียกโหมด 1,2,3 หรือ 0,1,2 เพราะประเด็นคือการบังคับให้คุณสร้างหมายเลขที่ไม่สะดวก แต่ถ้าได้รับอนุญาต:
# 16-bit mode:
42 detect123:
43 00000020 B80300 mov ax,3
44 00000023 FEC8 dec al
45
46 00000025 48 dec ax
47 00000026 C3 ret
ซึ่งถอดรหัสในโหมด 32 บิตเป็น
08048080 <detect123>:
8048080: b8 03 00 fe c8 mov eax,0xc8fe0003
8048085: 48 dec eax
8048086: c3 ret
และ 64 บิตเป็น
00000000004000a0 <detect123>:
4000a0: b8 03 00 fe c8 mov eax,0xc8fe0003
4000a5: 48 c3 rex.W ret