Bài 39.330 phút đọc
Thư viện động và vì sao cần -fPIC
Sau bài này bạn sẽ làm được
- Dịch với -fPIC và tạo thư viện chia sẻ
- Giải thích vì sao mã trong thư viện chia sẻ không được chứa địa chỉ tuyệt đối
- Chạy chương trình phụ thuộc thư viện động
- Xử lý lỗi không tìm thấy thư viện lúc chạy
Thư viện động khác thư viện tĩnh ở một điểm quyết định: nó là một chương trình đã liên kết xong, không phải một cái túi đựng tệp .o. Chính vì vậy nó cần -fPIC, và chính vì vậy nó gây ra một lớp vấn đề mới xảy ra lúc chạy chứ không lúc dịch.
#Tạo thư viện động
# Linux va macOS
gcc -fPIC -c -Iinclude src/hinhhoc.c -o build/hinhhoc.o
gcc -fPIC -c -Iinclude src/sohoc.c -o build/sohoc.o
gcc -shared build/hinhhoc.o build/sohoc.o -o build/libmath.so
# Windows voi MinGW
gcc -c -Iinclude src/hinhhoc.c -o build/hinhhoc.o
gcc -c -Iinclude src/sohoc.c -o build/sohoc.o
gcc -shared build/*.o -o build/libmath.dll \
-Wl,--out-implib,build/libmath.dll.a
# macOS goi no la .dylib va co mot vai co khac:
gcc -dynamiclib -install_name @rpath/libmath.dylib *.o -o libmath.dylibterminal
# Trên Windows, một lệnh sinh ra hai tệp
ls -l build/libmath.dll build/libmath.dll.a
-rwxr-xr-x 1 DELL 197609 48223 build/libmath.dll -rw-r--r-- 1 DELL 197609 3242 build/libmath.dll.a
gcc -Iinclude main.c -Lbuild -lmath -o app_dyn.exe
PATH="$PWD/build:$PATH" ./app_dyn.exe
dt(2) = 12.56636 cv(2) = 12.56636 5! = 120
| Linux | macOS | Windows | |
|---|---|---|---|
| Phần mở rộng | .so | .dylib | .dll |
| Cờ tạo | -shared | -dynamiclib | -shared |
| Cần -fPIC | Có, bắt buộc | Có, mặc định bật | Không, PE khác cơ chế |
| Tệp phụ khi liên kết | Không | Không | Thư viện nhập .dll.a hoặc .lib |
| Tìm lúc chạy theo | LD_LIBRARY_PATH, rpath, ldconfig | DYLD_LIBRARY_PATH, rpath | PATH, cùng thư mục exe |
| Xem phụ thuộc | ldd | otool -L | objdump -p, Dependencies |
#Vì sao -fPIC bắt buộc
Mã độc lập vị trí (position independent code)
Mã không chứa địa chỉ tuyệt đối nào. Mọi tham chiếu tới hàm và biến toàn cục đi qua một bảng được điền lúc nạp, nên cùng một thư viện nạp được vào bất kỳ địa chỉ nào của bất kỳ tiến trình nào.
Vấn đề, viết ra bằng hình
/* Tien trinh A nap libmath.so vao dia chi 0x7f1000000000
Tien trinh B nap CUNG libmath.so vao 0x7f9000000000
Neu ma trong thu vien co lenh:
mov eax, DWORD PTR [0x7f1000001234] <- dia chi TUYET DOI
thi no chi dung o tien trinh A. O tien trinh B, dia chi do
tro vao cho khac han.
Ma doc lap vi tri thay bang:
mov eax, DWORD PTR [rip + 0x1234] <- LECH so voi vi tri hien tai
Lech thi khong doi du nap o dau, nen mot ban ma dung duoc
cho moi tien trinh. Do la ca diem cua thu vien chia se. */Hai bảng: GOT và PLT
/* GOT, Global Offset Table:
bang dia chi cua BIEN toan cuc va cua ham, dat trong vung du lieu
cua tung tien trinh. Trinh nap dien vao luc nap.
PLT, Procedure Linkage Table:
mot doan ma trung gian cho moi ham. Lan goi dau tien no nho
trinh nap tim dia chi that roi ghi vao GOT. Cac lan sau nhay thang.
Do la co che LIEN KET LUOI: ham nao khong ai goi thi khong bao gio
phai tim dia chi. Voi thu vien lon nhu libQt thi tiet kiem dang ke
thoi gian khoi dong. */
$ objdump -R libmath.so | head -8
DYNAMIC RELOCATION RECORDS
OFFSET TYPE VALUE
0000000000003fa0 R_X86_64_GLOB_DAT __gmon_start__
0000000000004018 R_X86_64_JUMP_SLOT __stack_chk_fail@GLIBC_2.4#Chạy chương trình
terminal
# Xóa thư viện đi rồi chạy
mv build/libmath.dll build/libmath.dll.bak && ./app_dyn.exe
echo $?
127
Bốn cách để chương trình tìm thấy thư viện
# 1. Bien moi truong, tam thoi, tot khi phat trien
LD_LIBRARY_PATH=build ./app # Linux
DYLD_LIBRARY_PATH=build ./app # macOS
PATH="$PWD/build:$PATH" ./app.exe # Windows
# 2. Nhung duong dan vao chinh tep nhi phan: rpath
gcc main.c -Lbuild -lmath -Wl,-rpath,'$ORIGIN' -o app
# $ORIGIN nghia la "thu muc chua tep nhi phan nay"
# Nho dau nhay don de shell khong bung $ORIGIN
gcc main.c -Lbuild -lmath -Wl,-rpath,'$ORIGIN/../lib' -o app
# 3. Cai vao he thong
sudo cp build/libmath.so /usr/local/lib/
sudo ldconfig # cap nhat bo dem cua trinh nap
ldconfig -p | grep libmath # kiem tra
# 4. Windows: dat DLL canh tep exe
cp build/libmath.dll . # cach don gian va pho bien nhat| Thứ tự tìm kiếm trên Linux | Ghi chú |
|---|---|
| 1. DT_RPATH trong tệp nhị phân | Lỗi thời, LD_LIBRARY_PATH không đè được |
| 2. LD_LIBRARY_PATH | Biến môi trường |
| 3. DT_RUNPATH trong tệp nhị phân | Cách hiện đại, LD_LIBRARY_PATH đè được |
| 4. Bộ đệm /etc/ld.so.cache | Do ldconfig sinh |
| 5. /lib rồi /usr/lib | Mặc định của hệ thống |
#Trên Windows: DLL và thư viện nhập
terminal
objdump -p app_dyn.exe | grep 'DLL Name'
DLL Name: libmath.dll DLL Name: KERNEL32.dll DLL Name: msvcrt.dll
# So với bản liên kết tĩnh
objdump -p app.exe | grep 'DLL Name'
DLL Name: KERNEL32.dll DLL Name: msvcrt.dll
#Bốn lỗi thường gặp
Bốn cách mất thời gian
# 1. Quen -fPIC
gcc -c a.c && gcc -shared a.o -o lib.so
# relocation R_X86_64_PC32 ... recompile with -fPIC
# 2. Sua thu vien nhung quen chep sang cho chay
make && ./app # van chay ban .so cu trong /usr/local/lib
# 3. Hai ban thu vien khac phien ban tren cung may
ldd app | grep libfoo
libfoo.so.1 => /usr/lib/libfoo.so.1 # khong phai ban ban vua build
# 4. Header va thu vien lech phien ban
# dich voi foo.h ban 2.0, chay voi libfoo.so ban 1.0
# -> khong loi luc dich, sap luc chayBốn cách phòng
# 1. Luon -fPIC cho moi tep .o vao thu vien chia se
CFLAGS_SO = -fPIC $(CFLAGS)
# 2. Kiem tra chuong trinh dang nap ban NAO
ldd app # Linux
LD_DEBUG=libs ./app 2>&1 | head # rat chi tiet
objdump -p app.exe | grep DLL # Windows
# 3. Trong Makefile, cho app phu thuoc vao thu vien
app: main.o build/libmath.so
$(CC) main.o -Lbuild -lmath -Wl,-rpath,'$$ORIGIN/build' -o $@
# 4. Chot phien bang mot hang so trong header VA mot ham trong thu vien
# Xem Bai 39.9Tự làm thử
- Tạo cùng một thư viện ở cả hai dạng và so kích thước tệp nhị phân.
- Bỏ
-fPICtrên Linux và đọc thông báo lỗi của trình liên kết. - Xóa thư viện động rồi chạy chương trình, ghi lại thông báo.
- Dùng
-Wl,-rpath,'$ORIGIN'và xác nhận bằngreadelf -d. - Chạy
LD_DEBUG=libsvà đọc thứ tự tìm kiếm thật. - Trên Windows, để cả
libmath.avàlibmath.dll.atrong một thư mục rồi kiểm tra bằngobjdump -pxem cái nào được dùng.
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
- Thư viện động là một chương trình đã liên kết xong, không phải túi đựng tệp
.o. -fPICbắt buộc trên ELF vì cùng một thư viện nạp vào địa chỉ khác nhau ở mỗi tiến trình; chi phí khoảng một tới ba phần trăm.- Thư viện động sinh ra một lớp lỗi mới: dịch sạch, liên kết sạch, rồi không khởi động được vì thiếu tệp.
-Wl,-rpath,'$ORIGIN'tốt hơnLD_LIBRARY_PATHvì nó nằm trong tệp nhị phân và không lan sang tiến trình con.- Trên Windows,
-sharedsinh ra hai tệp: DLL chứa mã, còn thư viện nhập chỉ chứa các mẩu nhảy.