การเชื่อมโยง libstdc ++ แบบคงที่: gotchas ใด ๆ ?


93

ฉันต้องการปรับใช้แอปพลิเคชัน C ++ ที่สร้างขึ้นบน Ubuntu 12.10 พร้อม libstdc ++ ของ GCC 4.7 กับระบบที่ใช้ Ubuntu 10.04 ซึ่งมาพร้อมกับ libstdc ++ เวอร์ชันเก่ากว่ามาก

ขณะนี้ผมกำลังรวบรวมกับ-static-libstdc++ -static-libgccที่แนะนำโดยการโพสต์บล็อกนี้: การเชื่อมโยง libstdc ++ แบบคงที่ ผู้เขียนเตือนไม่ให้ใช้โค้ด C ++ ที่โหลดแบบไดนามิกเมื่อคอมไพล์ libstdc ++ แบบคงที่ซึ่งเป็นสิ่งที่ฉันยังไม่ได้ตรวจสอบ ถึงกระนั้นทุกอย่างดูเหมือนจะราบรื่นจนถึงตอนนี้: ฉันสามารถใช้ประโยชน์จากคุณสมบัติ C ++ 11 บน Ubuntu 10.04 ซึ่งเป็นสิ่งที่ฉันเป็นหลังจากนั้น

ฉันทราบว่าบทความนี้มาจากปี 2548 และอาจมีการเปลี่ยนแปลงมากมายตั้งแต่นั้นมา คำแนะนำยังคงเป็นปัจจุบันหรือไม่? มีปัญหาที่น่าสงสัยที่ฉันควรระวังหรือไม่?


ไม่การเชื่อมโยงแบบคงที่กับ libstdc ++ ไม่ได้หมายความว่า ถ้ามันบอกเป็นนัยว่าจะไม่มีทาง-static-libstdc++เลือกใด ๆ คุณก็แค่ใช้-static
Jonathan Wakely

@JonathanWakely -static จะได้รับkernel too oldข้อผิดพลาดในระบบ ubuntu 1404 บางระบบ glibc.so เป็นเหมือนkernel32.dllในหน้าต่างซึ่งเป็นส่วนหนึ่งของอินเทอร์เฟซระบบปฏิบัติการเราไม่ควรฝังไว้ในไบนารีของเรา คุณสามารถใช้objdump -T [binary path]เพื่อดูโหลดแบบไดนามิกlibstdc++.soหรือไม่ก็ได้ สำหรับโปรแกรม golang คุณสามารถเพิ่ม#cgo linux LDFLAGS: -static-libstdc++ -static-libgccก่อนนำเข้า "C"
บรอนซ์แมน

@bronzeman แต่เรากำลังพูดถึง-static-libstdc++ไม่ได้-staticดังนั้นlibc.soจะไม่เชื่อมโยงแบบคงที่
Jonathan Wakely

1
@NickHutchinson โพสต์บล็อกที่เชื่อมโยงไปยังหายไป คำถาม SO นี้เป็นคำถามยอดนิยมสำหรับคำที่เกี่ยวข้องที่นี่ คุณสามารถทำซ้ำข้อมูลสำคัญจากบล็อกโพสต์นั้นในคำถามของคุณหรือเสนอลิงก์ใหม่หากคุณทราบว่าย้ายไปที่ใด
Brian Cain

1
@BrianCain ที่เก็บข้อมูลทางอินเทอร์เน็ตมีอยู่: web.archive.org/web/20160313071116/http://www.trilithium.com/…
Rob Keniger

คำตอบ:


136

โพสต์บล็อกนั้นค่อนข้างไม่ถูกต้อง

เท่าที่ฉันทราบได้มีการนำการเปลี่ยนแปลง C ++ ABI มาใช้กับ GCC ทุกรุ่นที่สำคัญ (เช่นการเปลี่ยนแปลงที่มีส่วนประกอบหมายเลขเวอร์ชันแรกหรือเวอร์ชันที่สองต่างกัน)

ไม่จริง. การเปลี่ยนแปลง C ++ ABI เพียงอย่างเดียวที่นำมาใช้ตั้งแต่ GCC 3.4 สามารถใช้งานร่วมกันได้ซึ่งหมายความว่า C ++ ABI มีความเสถียรมาเกือบเก้าปี

เพื่อให้เรื่องแย่ลงการกระจาย Linux ที่สำคัญส่วนใหญ่ใช้สแน็ปช็อต GCC และ / หรือแพตช์เวอร์ชัน GCC ทำให้แทบจะเป็นไปไม่ได้เลยที่จะทราบว่า GCC เวอร์ชันใดที่คุณอาจจัดการเมื่อคุณแจกจ่ายไบนารี

