Dễ dàng kiểm tra các ký hiệu chưa được giải quyết trong các thư viện được chia sẻ?


82

Tôi đang viết một thư viện đối tượng chia sẻ C ++ khá lớn và đã gặp phải một vấn đề nhỏ khiến việc gỡ lỗi trở nên khó khăn:

Nếu tôi xác định một hàm / phương thức trong tệp tiêu đề và quên tạo sơ khai cho nó (trong quá trình phát triển), vì tôi đang xây dựng dưới dạng thư viện đối tượng được chia sẻ chứ không phải là tệp thực thi, không có lỗi nào xuất hiện tại thời điểm biên dịch cho tôi biết tôi có quên thực hiện chức năng đó. Cách duy nhất tôi phát hiện ra điều gì đó không ổn là trong thời gian chạy, khi cuối cùng một ứng dụng liên kết với thư viện này gặp lỗi 'ký hiệu không xác định'.

Tôi đang tìm một cách dễ dàng để kiểm tra xem tôi có tất cả các ký hiệu tôi cần tại thời điểm biên dịch hay không, có lẽ một số thứ tôi có thể thêm vào Makefile của mình.

Một giải pháp mà tôi đã đưa ra là chạy thư viện đã biên dịch nm -C -Uđể có được danh sách tất cả các tài liệu tham khảo không xác định được gỡ rối. Vấn đề là điều này cũng xuất hiện với danh sách tất cả các tài liệu tham khảo nằm trong các thư viện khác, chẳng hạn như GLibC, tất nhiên sẽ được liên kết với thư viện này khi ứng dụng cuối cùng được kết hợp với nhau. Có thể sử dụng đầu ra của nmđể grepthông qua tất cả các tệp tiêu đề của tôi và xem có tên nào tương ứng không .. nhưng điều này có vẻ điên rồ. Chắc chắn đây không phải là một vấn đề phổ biến và có một cách tốt hơn để giải quyết nó?


3
nm -C -uđã cứu tôi nhiều lần! (lưu ý chữ thường -utrên hệ thống của tôi.) Để lại nhận xét này ở đây để tôi có thể tìm thấy nó vào lần sau nếu tôi cần nó.
hung thủ

Câu trả lời:


93

Kiểm tra tùy chọn trình liên kết -z defs/ --no-undefined. Khi tạo đối tượng dùng chung sẽ khiến liên kết bị lỗi nếu có các ký hiệu chưa được giải quyết.

Nếu bạn đang sử dụng gcc để gọi trình liên kết, bạn sẽ sử dụng -Wltùy chọn trình biên dịch để chuyển tùy chọn đến trình liên kết:

gcc -shared ... -Wl,-z,defs

Ví dụ, hãy xem xét tệp sau:

#include <stdio.h>

void forgot_to_define(FILE *fp);

void doit(const char *filename)
{
    FILE *fp = fopen(filename, "r");
    if (fp != NULL)
    {
        forgot_to_define(fp);
        fclose(fp);
    }
}

Bây giờ, nếu bạn xây dựng nó thành một đối tượng được chia sẻ, nó sẽ thành công:

> gcc -shared -fPIC -o libsilly.so silly.c && echo succeeded || echo failed
succeeded

Nhưng nếu bạn thêm -z defs, liên kết sẽ không thành công và cho bạn biết về biểu tượng bị thiếu của bạn:

> gcc -shared -fPIC -o libsilly.so silly.c -Wl,-z,defs && echo succeeded || echo failed
/tmp/cccIwwbn.o: In function `doit':
silly.c:(.text+0x2c): undefined reference to `forgot_to_define'
collect2: ld returned 1 exit status
failed

3
+1 câu trả lời này có thể hỗ trợ những người đến từ nền Windows mà các ký hiệu bên ngoài DLL phải được giải quyết trong thời gian biên dịch. Cách để bắt đầu đoạn mã đẹp!
Shmil The Cat

@ShmilTheCat: Xin chào, tôi đã thử đưa -Wl, -z, defs vào tệp thực hiện của mình, tôi vẫn gặp lỗi ký hiệu không xác định khi chạy nhưng không phải trong khi biên dịch. Tôi có thể làm gì?. Trong kịch bản của tôi, tôi có hai thư mục bên trong một thư mục khác, (giả sử, A / B, i, e B nằm bên trong A) và tôi có thể thấy một vài ký hiệu hoặc B, trong "libA.so". Tôi không chắc liệu mọi ký hiệu trong B đều có trong "libA.so" hay không. Làm thế nào để tôi đảm bảo điều đó?
Vinay

