เหตุใดลำดับของไลบรารีที่ลิงก์ในบางครั้งทำให้เกิดข้อผิดพลาดใน GCC


449

เหตุใดลำดับของไลบรารีที่ลิงก์ในบางครั้งทำให้เกิดข้อผิดพลาดใน GCC


ดูเพิ่มเติมตอนนี้stackoverflow.com/questions/7826448/… - TLDR gccเปลี่ยนเป็นพฤติกรรมที่เข้มงวดมากขึ้นเมื่อเร็ว ๆ นี้
tripleee

คำตอบ:


558

(ดูประวัติของคำตอบนี้เพื่อให้ได้ข้อความที่ละเอียดยิ่งขึ้น แต่ตอนนี้ฉันคิดว่ามันง่ายกว่าที่ผู้อ่านจะเห็นบรรทัดคำสั่งจริง)


ไฟล์ทั่วไปที่แชร์โดยคำสั่งด้านล่างทั้งหมด

$ cat a.cpp
extern int a;
int main() {
  return a;
}

$ cat b.cpp
extern int b;
int a = b;

$ cat d.cpp
int b;

เชื่อมโยงไปยังไลบรารีคงที่

$ g++ -c b.cpp -o b.o
$ ar cr libb.a b.o
$ g++ -c d.cpp -o d.o
$ ar cr libd.a d.o

$ g++ -L. -ld -lb a.cpp # wrong order
$ g++ -L. -lb -ld a.cpp # wrong order
$ g++ a.cpp -L. -ld -lb # wrong order
$ g++ a.cpp -L. -lb -ld # right order

ตัวเชื่อมโยงค้นหาจากซ้ายไปขวาและบันทึกสัญลักษณ์ที่ไม่ได้แก้ไขขณะที่มันไป หากไลบรารีแก้ไขสัญลักษณ์ใช้ไฟล์อ็อบเจ็กต์ของไลบรารีนั้นเพื่อแก้ไขสัญลักษณ์ (bo จาก libb.a ในกรณีนี้)

การพึ่งพาของไลบรารีแบบสแตติกเทียบกับงานอื่น ๆ เหมือนกัน - ไลบรารีที่ต้องการสัญลักษณ์ต้องเป็นอันดับแรกจากนั้นไลบรารีที่แก้ไขสัญลักษณ์

หากห้องสมุดคงขึ้นอยู่กับห้องสมุดอื่น แต่ห้องสมุดอื่นอีกครั้งขึ้นอยู่กับห้องสมุดเดิมมีรอบ คุณสามารถแก้ไขปัญหานี้ได้ด้วยการใส่ไลบรารี่ที่ขึ้นกับวัฏจักรโดย-(และ-)เช่น-( -la -lb -)(คุณอาจต้องหลีกเลี่ยง parens เช่น-\(และ-\)) ตัวเชื่อมโยงจะค้นหา lib ที่ปิดล้อมเหล่านั้นหลายครั้งเพื่อให้แน่ใจว่าการอ้างอิงการวนรอบได้รับการแก้ไขแล้ว หรือคุณสามารถระบุไลบรารีหลาย ๆ ครั้ง-la -lb -laได้

เชื่อมโยงไปยังไลบรารีแบบไดนามิก

$ export LD_LIBRARY_PATH=. # not needed if libs go to /usr/lib etc
$ g++ -fpic -shared d.cpp -o libd.so
$ g++ -fpic -shared b.cpp -L. -ld -o libb.so # specifies its dependency!

$ g++ -L. -lb a.cpp # wrong order (works on some distributions)
$ g++ -Wl,--as-needed -L. -lb a.cpp # wrong order
$ g++ -Wl,--as-needed a.cpp -L. -lb # right order

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

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

มันไม่ถูกต้องที่จะละเว้นการพึ่งพาของb.soกับd.soเมื่อมีการสร้างในอดีต คุณจะต้องระบุไลบรารีเมื่อทำการเชื่อมโยงaแต่aไม่ต้องการจำนวนเต็มbจริง ๆ ดังนั้นจึงไม่ควรสนใจการbพึ่งพาของตัวเอง

นี่คือตัวอย่างของความหมายหากคุณไม่ได้ระบุการพึ่งพาสำหรับ libb.so