ความแตกต่างระหว่าง GCC เวอร์ชันแพตช์ของดิสทริบิวชันนั้นมีเพียงเล็กน้อยและไม่มีการเปลี่ยนแปลง ABI เช่น 4.6.3 20120306 (Red Hat 4.6.3-2) ของ Fedora คือ ABI เข้ากันได้กับรุ่นอัปสตรีม FSF 4.6.x และเกือบจะแน่นอนกับ 4.6 ใด ๆ x จาก distro อื่น ๆ

บนไลบรารีรันไทม์ของ GNU / Linux GCC ใช้การกำหนดเวอร์ชันสัญลักษณ์ ELF ดังนั้นจึงง่ายต่อการตรวจสอบเวอร์ชันสัญลักษณ์ที่ต้องการโดยอ็อบเจ็กต์และไลบรารีและหากคุณมีlibstdc++.soสัญลักษณ์ที่ให้สัญลักษณ์เหล่านั้นก็จะใช้งานได้ไม่สำคัญว่าจะเป็นเวอร์ชันที่ได้รับการแก้ไขที่แตกต่างกันเล็กน้อย จาก distro เวอร์ชันอื่นของคุณ

แต่ไม่มีรหัส C ++ (หรือรหัสใด ๆ ที่ใช้การสนับสนุนรันไทม์ C ++) อาจเชื่อมโยงแบบไดนามิกหากใช้งานได้

นี่ไม่เป็นความจริงเช่นกัน

ที่กล่าวว่าการลิงก์แบบคงที่libstdc++.aเป็นทางเลือกหนึ่งสำหรับคุณ

สาเหตุที่อาจใช้ไม่ได้หากคุณโหลดไลบรารี (โดยใช้dlopen) แบบไดนามิกคือสัญลักษณ์ libstdc ++ ซึ่งขึ้นอยู่กับว่าแอปพลิเคชันของคุณอาจไม่ต้องการเมื่อคุณเชื่อมโยง (แบบคงที่) ดังนั้นสัญลักษณ์เหล่านั้นจะไม่ปรากฏในไฟล์ปฏิบัติการของคุณ ซึ่งสามารถแก้ไขได้โดยการเชื่อมโยงไลบรารีที่แบ่งใช้แบบไดนามิกเข้ากับlibstdc++.so(ซึ่งเป็นสิ่งที่ถูกต้องที่จะทำต่อไปหากขึ้นอยู่กับมัน) การคั่นด้วยสัญลักษณ์ ELF หมายถึงสัญลักษณ์ที่มีอยู่ในไฟล์ปฏิบัติการของคุณจะถูกใช้โดยไลบรารีที่แบ่งใช้ แต่อื่น ๆ ไม่ใช้ ที่มีอยู่ในไฟล์ปฏิบัติการของคุณจะพบได้ในสิ่งที่libstdc++.soเชื่อมโยงไปยัง หากแอปพลิเคชันของคุณไม่ได้ใช้งานdlopenคุณไม่จำเป็นต้องสนใจเรื่องนั้น

อีกทางเลือกหนึ่ง (และทางเลือกหนึ่งที่ฉันชอบ) คือการปรับใช้เวอร์ชันใหม่libstdc++.soควบคู่ไปกับแอปพลิเคชันของคุณและตรวจสอบให้แน่ใจว่าพบก่อนระบบเริ่มต้นlibstdc++.soซึ่งสามารถทำได้โดยบังคับให้ตัวเชื่อมโยงแบบไดนามิกดูในตำแหน่งที่ถูกต้องไม่ว่าจะใช้$LD_LIBRARY_PATHตัวแปรสภาพแวดล้อมที่รัน - เวลาหรือโดยการตั้งค่าRPATHในปฏิบัติการในเวลาลิงค์ ฉันชอบใช้RPATHเพราะมันไม่ได้ขึ้นอยู่กับสภาพแวดล้อมที่ตั้งไว้อย่างถูกต้องเพื่อให้แอปพลิเคชันทำงาน หากคุณเชื่อมโยงแอพลิเคชันของคุณด้วย'-Wl,-rpath,$ORIGIN'(หมายเหตุราคาเดียวเพื่อป้องกันไม่ให้เปลือกพยายามที่จะขยาย$ORIGIN) แล้วปฏิบัติการจะมีRPATHของ$ORIGINที่บอกลิงเกอร์แบบไดนามิกที่จะมองหาห้องสมุดสาธารณะในไดเรกทอรีเดียวกันเป็นปฏิบัติการของตัวเอง ถ้าใส่ใหม่กว่าlibstdc++.soในไดเร็กทอรีเดียวกันกับไฟล์ปฏิบัติการซึ่งจะพบได้ในขณะรันไทม์แก้ไขปัญหาได้ (อีกทางเลือกหนึ่งคือการใส่ไฟล์ปฏิบัติการลงใน/some/path/bin/libstdc ++ ที่ใหม่กว่าดังนั้นใน/some/path/lib/และเชื่อมโยงกับ'-Wl,-rpath,$ORIGIN/../lib'หรือตำแหน่งคงที่อื่น ๆ ที่สัมพันธ์กับไฟล์ปฏิบัติการและตั้งค่า RPATH ให้สัมพันธ์กับ$ORIGIN)


