Bỏ qua điều hướng, tới nội dung chính
Học C
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.dylib
terminal
# 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
LinuxmacOSWindows
Phần mở rộng.so.dylib.dll
Cờ tạo-shared-dynamiclib-shared
Cần -fPICCó, bắt buộcCó, mặc định bậtKhông, PE khác cơ chế
Tệp phụ khi liên kếtKhôngKhôngThư viện nhập .dll.a hoặc .lib
Tìm lúc chạy theoLD_LIBRARY_PATH, rpath, ldconfigDYLD_LIBRARY_PATH, rpathPATH, cùng thư mục exe
Xem phụ thuộclddotool -Lobjdump -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 LinuxGhi chú
1. DT_RPATH trong tệp nhị phânLỗi thời, LD_LIBRARY_PATH không đè được
2. LD_LIBRARY_PATHBiến môi trường
3. DT_RUNPATH trong tệp nhị phânCách hiện đại, LD_LIBRARY_PATH đè được
4. Bộ đệm /etc/ld.so.cacheDo ldconfig sinh
5. /lib rồi /usr/libMặ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 chay
Bố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.9

Tự làm thử

  1. Tạo cùng một thư viện ở cả hai dạng và so kích thước tệp nhị phân.
  2. Bỏ -fPIC trên Linux và đọc thông báo lỗi của trình liên kết.
  3. Xóa thư viện động rồi chạy chương trình, ghi lại thông báo.
  4. Dùng -Wl,-rpath,'$ORIGIN' và xác nhận bằng readelf -d.
  5. Chạy LD_DEBUG=libs và đọc thứ tự tìm kiếm thật.
  6. Trên Windows, để cả libmath.a và libmath.dll.a trong một thư mục rồi kiểm tra bằng objdump -p xem 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.
  • -fPIC bắ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ơn LD_LIBRARY_PATH vì nó nằm trong tệp nhị phân và không lan sang tiến trình con.
  • Trên Windows, -shared sinh ra hai tệp: DLL chứa mã, còn thư viện nhập chỉ chứa các mẩu nhảy.