$ export LD_LIBRARY_PATH=. # not needed if libs go to /usr/lib etc
$ g++ -fpic -shared d.cpp -o libd.so
$ g++ -fpic -shared b.cpp -o libb.so # wrong (but links)

$ g++ -L. -lb a.cpp # wrong, as above
$ g++ -Wl,--as-needed -L. -lb a.cpp # wrong, as above
$ g++ a.cpp -L. -lb # wrong, missing libd.so
$ g++ a.cpp -L. -ld -lb # wrong order (works on some distributions)
$ g++ -Wl,--as-needed a.cpp -L. -ld -lb # wrong order (like static libs)
$ g++ -Wl,--as-needed a.cpp -L. -lb -ld # "right"

ถ้าตอนนี้คุณดูว่าการพึ่งพาไบนารีมีอะไรบ้างคุณสังเกตว่าไบนารีนั้นขึ้นอยู่กับlibdว่าไม่ใช่แค่libbเท่าที่ควร ไบนารีจะต้องมีการเชื่อมโยงlibbใหม่ในภายหลังหากขึ้นอยู่กับห้องสมุดอื่นถ้าคุณทำเช่นนี้ และถ้ามีคนอื่นโหลดlibbโดยใช้dlopenขณะทำงาน (คิดว่าการโหลดปลั๊กอินแบบไดนามิก) การโทรจะล้มเหลวเช่นกัน ดังนั้น"right"สิ่งที่ควรจะเป็นwrongเช่นกัน


10
ทำซ้ำจนกว่าสัญลักษณ์ทั้งหมดจะได้รับการแก้ไขใช่มั้ย - คุณคิดว่าพวกเขาสามารถจัดการเรียงลำดับโทโพโลยีได้ LLVM มีห้องสมุดแบบสแตติก 78 แห่งเป็นของตัวเองพร้อมการพึ่งพาใครรู้ ความจริงมันยังมีสคริปต์เพื่อหาตัวเลือกการคอมไพล์ / ลิงค์ - แต่คุณไม่สามารถใช้มันได้ในทุกสถานการณ์
Steve314

6
@ Steve นั่นคือสิ่งที่โปรแกรมlorder+ tsortทำ แต่บางครั้งก็ไม่มีคำสั่งถ้าคุณมีการอ้างอิงครบวงจร จากนั้นคุณเพียงต้องวนไปตามรายการไลบรารีจนกว่าทุกอย่างจะได้รับการแก้ไข
Johannes Schaub - litb

10
@Johannes - ตรวจสอบส่วนประกอบที่เชื่อมต่ออย่างมากที่สุด (เช่นอัลกอริธึม Tarjans) จากนั้นจัดเรียงทอพอโลยีแบบเรียงลำดับ แต่ละคอมโพเนนต์สามารถถูกถือว่าเป็นหนึ่งไลบรารี - ถ้าต้องการไลบรารีหนึ่งไลบรารีจากคอมโพเนนต์วงจรการพึ่งพาจะทำให้ไลบรารีทั้งหมดในคอมโพเนนต์นั้นเป็นสิ่งจำเป็น ดังนั้นไม่จำเป็นต้องวนผ่านไลบรารีทั้งหมดเพื่อแก้ไขทุกอย่างและไม่จำเป็นต้องใช้ตัวเลือกบรรทัดคำสั่งที่น่าอึดอัดใจวิธีหนึ่งที่ใช้อัลกอริทึมที่รู้จักกันดีสองวิธีสามารถจัดการทุกกรณีได้อย่างถูกต้อง
Steve314

4
ฉันต้องการเพิ่มรายละเอียดที่สำคัญหนึ่งคำตอบที่ยอดเยี่ยมนี้: การใช้ "- (จดหมายเหตุ -)" หรือ "- จดหมายเหตุเริ่มต้นกลุ่ม - ปลายกลุ่ม" เป็นวิธีเดียวที่แน่นอนในการแก้ไขการอ้างอิงแบบวงกลมตั้งแต่แต่ละครั้ง การเข้าชมลิงเกอร์ที่เก็บก็ดึงใน (และลงทะเบียนในสัญลักษณ์ที่ได้รับการแก้ไขของ) เฉพาะไฟล์วัตถุที่แก้ไขสัญลักษณ์ยังไม่ได้แก้ไขในขณะนี้ ด้วยเหตุนี้อัลกอริทึมของ CMake ในการทำซ้ำส่วนประกอบที่เชื่อมต่อในกราฟพึ่งพาอาจล้มเหลวเป็นครั้งคราว (ดูโพสต์บล็อกที่ยอดเยี่ยมของ Ian Lance Taylorบน linkers เพื่อดูรายละเอียดเพิ่มเติม)
jorgen

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

