Liên kết libstdc ++ tĩnh: bất kỳ lỗi nào?


90

Tôi cần triển khai ứng dụng C ++ được xây dựng trên Ubuntu 12.10 với libstdc ++ của GCC 4.7 cho các hệ thống chạy Ubuntu 10.04, đi kèm với phiên bản libstdc ++ cũ hơn đáng kể.

Hiện tại, tôi đang biên dịch -static-libstdc++ -static-libgcc, theo đề xuất của bài đăng trên blog này: Liên kết tĩnh libstdc ++ . Tác giả cảnh báo không sử dụng bất kỳ mã C ++ nào được tải động khi biên dịch tĩnh libstdc ++, đây là điều tôi chưa kiểm tra. Tuy nhiên, mọi thứ dường như vẫn diễn ra suôn sẻ cho đến nay: Tôi có thể sử dụng các tính năng C ++ 11 trên Ubuntu 10.04, đó là những gì tôi đã làm.

Tôi lưu ý rằng bài báo này là từ năm 2005, và có lẽ đã thay đổi nhiều kể từ đó. Lời khuyên của nó vẫn còn hiện tại? Có bất kỳ vấn đề tiềm ẩn nào tôi nên biết không?


Không, liên kết tĩnh với libstdc ++ không ngụ ý điều đó. Nếu nó đã ngụ ý rằng sau đó sẽ không có điểm đến -static-libstdc++tùy chọn, bạn sẽ chỉ cần sử dụng-static
Jonathan Wakely

@JonathanWakely -static sẽ gặp kernel too oldlỗi trong một số hệ thống ubuntu 1404. Glibc.so giống như kernel32.dlltrong window, nó là một phần của giao diện hệ điều hành, chúng ta không nên nhúng nó vào tệp nhị phân của mình. Bạn có thể sử dụng objdump -T [binary path]để xem nó được tải động libstdc++.sohay không. Đối với trình lập trình golang, bạn có thể thêm #cgo linux LDFLAGS: -static-libstdc++ -static-libgcctrước khi nhập "C"
Bronze man.

@bronzeman, nhưng chúng ta đang nói về -static-libstdc++không phải -staticnhư vậy libc.sosẽ không được liên kết tĩnh.
Jonathan Wakely

1
@NickHutchinson bài đăng trên blog được liên kết đến đã biến mất. Câu hỏi SO này là một lượt tìm kiếm phổ biến cho các thuật ngữ có liên quan ở đây. Bạn có thể tái tạo thông tin quan trọng từ bài đăng blog đó trong câu hỏi của mình hoặc cung cấp một liên kết mới nếu bạn biết nó được chuyển đến đâu không?
Brian Cain

1
@BrianCain Kho lưu trữ trên internet có nó: web.archive.org/web/20160313071116/http://www.trilithium.com/…
Rob Keniger

Câu trả lời:


134

Bài đăng trên blog đó là khá không chính xác.

Theo như tôi biết thì những thay đổi về C ++ ABI đã được đưa ra với mọi bản phát hành chính của GCC (tức là những thay đổi có thành phần số phiên bản thứ nhất hoặc thứ hai khác nhau).

Không đúng. Các thay đổi C ++ ABI duy nhất được giới thiệu kể từ GCC 3.4 đã tương thích ngược, có nghĩa là C ++ ABI đã ổn định trong gần chín năm.

Để làm cho vấn đề tồi tệ hơn, hầu hết các bản phân phối Linux lớn đều sử dụng ảnh chụp nhanh GCC và / hoặc vá các phiên bản GCC của chúng, khiến bạn hầu như không thể biết chính xác phiên bản GCC nào mà bạn có thể xử lý khi phân phối tệp nhị phân.

Sự khác biệt giữa các phiên bản vá lỗi của bản phân phối của GCC là nhỏ và ABI không thay đổi, ví dụ như Fedora's 4.6.3 20120306 (Red Hat 4.6.3-2) ABI tương thích với các bản phát hành FSF 4.6.x ngược dòng và gần như chắc chắn với bất kỳ 4.6 nào. x từ bất kỳ bản phân phối nào khác.

Trên GNU / Linux, các thư viện thời gian chạy của GCC sử dụng lập phiên bản ký hiệu ELF để dễ dàng kiểm tra các phiên bản ký hiệu mà các đối tượng và thư viện cần và nếu bạn libstdc++.socung cấp các ký hiệu đó thì nó sẽ hoạt động, không thành vấn đề nếu đó là một phiên bản vá lỗi hơi khác từ một phiên bản khác của bản phân phối của bạn.

