เราจะไปจากการชุมนุมเพื่อรหัสเครื่อง (การสร้างรหัส)


16

มีวิธีง่าย ๆ ในการเห็นภาพขั้นตอนระหว่างการประกอบรหัสกับรหัสเครื่องหรือไม่?

ตัวอย่างเช่นถ้าคุณเปิดเกี่ยวกับไฟล์ไบนารีในแผ่นจดบันทึกคุณจะเห็นรูปแบบการแสดงข้อความของรหัสเครื่อง ฉันสมมติว่าแต่ละไบต์ (สัญลักษณ์) ที่คุณเห็นคือตัวอักษร ASCII ที่สอดคล้องกันสำหรับมันเป็นค่าไบนารี?

แต่เราจะเปลี่ยนจากการประกอบเป็นไบนารีได้อย่างไรจะเกิดอะไรขึ้นเบื้องหลัง?

คำตอบ:


28

ดูเอกสารชุดคำสั่งและคุณจะพบรายการเช่นนี้จากไมโครคอนโทรลเลอร์รูปสำหรับแต่ละคำสั่ง:

ตัวอย่างคำสั่ง addlw

บรรทัด "การเข้ารหัส" บอกว่าคำสั่งนั้นมีลักษณะอย่างไรในรูปแบบไบนารี ในกรณีนี้มันเริ่มต้นด้วย 5 เสมอจากนั้นไม่สนใจบิต (ซึ่งอาจเป็นหนึ่งหรือศูนย์) จากนั้น "k" ยืนสำหรับตัวอักษรที่คุณกำลังเพิ่ม

บิตสองสามตัวแรกเรียกว่า "opcode" นั้นมีความพิเศษสำหรับแต่ละคำสั่ง ซีพียูโดยทั่วไปจะมองที่ opcode เพื่อดูว่าคำสั่งนั้นคืออะไรแล้วจะรู้วิธีการถอดรหัส "k" เป็นตัวเลขที่จะเพิ่ม

มันน่าเบื่อ แต่ก็ไม่ยากที่จะเข้ารหัสและถอดรหัส ฉันมีชั้นเรียนระดับปริญญาตรีที่เราต้องทำด้วยมือในการสอบ

ในการสร้างไฟล์ที่สามารถเรียกใช้งานได้จริงคุณต้องทำสิ่งต่าง ๆ เช่นจัดสรรหน่วยความจำคำนวณออฟเซ็ตสาขาและวางเป็นรูปแบบเช่นELFขึ้นอยู่กับระบบปฏิบัติการของคุณ


10

แอสเซมบลี opcodes มีส่วนใหญ่การติดต่อแบบหนึ่งต่อหนึ่งกับคำแนะนำเครื่องพื้นฐาน ดังนั้นสิ่งที่คุณต้องทำคือระบุแต่ละ opcode ในภาษาแอสเซมบลีแมปไปยังคำสั่งเครื่องที่สอดคล้องกันและเขียนคำสั่งเครื่องลงในไฟล์พร้อมกับพารามิเตอร์ที่เกี่ยวข้อง (ถ้ามี) จากนั้นคุณทำซ้ำกระบวนการสำหรับ opcode เพิ่มเติมแต่ละรายการในไฟล์ต้นฉบับ

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


7

สิ่งแรกที่คุณต้องการคือไฟล์แบบนี้ นี่คือฐานข้อมูลคำแนะนำสำหรับโปรเซสเซอร์ x86 ที่ใช้โดยแอสเซมเบลอร์ NASM (ซึ่งฉันช่วยเขียนแม้ว่าจะไม่ใช่ชิ้นส่วนที่แปลคำสั่งจริง) ให้เลือกบรรทัดโดยพลการจากฐานข้อมูล:

ADD   rm32,imm8    [mi:    hle o32 83 /0 ib,s]      386,LOCK

ADDสิ่งที่หมายถึงนี้ก็คือว่ามันอธิบายถึงการเรียนการสอน คำสั่งนี้มีหลายรูปแบบและชุดคำสั่งเฉพาะที่อธิบายไว้ที่นี่เป็นชุดตัวเลือกที่ทำการลงทะเบียน 32 บิตหรือที่อยู่หน่วยความจำและเพิ่มค่า 8 บิตทันที (เช่นค่าคงที่รวมอยู่ในคำสั่งโดยตรง) ตัวอย่างคำสั่งแอสเซมบลีที่จะใช้รุ่นนี้คือ:

add eax, 42

ตอนนี้คุณต้องป้อนข้อความของคุณและแยกมันเป็นคำแนะนำและตัวถูกดำเนินการแต่ละรายการ สำหรับคำสั่งข้างต้นนี้อาจส่งผลให้โครงสร้างที่มีคำสั่งADDและอาร์เรย์ของตัวถูกดำเนินการ (อ้างอิงถึงการลงทะเบียนEAXและค่า42) เมื่อคุณมีโครงสร้างนี้คุณจะรันฐานข้อมูลคำสั่งและค้นหาบรรทัดที่ตรงกับชื่อคำสั่งและประเภทของตัวถูกดำเนินการ หากคุณไม่พบคู่ที่ตรงกันนั่นเป็นข้อผิดพลาดที่ต้องแสดงต่อผู้ใช้ ("การรวมกันของ opcode และตัวถูกดำเนินการ" หรือคล้ายกันคือข้อความปกติ)