102

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

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


2
นี่เป็นสิ่งที่มีเพียง gnu ld / gcc หรือไม่ หรือสิ่งนี้เป็นเรื่องธรรมดากับ linkers?
ไมค์

2
เห็นได้ชัดว่าคอมไพเลอร์ Unix มากกว่ามีปัญหาที่คล้ายกัน MSVC ไม่ได้เป็นอิสระจากปัญหาเหล่านี้ทั้งหมด แต่ดูเหมือนว่าจะไม่เลว
MSalters

4
เครื่องมือ MS dev ไม่มีแนวโน้มที่จะแสดงปัญหาเหล่านี้มากนักเพราะถ้าคุณใช้โซ่เครื่องมือแบบ All-MS มันจะสิ้นสุดการตั้งค่าลำดับลิงเกอร์อย่างถูกต้องและคุณไม่เคยสังเกตเห็นปัญหา
Michael Kohne

16
ตัวเชื่อมโยง MSVC มีความไวต่อปัญหานี้น้อยลงเนื่องจากจะค้นหาสัญลักษณ์ทั้งหมดที่ไม่ได้รับการอ้างอิง เพื่อห้องสมุดยังสามารถส่งผลกระทบต่อซึ่งสัญลักษณ์ได้รับการแก้ไขถ้าห้องสมุดมากกว่าหนึ่งมีสัญลักษณ์ จาก MSDN: "ไลบรารีถูกค้นหาตามลำดับบรรทัดคำสั่งด้วยคำเตือนต่อไปนี้: สัญลักษณ์ที่ไม่ได้แก้ไขเมื่อนำวัตถุไฟล์จากไลบรารีถูกค้นหาในห้องสมุดนั้นก่อนจากนั้นจึงค้นหาไลบรารีต่อไปนี้จากบรรทัดคำสั่งและ / DEFAULTLIB (ระบุไลบรารีเริ่มต้น) คำสั่งจากนั้นไปยังไลบรารีใด ๆ ที่จุดเริ่มต้นของบรรทัดคำสั่ง "
Michael Burr

4
"... smart linker ... " - ฉันเชื่อว่ามันจัดอยู่ในหมวดหมู่ "single pass" ไม่ใช่ "smart linker"
jww

54

นี่คือตัวอย่างเพื่อให้ชัดเจนว่าสิ่งต่าง ๆ ทำงานกับ GCC อย่างไรเมื่อมีการเข้าใช้ห้องสมุดแบบคงที่ ดังนั้นสมมติว่าเรามีสถานการณ์ต่อไปนี้:

  • myprog.o- main()ฟังก์ชั่นที่มีขึ้นอยู่กับlibmysqlclient
  • libmysqlclient- คงที่เพื่อประโยชน์ของตัวอย่าง (คุณต้องการห้องสมุดสาธารณะที่libmysqlclientมีขนาดใหญ่) ใน/usr/local/lib; และขึ้นอยู่กับเนื้อหาจากlibz
  • libz (แบบไดนามิก)

เราจะเชื่อมโยงสิ่งนี้ได้อย่างไร (หมายเหตุ: ตัวอย่างจากการคอมไพล์บน Cygwin โดยใช้ gcc 4.3.4)

gcc -L/usr/local/lib -lmysqlclient myprog.o
# undefined reference to `_mysql_init'
# myprog depends on libmysqlclient
# so myprog has to come earlier on the command line

gcc myprog.o -L/usr/local/lib -lmysqlclient
# undefined reference to `_uncompress'
# we have to link with libz, too

gcc myprog.o -lz -L/usr/local/lib -lmysqlclient
# undefined reference to `_uncompress'
# libz is needed by libmysqlclient
# so it has to appear *after* it on the command line

gcc myprog.o -L/usr/local/lib -lmysqlclient -lz
# this works

31