nhưng không có mã C ++ nào (hoặc bất kỳ mã nào sử dụng hỗ trợ thời gian chạy C ++) có thể được liên kết động nếu điều này hoạt động.

Điều này cũng không đúng.

Điều đó nói rằng, liên kết tĩnh đến libstdc++.alà một lựa chọn cho bạn.

Lý do nó có thể không hoạt động nếu bạn tải động một thư viện (bằng cách sử dụng dlopen) là các ký hiệu libstdc ++ mà nó phụ thuộc vào có thể không được ứng dụng của bạn cần khi bạn (tĩnh) liên kết nó, vì vậy những ký hiệu đó sẽ không có trong tệp thực thi của bạn. Điều đó có thể được giải quyết bằng cách liên kết động thư viện được chia sẻ với libstdc++.so(dù sao cũng là điều nên làm nếu tùy thuộc vào nó.) Sự xen kẽ biểu tượng ELF có nghĩa là các biểu tượng có trong tệp thực thi của bạn sẽ được sử dụng bởi thư viện được chia sẻ, nhưng những người khác thì không hiện trong tệp thực thi của bạn sẽ được tìm thấy trong bất kỳ libstdc++.sotệp nào liên kết đến. Nếu ứng dụng của bạn không sử dụng, dlopenbạn không cần quan tâm đến điều đó.

Một tùy chọn khác (và tùy chọn tôi thích hơn) là triển khai phiên bản mới hơn libstdc++.socùng với ứng dụng của bạn và đảm bảo nó được tìm thấy trước hệ thống mặc định libstdc++.so, điều này có thể được thực hiện bằng cách buộc trình liên kết động tìm đúng chỗ, hoặc sử dụng $LD_LIBRARY_PATHbiến môi trường khi chạy- thời gian hoặc bằng cách đặt một RPATHtrong tệp thực thi tại thời điểm liên kết. Tôi thích sử dụng hơn RPATHvì nó không dựa vào môi trường được thiết lập chính xác để ứng dụng hoạt động. Nếu bạn liên kết ứng dụng của mình với '-Wl,-rpath,$ORIGIN'(lưu ý các dấu nháy đơn để ngăn trình bao cố gắng mở rộng $ORIGIN) thì tệp thực thi sẽ có một RPATHtrong số $ORIGINđó yêu cầu trình liên kết động tìm kiếm các thư viện được chia sẻ trong cùng thư mục với tệp thực thi. Nếu bạn đặt cái mới hơnlibstdc++.sotrong cùng thư mục với tệp thực thi, nó sẽ được tìm thấy tại thời điểm chạy, vấn đề đã được giải quyết. (Một tùy chọn khác là đưa tệp thực thi vào /some/path/bin/và libstdc ++ mới hơn. Vì vậy, vào /some/path/lib/và liên kết với '-Wl,-rpath,$ORIGIN/../lib'hoặc bất kỳ vị trí cố định nào khác liên quan đến tệp thực thi và đặt RPATH liên quan đến $ORIGIN)


8
Lời giải thích này, đặc biệt là về RPATH, thật tuyệt vời.
nilweed

3
Gửi libstdc ++ với ứng dụng của bạn trên Linux là một lời khuyên tồi. Google cho "steam libstdc ++" để xem tất cả kịch tính mà điều này mang lại. Nói tóm lại, nếu exe của bạn tải các lib bên ngoài (như opengl) muốn dlopen libstdc ++ một lần nữa (như, trình điều khiển radeon), thì các lib đó sẽ sử dụng libstdc ++ của bạn vì nó đã được tải, thay vì của chúng, đó là những gì chúng cần và chờ đợi. Vì vậy, bạn trở lại hình vuông một.

7
@cap, OP đang hỏi cụ thể về việc triển khai tới một bản phân phối có hệ thống libstdc ++ cũ hơn. Vấn đề của Steam là họ đã đóng gói libstdc ++. Cái đó cũ hơn hệ thống (có lẽ nó mới hơn vào thời điểm họ đóng gói nó, nhưng các bản phân phối đã chuyển sang những cái mới hơn). Điều đó có thể được giải quyết bằng cách để RPATH trỏ đến thư mục chứa libstdc++.so.6liên kết tượng trưng được đặt tại thời điểm cài đặt để trỏ đến lib đi kèm hoặc đến hệ thống nếu nó mới hơn. Có nhiều mô hình liên kết hỗn hợp phức tạp hơn, như được sử dụng bởi Red Hat DTS, nhưng chúng rất khó để tự thực hiện.
Jonathan Wakely,

