Bài 39.224 phút đọc
Thứ tự -l và cách trình liên kết duyệt
Sau bài này bạn sẽ làm được
- Tái hiện lỗi undefined reference do sai thứ tự
- Giải thích cách trình liên kết duyệt trái sang phải
- Xử lý phụ thuộc vòng giữa hai thư viện
- Biết khi nào cần --start-group và --whole-archive
Đây là câu hỏi phỏng vấn kinh điển và cũng là thứ làm mất nhiều giờ nhất của người mới: cùng một lệnh, chỉ đổi chỗ hai từ, một cái chạy một cái không. Lý do rất đơn giản và rất hợp lý.
#Hiện tượng
terminal
# Thư viện đứng TRƯỚC
gcc -lmath -Lbuild -Iinclude main.c -o app.exe
main.c:(.text+0x1a): undefined reference to `dt_hinh_tron' main.c:(.text+0x47): undefined reference to `cv_hinh_tron' main.c:(.text+0x6d): undefined reference to `giai_thua' collect2.exe: error: ld returned 1 exit status
# Thư viện đứng SAU
gcc -Iinclude main.c -Lbuild -lmath -o app.exe && ./app.exe
dt(2) = 12.56636 cv(2) = 12.56636 5! = 120
#Vì sao trình liên kết làm vậy
Thuật toán của trình liên kết, viết ra bằng lời
tap_thieu = {} /* ky hieu can ma chua co */
tap_co = {} /* ky hieu da co dinh nghia */
cho moi doi so tren dong lenh, TU TRAI SANG PHAI:
neu la mot tep .o:
nap NO VAO KET QUA, khong dieu kien
them ky hieu no dinh nghia vao tap_co
them ky hieu no can vao tap_thieu
neu la mot thu vien .a:
cho moi tep .o trong thu vien:
neu no dinh nghia mot ky hieu dang nam trong tap_thieu:
nap .o do vao ket qua
cap nhat tap_co va tap_thieu
nguoc lai:
BO QUA, va khong bao gio xet lai
cuoi cung: neu tap_thieu con phan tu -> undefined reference#Phụ thuộc vòng
/* liba.a can mot ham trong libb.a
libb.a can mot ham trong liba.a */
gcc main.c -la -lb /* liba lay xong, den libb lay xong,
nhung libb lam liba thieu them ->
undefined reference */
gcc main.c -la -lb -la /* GIAI PHAP THO: liet ke lai */
gcc main.c -Wl,--start-group -la -lb -Wl,--end-group /* GIAI PHAP DUNG */
/* --start-group va --end-group bao trinh lien ket:
quet cac thu vien trong nhom NHIEU LUOT cho toi khi khong
lay them duoc gi nua.
Cham hon mot chut, nhung giai quyet moi phu thuoc vong. */Phụ thuộc bắc cầu
/* app can libfoo, libfoo can libz */
gcc main.c -lfoo /* thieu: undefined reference toi ham cua libz */
gcc main.c -lfoo -lz /* dung: libz dat SAU libfoo */
gcc main.c -lz -lfoo /* SAI: luc gap -lz chua ai can no */
/* Quy tac tong quat: sap xep theo THU TU PHU THUOC GIAM DAN.
Thu gan chuong trinh nhat dung truoc, thu nen tang nhat dung sau.
Do la ly do -lm gan nhu luon la co cuoi cung:
gcc main.c -lmylib -lpng -lz -lm
*/#Khi cần cả thư viện
gcc main.c -Wl,--whole-archive -lplugin -Wl,--no-whole-archive -lc
/* --whole-archive: nap MOI tep .o trong thu vien, ke ca cai
khong ai can toi.
Nho dat --no-whole-archive ngay sau, neu khong thi moi thu vien
phia sau, ke ca libc, cung bi nap toan bo. */Mẫu tự đăng ký, và vì sao nó biến mất
/* test_sinhvien.c, mot tep trong thu vien kiem thu */
#include "khung_thu.h"
static void thu_tao(void) { ... }
__attribute__((constructor))
static void dang_ky(void) {
khung_them("sinhvien.tao", thu_tao);
}
/* KHONG ham nao ben ngoai goi vao tep .o nay.
Nen khi lien ket "gcc main.c -ltest", trinh lien ket thay
test_sinhvien.o khong dinh nghia ky hieu nao dang thieu,
va BO QUA no.
Ket qua: bo kiem thu chay ra "0 kiem tra" ma khong bao loi gi.
Ba cach chua: */
/* 1. --whole-archive */
gcc main.c -Wl,--whole-archive -ltest -Wl,--no-whole-archive
/* 2. Lien ket thang tep .o, khong qua thu vien */
gcc main.c test_*.o
/* 3. Bo tu dang ky, goi tuong minh trong main. Cach nay do "ao thuat"
nhat va de go loi nhat, nen thuong la lua chon dung. */#Quy tắc sắp thứ tự
Thư viện đứng trước
gcc -lm -lpng -lz main.c -o app
gcc -L. -lmylib main.c foo.o -o app
gcc $(LDFLAGS) main.c -o app # LDFLAGS chua -l..., dat truocThư viện đứng sau, xếp theo phụ thuộc giảm dần
gcc main.c -lpng -lz -lm -o app
gcc main.c foo.o -L. -lmylib -o app
gcc main.c -o app $(LDLIBS) # LDLIBS o cuoi
# Quy uoc cua Make:
# CFLAGS co cho trinh bien dich, dat truoc
# LDFLAGS duong dan tim thu vien (-L), dat truoc
# LDLIBS danh sach thu vien (-l), dat SAU CUNG
#
# $(CC) $(CFLAGS) $(LDFLAGS) main.c -o app $(LDLIBS)| Tình huống | Cách xử lý |
|---|---|
| undefined reference tới hàm bạn biết là có trong thư viện | Kiểm tra thứ tự -l trước tiên |
| libA cần libB | Đặt -lA trước -lB |
| libA cần libB và libB cần libA | --start-group ... --end-group, và ghi vào việc cần làm |
| Mã tự đăng ký bị biến mất | --whole-archive, hoặc gọi tường minh |
| Không rõ trình liên kết mở tệp nào | gcc -Wl,-t để in ra từng tệp |
| Không rõ ký hiệu đến từ đâu | gcc -Wl,--trace-symbol=ten_ham |
terminal
# Hai cờ chẩn đoán đáng nhớ
gcc main.c -Lbuild -lmath -Wl,--trace-symbol=dt_hinh_tron -o app
build/libmath.a(hinhhoc.o): definition of dt_hinh_tron /tmp/ccXXXX.o: reference to dt_hinh_tron
Tự làm thử
- Tái hiện lỗi bằng cách đặt
-lmathtrướcmain.c. - Chứng minh bằng
nmrằng thư viện thật sự có hàm đó. - Tạo hai thư viện phụ thuộc vòng và sửa bằng
--start-group. - Viết một tệp có
__attribute__((constructor)), đóng vào thư viện, và xác nhận nó bị bỏ qua. - Sửa trường hợp trên bằng
--whole-archive. - Dùng
-Wl,-tđể xem trình liên kết mở những tệp nào.
Trình chấm điểm tự động sẽ được bổ sung ở giai đoạn sau. Hiện tại bạn tự chạy thử trên máy.
Tóm tắt
- Trình liên kết đi một lượt duy nhất từ trái sang phải và không quay lại.
- Tệp
.oluôn được nạp; tệp.achỉ đóng góp những.ođang được cần tại thời điểm gặp nó. - Sắp thư viện theo thứ tự phụ thuộc giảm dần, và
-lmgần như luôn đứng cuối. - Phụ thuộc vòng chữa được bằng
--start-group, nhưng nó là dấu hiệu hai thư viện nên gộp làm một. - Mã tự đăng ký trong thư viện tĩnh bị bỏ qua nếu không ai gọi tới, nên cần
--whole-archivehoặc gọi tường minh.