8
คำอธิบายนี้โดยเฉพาะอย่างยิ่งเกี่ยวกับ RPATH เป็นเรื่องที่น่ายินดี
nilweed

3
การจัดส่ง libstdc ++ ด้วยแอปของคุณบน Linux เป็นคำแนะนำที่ไม่ดี Google สำหรับ "steam libstdc ++" เพื่อดูละครทั้งหมดที่นำเสนอ ในระยะสั้นหาก exe ของคุณโหลด libs ภายนอก (เช่น opengl) ที่ต้องการ dlopen libstdc ++ อีกครั้ง (เช่นไดรเวอร์ radeon) libs เหล่านั้นจะใช้libstdc ++ ของคุณเพราะมันโหลดแล้วแทนที่จะเป็นของตัวเองซึ่งเป็นสิ่งที่พวกเขาต้องการและ คาดหวัง คุณกลับมาที่กำลังสอง

7
@cap OP จะถามเฉพาะเกี่ยวกับการปรับใช้กับ distro ที่ระบบ libstdc ++ เก่ากว่า ปัญหาของ Steam คือพวกเขารวม libstdc ++ ซึ่งเก่ากว่าระบบหนึ่ง (น่าจะเป็นรุ่นใหม่กว่าในขณะที่รวมเข้าด้วยกัน แต่ distros จะย้ายไปยังรุ่นใหม่กว่า) ซึ่งสามารถแก้ไขได้โดยให้ RPATH ชี้ไปที่ไดเร็กทอรีที่มีlibstdc++.so.6symlink ที่ตั้งค่าไว้ในขณะติดตั้งเพื่อชี้ไปที่ lib ที่รวมมาหรือระบบถ้าใหม่กว่า มีโมเดลการเชื่อมโยงแบบผสมที่ซับซ้อนกว่าเช่นเดียวกับที่ Red Hat DTS ใช้ แต่ก็ยากที่จะทำด้วยตัวเอง
Jonathan Wakely

5
เดี๋ยวก่อนฉันขอโทษถ้าฉันไม่ต้องการให้โมเดลของฉันสำหรับการจัดส่งไบนารีที่เข้ากันได้แบบย้อนกลับรวมถึง "การไว้วางใจให้คนอื่นรักษา libstdc ++ ABI compat" หรือ "การเชื่อมโยง libstdc ++ แบบมีเงื่อนไขที่รันไทม์" ... หากสิ่งนั้นทำให้ขนบางส่วนที่นี่ และที่นั่นฉันจะทำอย่างไรฉันหมายถึงไม่ดูหมิ่น และถ้าคุณจำละคร memcpy@GLIBC_2.14 ได้คุณคงไม่สามารถจับผิดฉันได้จริงๆที่มีปัญหาเรื่องความไว้วางใจ :)

6
ฉันต้องใช้ '-Wl, -rpath, $ ORIGIN' (สังเกต '-' ข้างหน้า rpath) ฉันไม่สามารถแก้ไขคำตอบได้เนื่องจากการแก้ไขต้องมีอย่างน้อย 6 ตัวอักษร ....
user368507

11

อีกหนึ่งคำตอบที่ยอดเยี่ยมของ Jonathan Wakely ทำไม dlopen () ถึงมีปัญหา:

เนื่องจากพูลการจัดการข้อยกเว้นใหม่ใน GCC 5 (ดูPR 64535และPR 65434 ) หากคุณ dlopen และ dl ปิดไลบรารีที่เชื่อมโยงกับ libstdc ++ แบบคงที่คุณจะได้รับหน่วยความจำรั่ว (ของวัตถุพูล) ทุกครั้ง ดังนั้นหากมีโอกาสที่คุณจะใช้ dlopen ดูเหมือนว่าเป็นความคิดที่ไม่ดีจริงๆในการลิงก์ libstdc ++ แบบคงที่ หมายเหตุว่านี่คือการรั่วไหลจริงเมื่อเทียบกับคนใจดีที่กล่าวถึงในPR 65434