5
Này anh bạn, tôi xin lỗi nếu tôi không muốn mô hình vận chuyển các tệp nhị phân tính toán ngược của tôi bao gồm "tin tưởng người khác giữ libstdc ++ ABI compat" hoặc "liên kết có điều kiện libstdc ++ trong thời gian chạy" ... nếu điều đó làm xù lông ở đây và ở đó, tôi có thể làm gì, ý tôi là không có sự thiếu tôn trọng. Và nếu bạn còn nhớ bộ phim memcpy@GLIBC_2.14, bạn có thể không thực sự lỗi tôi vì có vấn đề niềm tin với :) này

6
Tôi đã phải sử dụng '-Wl, -rpath, $ ORIGIN' (lưu ý dấu '-' trước rpath). Tôi không thể chỉnh sửa câu trả lời vì chỉnh sửa phải có ít nhất 6 ký tự ....
user368507

11

Một bổ sung cho câu trả lời xuất sắc của Jonathan Wakely, tại sao dlopen () lại có vấn đề:

Do nhóm xử lý ngoại lệ mới trong GCC 5 (xem PR 64535PR 65434 ), nếu bạn dlopen và dlclose một thư viện được liên kết tĩnh với libstdc ++, bạn sẽ bị rò rỉ bộ nhớ (của đối tượng nhóm) mỗi lần. Vì vậy, nếu có bất kỳ cơ hội nào bạn sẽ sử dụng dlopen, có vẻ như là một ý tưởng thực sự tồi khi liên kết tĩnh libstdc ++. Lưu ý rằng đây là một rò rỉ thực sự trái ngược với rò rỉ lành tính được đề cập trong PR 65434 .


1
Hàm __gnu_cxx::__freeres()dường như cung cấp ít nhất một số trợ giúp cho vấn đề này, vì nó giải phóng bộ đệm bên trong của đối tượng pool. Nhưng đối với tôi, nó không rõ ràng là hàm ý nào mà một lệnh gọi đến hàm này có liên quan đến các ngoại lệ vô tình được ném sau đó.
phlipsy

3

Phần bổ sung cho câu trả lời của Jonathan Wakely về RPATH:

RPATH sẽ chỉ hoạt động nếu RPATH được đề cập là RPATH của ứng dụng đang chạy . Nếu bạn có một thư viện liên kết động với bất kỳ thư viện nào thông qua RPATH của chính nó, RPATH của thư viện sẽ bị ghi đè bởi RPATH của ứng dụng tải nó. Đây là một vấn đề khi bạn không thể đảm bảo rằng RPATH của ứng dụng giống với RPATH của thư viện của bạn, ví dụ: nếu bạn mong đợi các phần phụ thuộc của mình nằm trong một thư mục cụ thể, nhưng thư mục đó không phải là một phần của RPATH của ứng dụng.

Ví dụ: giả sử bạn có một ứng dụng App.exe có phần phụ thuộc được liên kết động vào libstdc ++. So.x cho GCC 4.9. App.exe có sự phụ thuộc này được giải quyết thông qua RPATH, tức là

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

Bây giờ, giả sử có một thư viện khác Dependency.so, có phụ thuộc được liên kết động vào libstdc ++. So.y cho GCC 5.5. Sự phụ thuộc ở đây được giải quyết thông qua RPATH của thư viện, tức là

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

Khi App.exe tải Dependency.so, nó sẽ không thêm vào hoặc thêm vào trước RPATH của thư viện . Nó không tham khảo nó ở tất cả. RPATH duy nhất được coi là của ứng dụng đang chạy hoặc App.exe trong ví dụ này. Điều đó có nghĩa là nếu thư viện dựa vào các ký hiệu có trong gcc5_5 / libstdc ++. So.y nhưng không phải trong gcc4_9 / libstdc ++. So.x, thì thư viện sẽ không tải được.

Đây chỉ là một lời cảnh báo, vì tôi đã từng gặp phải những vấn đề này trong quá khứ. RPATH là một công cụ rất hữu ích nhưng việc triển khai nó vẫn còn một số lỗi.


vì vậy RPATH cho các thư viện được chia sẻ là loại vô nghĩa! Và tôi đã hy vọng rằng họ đã cải thiện Linux một chút về mặt này trong 2 thập kỷ qua ...
Frank Puck

