ไม่กี่ปีที่ผ่านมา ฟาบริซ เบลลาร์ด
- โอเพ่นซอร์ส
- ความสามารถในการทำงานโดยไม่ต้องใช้ไดรเวอร์เคอร์เนล
- ความสามารถในการทำงานในโหมดล่าม
- รองรับสถาปัตยกรรมโฮสต์และแขกจำนวนมาก
เกี่ยวกับประเด็นที่สาม ตอนนี้ฉันสามารถอธิบายได้ว่าในความเป็นจริงในโหมด TCI ไม่ใช่คำสั่งของเครื่องแขกที่ตีความ แต่เป็นรหัสไบต์ที่ได้รับจากพวกเขา แต่สิ่งนี้ไม่ได้เปลี่ยนสาระสำคัญ - เพื่อที่จะสร้างและเรียกใช้ Qemu บนสถาปัตยกรรมใหม่ หากคุณโชคดี คอมไพเลอร์ AC ก็เพียงพอแล้ว - การเขียนตัวสร้างโค้ดสามารถเลื่อนออกไปได้
และตอนนี้หลังจากสองปีของการแก้ไขซอร์สโค้ด Qemu ในเวลาว่างของฉันต้นแบบที่ใช้งานได้ก็ปรากฏขึ้นซึ่งคุณสามารถเรียกใช้ได้แล้วเช่น Kolibri OS
Emscripten คืออะไร
ปัจจุบันมีคอมไพเลอร์จำนวนมากปรากฏขึ้น ผลลัพธ์สุดท้ายคือ JavaScript บางอย่าง เช่น Type Script เดิมทีตั้งใจให้เป็นวิธีที่ดีที่สุดในการเขียนสำหรับเว็บ ในเวลาเดียวกัน Emscripten เป็นวิธีหนึ่งในการนำโค้ด C หรือ C++ ที่มีอยู่แล้วคอมไพล์เป็นรูปแบบที่เบราว์เซอร์อ่านได้ บน
ครั้งแรกลอง
โดยทั่วไปแล้ว ฉันไม่ใช่คนแรกที่มีแนวคิดในการย้าย Qemu ไปยัง JavaScript มีคำถามถามในฟอรัม ReactOS ว่าเป็นไปได้โดยใช้ Emscripten หรือไม่ ก่อนหน้านี้ มีข่าวลือว่า Fabrice Bellard ทำสิ่งนี้เป็นการส่วนตัว แต่เรากำลังพูดถึง jslinux ซึ่งเท่าที่ฉันรู้ เป็นเพียงความพยายามที่จะบรรลุประสิทธิภาพที่เพียงพอด้วยตนเองใน JS และถูกเขียนตั้งแต่เริ่มต้น ต่อมามีการเขียน Virtual x86 - มีการโพสต์แหล่งที่มาที่ไม่ซับซ้อนและตามที่ระบุไว้ "ความสมจริง" ที่ยิ่งใหญ่กว่าของการจำลองทำให้สามารถใช้ SeaBIOS เป็นเฟิร์มแวร์ได้ นอกจากนี้ยังมีความพยายามอย่างน้อยหนึ่งครั้งในการย้ายพอร์ต Qemu โดยใช้ Emscripten - ฉันพยายามทำสิ่งนี้
ดูเหมือนว่านี่คือแหล่งที่มานี่คือ Emscripten - นำไปและรวบรวม แต่ก็มีห้องสมุดที่ Qemu ขึ้นอยู่กับ และห้องสมุดที่ห้องสมุดเหล่านั้นพึ่งพา ฯลฯ และหนึ่งในนั้นคือ
ในตอนแรกมีแนวคิดที่จะเขียนการแทนที่ libffi ด้วย JS และรันการทดสอบมาตรฐาน แต่สุดท้ายฉันก็สับสนเกี่ยวกับวิธีการสร้างไฟล์ส่วนหัวของฉันเพื่อที่พวกเขาจะทำงานกับโค้ดที่มีอยู่ - ฉันจะทำอย่างไร อย่างที่พวกเขาพูดว่า “งานมันซับซ้อนขนาดนั้นเลยเหรอ “เราโง่ขนาดนั้นเลยเหรอ?” ฉันต้องย้ายพอร์ต libffi ไปยังสถาปัตยกรรมอื่น ดังนั้นโชคดีที่ Emscripten มีทั้งมาโครสำหรับการประกอบแบบอินไลน์ (ใน Javascript ใช่แล้ว ไม่ว่าจะเป็นสถาปัตยกรรมใดก็ตาม ดังนั้นแอสเซมเบลอร์) และความสามารถในการเรียกใช้โค้ดที่สร้างขึ้นได้ทันที โดยทั่วไป หลังจากซ่อมแซมชิ้นส่วน libffi ที่ขึ้นกับแพลตฟอร์มมาระยะหนึ่งแล้ว ฉันก็ได้รับโค้ดที่คอมไพล์ได้และรันมันในการทดสอบครั้งแรกที่ฉันเจอ ฉันประหลาดใจมากที่การทดสอบประสบความสำเร็จ ตะลึงกับอัจฉริยะของฉัน - ไม่ตลกเลย มันใช้งานได้ตั้งแต่การเปิดตัวครั้งแรก - ฉันยังไม่เชื่อสายตาตัวเองเลยไปดูโค้ดผลลัพธ์อีกครั้งเพื่อประเมินว่าจะขุดที่ไหนต่อไป ที่นี่ฉันแทบบ้าเป็นครั้งที่สอง - สิ่งเดียวที่ฟังก์ชั่นของฉันทำคือ ffi_call
- สิ่งนี้รายงานว่าการโทรสำเร็จ ไม่มีการเรียกตัวเอง ดังนั้นฉันจึงส่งคำขอดึงครั้งแรกซึ่งแก้ไขข้อผิดพลาดในการทดสอบที่ชัดเจนสำหรับนักเรียนโอลิมปิกคนใดคนหนึ่ง - ไม่ควรเปรียบเทียบจำนวนจริงเป็น a == b
และอย่างไร a - b < EPS
- คุณต้องจำโมดูลด้วยไม่เช่นนั้น 0 จะเท่ากับ 1/3 อย่างมาก... โดยทั่วไปแล้วฉันพบพอร์ต libffi บางตัวซึ่งผ่านการทดสอบที่ง่ายที่สุดและ glib ใดคือ เรียบเรียงแล้ว - ฉันตัดสินใจว่าจำเป็น ฉันจะเพิ่มในภายหลัง เมื่อมองไปข้างหน้าฉันจะบอกว่าเมื่อปรากฎว่าคอมไพเลอร์ไม่ได้รวมฟังก์ชัน libffi ไว้ในโค้ดสุดท้ายด้วยซ้ำ
แต่อย่างที่ฉันได้กล่าวไปแล้วว่ามีข้อ จำกัด บางประการและในบรรดาการใช้งานพฤติกรรมที่ไม่ได้กำหนดต่างๆฟรีคุณลักษณะที่ไม่พึงประสงค์ยิ่งกว่านั้นได้ถูกซ่อนไว้ - JavaScript ตามการออกแบบไม่รองรับมัลติเธรดพร้อมหน่วยความจำที่ใช้ร่วมกัน โดยหลักการแล้ว สิ่งนี้สามารถเรียกได้ว่าเป็นแนวคิดที่ดี แต่ไม่ใช่สำหรับโค้ดพอร์ตที่มีสถาปัตยกรรมเชื่อมโยงกับเธรด C โดยทั่วไปแล้ว Firefox กำลังทดลองใช้การสนับสนุนผู้ทำงานร่วมกัน และ Emscripten ก็มีการใช้งาน pthread สำหรับพวกเขา แต่ฉันไม่ต้องการพึ่งพามัน ฉันต้องค่อยๆ รูทมัลติเธรดจากโค้ด Qemu นั่นคือค้นหาว่าเธรดทำงานอยู่ที่ใด ย้ายเนื้อความของลูปที่ทำงานในเธรดนี้ไปยังฟังก์ชันแยกต่างหาก และเรียกใช้ฟังก์ชันดังกล่าวทีละรายการจากลูปหลัก
ลองครั้งที่สอง
เมื่อถึงจุดหนึ่ง ก็ชัดเจนว่าปัญหายังคงมีอยู่ และการดันไม้ค้ำยันไปรอบๆ รหัสอย่างไม่ได้ตั้งใจจะไม่ทำให้เกิดผลดีใดๆ เลย สรุป: เราจำเป็นต้องจัดระบบกระบวนการเพิ่มไม้ค้ำยัน เลยเอาเวอร์ชั่น 2.4.1 ที่ยังใหม่อยู่ ณ ขณะนั้นมา (ไม่ใช่ 2.5.0 เพราะใครจะรู้ในเวอร์ชั่นใหม่จะมีจุดบกพร่องที่ยังตรวจไม่พบและผมก็มีจุดบกพร่องของตัวเองมากพอแล้ว ) และสิ่งแรกคือเขียนใหม่อย่างปลอดภัย thread-posix.c
. นั่นคือปลอดภัย: หากมีคนพยายามดำเนินการที่นำไปสู่การบล็อกฟังก์ชันนั้นจะถูกเรียกใช้ทันที abort()
- แน่นอนว่านี่ไม่ได้แก้ปัญหาทั้งหมดในคราวเดียว แต่อย่างน้อยก็ยังดีกว่าการรับข้อมูลที่ไม่สอดคล้องกันอย่างเงียบๆ
โดยทั่วไป ตัวเลือก Emscripten จะมีประโยชน์มากในการย้ายโค้ดไปยัง JS -s ASSERTIONS=1 -s SAFE_HEAP=1
- ตรวจจับพฤติกรรมที่ไม่ได้กำหนดบางประเภท เช่น การเรียกไปยังที่อยู่ที่ไม่สอดคล้องกัน (ซึ่งไม่สอดคล้องกับโค้ดสำหรับอาร์เรย์ที่พิมพ์เลย เช่น HEAP32[addr >> 2] = 1
) หรือการเรียกใช้ฟังก์ชันด้วยจำนวนอาร์กิวเมนต์ที่ไม่ถูกต้อง
อย่างไรก็ตาม ข้อผิดพลาดในการจัดตำแหน่งเป็นปัญหาแยกต่างหาก ดังที่ฉันได้กล่าวไปแล้ว Qemu มีแบ็กเอนด์การตีความที่ "เสื่อมถอย" สำหรับการสร้างโค้ด TCI (ตัวแปลโค้ดขนาดเล็ก) และเพื่อสร้างและรัน Qemu บนสถาปัตยกรรมใหม่ หากคุณโชคดี คอมไพเลอร์ C ก็เพียงพอแล้ว "ถ้าคุณโชคดี". ฉันโชคไม่ดี และปรากฎว่า TCI ใช้การเข้าถึงที่ไม่สอดคล้องกันเมื่อแยกวิเคราะห์โค้ดไบต์ นั่นคือใน ARM ทุกประเภทและสถาปัตยกรรมอื่นๆ ที่มีการเข้าถึงระดับที่จำเป็น Qemu คอมไพล์เนื่องจากมีแบ็กเอนด์ TCG ปกติที่สร้างโค้ดเนทีฟ แต่ TCI จะทำงานกับพวกมันได้หรือไม่นั้นเป็นอีกคำถามหนึ่ง อย่างไรก็ตาม เมื่อปรากฏว่า เอกสาร TCI ระบุสิ่งที่คล้ายกันอย่างชัดเจน เป็นผลให้มีการเพิ่มการเรียกใช้ฟังก์ชันสำหรับการอ่านที่ไม่สอดคล้องกันในโค้ด ซึ่งถูกค้นพบในส่วนอื่นของ Qemu
การทำลายล้างกอง
เป็นผลให้การเข้าถึง TCI ที่ไม่สอดคล้องกันได้รับการแก้ไข วงหลักถูกสร้างขึ้นซึ่งในทางกลับกันเรียกว่าโปรเซสเซอร์ RCU และสิ่งเล็ก ๆ อื่น ๆ ดังนั้นฉันจึงเปิดตัว Qemu พร้อมตัวเลือก -d exec,in_asm,out_asm
ซึ่งหมายความว่าคุณต้องบอกว่าบล็อกของโค้ดใดที่กำลังดำเนินการอยู่ และในขณะที่ออกอากาศเพื่อเขียนว่ารหัสแขกคืออะไร รหัสโฮสต์คืออะไร (ในกรณีนี้คือ ไบต์โค้ด) มันเริ่มต้น ดำเนินการบล็อกการแปลหลายบล็อก เขียนข้อความการดีบักที่ฉันทิ้งไว้ซึ่ง RCU จะเริ่มทำงานและ... ขัดข้อง abort()
ภายในฟังก์ชัน free()
. โดยการซ่อมฟังก์ชั่น free()
เราพบว่าในส่วนหัวของบล็อกฮีปซึ่งอยู่ในแปดไบต์ก่อนหน่วยความจำที่จัดสรร แทนที่จะเป็นขนาดบล็อกหรืออะไรที่คล้ายกัน กลับกลายเป็นขยะ
การทำลายฮีป - น่ารักจริงๆ... ในกรณีเช่นนี้ มีวิธีแก้ไขที่มีประโยชน์ - จาก (ถ้าเป็นไปได้) แหล่งเดียวกัน ให้รวบรวมไบนารีดั้งเดิมและรันภายใต้ Valgrind หลังจากนั้นไม่นาน ไบนารี่ก็พร้อม ฉันเปิดใช้งานด้วยตัวเลือกเดียวกัน - มันหยุดทำงานแม้ในระหว่างการเริ่มต้นก่อนที่จะถึงการดำเนินการจริง แน่นอนว่ามันไม่เป็นที่พอใจ - เห็นได้ชัดว่าแหล่งที่มาไม่เหมือนกันทุกประการซึ่งไม่น่าแปลกใจเพราะ การกำหนดค่าค้นหาตัวเลือกที่แตกต่างกันเล็กน้อย แต่ฉันมี Valgrind - ก่อนอื่นฉันจะแก้ไขข้อผิดพลาดนี้ จากนั้นหากฉันโชคดี อันเดิมก็จะปรากฏขึ้นมา ฉันกำลังใช้งานแบบเดียวกันภายใต้ Valgrind... เย้ เย้ เย้ มันเริ่มต้นแล้ว ผ่านการรีเซ็ตตามปกติและก้าวข้ามจุดบกพร่องเดิมโดยไม่มีคำเตือนแม้แต่ครั้งเดียวเกี่ยวกับการเข้าถึงหน่วยความจำที่ไม่ถูกต้อง ไม่ต้องพูดถึงเรื่องการล้ม อย่างที่พวกเขาพูดกันว่าชีวิตไม่ได้เตรียมฉันให้พร้อมสำหรับสิ่งนี้ - โปรแกรมที่หยุดทำงานจะหยุดทำงานเมื่อเปิดตัวภายใต้ Walgrind สิ่งที่มันเป็นปริศนา สมมติฐานของฉันคือครั้งหนึ่งในบริเวณใกล้เคียงกับคำสั่งปัจจุบันหลังจากเกิดข้อขัดข้องระหว่างการเริ่มต้น gdb ก็แสดงการทำงาน memset
-a พร้อมตัวชี้ที่ถูกต้องโดยใช้อย่างใดอย่างหนึ่ง mmx
, หรือ xmm
registers แล้วบางทีอาจเป็นข้อผิดพลาดในการจัดตำแหน่ง แม้ว่าจะยังยากที่จะเชื่อก็ตาม
โอเค ดูเหมือนว่า Valgrind จะไม่ช่วยอะไรที่นี่ และนี่คือสิ่งที่น่ารังเกียจที่สุดเริ่มต้นขึ้น - ดูเหมือนว่าทุกอย่างจะเริ่มต้นขึ้นด้วยซ้ำ แต่เกิดข้อผิดพลาดโดยไม่ทราบสาเหตุอย่างแน่นอนเนื่องจากเหตุการณ์ที่อาจเกิดขึ้นเมื่อหลายล้านคำสั่งที่ผ่านมา เป็นเวลานานแล้วที่ยังไม่ชัดเจนว่าจะเข้าใกล้อย่างไร สุดท้ายฉันก็ยังต้องนั่งลงและแก้ไขจุดบกพร่อง การพิมพ์สิ่งที่ส่วนหัวถูกเขียนใหม่แสดงให้เห็นว่ามันไม่ได้ดูเหมือนตัวเลข แต่เป็นข้อมูลไบนารีบางประเภท และดูเถิด พบสตริงไบนารี่นี้ในไฟล์ BIOS นั่นคือตอนนี้สามารถพูดได้อย่างมั่นใจว่าเป็นบัฟเฟอร์ล้น และชัดเจนว่ามันถูกเขียนลงในบัฟเฟอร์นี้ด้วยซ้ำ ถ้าอย่างนั้นอะไรทำนองนี้ - ใน Emscripten โชคดีที่ไม่มีการสุ่มพื้นที่ที่อยู่ไม่มีช่องว่างดังนั้นคุณสามารถเขียนที่ไหนสักแห่งตรงกลางโค้ดเพื่อส่งออกข้อมูลตามตัวชี้จากการเปิดตัวครั้งล่าสุด ดูข้อมูล ดูที่พอยน์เตอร์ และถ้าไม่เปลี่ยนแปลง ให้กินอาหารสมอง จริงอยู่ที่การเชื่อมโยงจะใช้เวลาสองสามนาทีหลังจากการเปลี่ยนแปลง แต่คุณจะทำอย่างไร? เป็นผลให้พบบรรทัดเฉพาะที่คัดลอก BIOS จากบัฟเฟอร์ชั่วคราวไปยังหน่วยความจำของแขก - และแท้จริงแล้วมีพื้นที่ในบัฟเฟอร์ไม่เพียงพอ การค้นหาแหล่งที่มาของที่อยู่บัฟเฟอร์แปลก ๆ นั้นส่งผลให้เกิดฟังก์ชัน qemu_anon_ram_alloc
ในไฟล์ oslib-posix.c
- ตรรกะมีอยู่ดังนี้: บางครั้งการจัดที่อยู่ให้มีขนาดหน้าใหญ่ 2 MB อาจมีประโยชน์สำหรับสิ่งนี้เราจะถาม mmap
ขั้นแรกอีกสักหน่อยแล้วเราจะคืนส่วนที่เกินพร้อมความช่วยเหลือ munmap
. และหากไม่จำเป็นต้องจัดตำแหน่งดังกล่าว เราจะระบุผลลัพธ์แทน 2 MB getpagesize()
- mmap
มันจะยังคงให้ที่อยู่ที่สอดคล้องกัน... ดังนั้นใน Emscripten mmap
แค่โทร malloc
แต่แน่นอนว่ามันไม่สอดคล้องกับหน้า โดยทั่วไปแล้ว จุดบกพร่องที่ทำให้ฉันหงุดหงิดเป็นเวลาสองสามเดือนได้รับการแก้ไขด้วยการเปลี่ยนแปลง двух เส้น
คุณสมบัติของฟังก์ชั่นการโทร
และตอนนี้โปรเซสเซอร์กำลังนับบางสิ่งบางอย่าง Qemu ไม่มีปัญหา แต่หน้าจอไม่เปิดขึ้นและโปรเซสเซอร์จะเข้าสู่ลูปอย่างรวดเร็วโดยตัดสินจากเอาต์พุต -d exec,in_asm,out_asm
. มีสมมติฐานเกิดขึ้น: การขัดจังหวะตัวจับเวลา (หรือโดยทั่วไป การขัดจังหวะทั้งหมด) มาไม่ถึง และแน่นอนถ้าคุณคลายเกลียวการขัดจังหวะจากแอสเซมบลีดั้งเดิมซึ่งใช้งานได้ด้วยเหตุผลบางประการ คุณจะได้ภาพที่คล้ายกัน แต่นี่ไม่ใช่คำตอบเลย: การเปรียบเทียบร่องรอยที่ออกมาพร้อมกับตัวเลือกข้างต้นแสดงให้เห็นว่าวิถีการดำเนินการแยกจากกันเร็วมาก ที่นี่ต้องบอกว่าการเปรียบเทียบสิ่งที่บันทึกโดยใช้ตัวเรียกใช้งาน emrun
การดีบักเอาต์พุตด้วยเอาต์พุตของแอสเซมบลีดั้งเดิมไม่ใช่กระบวนการทางกลที่สมบูรณ์ ฉันไม่ทราบแน่ชัดว่าโปรแกรมที่ทำงานอยู่ในเบราว์เซอร์เชื่อมต่อกันอย่างไร emrun
แต่บางบรรทัดในเอาต์พุตกลับกลายเป็นว่ามีการจัดเรียงใหม่ ดังนั้นความแตกต่างในส่วนต่างจึงไม่ใช่เหตุผลที่จะสันนิษฐานได้ว่าวิถีได้แยกออกจากกัน โดยทั่วไปปรากฏชัดเจนว่าตามคำแนะนำ ljmpl
มีการเปลี่ยนไปใช้ที่อยู่ที่แตกต่างกัน และไบต์โค้ดที่สร้างขึ้นมีความแตกต่างโดยพื้นฐาน: อันหนึ่งมีคำสั่งให้เรียกใช้ฟังก์ชันตัวช่วย ส่วนอีกอันไม่มี หลังจาก googling คำแนะนำและศึกษาโค้ดที่แปลคำสั่งเหล่านี้แล้ว ก็ชัดเจนว่า ประการแรก ทันทีก่อนที่จะอยู่ในทะเบียน cr0
มีการบันทึก - โดยใช้ตัวช่วย - ซึ่งเปลี่ยนโปรเซสเซอร์เป็นโหมดที่ได้รับการป้องกันและประการที่สองว่าเวอร์ชัน js ไม่เคยเปลี่ยนเป็นโหมดที่ได้รับการป้องกัน แต่ความจริงก็คือคุณสมบัติอีกประการหนึ่งของ Emscripten คือการไม่เต็มใจที่จะยอมรับโค้ดเช่นการนำคำสั่งไปใช้ call
ใน TCI ซึ่งตัวชี้ฟังก์ชันใดๆ จะให้ผลลัพธ์เป็นประเภท long long f(int arg0, .. int arg9)
- ต้องเรียกใช้ฟังก์ชันด้วยจำนวนอาร์กิวเมนต์ที่ถูกต้อง หากกฎนี้ถูกละเมิด ขึ้นอยู่กับการตั้งค่าการดีบัก โปรแกรมอาจหยุดทำงาน (ซึ่งดี) หรือเรียกใช้ฟังก์ชันที่ไม่ถูกต้องเลย (ซึ่งจะต้องเสียใจที่ต้องแก้ไขจุดบกพร่อง) นอกจากนี้ยังมีตัวเลือกที่สาม - เปิดใช้งานการสร้าง wrappers ที่เพิ่ม / ลบอาร์กิวเมนต์ แต่โดยรวมแล้ว wrappers เหล่านี้ใช้พื้นที่มากแม้ว่าที่จริงแล้วฉันต้องการเพียงมากกว่าร้อย wrappers เพียงเล็กน้อยเท่านั้น เพียงอย่างเดียวนี้น่าเศร้ามาก แต่กลับกลายเป็นปัญหาที่ร้ายแรงกว่านี้: ในโค้ดที่สร้างขึ้นของฟังก์ชัน wrapper อาร์กิวเมนต์จะถูกแปลงและแปลง แต่บางครั้งฟังก์ชันที่มีอาร์กิวเมนต์ที่สร้างขึ้นก็ไม่ได้ถูกเรียก - ก็เหมือนกับใน การใช้งาน libffi ของฉัน นั่นคือผู้ช่วยบางคนไม่ได้ถูกประหารชีวิต
โชคดีที่ Qemu มีรายการตัวช่วยที่เครื่องอ่านได้ในรูปแบบของไฟล์ส่วนหัวเช่น
DEF_HELPER_0(lock, void)
DEF_HELPER_0(unlock, void)
DEF_HELPER_3(write_eflags, void, env, tl, i32)
ใช้ค่อนข้างตลก: ประการแรก มาโครถูกกำหนดใหม่ด้วยวิธีที่แปลกประหลาดที่สุด DEF_HELPER_n
แล้วเปิดเครื่อง helper.h
. ในขอบเขตที่แมโครถูกขยายไปสู่โครงสร้างเริ่มต้นและลูกน้ำ จากนั้นจึงกำหนดอาร์เรย์ และแทนที่จะเป็นองค์ประกอบ - #include <helper.h>
ในที่สุดฉันก็มีโอกาสได้ลองใช้ห้องสมุดในที่ทำงาน
หลังจากนั้นดูเหมือนว่าโปรเซสเซอร์จะทำงานได้ ดูเหมือนว่าจะเป็นเพราะหน้าจอไม่เคยถูกเตรียมใช้งาน แม้ว่า memtest86+ จะสามารถทำงานในแอสเซมบลีดั้งเดิมได้ ในที่นี้มีความจำเป็นต้องชี้แจงว่าโค้ด Qemu block I/O ถูกเขียนด้วย coroutines Emscripten มีการใช้งานที่ยุ่งยากมาก แต่ก็ยังจำเป็นต้องได้รับการสนับสนุนในโค้ด Qemu และคุณสามารถดีบักโปรเซสเซอร์ได้ทันที: Qemu รองรับตัวเลือกต่างๆ -kernel
, -initrd
, -append
ซึ่งคุณสามารถบูต Linux หรือ memtest86+ ได้โดยไม่ต้องใช้อุปกรณ์บล็อกเลย แต่นี่คือปัญหา: ในแอสเซมบลีดั้งเดิมเราสามารถเห็นเอาต์พุตเคอร์เนล Linux ไปยังคอนโซลพร้อมตัวเลือก -nographic
และไม่มีเอาต์พุตจากเบราว์เซอร์ไปยังเทอร์มินัลจากตำแหน่งที่เปิดใช้งาน emrun
, ไม่ได้มา. นั่นคือไม่ชัดเจน: โปรเซสเซอร์ไม่ทำงานหรือเอาต์พุตกราฟิกไม่ทำงาน แล้วมันก็เกิดขึ้นกับฉันที่จะรอสักหน่อย ปรากฎว่า "โปรเซสเซอร์ไม่ได้หลับ แต่เพียงกระพริบช้าๆ" และหลังจากนั้นประมาณห้านาทีเคอร์เนลก็โยนข้อความจำนวนมากลงบนคอนโซลและยังคงค้างต่อไป เห็นได้ชัดว่าโดยทั่วไปโปรเซสเซอร์ใช้งานได้และเราจำเป็นต้องเจาะลึกโค้ดเพื่อทำงานกับ SDL2 น่าเสียดายที่ฉันไม่รู้วิธีใช้ห้องสมุดนี้ ดังนั้นในบางแห่งฉันต้องดำเนินการแบบสุ่ม เมื่อถึงจุดหนึ่ง เส้น Parallel0 ก็กะพริบบนหน้าจอบนพื้นหลังสีน้ำเงิน ซึ่งบ่งบอกถึงความคิดบางอย่าง ท้ายที่สุดปรากฎว่าปัญหาคือ Qemu เปิดหน้าต่างเสมือนหลายหน้าต่างในหน้าต่างจริงเดียว ซึ่งคุณสามารถสลับได้โดยใช้ Ctrl-Alt-n: มันทำงานได้ในเนทิฟบิลด์ แต่ไม่ใช่ใน Emscripten หลังจากกำจัดหน้าต่างที่ไม่จำเป็นออกไปโดยใช้ตัวเลือกต่างๆ -monitor none -parallel none -serial none
และคำแนะนำในการวาดหน้าจอทั้งหมดใหม่ในแต่ละเฟรม ทุกอย่างทำงานกะทันหัน
โครูทีน
ดังนั้นการจำลองในเบราว์เซอร์จึงใช้งานได้ แต่คุณไม่สามารถเรียกใช้ฟลอปปีเดียวที่น่าสนใจได้ เนื่องจากไม่มีบล็อก I/O - คุณต้องใช้การรองรับโครูทีน Qemu มีแบ็กเอนด์ Coroutine หลายตัวอยู่แล้ว แต่เนื่องจากลักษณะของ JavaScript และตัวสร้างโค้ด Emscripten คุณจึงไม่สามารถเริ่มเล่นกลกองซ้อนได้ ดูเหมือนว่า "ทุกอย่างหายไปแล้ว ปูนปลาสเตอร์กำลังถูกเอาออก" แต่นักพัฒนา Emscripten ได้ดูแลทุกอย่างแล้ว สิ่งนี้มีการใช้งานที่ค่อนข้างตลก: มาเรียกใช้การเรียกใช้ฟังก์ชันที่น่าสงสัยกันดีกว่า emscripten_sleep
และอื่น ๆ อีกมากมายที่ใช้กลไก Asyncify รวมถึงการเรียกตัวชี้และการเรียกไปยังฟังก์ชันใด ๆ ที่หนึ่งในสองกรณีก่อนหน้านี้อาจเกิดขึ้นต่อไปในสแต็ก และตอนนี้ ก่อนการโทรที่น่าสงสัยแต่ละครั้ง เราจะเลือกบริบทอะซิงโครนัส และทันทีหลังจากการโทร เราจะตรวจสอบว่ามีการเรียกแบบอะซิงโครนัสเกิดขึ้นหรือไม่ และหากมี เราจะบันทึกตัวแปรท้องถิ่นทั้งหมดในบริบทอะซิงก์นี้ ระบุว่าฟังก์ชันใด เพื่อถ่ายโอนการควบคุมไปยังเวลาที่เราต้องการดำเนินการต่อไป และออกจากฟังก์ชันปัจจุบัน นี่คือจุดที่มีขอบเขตในการศึกษาผลกระทบ -O3
. ฉันรันโค้ดที่สร้างขึ้น และ Chromium กินหน่วยความจำจนหมดและขัดข้อง จากนั้นฉันก็ดูสิ่งที่เขาพยายามดาวน์โหลดโดยไม่ได้ตั้งใจ... ฉันจะว่าอย่างไรได้ ฉันก็คงหยุดนิ่งเหมือนกันหากฉันถูกขอให้ศึกษาอย่างรอบคอบและเพิ่มประสิทธิภาพ Javascript ที่มีขนาด 500+ MB
น่าเสียดายที่การตรวจสอบในโค้ดไลบรารีสนับสนุน Asyncify นั้นไม่เป็นมิตรเลย longjmp
-s ที่ใช้ในโค้ดตัวประมวลผลเสมือน แต่หลังจากแพตช์เล็กๆ ที่ปิดใช้งานการตรวจสอบเหล่านี้และกู้คืนบริบทอย่างจริงจัง ราวกับว่าทุกอย่างเรียบร้อยดี โค้ดก็ใช้งานได้ แล้วสิ่งแปลก ๆ ก็เริ่มขึ้น: บางครั้งการตรวจสอบในรหัสการซิงโครไนซ์ก็ถูกทริกเกอร์ - อันเดียวกับที่ทำให้โค้ดเสียหายหากตามตรรกะการดำเนินการควรถูกบล็อก - มีคนพยายามคว้า mutex ที่บันทึกไว้แล้ว โชคดีที่สิ่งนี้กลับกลายเป็นว่าไม่ใช่ปัญหาเชิงตรรกะในโค้ดซีเรียลไลซ์ - ฉันเพียงใช้ฟังก์ชันลูปหลักมาตรฐานที่ Emscripten มอบให้ แต่บางครั้งการโทรแบบอะซิงโครนัสจะแกะสแต็กออกทั้งหมด และในขณะนั้นก็จะล้มเหลว setTimeout
จากลูปหลัก - ดังนั้นโค้ดจึงเข้าสู่การวนซ้ำหลักโดยไม่ออกจากการวนซ้ำครั้งก่อน เขียนใหม่บนวงวนไม่สิ้นสุดและ emscripten_sleep
และปัญหาเกี่ยวกับ mutexes ก็หยุดลง โค้ดมีความสมเหตุสมผลมากขึ้น - ท้ายที่สุดแล้วฉันไม่มีโค้ดบางตัวที่เตรียมเฟรมแอนิเมชั่นถัดไป - โปรเซสเซอร์เพิ่งคำนวณบางอย่างและหน้าจอจะได้รับการอัปเดตเป็นระยะ อย่างไรก็ตาม ปัญหาไม่ได้หยุดอยู่แค่นั้น บางครั้งการดำเนินการของ Qemu ก็ยุติลงอย่างเงียบๆ โดยไม่มีข้อยกเว้นหรือข้อผิดพลาดใดๆ ในขณะนั้นฉันก็ยอมแพ้ แต่เมื่อมองไปข้างหน้าฉันจะบอกว่าปัญหาคือ: อันที่จริงรหัส Coroutine ไม่ได้ใช้ setTimeout
(หรืออย่างน้อยก็ไม่บ่อยเท่าที่คุณคิด): ฟังก์ชั่น emscripten_yield
เพียงตั้งค่าสถานะการโทรแบบอะซิงโครนัส ประเด็นทั้งหมดก็คือ emscripten_coroutine_next
ไม่ใช่ฟังก์ชันอะซิงโครนัส: ภายในจะตรวจสอบแฟล็ก รีเซ็ต และถ่ายโอนการควบคุมไปยังตำแหน่งที่ต้องการ นั่นคือการเลื่อนระดับของสแต็กสิ้นสุดลงตรงนั้น ปัญหาคือเนื่องจากการใช้งานฟรี ซึ่งปรากฏขึ้นเมื่อพูล coroutine ถูกปิดใช้งานเนื่องจากความจริงที่ว่าฉันไม่ได้คัดลอกบรรทัดโค้ดที่สำคัญจากแบ็กเอนด์ coroutine ที่มีอยู่ ฟังก์ชันนี้ qemu_in_coroutine
กลับเป็นจริงทั้งที่ในความเป็นจริงแล้วควรส่งคืนเท็จ สิ่งนี้นำไปสู่การโทร emscripten_yield
เหนือนั้นไม่มีใครอยู่บนสแต็ก emscripten_coroutine_next
สแต็กคลี่ออกไปจนถึงด้านบนสุด แต่ไม่มี setTimeout
อย่างที่ฉันบอกไปแล้วว่าไม่ได้จัดแสดง
การสร้างโค้ดจาวาสคริปต์
และนี่คือคำสัญญาที่ว่า "คืนเนื้อสับกลับ" ไม่เชิง. แน่นอนว่าหากเรารัน Qemu ในเบราว์เซอร์และ Node.js ในนั้น แน่นอนว่าหลังจากการสร้างโค้ดใน Qemu เราจะได้ JavaScript ผิดโดยสิ้นเชิง แต่ถึงกระนั้นก็มีการเปลี่ยนแปลงแบบย้อนกลับบ้าง
ก่อนอื่น เล็กน้อยเกี่ยวกับวิธีการทำงานของ Qemu โปรดยกโทษให้ฉันทันที: ฉันไม่ใช่นักพัฒนา Qemu มืออาชีพและข้อสรุปของฉันอาจมีข้อผิดพลาดในบางแห่ง ดังที่พวกเขากล่าวว่า “ความคิดเห็นของนักเรียนไม่จำเป็นต้องตรงกับความคิดเห็นของครู สัจพจน์และสามัญสำนึกของ Peano” Qemu มีสถาปัตยกรรมแขกที่รองรับจำนวนหนึ่ง และแต่ละสถาปัตยกรรมก็มีไดเร็กทอรีที่คล้ายกัน target-i386
. เมื่อสร้าง คุณสามารถระบุการสนับสนุนสำหรับสถาปัตยกรรมแบบแขกได้หลายแบบ แต่ผลลัพธ์จะเป็นเพียงไบนารีหลายแบบ ในทางกลับกัน โค้ดที่รองรับสถาปัตยกรรมของแขกจะสร้างการดำเนินการ Qemu ภายในบางส่วน ซึ่ง TCG (Tiny Code Generator) จะกลายเป็นโค้ดเครื่องสำหรับสถาปัตยกรรมโฮสต์อยู่แล้ว ตามที่ระบุไว้ในไฟล์ readme ที่อยู่ในไดเร็กทอรี tcg เดิมทีนี่เป็นส่วนหนึ่งของคอมไพเลอร์ C ทั่วไป ซึ่งต่อมาถูกดัดแปลงสำหรับ JIT ตัวอย่างเช่น สถาปัตยกรรมเป้าหมายในแง่ของเอกสารนี้จึงไม่ใช่สถาปัตยกรรมแบบแขกอีกต่อไป แต่เป็นสถาปัตยกรรมโฮสต์ เมื่อถึงจุดหนึ่งองค์ประกอบอื่นก็ปรากฏขึ้น - Tiny Code Interpreter (TCI) ซึ่งควรรันโค้ด (เกือบจะเป็นการดำเนินการภายในเดียวกัน) ในกรณีที่ไม่มีตัวสร้างโค้ดสำหรับสถาปัตยกรรมโฮสต์เฉพาะ ตามที่ระบุไว้ในเอกสารประกอบ ล่ามนี้อาจทำงานได้ไม่ดีเท่ากับตัวสร้างโค้ด JIT เสมอไป ไม่เพียงแต่ในเชิงปริมาณในแง่ของความเร็วเท่านั้น แต่ยังรวมถึงในเชิงคุณภาพด้วย แม้ว่าฉันจะไม่แน่ใจว่าคำอธิบายของเขามีความเกี่ยวข้องอย่างสมบูรณ์ก็ตาม
ตอนแรกฉันพยายามสร้างแบ็กเอนด์ TCG ที่มีคุณสมบัติครบถ้วน แต่เกิดความสับสนอย่างรวดเร็วในซอร์สโค้ดและคำอธิบายคำสั่งไบต์โค้ดที่ไม่ชัดเจนทั้งหมด ดังนั้นฉันจึงตัดสินใจรวมล่าม TCI สิ่งนี้ให้ข้อดีหลายประการ:
- เมื่อใช้งานตัวสร้างโค้ดคุณไม่สามารถดูคำอธิบายของคำแนะนำได้ แต่ดูที่โค้ดล่าม
- คุณสามารถสร้างฟังก์ชันได้ไม่ใช่สำหรับบล็อกการแปลทุกบล็อกที่พบ แต่หลังจากการดำเนินการครั้งที่ร้อยเท่านั้น
- หากโค้ดที่สร้างขึ้นมีการเปลี่ยนแปลง (และดูเหมือนว่าจะเป็นไปได้ โดยตัดสินโดยฟังก์ชันที่มีชื่อที่มีคำว่า patch) ฉันจะต้องทำให้โค้ด JS ที่สร้างขึ้นเป็นโมฆะ แต่อย่างน้อยฉันก็จะมีบางอย่างที่จะสร้างใหม่
สำหรับประเด็นที่สาม ฉันไม่แน่ใจว่าจะสามารถทำการแพตช์ได้หลังจากที่โค้ดถูกดำเนินการเป็นครั้งแรก แต่สองจุดแรกก็เพียงพอแล้ว
เริ่มแรก รหัสถูกสร้างขึ้นในรูปแบบของสวิตช์ขนาดใหญ่ตามที่อยู่ของคำสั่ง bytecode ดั้งเดิม แต่จากนั้น เมื่อนึกถึงบทความเกี่ยวกับ Emscripten การเพิ่มประสิทธิภาพของ JS ที่สร้างขึ้น และการวนซ้ำ ฉันจึงตัดสินใจสร้างโค้ดของมนุษย์มากขึ้น โดยเฉพาะอย่างยิ่งเมื่อสังเกตจากเชิงประจักษ์แล้ว ปรากฎว่าจุดเริ่มต้นเพียงจุดเดียวในบล็อกการแปลคือการเริ่มต้น ไม่ช้ากว่าจะพูดเสร็จ หลังจากนั้นไม่นานเราก็มีตัวสร้างโค้ดที่สร้างโค้ดด้วย ifs (แม้ว่าจะไม่มีลูปก็ตาม) แต่โชคร้ายที่มันพัง โดยแจ้งว่าคำแนะนำมีความยาวไม่ถูกต้อง นอกจากนี้ คำสั่งสุดท้ายในระดับการเรียกซ้ำนี้คือ brcond
. โอเค ฉันจะเพิ่มการตรวจสอบที่เหมือนกันให้กับการสร้างคำสั่งนี้ก่อนและหลังการเรียกซ้ำ และ... ไม่มีการดำเนินการใดเลย แต่หลังจากสวิตช์ยืนยัน คำสั่งเหล่านั้นยังคงล้มเหลว ในท้ายที่สุด หลังจากศึกษาโค้ดที่สร้างขึ้น ฉันพบว่าหลังจากสวิตช์ ตัวชี้ไปยังคำสั่งปัจจุบันจะถูกโหลดใหม่จากสแต็ก และอาจถูกเขียนทับด้วยโค้ด JavaScript ที่สร้างขึ้น และมันก็ปรากฏออกมา การเพิ่มบัฟเฟอร์จากหนึ่งเมกะไบต์เป็นสิบไม่ได้นำไปสู่สิ่งใด และเห็นได้ชัดว่าตัวสร้างโค้ดกำลังทำงานเป็นวงกลม เราต้องตรวจสอบว่าเราไม่ได้เกินขอบเขตของวัณโรคในปัจจุบัน และหากทำได้ ให้ออกที่อยู่ของวัณโรคถัดไปด้วยเครื่องหมายลบ เพื่อที่เราจะได้ดำเนินการดำเนินการต่อไปได้ นอกจากนี้ยังช่วยแก้ปัญหา "ฟังก์ชันที่สร้างขึ้นใดที่ควรใช้งานไม่ได้หากโค้ดไบต์นี้มีการเปลี่ยนแปลง" — เฉพาะฟังก์ชันที่สอดคล้องกับบล็อกการแปลนี้เท่านั้นที่ต้องใช้งานไม่ได้ อย่างไรก็ตามแม้ว่าฉันจะแก้ไขทุกอย่างใน Chromium (เนื่องจากฉันใช้ Firefox และง่ายกว่าสำหรับฉันที่จะใช้เบราว์เซอร์แยกต่างหากสำหรับการทดลอง) Firefox ช่วยฉันแก้ไขความไม่เข้ากันกับมาตรฐาน asm.js หลังจากนั้นโค้ดก็เริ่มทำงานเร็วขึ้นใน โครเมียม.
ตัวอย่างโค้ดที่สร้างขึ้น
Compiling 0x15b46d0:
CompiledTB[0x015b46d0] = function(stdlib, ffi, heap) {
"use asm";
var HEAP8 = new stdlib.Int8Array(heap);
var HEAP16 = new stdlib.Int16Array(heap);
var HEAP32 = new stdlib.Int32Array(heap);
var HEAPU8 = new stdlib.Uint8Array(heap);
var HEAPU16 = new stdlib.Uint16Array(heap);
var HEAPU32 = new stdlib.Uint32Array(heap);
var dynCall_iiiiiiiiiii = ffi.dynCall_iiiiiiiiiii;
var getTempRet0 = ffi.getTempRet0;
var badAlignment = ffi.badAlignment;
var _i64Add = ffi._i64Add;
var _i64Subtract = ffi._i64Subtract;
var Math_imul = ffi.Math_imul;
var _mul_unsigned_long_long = ffi._mul_unsigned_long_long;
var execute_if_compiled = ffi.execute_if_compiled;
var getThrew = ffi.getThrew;
var abort = ffi.abort;
var qemu_ld_ub = ffi.qemu_ld_ub;
var qemu_ld_leuw = ffi.qemu_ld_leuw;
var qemu_ld_leul = ffi.qemu_ld_leul;
var qemu_ld_beuw = ffi.qemu_ld_beuw;
var qemu_ld_beul = ffi.qemu_ld_beul;
var qemu_ld_beq = ffi.qemu_ld_beq;
var qemu_ld_leq = ffi.qemu_ld_leq;
var qemu_st_b = ffi.qemu_st_b;
var qemu_st_lew = ffi.qemu_st_lew;
var qemu_st_lel = ffi.qemu_st_lel;
var qemu_st_bew = ffi.qemu_st_bew;
var qemu_st_bel = ffi.qemu_st_bel;
var qemu_st_leq = ffi.qemu_st_leq;
var qemu_st_beq = ffi.qemu_st_beq;
function tb_fun(tb_ptr, env, sp_value, depth) {
tb_ptr = tb_ptr|0;
env = env|0;
sp_value = sp_value|0;
depth = depth|0;
var u0 = 0, u1 = 0, u2 = 0, u3 = 0, result = 0;
var r0 = 0, r1 = 0, r2 = 0, r3 = 0, r4 = 0, r5 = 0, r6 = 0, r7 = 0, r8 = 0, r9 = 0;
var r10 = 0, r11 = 0, r12 = 0, r13 = 0, r14 = 0, r15 = 0, r16 = 0, r17 = 0, r18 = 0, r19 = 0;
var r20 = 0, r21 = 0, r22 = 0, r23 = 0, r24 = 0, r25 = 0, r26 = 0, r27 = 0, r28 = 0, r29 = 0;
var r30 = 0, r31 = 0, r41 = 0, r42 = 0, r43 = 0, r44 = 0;
r14 = env|0;
r15 = sp_value|0;
START: do {
r0 = HEAPU32[((r14 + (-4))|0) >> 2] | 0;
r42 = 0;
result = ((r0|0) != (r42|0))|0;
HEAPU32[1445307] = r0;
HEAPU32[1445321] = r14;
if(result|0) {
HEAPU32[1445322] = r15;
return 0x0345bf93|0;
}
r0 = HEAPU32[((r14 + (16))|0) >> 2] | 0;
r42 = 8;
r0 = ((r0|0) - (r42|0))|0;
HEAPU32[(r14 + (16)) >> 2] = r0;
r1 = 8;
HEAPU32[(r14 + (44)) >> 2] = r1;
r1 = r0|0;
HEAPU32[(r14 + (40)) >> 2] = r1;
r42 = 4;
r0 = ((r0|0) + (r42|0))|0;
r2 = HEAPU32[((r14 + (24))|0) >> 2] | 0;
HEAPU32[1445307] = r0;
HEAPU32[1445308] = r1;
HEAPU32[1445309] = r2;
HEAPU32[1445321] = r14;
HEAPU32[1445322] = r15;
qemu_st_lel(env|0, r0|0, r2|0, 34, 22759218);
if(getThrew() | 0) abort();
r0 = 3241038392;
HEAPU32[1445307] = r0;
r0 = qemu_ld_leul(env|0, r0|0, 34, 22759233)|0;
if(getThrew() | 0) abort();
HEAPU32[(r14 + (24)) >> 2] = r0;
r1 = HEAPU32[((r14 + (12))|0) >> 2] | 0;
r2 = HEAPU32[((r14 + (40))|0) >> 2] | 0;
HEAPU32[1445307] = r0;
HEAPU32[1445308] = r1;
HEAPU32[1445309] = r2;
qemu_st_lel(env|0, r2|0, r1|0, 34, 22759265);
if(getThrew() | 0) abort();
r0 = HEAPU32[((r14 + (24))|0) >> 2] | 0;
HEAPU32[(r14 + (40)) >> 2] = r0;
r1 = 24;
HEAPU32[(r14 + (52)) >> 2] = r1;
r42 = 0;
result = ((r0|0) == (r42|0))|0;
if(result|0) {
HEAPU32[1445307] = r0;
HEAPU32[1445308] = r1;
}
HEAPU32[1445307] = r0;
HEAPU32[1445308] = r1;
return execute_if_compiled(22759392|0, env|0, sp_value|0, depth|0) | 0;
return execute_if_compiled(23164080|0, env|0, sp_value|0, depth|0) | 0;
break;
} while(1); abort(); return 0|0;
}
return {tb_fun: tb_fun};
}(window, CompilerFFI, Module.buffer)["tb_fun"]
ข้อสรุป
งานยังไม่เสร็จแต่เหนื่อยกับการแอบนำการก่อสร้างระยะยาวนี้มาสมบูรณ์แบบ ดังนั้นฉันจึงตัดสินใจเผยแพร่สิ่งที่ฉันมีตอนนี้ โค้ดอาจดูน่ากลัวเล็กน้อยในบางจุด เนื่องจากนี่คือการทดลอง และไม่มีความชัดเจนล่วงหน้าว่าต้องทำอะไร อาจเป็นไปได้ว่าคุ้มค่าที่จะออกคอมมิตอะตอมมิกแบบปกตินอกเหนือจาก Qemu เวอร์ชันที่ทันสมัยกว่า ในระหว่างนี้ มีเธรดในรูปแบบบล็อกใน Gita: สำหรับแต่ละ "ระดับ" ที่ผ่านไปแล้วอย่างน้อยก็มีการเพิ่มคำอธิบายโดยละเอียดในภาษารัสเซีย ที่จริงแล้วบทความนี้เป็นการบอกเล่าข้อสรุปในระดับสูง git log
.
คุณสามารถลองได้ทั้งหมด
สิ่งที่กำลังทำงานอยู่:
- โปรเซสเซอร์เสมือน x86 ทำงานอยู่
- มีต้นแบบการทำงานของตัวสร้างโค้ด JIT ตั้งแต่โค้ดเครื่องไปจนถึง JavaScript
- มีเทมเพลตสำหรับประกอบสถาปัตยกรรมแขก 32 บิตอื่นๆ: ตอนนี้คุณสามารถชื่นชม Linux สำหรับสถาปัตยกรรม MIPS ที่ค้างในเบราว์เซอร์ที่ขั้นตอนการโหลด
คุณทำอะไรได้อีก
- เร่งความเร็วการจำลอง แม้ในโหมด JIT ดูเหมือนว่าจะทำงานช้ากว่า Virtual x86 (แต่อาจมี Qemu ทั้งหมดที่มีฮาร์ดแวร์และสถาปัตยกรรมจำลองจำนวนมาก)
- เพื่อสร้างอินเทอร์เฟซแบบปกติ พูดตามตรงว่าฉันไม่ใช่นักพัฒนาเว็บที่ดี ดังนั้นตอนนี้ฉันจึงสร้าง Emscripten Shell มาตรฐานใหม่อย่างดีที่สุดเท่าที่จะทำได้
- ลองเปิดใช้ฟังก์ชัน Qemu ที่ซับซ้อนมากขึ้น เช่น ระบบเครือข่าย การโยกย้าย VM ฯลฯ
- ยูพีเอส: คุณจะต้องส่งการพัฒนาและรายงานข้อบกพร่องบางส่วนของคุณไปยัง Emscripten อัพสตรีม เช่นเดียวกับที่ผู้ขนย้าย Qemu และโครงการอื่น ๆ ก่อนหน้านี้ทำ ขอขอบคุณพวกเขาที่สามารถใช้การมีส่วนร่วมกับ Emscripten โดยปริยายเป็นส่วนหนึ่งของงานของฉัน
ที่มา: will.com