ฉันเกลียดคอมไพเลอร์
(xeiaso.net)- Anubis กำลังออกแบบให้การขยาย proof-of-work สำหรับปกป้องเว็บไซต์ออกไปนอกเหนือจาก SHA-256 โดยให้ไคลเอนต์และเซิร์ฟเวอร์รัน ตรรกะการตรวจสอบ WebAssembly ชุดเดียวกัน
- เพื่อไม่ตัดสภาพแวดล้อมที่ปิด WebAssembly ออกไป จึงเตรียม เส้นทางรีคอมไพล์เป็น JavaScript ไว้ แต่จะช้ากว่า WebAssembly และถ้า JIT ถูกปิดไปด้วยก็อาจช้าลงอีก
wasm2jsในดิสโทร Linux นั้นเก่า ทำให้ผลลัพธ์ต่างจากเวอร์ชัน Homebrew จึงต้องบันเดิลwasm2jsที่บิลด์ด้วย wasi-sdk เพื่อให้ได้บิลด์ที่ทำซ้ำได้- การบิลด์ C/C++ อาจทำให้ เอาต์พุตระดับไบต์ แกว่งได้แม้ใช้อินพุตเดียวกัน จาก
__DATE__,__TIME__,wasm-optใน$PATHและลำดับพอยน์เตอร์ของโค้ดจัดการข้อยกเว้น - การติดตั้งใช้งานสุดท้ายใช้
--no-wasm-opt,setarch --addr-no-randomize, การตรวจสอบ SHA-256 แยกตาม x86_64·arm64 และการยืนยันรีบิลด์ใน CI เพื่อให้ได้ ความเป็นกำหนดแน่นอนภายในสถาปัตยกรรมเดียวกัน
proof-of-work แบบ WebAssembly ของ Anubis และเส้นทางสำรองด้วย JavaScript
- Anubis ต้องการเพิ่ม การตรวจสอบ proof-of-work บนพื้นฐาน WebAssembly เพื่อให้ผู้ดูแลระบบใช้วิธี proof-of-work ที่ไม่ใช่ SHA-256 ในการปกป้องเว็บไซต์ได้
- เป้าหมายหลักคือไม่ต้องแยกอิมพลีเมนต์ตรรกะการตรวจสอบสำหรับไคลเอนต์และเซิร์ฟเวอร์ แต่ นิยามไว้เพียงที่เดียว
- ไคลเอนต์และเซิร์ฟเวอร์เชื่อมต่อกับ WebAssembly ชุดเดียวกันเพื่อรันตรรกะการตรวจสอบ
- มุ่งไปสู่โครงสร้างที่ตรวจสอบได้ว่าทั้งสองฝั่งทำงานแบบ lockstep กัน
- ไคลเอนต์ที่ปิด WebAssembly ก็ยังอยู่ในขอบเขตที่ต้องรองรับ
- มีข้อจำกัดว่าไม่ต้องการกีดกันผู้ใช้ออกจากเว็บไซต์โดยพฤตินัย
- Anubis ต้องหาสมดุลระหว่างประสบการณ์ผู้ใช้ ประสบการณ์ผู้ดูแลระบบ และประสบการณ์นักพัฒนา
- ทางอ้อมที่เลือกคือการคอมไพล์ WebAssembly กลับเป็น JavaScript อีกครั้ง
- ได้แรงบันดาลใจจาก The Birth and Death of JavaScript
- JavaScript ที่ได้จะช้ากว่า WebAssembly ที่เทียบเท่ากัน
- การปิด WebAssembly บางครั้งทำให้ JavaScript JIT ถูกปิดตามไปด้วย จึงอาจช้าลงอีก
- ยังต้องมีการศึกษาต่อว่าในฮาร์ดแวร์สเปกต่ำ วิธีนี้จะมีประสิทธิภาพกว่าการใช้ JavaScript แบบเดิมหรือไม่
ทำไมจึงต้องบันเดิล wasm2js
- เครื่องมือที่ต้องใช้คือ
wasm2jsจากโปรเจกต์ binaryen wasm2jsมีแพ็กเกจอยู่ในดิสโทร Linux แต่เวอร์ชันของดิสโทรเก่าเกินไป จึงไม่สามารถสร้างผลลัพธ์แบบเดียวกับเวอร์ชัน Homebrew ในสภาพแวดล้อมพัฒนาได้- สำหรับบิลด์ที่ทำซ้ำได้ ความเป็น กำหนดแน่นอน ของผลลัพธ์เป็นสิ่งจำเป็น
- หากต้องการให้ผู้ใช้และผู้แพ็กเกจเชื่อถือไบนารี
wasm2jsที่คอมมิตไว้ในรีโพของ Anubis ก็ต้องสามารถบิลด์เวอร์ชันเดียวกันเองแล้วได้ไบต์เดียวกัน - หากเป็นไปได้ ก็ควรได้ไบต์เดียวกันบนเครื่องของคนอื่นด้วย
- หากต้องการให้ผู้ใช้และผู้แพ็กเกจเชื่อถือไบนารี
- เพื่อสิ่งนี้จึงรวมสำเนา
wasm2jsที่บิลด์ด้วย wasi-sdk สำหรับเป้าหมาย WebAssembly เอาไว้
จุดที่ความสามารถในการทำซ้ำพังได้ง่ายในการบิลด์ C/C++
- ต่อให้ใส่ซอร์สไบต์เดิมและอินพุตเดิม เอาต์พุตของคอมไพเลอร์ก็ไม่ได้ออกมาเป็นไบต์เดิมเสมอไป
- ใน C/C++ เพียงใช้แมโครในตัวอย่าง
__DATE__และ__TIME__ก็ทำให้เกิด เอาต์พุตที่ไม่เป็นกำหนดแน่นอน ได้- ตัวอย่าง
hello.cppถูกเขียนให้พิมพ์วันที่และเวลาของจังหวะที่บิลด์ - บิลด์หนึ่งพิมพ์
Jun 18 2026 00:00:59และอีกบิลด์พิมพ์Jun 18 2026 00:01:11 - ซอร์สโค้ดเป็นไบต์เดียวกัน แต่เอาต์พุตของคอมไพเลอร์ต่างกัน
- ตัวอย่าง
- คอมไพเลอร์ขนาดเล็กมากอาจเป็นไปได้ในทางทฤษฎีว่าจะกำหนดแน่นอนได้ แต่ในคอมไพเลอร์จริงมีตัวแปรที่ซับซ้อนกว่านั้นมาก
ปัญหาที่ Clang เรียก wasm-opt จาก $PATH แบบเงียบ ๆ
- ใน binaryen นอกจาก
wasm2jsแล้ว ยังมีwasm-optสำหรับปรับแต่งเอาต์พุตจากคอมไพเลอร์ WebAssembly ให้เหมาะขึ้น - Clang จะ shell out ไปเรียก
wasm-optระหว่างการบิลด์- โดยทั่วไปถือเป็นพฤติกรรมที่สมเหตุสมผลเพื่อปรับปรุงประสิทธิภาพ
- แต่ครั้งนี้ความต่างของเวอร์ชัน
wasm-optที่อยู่ใน$PATHทำลายความสามารถในการทำซ้ำ
wasm-optบน DGX Spark เป็น version 108 ที่/usr/bin/wasm-optส่วนwasm-optจาก Homebrew บนเวิร์กสเตชันเป็น version 130- wasi-sdk และ binaryen พึ่งพา WebAssembly Exceptions extension
- ตาม Can I use ผู้ใช้เบราว์เซอร์ 93.86% ใช้เอนจินเบราว์เซอร์ที่รองรับสิ่งนี้
- C++ เป็นภาษาที่ใช้ข้อยกเว้นมาก ดังนั้นการจัดการข้อยกเว้นแบบเนทีฟของ WebAssembly จึงช่วยลด boilerplate ได้
- ทั้ง wasmtime และ wazero ต้องเปิดการรองรับข้อยกเว้นอย่างชัดเจน
- ใน wasmtime สามารถส่ง
-W exceptions=yได้ - ใน wazero ต้องมี runner harness แบบกำหนดเอง
- ใน wasmtime สามารถส่ง
wasm-optเวอร์ชันเก่าบนเครื่อง arm จะหยุดทำงานเมื่อเจอคำสั่งจัดการข้อยกเว้น ทำให้บิลด์ล้มเหลว- จึงส่ง
--no-wasm-optในขั้นตอนลิงก์เพื่อตัด เส้นทางที่ไม่ทำซ้ำได้นี้ ออกไป
การจัดวางแอดเดรสส่งผลต่อการสร้างโค้ดจัดการข้อยกเว้นอย่างไร
- Clang เวอร์ชันที่ใช้อยู่แสดงพฤติกรรมสร้างโค้ดที่ไวต่อแอดเดรสในเส้นทางจัดการข้อยกเว้นระหว่างคอมไพล์
wasm2js - ค่าพอยน์เตอร์ดิบมีผลต่อลำดับเอาต์พุตของบางบล็อก
try_table- ทุกครั้งที่บิลด์จะเกิดความต่างประมาณ 29 ไบต์
- การคำนวณแทบเหมือนเดิม แต่ลำดับไบต์เปลี่ยนไป และการอ้างอิง catch ก็เปลี่ยนตาม
- แม้จะบิลด์
wasm2jsเวอร์ชันตรึงเดียวกันบนเครื่อง arm64 ลำดับการวนพอยน์เตอร์ก็ยังต่างจากเวิร์กสเตชัน ทำให้เกิดปัญหาเดียวกัน - มีวิธีหลบเลี่ยงอยู่สองอย่าง
- ปิด การสุ่มพื้นที่แอดเดรส สำหรับบิลด์นั้นด้วย
setarch --addr-no-randomize - สร้างเช็กซัม SHA-256 known-good แยกสำหรับ x86_64 และ arm64 บนเครื่องที่เชื่อถือได้
- ปิด การสุ่มพื้นที่แอดเดรส สำหรับบิลด์นั้นด้วย
- CI จะรัน
./build.shใน./utils/wasm/wasm2jsแล้วตรวจสอบเช็กซัม- ถ้าตรงกับ
shasums.x86_64ก็ถือว่าผ่านเช็กซัม x86_64 - ถ้าตรงกับ
shasums.arm64ก็ถือว่าผ่านเช็กซัม arm64 - ถ้าไม่ตรงทั้งคู่ จะพิมพ์ SHA-256 ของ
wasm-opt_130.wasmและwasm2js_130.wasmแล้วล้มเหลว
- ถ้าตรงกับ
- งาน CI นี้รันทั้งบนโฮสต์ x86_64 และ arm64
- ความสามารถในการทำซ้ำข้ามโฮสต์ทั้งหมดยังทำไม่ได้ และปัญหานี้ยังคงเป็นบั๊กฝั่ง upstream ของ LLVM
- อย่างน้อยในสถานะปัจจุบัน บิลด์ก็ทำงานแบบกำหนดแน่นอนได้ ภายในสถาปัตยกรรมเดียวกัน
1 ความคิดเห็น
ความคิดเห็นจาก Lobste.rs
เพิ่งรู้เป็นครั้งแรกว่า
clangแอบเรียกwasm-optจาก$PATHและรู้สึกว่ามันไร้เหตุผลมากเลยตรวจดูว่ามันส่งผลกับ
zig ccด้วยไหม และโชคดีที่มันจะถูกรันเฉพาะตอนใช้clangเป็น linker driver เท่านั้น จึงไม่เข้าข่ายนี้ถ้า
clangจัดลำดับโดยอาศัยการจัดวางแอดเดรส ส่วนตัวผมมองว่านี่เป็น บั๊ก และถ้ายังทำซ้ำได้ในรีลีสล่าสุดก็น่าจะรายงานแบบนั้นความพยายามในการกำจัดปัญหาแบบนี้ดำเนินมาหลายปีแล้ว
clang.exeบน Windows เป็น cross-compiler แบบเสถียร จะยิ่งน่าหงุดหงิดเข้าไปอีกclang มีสมมติฐานราว 500 อย่างว่ามันจะถูก build สำหรับระบบ native
ไม่ได้จะวิจารณ์นะ เพราะเคารพที่เป็นโอเพนซอร์สและเจ้าของโพสต์ให้บริการยอดนิยมฟรี
ถึงอย่างนั้นก็เกลียดที่เว็บกำลังเปลี่ยนไปในทางนี้จริง ๆ เดี๋ยวนี้พอเข้าเว็บก็มักจะเจอ หน้าโหลดของ Anubis โผล่ขึ้นมา ไม่แน่ใจเลยว่าเราอยากได้เว็บที่แต่ละเว็บยอดนิยมต้องมีหน้าจอ proof-of-work เด้งขึ้นมาหรือเปล่า
ก็ไม่รู้เหมือนกันว่ามีทางเลือกอะไรบ้างเมื่อ AI crawler ถาโถมเข้ามาไม่หยุด แต่ก็ยังสงสัยว่ามีหลักฐานไหมว่า proof-of-work ขัดขวาง AI crawler ได้จริง เพราะพวกนี้มีเงินทุนมหาศาล และยังใช้พลังประมวลผลเพื่ออ่านหน้าเว็บมากกว่านี้อยู่แล้ว ดังนั้นต้นทุนในการแก้ proof-of-work ดูเล็กน้อยมาก
ในโครงการนำร่อง Anubis มันเป็นตัวยับยั้งทราฟฟิกไม่พึงประสงค์ที่มีประสิทธิภาพชัดเจน และด้วยกฎที่แทบจะเป็นค่าพื้นฐานก็ยังบล็อกคำขอของแอปพลิเคชันได้ราว 90% อย่างต่อเนื่อง โดย DDR อยู่ที่ 71.0%, ArcLight 94.6% และ Catalog 92.4%
วันที่ 30 พฤษภาคม ทราฟฟิกบอตพุ่งสูงขึ้น ทำให้ Catalog แทบใช้งานไม่ได้จนกว่าจะติดตั้ง Anubis ในวันที่ 3 มิถุนายน และที่จุดพีควันที่ 1 มิถุนายน มีคำขอ HTTP 3.4 ล้านครั้งจาก IP ไม่ซ้ำกัน 2.1 ล้านรายการ พร้อมเวลาโหลดหน้าเกิน 70 วินาที หลังติดตั้ง Anubis เมื่อวันที่ 4 มิถุนายน ก็กลับมาให้บริการผู้ใช้ได้อีกครั้ง โดยจำนวนคำขอทั้งหมดที่แอปพลิเคชันประมวลผลอยู่ที่ 125,000 ครั้ง และเวลาโหลดหน้าดีขึ้นเป็น 2.12 วินาที
https://lobste.rs/s/ncyfcp/anubis_pilot_project_report_june_2025
อีกกรณีหนึ่งก็ระบุว่าปัญหาหมดไปทันทีหลัง deploy Anubis และเห็นจังหวะนั้นได้ชัดเจนในระบบมอนิเตอร์ หลังจากนั้นก็ไม่มีการแจ้งเตือนอีกเลย แม้ว่าการโจมตียังคงดำเนินต่อไป แต่ภาระบนเซิร์ฟเวอร์อยู่ในระดับต่ำสุด จึงมองว่า Anubis ไม่ได้แค่บล็อก AI scraper แต่ยังทำหน้าที่เป็น การป้องกัน DDoS ด้วย
https://lobste.rs/s/67ijih/day_anubis_saved_our_websites_from_ddos
https://orib.dev/tmp/bandwidth.png
บางคนกันพวกมันออกได้ด้วยแค่แท็ก
meta refreshหรือปุ่มที่ต้องกด ดังนั้น Anubis จึงได้ผล แต่หัวใจสำคัญไม่ใช่ proof-of-work เอง หากเป็นพฤติกรรมที่คาดไม่ถึงต่างหากมันกำลังทรมานยิ่งกว่าสมัยที่ใช้เว็บด้วยเบราว์เซอร์ที่ปิด JavaScript ไว้อีก เว็บควรเป็นสื่อที่เน้นเอกสารธรรมดา แต่ตอนนี้ไม่ว่าที่ไหนก็ต้องผ่านด่าน Cloudflare, Anubis หรือ captcha
เนื้อหาคือบอตจะหาวิธีหลบเลี่ยง WAF ได้เสมอ และผู้ใช้จริงก็ลงเอยด้วยการเสีย CPU cycle ไปกับหน้าจอโหลด
น่าเสียดาย แต่ก็ไม่ได้น่าแปลกใจ ประวัติของ toolchain สำหรับคอมไพเลอร์นั้นพึ่งพาความสัมพันธ์แฝงไร้สาระในทำนองว่า “บริบทในเครื่องต้องถูกต้องอยู่แล้ว” มายาวนานมาก
เพียงแต่ LLVM เองกลับเป็นฝ่ายที่ผลักดันการลบ dependency แบบนี้มาโดยตลอด จึงรู้สึกแปลกที่มาเห็นสิ่งนี้ใน clang ผลที่ตามมาคือ ตัวอย่างเช่น Rust compiler จึงทำงานได้โดยแทบไม่ต้องมีแนวคิดเรื่อง cross-compiler แบบแยกต่างหาก
ถ้าลอง bootstrap ระบบปฏิบัติการโดยไม่พึ่ง build tool เดิม ๆ จะเห็นชัดทันที การสร้างเคอร์เนล สร้าง libc และคอมไพเลอร์สำหรับเคอร์เนลนั้นให้รันได้ แล้วค่อย build ทุกอย่างใหม่ทั้งหมดบน OS ใหม่นั้น เป็นงานที่ซับซ้อนและเปราะบางอย่างเหลือเชื่อ เต็มไปด้วยสมมติฐานที่ไม่ได้พูดออกมาตรง ๆ
มันเป็นปัญหาที่พบไม่บ่อยและส่วนใหญ่อยู่ในโลกของนักพัฒนา OS กับคอมไพเลอร์ จึงแทบไม่มีทั้งเครื่องมือดี ๆ และแนวปฏิบัติที่เป็นแบบอย่าง และสำหรับคู่ผสมคอมไพเลอร์+OS แต่ละแบบ ก็น่าจะมีคนบนโลกที่เข้าใจภาพรวมทั้งหมดจริง ๆ แค่ประมาณ 5 คน
และก็คิดว่า toolchain ของ Zig ได้ความสามารถบางส่วนแบบนั้นมาจาก LLVM ด้วย แน่นอนว่าเข้าใจว่าพวกเขาทำงานหนักมากเพื่อแยกส่วนต่าง ๆ ให้สะอาดขึ้น เลยสงสัยเหมือนกันว่าตอนนี้ยังใช้ LLVM อยู่หรือเปล่า
แต่ถ้า clang เองก็มีปัญหาเดียวกัน ก็ชวนให้คิดว่าไม่ได้รับโครงสร้างที่สะอาดกว่ามาจาก LLVM เท่าไรนัก
เข้าใจว่าใช้ Nix อยู่ เลยสงสัยว่าทำไมถึงไม่พูดถึงหรือใช้ Nix เพื่อลดความผันผวนของสภาพแวดล้อมลงบ้าง
อย่างเช่นปัญหา
wasm-optใน$PATHก็ดูเหมือนจะบรรเทาได้ด้วย Nix หรือใช้ไปแล้วแต่ผมอ่านตกไป?ตอนแรกคิดใส ๆ ว่าการย้าย wasm ไปเป็น asm.js น่าจะ “ง่าย” แต่วันนี้ได้รู้อะไรใหม่เลย
ชื่อบล็อกดูเหมือน คลิกเบต แต่เนื้อหาดี
ผมเกลียดคลิกเบตจริง ๆ