2

Bạn cũng có thể cần đảm bảo rằng bạn không phụ thuộc vào glibc động. Chạy lddtrên tệp thực thi kết quả của bạn và lưu ý bất kỳ phụ thuộc động nào (libc / libm / libpthread là những nghi ngờ đã sử dụng).

Bài tập bổ sung sẽ là xây dựng một loạt các ví dụ C ++ 11 có liên quan bằng cách sử dụng phương pháp này và thực sự thử các tệp nhị phân kết quả trên hệ thống 10.04 thực. Trong hầu hết các trường hợp, trừ khi bạn làm điều gì đó kỳ lạ với tải động, bạn sẽ biết ngay chương trình có hoạt động hay không hay nó bị treo.


1
Vấn đề với phụ thuộc vào glibc động là gì?
Nick Hutchinson

Tôi tin rằng ít nhất một thời gian trước đây libstdc ++ ngụ ý sự phụ thuộc vào glibc. Không chắc mọi thứ đứng ở đâu ngày hôm nay.
Alexander L. Belikoff 10/12/12

9
libstdc ++ không phụ thuộc vào glibc (ví dụ: iostreams được triển khai về mặt printf) nhưng miễn là glibc trên Ubuntu 10.04 cung cấp tất cả các tính năng cần thiết của libstdc ++ mới hơn thì không có vấn đề gì với việc phụ thuộc vào glibc động, trên thực tế, bạn không nên liên kết tĩnh để glibc
Jonathan Wakely

1

Tôi muốn thêm vào câu trả lời của Jonathan Wakely phần sau.

Chơi xung quanh -static-libstdc++linux, tôi đã phải đối mặt với vấn đề dlclose(). Giả sử chúng ta có một ứng dụng 'A' được liên kết tĩnh libstdc++và nó tải được liên kết động với libstdc++plugin 'P' trong thời gian chạy. Tốt rồi. Nhưng khi 'A' dỡ 'P', lỗi phân đoạn xảy ra. Giả định của tôi là sau khi dỡ bỏ libstdc++.so, 'A' không còn có thể sử dụng các ký hiệu liên quan đến libstdc++. Lưu ý rằng nếu cả 'A' và 'P' được liên kết tĩnh libstdc++hoặc nếu 'A' được liên kết động và 'P' tĩnh, sự cố sẽ không xảy ra.

Tóm tắt: nếu ứng dụng của bạn tải / dỡ các plugin có thể liên kết động đến libstdc++, ứng dụng cũng phải được liên kết động với nó. Đây chỉ là quan sát của tôi và tôi muốn nhận được ý kiến ​​của bạn.


1
Điều này có lẽ giống với việc trộn các triển khai libc (giả sử liên kết động với một plugin có thể liên kết động đến glibc, trong khi bản thân ứng dụng được liên kết tĩnh với musl-libc). Rich Felker, tác giả của musl-libc, tuyên bố rằng vấn đề trong một tình huống như vậy là việc quản lý bộ nhớ glibc (sử dụng sbrk) đưa ra một số giả định nhất định và khá nhiều hy vọng sẽ ở một mình trong một quá trình ... không chắc điều này có giới hạn ở một phiên bản glibc cụ thể hoặc bất cứ điều gì, mặc dù.
0xC0000022L

và mọi người vẫn không thấy những ưu điểm của giao diện heap cửa sổ, giao diện này có thể xử lý nhiều bản sao độc lập của libc ++ / libc trong một quy trình duy nhất. Những người như vậy không nên thiết kế phần mềm.
Frank Puck

@FrankPuck có khá nhiều kinh nghiệm về cả Windows và Linux, tôi có thể nói với bạn rằng cách "Windows" thực hiện sẽ không giúp ích được gì cho bạn khi MSVC là bên quyết định việc cấp phát được sử dụng như thế nào và như thế nào. Ưu điểm chính mà tôi thấy với heaps trên Windows là bạn có thể phân phối các bit và mảnh và sau đó giải phóng chúng trong một lần rơi. Nhưng với MSVC, bạn vẫn sẽ gặp phải khá nhiều vấn đề được mô tả ở trên, ví dụ: khi chuyển xung quanh các con trỏ được phân bổ bởi thời gian chạy VC khác (phát hành so với gỡ lỗi hoặc liên kết tĩnh so với liên kết động). Vì vậy, "Windows" không được miễn dịch. Cần phải cẩn thận trên cả hai hệ thống.
0xC0000022L
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.