1
ฟังก์ชั่นนี้__gnu_cxx::__freeres()ดูเหมือนจะให้ความช่วยเหลือบางอย่างเกี่ยวกับปัญหานี้เนื่องจากจะทำให้บัฟเฟอร์ภายในของวัตถุพูลเป็นอิสระ แต่สำหรับฉันมันค่อนข้างไม่ชัดเจนว่าการเรียกใช้ฟังก์ชันนี้มีความหมายใดเกี่ยวกับข้อยกเว้นที่เกิดขึ้นโดยบังเอิญในภายหลัง
phlipsy

3

เพิ่มเติมสำหรับคำตอบของ Jonathan Wakely เกี่ยวกับ RPATH:

RPATH จะใช้ได้ก็ต่อเมื่อ RPATH ที่เป็นปัญหาคือ RPATH ของแอปพลิเคชันที่กำลังทำงานอยู่ หากคุณมีไลบรารีที่เชื่อมโยงไปยังไลบรารีใด ๆ แบบไดนามิกผ่าน RPATH ของตัวเอง RPATH ของไลบรารีจะถูกเขียนทับโดย RPATH ของแอปพลิเคชันที่โหลดขึ้นมา ปัญหานี้เป็นปัญหาเมื่อคุณไม่สามารถรับประกันได้ว่า RPATH ของแอปพลิเคชันจะเหมือนกับของไลบรารีของคุณเช่นหากคุณคาดว่าการอ้างอิงของคุณจะอยู่ในไดเร็กทอรีเฉพาะ แต่ไดเร็กทอรีนั้นไม่ได้เป็นส่วนหนึ่งของ RPATH ของแอปพลิเคชัน

ตัวอย่างเช่นสมมติว่าคุณมีแอปพลิเคชัน App.exe ซึ่งมีการเชื่อมโยงแบบไดนามิกบน libstdc ++ so.x สำหรับ GCC 4.9 App.exe มีการอ้างอิงนี้ได้รับการแก้ไขผ่าน RPATH เช่น

App.exe (RPATH=.:./gcc4_9/libstdc++.so.x)

ตอนนี้สมมติว่ามี Dependency.so ไลบรารีอื่นซึ่งมีการพึ่งพาแบบไดนามิกที่เชื่อมโยงกับ libstdc ++ ดังนั้นyสำหรับ GCC 5.5 การอ้างอิงที่นี่ได้รับการแก้ไขผ่าน RPATH ของไลบรารีเช่น

Dependency.so (RPATH=.:./gcc5_5/libstdc++.so.y)

เมื่อโหลด App.exe Dependency.so, มันไม่ผนวกมิได้ prepends RPATH ของห้องสมุด มันไม่ปรึกษามันเลย RPATH เดียวที่ถูกพิจารณาจะเป็นของแอปพลิเคชันที่ทำงานอยู่หรือ App.exe ในตัวอย่างนี้ นั่นหมายความว่าหากไลบรารีอาศัยสัญลักษณ์ที่อยู่ใน gcc5_5 / libstdc ++ so.y แต่ไม่ใช่ใน gcc4_9 / libstdc ++ so.x ไลบรารีจะไม่สามารถโหลดได้

นี่เป็นเพียงคำเตือนเนื่องจากฉันเคยพบปัญหาเหล่านี้ในอดีต RPATH เป็นเครื่องมือที่มีประโยชน์มาก แต่การนำไปใช้งานยังมี gotchas อยู่บ้าง


ดังนั้น RPATH สำหรับไลบรารีที่แชร์จึงไม่มีจุดหมาย! และฉันหวังว่าพวกเขาจะปรับปรุง Linux ในแง่นี้ในช่วง 2 ทศวรรษที่ผ่านมา ...
Frank Puck

2

คุณอาจต้องตรวจสอบให้แน่ใจว่าไม่ได้ขึ้นอยู่กับไดนามิกกะล่อน รันlddบนไฟล์ปฏิบัติการที่เป็นผลลัพธ์ของคุณและจดบันทึกการอ้างอิงแบบไดนามิก (libc / libm / libpthread เป็นสิ่งต้องสงสัยที่ใช้งานได้)

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


1
ปัญหาคืออะไรขึ้นอยู่กับไดนามิกกะล่อน?
Nick Hutchinson