ถ้าคุณเพิ่ม-Wl,--start-groupในตัวเชื่อมโยงการตั้งค่าสถานะมันไม่สนใจลำดับใดที่อยู่ในหรือถ้ามีการอ้างอิงแบบวงกลม

ใน Qt หมายถึงการเพิ่ม:

QMAKE_LFLAGS += -Wl,--start-group

ประหยัดเวลาที่ยุ่งวุ่นวายและดูเหมือนจะไม่ทำให้การเชื่อมโยงช้าลงเท่าไหร่ (ซึ่งใช้เวลาน้อยกว่าการคอมไพล์อยู่ดี)


8

อีกทางเลือกหนึ่งคือการระบุรายการของห้องสมุดสองครั้ง:

gcc prog.o libA.a libB.a libA.a libB.a -o prog.x

การทำเช่นนี้คุณไม่ต้องกังวลกับลำดับที่ถูกต้องเนื่องจากการอ้างอิงจะได้รับการแก้ไขในบล็อกที่สอง


5

คุณสามารถใช้ตัวเลือก -Xlinker

g++ -o foobar  -Xlinker -start-group  -Xlinker libA.a -Xlinker libB.a -Xlinker libC.a  -Xlinker -end-group 

เกือบเท่ากับ

g++ -o foobar  -Xlinker -start-group  -Xlinker libC.a -Xlinker libB.a -Xlinker libA.a  -Xlinker -end-group 

ระวัง!

  1. คำสั่งภายในกลุ่มมีความสำคัญ! นี่คือตัวอย่าง: ไลบรารีการดีบักมีรูทีนการดีบัก แต่ไลบรารีที่ไม่ใช่การดีบักมีเวอร์ชันที่อ่อนแอเหมือนกัน คุณต้องใส่ไลบรารี debug FIRST ไว้ในกลุ่มมิฉะนั้นคุณจะแก้ไขเป็นรุ่นที่ไม่ใช่ debug
  2. คุณต้องนำหน้าแต่ละไลบรารีในรายการกลุ่มด้วย -Xlinker

5

เคล็ดลับสั้น ๆ ที่ทำให้ฉันสะดุด: หากคุณเรียกใช้ตัวเชื่อมโยงเป็น "gcc" หรือ "g ++" ให้ใช้ "- เริ่มต้นกลุ่ม" และ "- สิ้นสุดกลุ่ม" จะไม่ผ่านตัวเลือกเหล่านั้นผ่านไปยัง ตัวเชื่อมโยง - และจะไม่มีการตั้งค่าสถานะข้อผิดพลาด มันจะล้มเหลวในการเชื่อมโยงกับสัญลักษณ์ที่ไม่ได้กำหนดถ้าคุณมีคำสั่งห้องสมุดผิด

คุณต้องเขียนเป็น "-Wl, - start-group" ฯลฯ เพื่อบอก GCC ให้ส่งอาร์กิวเมนต์ไปยังลิงเกอร์


2

ลำดับการเชื่อมโยงมีความสำคัญอย่างน้อยในบางแพลตฟอร์ม ฉันได้เห็นข้อขัดข้องสำหรับแอปพลิเคชันที่เชื่อมโยงกับไลบรารีในลำดับที่ไม่ถูกต้อง (ที่ผิดหมายถึง A ที่เชื่อมโยงก่อน B แต่ B ขึ้นอยู่กับ A)


2

ฉันได้เห็นสิ่งนี้มากมายโมดูลของเราบางอันเชื่อมโยงกับเกิน 100 ไลบรารี่ของรหัสของเรารวมถึงระบบ & libs บุคคลที่สาม

ขึ้นอยู่กับ linkers ที่แตกต่างกัน HP / Intel / GCC / SUN / SGI / IBM / ฯลฯ คุณสามารถรับฟังก์ชั่น / ตัวแปรที่ไม่ได้รับการแก้ไข ฯลฯ ในบางแพลตฟอร์มคุณต้องแสดงรายการห้องสมุดสองครั้ง

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

เมื่อคุณเข้าสู่เอกสารโซลูชันแล้วนักพัฒนาซอฟต์แวร์รายต่อไปก็ไม่จำเป็นต้องทำมันอีก

อาจารย์เก่าของฉันเคยพูดว่า "การติดต่อกันสูงและการมีเพศสัมพันธ์ต่ำ " มันยังคงเป็นจริงในวันนี้

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.