เมื่อเราได้รับบรรทัดจากฐานข้อมูลเราจะดูคอลัมน์ที่สามซึ่งสำหรับคำสั่งนี้คือ:

[mi:    hle o32 83 /0 ib,s] 

นี่เป็นชุดคำสั่งที่อธิบายวิธีสร้างคำสั่งรหัสเครื่องที่ต้องการ:

  • นี่miคือคำอธิบายของตัวถูกดำเนินการ: ตัวถูกดำเนินการหนึ่งตัวmodr/m(ลงทะเบียนหรือหน่วยความจำ) (ซึ่งหมายความว่าเราจะต้องผนวกmodr/mไบต์ต่อท้ายคำสั่งซึ่งเราจะมาภายหลัง) และหนึ่งคำสั่งทันที (ซึ่งจะ นำมาใช้ในคำอธิบายของการเรียนการสอน)
  • hleถัดไปคือ นี่เป็นการระบุวิธีที่เราจัดการส่วนนำหน้า "ล็อค" เราไม่ได้ใช้ "ล็อค" ดังนั้นเราจึงไม่สนใจ
  • o32ถัดไปคือ สิ่งนี้บอกเราว่าหากเรากำลังรวบรวมรหัสสำหรับรูปแบบเอาต์พุตขนาด 16 บิตคำสั่งนั้นจำเป็นต้องมีคำนำหน้าแทนที่ขนาดตัวถูกดำเนินการ หากเราผลิตเอาต์พุต 16 บิตเราจะผลิตคำนำหน้าทันที ( 0x66) แต่ฉันจะสมมติว่าเราไม่ได้ดำเนินการต่อไป
  • 83ถัดไปคือ นี่คือไบต์ที่แท้จริงในฐานสิบหก เราส่งออกมัน
  • /0ถัดไปคือ สิ่งนี้ระบุบิตพิเศษบางอย่างที่เราต้องการใน modr / m bytem และทำให้เราสร้างมันขึ้นมา modr/mไบต์จะใช้ในการลงทะเบียนการเข้ารหัสหรือการอ้างอิงหน่วยความจำทางอ้อม เรามีตัวถูกดำเนินการเช่นเดียวกับการลงทะเบียน รีจิสเตอร์มีหมายเลขซึ่งระบุไว้ในไฟล์ข้อมูลอื่น :

    eax     REG_EAX         reg32           0
  • เราตรวจสอบว่าreg32เห็นด้วยกับขนาดที่ต้องการของคำสั่งจากฐานข้อมูลดั้งเดิม (ใช่) 0เป็นจำนวนที่ลงทะเบียน modr/mไบต์เป็นโครงสร้างข้อมูลที่ระบุโดยหน่วยประมวลผลที่มีลักษณะเช่นนี้

     (most significant bit)
     2 bits       mod    - 00 => indirect, e.g. [eax]
                           01 => indirect plus byte offset
                           10 => indirect plus word offset
                           11 => register
     3 bits       reg    - identifies register
     3 bits       rm     - identifies second register or additional data
     (least significant bit)
  • เพราะเรากำลังทำงานกับการลงทะเบียนที่เขตข้อมูลmod0b11

  • regข้อมูลเป็นจำนวนของการลงทะเบียนที่เรากำลังใช้,0b000
  • เนื่องจากมีเพียงหนึ่งการลงทะเบียนในคำสั่งนี้เราจึงต้องกรอกข้อมูลลงในrmฟิลด์ ว่าข้อมูลอะไรเป็นพิเศษที่ระบุไว้ใน/0เป็นดังนั้นเราใส่ที่อยู่ในสนามrm0b000
  • modr/mไบต์ดังนั้นจึงเป็นเรื่องหรือ0b11000000 0xC0เราส่งออกสิ่งนี้
  • ib,sถัดไปคือ นี้ระบุไบต์ทันทีที่ลงนาม เราดูตัวถูกดำเนินการและทราบว่าเรามีค่าทันที เราแปลงเป็นไบต์ที่ลงนามแล้วส่งออก ( 42=> 0x2A)

0x83 0xC0 0x2Aการเรียนการสอนการประกอบที่สมบูรณ์ดังนั้นจึงเป็น: ส่งไปยังโมดูลเอาต์พุตของคุณพร้อมกับทราบว่าไม่มีไบต์ใดที่ประกอบด้วยการอ้างอิงหน่วยความจำ

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


1
ขอขอบคุณ. คำอธิบายที่ดี แต่ไม่ควรเป็น "0x83 0xC0 0x2A" แทนที่จะเป็น "0x83 0xB0 0x2A" เพราะ 0b11000000 = 0xC0
Kamran

@Kamran - $ cat > test.asm bits 32 add eax,42 $ nasm -f bin test.asm -o test.bin $ od -t x1 test.bin 0000000 83 c0 2a 0000003... ใช่คุณพูดถูก :)
Jules