ฉันเชื่อว่าอย่างน้อยบางครั้งที่ผ่านมา libstdc ++ โดยนัยแล้วการพึ่งพา glibc ไม่แน่ใจว่าวันนี้จะอยู่ที่ไหน
Alexander L. Belikoff

9
libstdc ++ ขึ้นอยู่กับ glibc (เช่นใช้ iostreams ในแง่ของprintf) แต่ตราบใดที่ glibc บน Ubuntu 10.04 ให้คุณสมบัติทั้งหมดที่จำเป็นโดย libstdc ++ รุ่นใหม่ก็ไม่มีปัญหาขึ้นอยู่กับไดนามิก glibc อันที่จริงขอแนะนำอย่างยิ่งว่าอย่าเชื่อมโยง คงกะล่อน
Jonathan Wakely

1

ฉันต้องการเพิ่มคำตอบของ Jonathan Wakely ดังต่อไปนี้

เมื่อเล่น-static-libstdc++บน linux ฉันประสบปัญหากับdlclose(). สมมติว่าเรามีแอปพลิเคชัน "A" ที่เชื่อมโยงแบบคงที่libstdc++และโหลดแบบไดนามิกที่เชื่อมโยงกับlibstdc++ปลั๊กอิน "P" ที่รันไทม์ ไม่เป็นไร. แต่เมื่อ 'A' ยกเลิกการโหลด 'P' ความผิดพลาดในการแบ่งส่วนจะเกิดขึ้น สมมติฐานของฉันคือหลังจากยกเลิกการโหลดlibstdc++.so'A' จะไม่สามารถใช้สัญลักษณ์ที่เกี่ยวข้องกับlibstdc++. โปรดทราบว่าหากทั้ง 'A' และ 'P' เชื่อมโยงแบบคงที่libstdc++หรือถ้า 'A' ถูกเชื่อมโยงแบบไดนามิกและ 'P' แบบคงที่ปัญหาจะไม่เกิดขึ้น

สรุป: หากแอปพลิเคชันของคุณโหลด / ยกเลิกการโหลดปลั๊กอินที่อาจเชื่อมโยงแบบไดนามิกlibstdc++แอปนั้นจะต้องเชื่อมโยงแบบไดนามิกด้วย นี่เป็นเพียงข้อสังเกตของฉันและฉันต้องการรับความคิดเห็นของคุณ


1
นี่อาจคล้ายกับการผสมการใช้งาน libc (พูดแบบไดนามิกที่เชื่อมโยงไปยังปลั๊กอินที่จะเชื่อมโยง glibc แบบไดนามิกในขณะที่แอปพลิเคชันนั้นเชื่อมโยงกับ musl-libc แบบคงที่) Rich Felker ผู้เขียน musl-libc อ้างว่าปัญหาในสถานการณ์ดังกล่าวคือการจัดการหน่วยความจำ glibc (ใช้sbrk) ทำให้เกิดข้อสันนิษฐานบางอย่างและคาดว่าจะอยู่คนเดียวภายในกระบวนการเดียว ... ไม่แน่ใจว่าสิ่งนี้ จำกัด อยู่ที่ a รุ่น glibc โดยเฉพาะหรืออะไรก็ตาม
0xC0000022L

และผู้คนยังไม่เห็นข้อดีของอินเทอร์เฟซ Windows heap ซึ่งสามารถจัดการกับ libc ++ / libc ที่เป็นอิสระหลายชุดภายในกระบวนการเดียว บุคคลดังกล่าวไม่ควรออกแบบซอฟต์แวร์
Frank Puck

@FrankPuck มีประสบการณ์ทั้ง Windows และ Linux ในปริมาณที่เหมาะสมฉันสามารถบอกคุณได้ว่าวิธีที่ "Windows" ใช้มันจะไม่ช่วยคุณเมื่อ MSVC เป็นฝ่ายที่ตัดสินใจว่าจะใช้ตัวจัดสรรอะไรและอย่างไร ข้อได้เปรียบหลักที่ฉันเห็นจากฮีปบน Windows คือคุณสามารถแจกบิตและชิ้นส่วนแล้วปลดปล่อยพวกมันในบัดดล แต่ด้วย MSVC คุณจะยังคงพบปัญหาที่อธิบายไว้ข้างต้นเช่นเมื่อส่งผ่านพอยน์เตอร์ที่จัดสรรโดยรันไทม์ VC อื่น (รีลีสเทียบกับดีบักหรือสแตติกเทียบกับลิงก์แบบไดนามิก) ดังนั้น "Windows" จึงไม่มีภูมิคุ้มกัน ต้องมีการดูแลทั้งสองระบบ
0xC0000022L
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.