@ShmilTheCat Để làm cho mọi thứ giống như một Windows DLL, bạn có thể thêm -Bsymbolicvào dòng lệnh của trình liên kết. Ngay cả khi forgot_to_definebây giờ tồn tại trong thư viện nhờ -zkiểm tra, tệp thực thi vẫn có thể ghi đè nó bằng định nghĩa riêng của nó và định nghĩa riêng của thư viện sẽ chuyển sang mục ghi đè đó; -Bsymbolicbuộc mọi thứ để các định nghĩa riêng của thư viện đối với các chức năng của nó chuyển sang các chức năng của nó.
Kaz

Chỉ hoạt động cho các chức năng thực sự được sử dụng. Nếu tôi làm điều // forgot_to_define(fp);đó, nó không thông báo lỗi
thợ sửa chữa

17

Trên Linux (có vẻ như bạn đang sử dụng) ldd -r a.outsẽ cung cấp cho bạn chính xác câu trả lời mà bạn đang tìm kiếm.

CẬP NHẬT: một cách đơn giản để tạo ra a.outđể kiểm tra:

 echo "int main() { return 0; }" | g++ -xc++ - ./libMySharedLib.so
 ldd -r ./a.out

4
Điều đó gần như giống hệt như nm -C -U, mà tôi đã đề xuất trong bài viết gốc. Vấn đề là không có ứng dụng 'a.out' nào để nói, đó là một thư viện được chia sẻ, vì vậy ldd -r mylibrary.so cung cấp toàn bộ đầu ra, vì nó sử dụng các ký hiệu từ nhiều thư viện động khác .. Tôi đặc biệt quan tâm thiếu ký hiệu được xác định trong tệp tiêu đề của tôi , thay vì thư viện bên ngoài.
David Claridge

1
cái này nên được chấp nhận, câu hỏi là cách dễ dàng để kiểm tra những biểu tượng không xác định, không làm thế nào để tránh những biểu tượng không xác định là những gì
workplaylifecycle

8

Còn testsuite thì sao? Bạn tạo các tệp thực thi giả liên kết đến các ký hiệu bạn cần. Nếu liên kết không thành công, điều đó có nghĩa là giao diện thư viện của bạn chưa hoàn chỉnh.


3

Tôi đã có cùng một vấn đề một lần. Tôi đang phát triển một mô hình thành phần trong C ++, và tất nhiên, các thành phần sẽ tải động trong thời gian chạy. Tôi nghĩ đến ba giải pháp, đó là những giải pháp tôi đã áp dụng:

  1. Dành một chút thời gian để xác định một hệ thống xây dựng có thể biên dịch tĩnh. Bạn sẽ mất một chút thời gian để thiết kế nó, nhưng nó sẽ giúp bạn tiết kiệm được nhiều thời gian để bắt những lỗi thời gian chạy khó chịu này.
  2. Nhóm các chức năng của bạn trong các phần nổi tiếng và dễ hiểu, để bạn có thể nhóm các chức năng / sơ khai để đảm bảo rằng mỗi chức năng tương ứng đều có sơ khai. Nếu bạn dành thời gian để ghi lại nó tốt, bạn có thể viết một tập lệnh kiểm tra các định nghĩa (ví dụ: thông qua các nhận xét doxygen của nó) và kiểm tra tệp .cpp tương ứng cho nó.
  3. Thực hiện một số tệp thực thi thử nghiệm tải cùng một bộ thư viện và chỉ định cờ RTLD_NOW thành dlopen (nếu bạn dưới * NIX). Họ sẽ báo hiệu các ký hiệu còn thiếu.

Hy vọng rằng sẽ giúp.


trên các dòng của RTLD_NOW, có tồn tại một env var để buộc liên kết ngay lập tức (không lười biếng) không?
Stefano Borini

1
vâng. đây là LD_BIND_NOW (libc5; glibc kể từ 2.1.1) Nếu được đặt thành chuỗi không trống, trình liên kết động giải quyết tất cả các ký hiệu khi khởi động chương trình thay vì trì hoãn việc phân giải lệnh gọi hàm đến thời điểm chúng được tham chiếu lần đầu tiên. Điều này rất hữu ích khi sử dụng trình gỡ lỗi.
Stefano Borini

Điều này cũng có thể hữu ích cho mục đích của OP: LD_WARN (chỉ ELF) (glibc kể từ 2.1.3) Nếu được đặt thành chuỗi không trống, hãy cảnh báo về các ký hiệu chưa được giải quyết.
Stefano Borini 24/10/09

Stefano: Vâng, đúng vậy. Các biến đó tồn tại. Tuy nhiên, nếu bạn đang trì hoãn tải động cho đến sau này (giả sử khi người dùng tải mô-đun) thì các biến này không hữu ích trừ khi bạn thực sự viết chương trình thử nghiệm tải các thư viện dự định (tương lai).
Diego Sevilla
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.