แพตช์ GCC เพื่อสร้าง Actually Portable Executables
(ahgamut.github.io)- เพื่อให้สามารถคอมไพล์ซอฟต์แวร์ C เดิมได้มากขึ้นด้วย Cosmopolitan Libc จึงต้องทำมากกว่าการหลอกระบบบิลด์ โดยต้องใช้ แพตช์ GCC ขนาดราว 2,000 บรรทัด เพื่อให้คอมไพเลอร์แปลงโค้ดที่มีปัญหาโดยอัตโนมัติ
- อุปสรรคคือ ค่าระดับระบบ อย่าง
SIGTERM,EINVALไม่ใช่ค่าคงที่ตอนคอมไพล์ ทำให้ชนกับกฎของ C สำหรับป้ายswitch caseและการกำหนดค่าเริ่มต้นของstatic/const struct - มีการลองใช้ทั้ง
sed, regex ของ Python, ปลั๊กอิน GCC, ไปจนถึงการดักแมโครของพรีโปรเซสเซอร์ แต่เพราะข้อผิดพลาดของป้ายcaseเกิดขึ้นระหว่างการพาร์ส จึงยากที่จะกันไว้ได้ด้วยปลั๊กอินเพียงอย่างเดียว - แพตช์สุดท้ายเปิดใช้ด้วยแฟลก
-fportcosmoโดย GCC จะเปลี่ยนswitchเป็นif/gotoก่อนจะรายงานข้อผิดพลาด และเสริมการกำหนดค่าเริ่มต้นของ struct ด้วยการเติมค่าขณะรันไทม์ - แม้ยังมีข้อจำกัดกับ anonymous struct,
enum,const intบางส่วน และดัชนีอาร์เรย์ภายในการกำหนดค่าเริ่มต้น แต่ก็เปิดทางให้คอมไพล์ซอฟต์แวร์อย่างbash,curl,git,ninja,gccได้ โดยไม่ต้องแก้ซอร์สโค้ด
เป้าหมายของแพตช์ GCC
- มีการเขียน แพตช์
gccราว 2,000 บรรทัด เพื่อให้สร้าง Actually Portable Executables ด้วย Cosmopolitan Libc ได้ง่ายขึ้น - หลังใช้แพตช์แล้ว สามารถใช้ระบบบิลด์
./configureหรือcmakeเพื่อคอมไพล์ซอฟต์แวร์ต่อไปนี้ได้bashcurlgitninjagccเอง
- ไฟล์ปฏิบัติการที่คอมไพล์ได้ควรสามารถรันบน Linux, FreeBSD, MacOS, OpenBSD, NetBSD และ Windows ได้
- มีข้อสังเกตว่าไม่ได้ทดสอบการรันบน Windows โดยตรง
- ไบนารีที่บิลด์ผ่าน GitHub Actions สามารถดาวน์โหลดได้จาก superconfigure releases
จุดที่ติดขัดในการพอร์ต Cosmopolitan Libc
- หลังจากที่ Cosmopolitan Libc ได้รับความสนใจจากเว็บเซิร์ฟเวอร์
redbeanในปี 2021 คำถามสำคัญก็คือซอฟต์แวร์ C เดิมจะทำงานบน libc นี้ได้ดีแค่ไหน - ระหว่างการพอร์ต language runtime อย่าง Lua, Wren, Janet และ
quickjsก็พบรูปแบบปัญหาเดิมซ้ำ ๆ - เมื่อใช้ ค่าคงที่ของระบบ อย่าง
SIGTERM,EINVALเป็นป้ายcaseในคำสั่งswitchก็จะชนกับกฎการคอมไพล์ของ C - มาตรฐาน C กำหนดให้ป้าย
caseต้องเป็น ค่าคงที่ตอนคอมไพล์- ใน Cosmopolitan Libc แมโคร C preprocessor ที่เกี่ยวกับ system interface จำเป็นต้องเป็น symbolic
- ผลคือโค้ดที่พบได้ทั่วไปอย่าง
switch(errno) { case EINVAL: ... }อาจคอมไพล์ไม่ผ่าน
เปลี่ยน switch ให้เป็น if และ goto
- หากป้ายอย่าง
case EINVAL,case ENOSYSภายในswitch(errno)ไม่ใช่ค่าคงที่ตอนคอมไพล์ GCC จะรายงานข้อผิดพลาดcase label does not reduce to an integer constant - แพตช์นี้จะแปลงคำสั่ง
switchโดยอัตโนมัติให้เป็นโครงสร้างต่อไปนี้- เพิ่มแขนงในรูป
if (errno == EINVAL) goto caselabel_EINVAL;สำหรับค่าcaseแต่ละตัว - เพิ่ม
gotoเพื่อย้ายไปยังป้ายdefault - ปรับบล็อก
caseเดิมใหม่ด้วยป้ายและgoto endofthis_switch
- เพิ่มแขนงในรูป
- วิธีนี้อาจไม่ใช่วิธีที่สวยที่สุด แต่สามารถจัดการกรณีที่พบในหลายโค้ดเบสได้
- เพราะต้องรองรับ
switchที่มีทั้งifdefและ fallthrough ปะปนกันอยู่ การแปลงแบบอิงgotoจึงเป็นตัวเลือกที่ตรงไปตรงมา
การกำหนดค่าเริ่มต้นของ struct ก็มีข้อจำกัดเดียวกัน
- ปัญหาเรื่องค่าคงที่ตอนคอมไพล์ยังเกิดกับการกำหนดค่าเริ่มต้นของ
staticหรือconst structด้วย - หาก
EINVALไม่ใช่ค่าคงที่ตอนคอมไพล์ การกำหนดค่าเริ่มต้นบางแบบต่อไปนี้จะไม่ถูกต้องตาม C- การกำหนดค่าเริ่มต้นของ
structโลคัลทั่วไปภายในฟังก์ชันยังทำได้ const structstatic structconst structระดับโกลบอลstatic structระดับโกลบอล
- การกำหนดค่าเริ่มต้นของ
- ตัวอย่างจริงคือโมดูล
faulthandlerของ CPython - วิธีแก้คือกำหนดค่าเริ่มต้นของ struct ด้วยค่าหลอกก่อน แล้วเพิ่มโค้ดให้เติมค่าที่ถูกต้องก่อนใช้งาน
- ภายในฟังก์ชันจะเพิ่มคำสั่ง
if - สำหรับการกำหนดค่าเริ่มต้นแบบโกลบอลหรือแบบสแตติก จะใช้
__attribute__((constructor))
- ภายในฟังก์ชันจะเพิ่มคำสั่ง
ข้อจำกัดของการแทนที่ข้อความและปลั๊กอิน GCC
- ตอนแรกมีความพยายามทำการแปลงอัตโนมัติด้วยสคริปต์เชลล์
sedจากนั้นจึงลองสคริปต์ Python และ regex เพื่อรองรับ fallthrough ด้วย - แต่เพราะ C preprocessor และ
ifdefการแทนที่ข้อความจึงทำงานได้ไม่สมบูรณ์- เนื่องจากยากที่จะรู้ล่วงหน้าว่าระหว่างคอมไพล์
ifdefใดจะถูกเปิดใช้งาน
- เนื่องจากยากที่จะรู้ล่วงหน้าว่าระหว่างคอมไพล์
- ความพยายามถัดมาคือใช้ AST transformation ผ่านปลั๊กอิน GCC
- ปลั๊กอิน GCC สามารถโหลดได้ด้วย
-fplugin=your-plugin.so - สามารถเข้าถึง AST ได้ในอีเวนต์อย่าง
PLUGIN_PRE_GENERICIZE,PLUGIN_FINISH_PARSE_FUNCTION,PLUGIN_FINISH_DECL - สามารถพิมพ์ AST ด้วย
debug_treeและเดิน AST ด้วยwalk_tree_without_duplicates
- ปลั๊กอิน GCC สามารถโหลดได้ด้วย
- ปัญหาคือการตรวจสอบป้าย
caseเกิดขึ้นระหว่างที่ GCC กำลังพาร์ส- ปลั๊กอินจะเข้าถึง AST ได้หลังพาร์สเสร็จแล้ว
- ป้ายที่ผิดอย่าง
case SIGTERM:จะไม่เหลืออยู่ใน AST - เพราะ GCC รายงานข้อผิดพลาดไปแล้ว จึงแก้จากฝั่งปลั๊กอินได้ยาก
การทดลองดักแมโครของพรีโปรเซสเซอร์
- เมื่อปลั๊กอินเพียงอย่างเดียวกันข้อผิดพลาดก่อนพาร์สได้ยาก จึงลองหาวิธีโต้ตอบกับ C preprocessor
- ผ่านโครงสร้าง
cpp_readerในเฮดเดอร์ของปลั๊กอิน GCC จึงสามารถผูก callback ตอนมี#define,#undefและตอนมีการใช้แมโครได้ - มีการใช้ประโยชน์จากโครงสร้างของแมโครค่าระบบใน Cosmopolitan Libc เพื่อแทรกค่าคงที่ชั่วคราว
- ตัวอย่าง:
extern const int SIGTERM; - ตัวอย่าง:
#define SIGTERM SYMBOLIC(SIGTERM) - สำหรับปลั๊กอินมีค่าชั่วคราวอย่าง
static const int __tmpcosmo_SIGTERM = ...
- ตัวอย่าง:
- ปลั๊กอินจะดักการใช้
SYMBOLIC(SIGTERM)แล้วแทนด้วยค่าชั่วคราว จากนั้นในขั้นPLUGIN_PRE_GENERICIZEจึงค่อยแปลง AST กลับเป็นVAR_DECLที่ถูกต้อง - การแฮ็กแมโครนี้ ช่วยเลี่ยงข้อผิดพลาดของ
switchและการกำหนดค่าเริ่มต้นของstructได้สำเร็จ - วิธีนี้ทำให้สามารถคอมไพล์ CPython 3.11 ได้อย่างน้อย
เหตุผลที่แก้ GCC โดยตรงแทนการใช้ปลั๊กอิน
- หลังหารือกับ Justine Tunney และยืนยันได้ว่าปัญหานี้แก้ได้ จึงมีการเสนอแนวทางที่ง่ายกว่าคือแก้โค้ดเบสของ GCC โดยตรง
- ปลั๊กอินและการแฮ็กแมโครมีงานส่วนเกินมาก และทำให้การจัดการ edge case กับการล่มซับซ้อนขึ้น
- ตัวอย่างเช่น แม้จะต้องแปลงแค่
case ENOSYSแต่เพราะการแทนที่แมโคร ปลั๊กอินกลับต้องจัดการการใช้งานปกติอย่างENOSYS + 1หรือprintf("ENOSYS = %d", ENOSYS)ด้วย - แนวทางแพตช์ GCC จะเปิดใช้ฟีเจอร์ด้วย แฟลก
-fportcosmoแทน-fpluginแยกต่างหาก - ก่อนที่ GCC จะรายงานข้อผิดพลาด
case is not constantมันจะตรวจว่ามีการเปิดflag_portcosmoหรือไม่ และถ้าเปิดไว้ก็จะทำการแทนที่ที่จำเป็น - โค้ดแปลง AST จากปลั๊กอินเดิมถูกย้ายเข้ามาให้ทำงานภายใน GCC ก่อนเรียก callback ของปลั๊กอินอื่น
- การเปลี่ยนแปลงนี้ทำให้สามารถลบโค้ดแฮ็กที่เกี่ยวกับแมโครออกได้
การบิลด์จริงและการขยายแพตช์
- ณ วันที่ 5 มิถุนายน 2023 GCC ที่ถูกแพตช์ผ่านชุดทดสอบทั้งหมดของปลั๊กอินก่อนหน้า และมีการเพิ่มไบนารีใหม่เข้าใน monorepo ของ Cosmopolitan Libc
- ระหว่างที่คอมไพล์โค้ดมากขึ้นก็มีการแก้ไขและปรับปรุงต่อเนื่อง
- การบิลด์
luaง่ายขึ้น และยังสามารถหาตำแหน่งที่g++เจอข้อผิดพลาดcase constantจนทำให้คอมไพล์ninjaได้ด้วย - ระหว่างพยายามเพิ่ม
ncursesให้กับ Python 3.11 ก็พบปัญหาใหม่- ใน
ncursesมีโค้ดอย่างDATA2(PARODD | PARENB, PARODD)ซึ่งมี นิพจน์ทวิภาค อยู่ในค่ากำหนดเริ่มต้นของ struct - แพตช์เดิมรองรับการกำหนดค่าเริ่มต้นด้วยค่าคงที่แบบง่าย แต่ยังรองรับนิพจน์อย่าง
PARODD | PARENBไม่ได้
- ใน
- หลังจากนั้นจึงขยายแพตช์ให้ป้าย
caseและสมาชิกในการกำหนดค่าเริ่มต้นของ struct ในภาษา C สามารถเป็นนิพจน์ตามอำเภอใจได้- ตามมาตรฐาน C ป้าย
caseยังคงต้องเป็นค่าคงที่ตอนคอมไพล์ - คอมไพเลอร์ที่ถูกแพตช์จะเตือนเมื่อมีการใช้งานแบบนี้ แต่ยังคงคอมไพล์ต่อ
- C++ ถูกยกเว้นจากการแทนที่นิพจน์ตามอำเภอใจ เพราะไม่สอดคล้องกับการจัดการ
constexprของg++11
- ตามมาตรฐาน C ป้าย
- หลังการเปลี่ยนแปลงนี้
ncursesก็สามารถคอมไพล์ได้โดยไม่มีปัญหา
ข้อจำกัดที่ยังเหลือและบทสรุปเชิงปฏิบัติ
- แพตช์นี้ยังไม่สมบูรณ์
- ยังจัดการ anonymous struct บางกรณีไม่ได้
- ยังจัดการ
enumและบางกรณีของconst intไม่ได้ - ยังจัดการกรณีที่ใช้
SIGTERMเป็นดัชนีอาร์เรย์ภายในการกำหนดค่าเริ่มต้นไม่ได้ - ยังอาจมีการล่มของคอมไพเลอร์เป็นครั้งคราวในกรณีอย่างการกำหนดค่าเริ่มต้น
staticที่แปลก ๆ หรือการใส่SIGTERMลงในstatic const int8_t
- ถึงอย่างนั้นก็สามารถกำจัดตัวอย่างโต้แย้งที่ชัดเจนได้จำนวนมาก และทำให้ซอฟต์แวร์ยอดนิยมหลายตัวคอมไพล์ได้อย่างราบรื่น
- สภาพแวดล้อมทดสอบที่เข้มงวดกว่านี้อาจเผยให้เห็นจุดที่ควรปรับปรุงเพิ่ม
- หากต้องการก็ยังสามารถแก้คำสั่ง
switchและนิพจน์กำหนดค่าเริ่มต้นของstructด้วยมือได้ แต่ในหลายกรณีคอมไพเลอร์สามารถจัดการให้อัตโนมัติได้ - หากซอฟต์แวร์ C สามารถคอมไพล์แบบสแตติกได้ และโดยเฉพาะอย่างยิ่งถ้าคอมไพล์ด้วย
muslได้ ก็มีโอกาสสูงว่าจะคอมไพล์ด้วย Cosmopolitan Libc ได้เช่นกัน
3 ความคิดเห็น
ตอนนี้ neo จะใช้รูปแบบที่ครอบข้อความด้วยโค้ดบล็อกจริง ๆ แล้วเหรอ? ที่ต้องเลื่อนแนวนอนค่อนข้างน่าเสียดาย แล้วก็สีข้อความโทนแดงก็แอบน่าเสียดายนิดหน่อย...
อ๊ะ การถูกครอบด้วยโค้ดบล็อกเป็นบั๊กครับ ฮือ
ความคิดเห็นบน Hacker News
ฉันเป็นคนเขียนบทความนี้เอง และชื่อควรจะเป็น “Patching GCC to build Actually Portable Executables” เพราะมันอ้างถึงฟอร์แมต Actually Portable Executable ของ Cosmopolitan Libc และ jart
ตอนนี้ด้วยแพตช์ GCC ของฉัน เราสามารถบิลด์ซอฟต์แวร์อย่าง vim, emacs, ninja, bash, git และ gcc ด้วย Cosmopolitan Libc ผ่านระบบบิลด์แบบ autotools/cmake ตามปกติได้แล้ว ไฟล์ executable ที่ได้ควรจะรันได้บน Linux, FreeBSD, MacOS, OpenBSD, NetBSD และ Windows แต่ยังไม่ได้ทดสอบบน Windows
รายชื่อซอฟต์แวร์ที่ลองบิลด์ด้วยวิธีนี้มีอยู่ที่นี่: https://github.com/ahgamut/superconfigure
สคริปต์ superconfigure เป็นแค่ตัวห่อครอบ configure script ทั่วไปที่ใช้บิลด์ซอฟต์แวร์ และใส่แฟล็กอย่าง
--enable-staticให้ถ้าอยากลองบิลด์ GCC ด้วย Cosmopolitan Libc ให้ใช้รีโพซิทอรีนี้ได้: https://github.com/ahgamut/musl-cross-make/tree/gccbuild
หวังว่าจะได้รับการปฏิบัติที่ดีกว่าบน HN
เช่น เมื่อเทียบกับเวอร์ชัน glibc อยากรู้ว่ามี การทดสอบที่เพียงพอ มากพอจะยืนยันได้ไหมว่าไม่มี regression
มี issue ที่เกี่ยวข้องเรื่องนี้อยู่ในรีโพซิทอรี Go: https://github.com/golang/go/issues/51900
โดยรวมถือว่าเจ๋งมาก แต่ผมสงสัยเรื่องพื้นฐานทางทฤษฎีของมัน ถ้าจะสร้าง target ระบบปฏิบัติการใหม่ ตามตรรกะแล้วน่าจะเหมาะกว่าที่จะนิยาม ค่าคงที่ชุดใหม่
แทนที่จะส่งค่าคงที่ของแต่ละระบบปฏิบัติการลูกต่อไปตามเดิม ก็แปลงค่าคงที่ของแต่ละระบบให้กลายเป็นค่าเดียวกันได้ทุกที่ แบบนั้นโค้ดที่ใช้ค่าคงที่เป็นดัชนีอาร์เรย์หรือใน case statement ก็จะทำงานได้ตามปกติ
วิธีนี้อาจมี overhead มากกว่าแนวทางที่อธิบายในบทความ แต่ดูง่ายกว่ามาก และสำหรับคนที่อยากคอมไพล์โปรแกรมใหม่ให้ใช้ Cosmopolitan libc ก็น่าจะใช้ง่ายกว่า
ดูรายการค่าคงที่ได้ที่นี่: https://github.com/jart/cosmopolitan/blob/master/libc/sysv/c...
ทุกครั้งที่ใช้ค่าคงที่เหล่านี้ จะต้องฝังค่าไว้จำนวนมากในไบนารีเป็นตาราง lookup ขนาดใหญ่ และทุกครั้งที่โปรแกรมต้องตรวจสอบก็ต้องวิ่งผ่านตารางนั้น อาจจะไม่ช้ามาก แต่คิดว่าน่าจะรู้สึกได้แน่
เป้าหมายคือทำให้พอร์ตได้ง่ายโดยไม่ต้องแก้ซอร์สโค้ดของ libc หรือซอฟต์แวร์ที่จะพอร์ตมากนัก และยังได้ไบนารีที่มีประสิทธิภาพใกล้เคียงหรือดีกว่าเดิม ภายใต้ข้อจำกัดพวกนั้น แพตช์ GCC นี้ดูเหมือนจะเป็นวิธีที่ดีที่สุดในการทำให้กระบวนการง่ายขึ้น
ถ้าได้เจอโค้ดเบสที่ใช้
SIGHUPสำหรับกำหนดค่าเริ่มต้นดัชนีอาร์เรย์มากพอ ฉันก็ตั้งใจจะลองแนวทางที่คุณเสนอแล้ววัดจุดแลกเปลี่ยนดู หรือคุณจะลองเองแล้วมาบอกก็ได้ว่าการมีชุดค่าคงที่แยกต่างหากดีกว่าหรือเปล่าถ้า “ใช้เวลาไปมากในการกำจัดตัวอย่างโต้แย้งที่ชัดเจน และซอฟต์แวร์ยอดนิยมจำนวนมากบิลด์ได้อย่างราบรื่น” การเผยแพร่ รายชื่อซอฟต์แวร์ที่คอมไพล์สำเร็จ ก็น่าจะช่วยได้มาก อาจมีอยู่แล้วก็ได้
สคริปต์ superconfigure เป็นแค่ตัวห่อครอบ configure script ทั่วไปที่ใช้บิลด์ซอฟต์แวร์ และใส่แฟล็กอย่าง
--enable-staticให้สงสัยว่า APE มีจุดประสงค์เชิงใช้งานจริงหรือไม่ มีที่ไหนบ้างที่แจกจ่ายไฟล์ปฏิบัติการเดียวให้รันได้บนหลายระบบปฏิบัติการจริงๆ?
ตัว lnav เองไม่ได้บิลด์เป็น APE แต่เอเจนต์ที่ฝังอยู่ข้างในบิลด์เป็น APE เมื่อผู้ใช้ต้องการอ่านล็อกจากโฮสต์นั้น ก็จะส่งเอเจนต์นั้นไปยังเครื่องปลายทางจากระยะไกล
แบบนี้ไม่ต้องมีขั้นตอนเพิ่มเพื่อแยกชนิดระบบปฏิบัติการหรือแนบไฟล์ปฏิบัติการหลายเวอร์ชัน มีบทความสั้นที่เกี่ยวข้องอยู่ที่นี่: https://lnav.org/2021/05/03/tailing-remote-files.html
โปรแกรมที่คอมไพล์แบบสแตติกเป็นรากฐานของทุกอย่างก็จริง แต่ในระดับแอปพลิเคชันถือเป็นส่วนน้อย
APE ตั้งคำถามใหญ่ไว้ว่า “ถ้าภาษาแบบ AOT ระดับล่างอย่าง C คอมไพล์ครั้งเดียวแล้วรันได้ทุกที่ แล้วใครยังต้องการ virtual machine อีก?”
นอกจากนี้ cross-compilation ก็เป็นงานชวนปวดหัว โปรเจกต์โอเพนซอร์สเล็กๆ ที่ไม่มี build farm พร้อม BSD หลายตัว มักจะไม่แจกไบนารีสำหรับแพลตฟอร์มพวกนั้นเลย
แม้แต่ kubectl ก็มีไบนารีสำหรับ Linux แต่ไม่มีสำหรับ BSD เว้นแต่ว่าจะนับ OSX เป็น BSD ดังนั้นสำหรับโปรเจกต์แบบนี้มันดูเป็นประโยชน์ที่ได้มาง่ายๆ
มันใหญ่เกินกว่าจะบิลด์สดๆ บนเครื่องผู้ใช้ เพราะมี C++ dependency ภายนอกขนาดใหญ่หลายตัว และใช้เวลาบิลด์ตั้งแต่ 2 ถึง 20 นาทีตามฮาร์ดแวร์
มันต้องรันได้บน macOS x86-64+ARM, Linux x86-64, Windows x86-64 และถ้า ARM Linux ได้ด้วยก็ดี
ตอนนี้กำลังพิจารณา WASM อยู่ แต่ก็ต้องมี WASI runtime ติดตั้งไว้
เผื่อใครยังสับสน: “Actually Portable Executables” https://justine.lol/ape.html
การเปลี่ยน
switchเป็นifดูค่อนข้างหยาบ อาจแก้ได้ด้วยการแมปไปยังชุดค่าคงที่ที่เป็นกลางต่อแพลตฟอร์ม แล้ว dereference สัญลักษณ์รันไทม์ผ่านฟังก์ชันเช่น ใช้
switch(CosmoMappErrno(EINVAL))กับcase COSMO_EINVAL:แบบนี้ก็ไม่ต้องทำให้โค้ดกลายเป็น ก้อน gotoแน่นอน ถ้าหมายถึงให้คอมไพเลอร์รู้จัก switch แบบนี้ แล้วแทนที่จะเขียนกลับเป็นต้นไม้ if ก็เปลี่ยน label เป็นค่าคงที่พิเศษฝั่ง cosmo สำหรับแต่ละค่า และห่อค่าป้อนเข้า switch ด้วยการเรียกฟังก์ชันที่แมปค่าจากแพลตฟอร์มรันไทม์ปัจจุบันไปยังค่าคงที่ฝั่ง cosmo โดยให้ค่าอื่นผ่านไปโดยไม่เปลี่ยน แบบนั้นก็อีกเรื่อง
อันที่จริงนั่นอาจเป็น การแปลงในคอมไพเลอร์ ที่ง่ายกว่าด้วยซ้ำ เพียงแต่จะมีความซับซ้อนเพิ่มขึ้นจากการต้องรู้ว่ากำลังใช้ชุดค่าคงที่ใดอยู่ และต้องใส่การเรียกแมปที่เหมาะสมกับค่าป้อนเข้า switch ฝั่งไลบรารีก็ต้องสร้างแมปพวกนี้ด้วย จึงต้องรองรับความซับซ้อนดังกล่าว
ตอนนี้มี implementation ที่ทำงานได้ด้วยวิธีแปลงแบบอื่นอยู่แล้ว จึงไม่แน่ใจว่าผู้เขียนจะอยากลงมือทำแบบนี้ไหม
สุดท้าย วิธีแมปแบบนี้ก็เสี่ยงแมปผิดได้ และยังมีความจุกจิกอย่างโค้ดที่ใช้ค่า errno ติดลบซึ่งต้องกลับเครื่องหมายของอินพุตก่อนแมป
เหตุผลที่ผมชอบอาเรย์ if-else-goto คือมันเข้ากันได้พอดีกับส่วนอื่นๆ ของ GCC ถ้าดูแพตช์ของผม[1] จะเห็นว่าการเพิ่มฟีเจอร์นี้แทบแก้โค้ดเดิมของ GCC นิดเดียว
[1]: https://github.com/ahgamut/gcc/tree/portcosmo-11.2
ไม่เข้าใจว่าทำไมถึงประหลาดใจกับโค้ดที่ใช้
SIGxxxเป็นดัชนีขนาดนั้น หมายเลข signal ถูกนิยามไว้ชัดเจน ค่อนข้างต่อเนื่อง และเป็นที่รู้กันว่าเริ่มจาก 1แถมข้อผิดพลาดนั้นยังโผล่มาพร้อมกับ compiler error ในแพตช์ของผมที่ใช้เวลาดีบักนาน เลยยิ่งจำได้ว่าแปลกใจ
เลยสงสัยว่าคุณมองว่าแพตเทิร์นโค้ดแบบนั้นพบได้บ่อยไหม แล้วมีในโค้ดเบสไหนบ้างนอกจาก busybox ถ้ามีตัวอย่างเยอะ ผมอาจจะลองใช้เวลาอัปเดตแพตช์ให้รองรับแพตเทิร์นแบบนั้น
ถ้า errno เป็นปัญหาใหญ่ขนาดนั้น ก็ไม่รู้ว่าจะยังหวังให้ C++ standard library และ std::error_code ทำงานได้อย่างไร
ตรงนั้นไม่มีทางใช้แมโครวิเศษอะไรได้เลย
std::error_codeไม่ใช่ค่าคงที่ จึงน่าจะไม่มีปัญหาแบบเดียวกัน