2

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

ก่อนอื่นให้สังเกตว่าแอสเซมบลีจำนวนมากเป็นโปรแกรมซอฟต์แวร์ฟรีในปัจจุบัน ดังนั้นการดาวน์โหลดและการรวบรวมในคอมพิวเตอร์ของคุณรหัสที่มาของGNU เป็น (ส่วนหนึ่งของbinutils ) และNASM จากนั้นศึกษารหัสต้นฉบับ BTW ฉันขอแนะนำให้ใช้ Linux เพื่อจุดประสงค์นั้น (เป็นระบบปฏิบัติการที่เป็นมิตรกับผู้พัฒนาและเป็นซอฟต์แวร์ฟรี)

ไฟล์อ็อบเจ็กต์ที่สร้างโดยแอสเซมเบลอร์ประกอบด้วยส่วนรหัสและคำแนะนำการย้ายที่สะดุดตา มันถูกจัดระเบียบในรูปแบบไฟล์เอกสารที่ดีซึ่งขึ้นอยู่กับระบบปฏิบัติการ บน Linux รูปแบบ (ใช้สำหรับไฟล์วัตถุห้องสมุดที่ใช้ร่วมกันทิ้งหลักและ executables) เป็นเอลฟ์ วัตถุไฟล์นั้นจะถูกป้อนเข้าสู่linkerในภายหลัง(ซึ่งในที่สุดก็สร้างปฏิบัติการ) การย้ายถิ่นฐานถูกระบุโดยABI (เช่นx86-64 ABI ) อ่านหนังสือLinkers and Loadersของ Levine เพื่ออ่านเพิ่มเติม

ส่วนของรหัสในไฟล์ออบเจ็กต์นั้นมีรหัสเครื่องที่มีรู (ต้องกรอกข้อมูลด้วยตัวช่วยการย้ายข้อมูล) (relocatable) รหัสเครื่องที่สร้างโดยผู้ประกอบจะเห็นได้ชัดที่เฉพาะเจาะจงไปยังสถาปัตยกรรมชุดคำสั่ง x86หรือx86-64 (ใช้ไปในที่สุดแล็ปท็อปหรือคอมพิวเตอร์ประมวลผล) อกหักมีความซับซ้อนชะมัดในรายละเอียดของพวกเขา แต่ชุดย่อยที่เรียบง่ายที่เรียกว่า y86 หรือ y86-64 ได้ถูกคิดค้นเพื่อการสอน อ่านสไลด์บนพวกเขา คำตอบอื่น ๆ สำหรับคำถามนี้ยังอธิบายเล็กน้อย คุณอาจต้องการที่จะอ่านดีหนังสือเกี่ยวกับสถาปัตยกรรมคอมพิวเตอร์

แอสเซมเบลอร์ส่วนใหญ่ทำงานในสองรอบอันที่สองจะทำการย้ายตำแหน่งใหม่หรือแก้ไขเอาต์พุตบางส่วนของรอบแรก พวกเขาใช้เทคนิคการแยกวิเคราะห์ตามปกติในขณะนี้(ดังนั้นอาจจะอ่านThe Dragon Book )

วิธีการเรียกใช้งานเริ่มต้นโดยเคอร์เนลระบบปฏิบัติการ(เช่นการexecveเรียกระบบทำงานบน Linux) เป็นคำถามที่แตกต่าง (และซับซ้อน) มันมักจะตั้งค่าพื้นที่ที่อยู่เสมือนบางส่วน(ในกระบวนการทำที่execve (2) ... ) จากนั้นกำหนดค่าเริ่มต้นสถานะภายในกระบวนการ (รวมถึงการลงทะเบียนโหมดผู้ใช้ ) ลิงเกอร์แบบไดนามิก -such เป็นld-linux.so (8)ใน Linux- อาจจะเกี่ยวข้องกับการที่รันไทม์ อ่านหนังสือที่ดีเช่นระบบปฏิบัติการ: สามชิ้นง่าย OSDEVวิกิพีเดียนอกจากนี้ยังให้ข้อมูลที่เป็นประโยชน์

PS คำถามของคุณกว้างมากจนคุณต้องอ่านหนังสือหลายเล่มเกี่ยวกับเรื่องนี้ ฉันได้ให้การอ้างอิงบางส่วน (ไม่สมบูรณ์มาก) คุณควรหาพวกเขามากขึ้น


1
สำหรับผู้เริ่มต้นฉันขอแนะนำให้ดูที่ฟอร์แมต RDOFF ที่ผลิตโดย NASM สิ่งนี้ได้รับการออกแบบโดยเจตนาให้ง่ายที่สุดเท่าที่จะเป็นไปได้และยังคงทำงานในสถานการณ์ที่หลากหลาย แหล่งที่มาของ NASM มีตัวเชื่อมโยงและตัวโหลดสำหรับรูปแบบ (การเปิดเผยอย่างเต็มรูปแบบ - ฉันออกแบบและเขียนสิ่งเหล่านี้ทั้งหมด)
Jules
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.