Quản lý Shared Library trên Linux: Giải thích ldconfig, ldd và LD_LIBRARY_PATH

Linux tutorial - IT technology blog
Linux tutorial - IT technology blog

Vấn đề: App Biên dịch Thành công Nhưng Không Chạy Được

Bạn build một ứng dụng C++, mọi thứ biên dịch sạch, rồi chạy thử:

./myapp
./myapp: error while loading shared libraries: libfoo.so.2: cannot open shared object file: No such file or directory

Library đang nằm ngay đó trong /usr/local/lib. Bạn nhìn thấy nó rõ ràng. Vậy mà Linux vẫn không tìm được — cảm giác như hệ thống đang nói dối.

Không phải vậy. Linux không duyệt toàn bộ filesystem để tìm shared library lúc runtime. Nó đọc từ một cache được dựng sẵn. Khi cache đó không chứa thư mục của bạn, dynamic linker sẽ thất bại dù file nằm cách đó chỉ vài folder. Hiểu được điều này, cách sửa sẽ chuyển từ bí ẩn thành đơn giản.

Cách Linux Tìm Shared Library: Ba Phương pháp

Có ba cơ chế kiểm soát nơi Linux tìm kiếm shared library. Chúng khác nhau về phạm vi, tính bền vững và tình huống phù hợp để dùng.

Phương pháp 1: Đăng ký Toàn hệ thống với ldconfig

ldconfig đọc /etc/ld.so.conf và các file cấu hình con trong /etc/ld.so.conf.d/, quét tất cả các thư mục được liệt kê, rồi ghi một cache nhị phân vào /etc/ld.so.cache. Khi khởi động, dynamic linker (ld.so) đọc trực tiếp cache đó — không duyệt filesystem, chỉ là tra cứu nhanh.

Các đường dẫn tìm kiếm mặc định là /lib, /lib64, /usr/lib/usr/lib64. Bất kỳ thư mục nào nằm ngoài những đường dẫn này đều cần được đăng ký tường minh.

Phương pháp 2: Biến Môi trường LD_LIBRARY_PATH

LD_LIBRARY_PATH thêm các thư mục bổ sung vào đầu đường dẫn tìm kiếm library cho tiến trình hiện tại và các tiến trình con. Nó ghi đè hoàn toàn ldconfig cache.

export LD_LIBRARY_PATH=/usr/local/lib/myapp:$LD_LIBRARY_PATH
./myapp

Không cần rebuild cache. Không cần chỉnh file cấu hình. Không cần quyền root. Thay đổi có hiệu lực ngay lập tức trong shell hiện tại.

Phương pháp 3: Nhúng Đường dẫn vào Lúc Biên dịch (rpath)

Khi biên dịch, bạn có thể gắn cứng một đường dẫn tìm kiếm vào trong binary bằng -Wl,-rpath:

gcc -o myapp main.c -L/usr/local/lib -lfoo -Wl,-rpath,/usr/local/lib

Binary đó sẽ luôn kiểm tra thư mục này trước, bất kể cấu hình ldconfig hay LD_LIBRARY_PATH là gì. Không cần cấu hình bên ngoài nào sau khi triển khai.

Ưu và Nhược điểm của Mỗi Phương pháp

Phương pháp Ưu điểm Nhược điểm
ldconfig Toàn hệ thống, bền vững, gọn gàng, mọi tiến trình đều hưởng lợi Cần quyền root, phải rebuild sau khi thêm library mới
LD_LIBRARY_PATH Tức thì, không cần root, lý tưởng để kiểm thử Chỉ có hiệu lực trong phiên hiện tại, rủi ro bảo mật nếu đặt toàn cục, có thể ghi đè thầm lặng lên system lib
rpath Binary tự chứa, hoạt động không cần cấu hình hệ thống Khó thay đổi sau khi biên dịch, hỏng nếu library bị di chuyển

Rủi ro bảo mật của LD_LIBRARY_PATH cần được coi trọng. Một library độc hại được đặt vào đường dẫn đó có thể chiếm quyền kiểm soát bất kỳ chương trình nào chạy trong phiên đó. Đó là lý do kernel hoàn toàn bỏ qua nó với các binary setuid. Tuyệt đối không thêm vào /etc/environment hay các script đăng nhập toàn hệ thống.

Cách Thiết lập Được Khuyến nghị cho Hầu hết Trường hợp

Library cài vào /usr/local, hoặc bất kỳ thứ gì cài qua package manager? Dùng ldconfig. Gọn gàng, bền vững, có sẵn cho mọi tiến trình trên hệ thống.

Thử nghiệm một phiên bản library hay debug một bản build? LD_LIBRARY_PATH rất tiện — nhưng chỉ trong phiên terminal hiện tại. Đừng để nó lọt vào các init script của môi trường production.

Triển khai một gói ứng dụng độc lập hoặc container image? rpath phát huy tác dụng ở đây. Binary mang theo thông tin đường dẫn của riêng nó và không phụ thuộc vào trạng thái ldconfig trên máy đích.

Trên một server production Ubuntu 22.04 tôi đang quản lý, kết hợp cả hai chiến lược — ldconfig cho system library, rpath cho các công cụ đóng gói sẵn — giúp giảm đáng kể ma sát khi triển khai. Một kỹ sư mới có thể dựng lại máy và các library sẽ tự resolve mà không cần thao tác thủ công nào.

Hướng dẫn Thực hiện

Bước 1: Chẩn đoán Vấn đề bằng ldd

Luôn bắt đầu từ đây. ldd liệt kê mọi shared library mà một binary cần và cho biết linker có tìm thấy từng cái không:

ldd /usr/bin/myapp

Ví dụ output:

linux-vdso.so.1 (0x00007ffd3e9f7000)
libfoo.so.2 => not found
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f8b2a200000)
/lib64/ld-linux-x86-64.so.2 (0x00007f8b2a600000)

Dòng not found chính là mục tiêu cần xử lý. Mọi thứ còn lại đã được resolve rồi. Bây giờ tìm xem file thực sự nằm ở đâu:

find /usr -name "libfoo.so*" 2>/dev/null
find /usr/local -name "libfoo.so*" 2>/dev/null

Bước 2: Đăng ký Thư mục Library Mới với ldconfig

Đã biên dịch một library từ source và nó nằm ở /usr/local/lib? Đường dẫn này thường không có trong danh sách ldconfig mặc định. Thêm vào bằng một lệnh:

echo "/usr/local/lib" | sudo tee /etc/ld.so.conf.d/local-libs.conf

Rebuild cache:

sudo ldconfig

Kiểm tra kết quả:

ldconfig -p | grep libfoo
# Kết quả mong đợi:
# libfoo.so.2 (libc6,x86-64) => /usr/local/lib/libfoo.so.2

Chạy lại ldd. Dòng not found lẽ ra đã biến mất.

Bước 3: Xử lý Symlink Bị Thiếu

Đôi khi file library tồn tại nhưng các symlink của nó bị hỏng hoặc vắng mặt. Shared library dùng quy ước đặt tên ba lớp:

  • libfoo.so.2.1.3 — file versioned thực tế
  • libfoo.so.2 — soname symlink (thứ các chương trình link vào lúc runtime)
  • libfoo.so — linker name (chỉ dùng lúc biên dịch)

Nếu chỉ có file versioned, hãy tạo symlink thủ công:

cd /usr/local/lib
sudo ln -sf libfoo.so.2.1.3 libfoo.so.2
sudo ln -sf libfoo.so.2 libfoo.so
sudo ldconfig

Bước 4: Dùng LD_LIBRARY_PATH để Kiểm thử

Cần kiểm thử một phiên bản library cụ thể mà không động vào cấu hình hệ thống? Đặt biến trong shell hiện tại:

# Tạm thời, chỉ trong phiên hiện tại
export LD_LIBRARY_PATH=/home/user/myproject/libs:$LD_LIBRARY_PATH
./myapp

# Hoặc inline cho một lần chạy duy nhất
LD_LIBRARY_PATH=/home/user/myproject/libs ./myapp

Khi nhiều phiên bản của cùng một library tồn tại, không phải lúc nào cũng rõ linker chọn cái nào. Dùng LD_DEBUG để xem chính xác điều gì đang xảy ra:

LD_DEBUG=libs ./myapp 2>&1 | grep libfoo

Bước 5: Kiểm tra Sau khi Cài qua Package Manager

Các package manager như apt và dnf tự động chạy ldconfig trong script post-install của chúng. Cài thủ công từ tarball thì không. Sau bất kỳ lần cài library thủ công nào, hãy tự chạy:

# Sau khi cài library thủ công
sudo ldconfig

# Xác nhận
ldconfig -p | grep "tên-library-của-bạn"

Thêm: Kiểm tra Library mà Một Tiến trình Đang Dùng

Không cần restart để kiểm tra một tiến trình đang chạy. Đọc trực tiếp từ /proc:

# Lấy PID
pgrep myapp

# Liệt kê tất cả library đã nạp cho tiến trình đó
cat /proc/<PID>/maps | grep "\.so"

# Hoặc dùng lsof
lsof -p <PID> | grep ".so"

Tóm tắt Nhanh: Cây Quyết định

  1. Chạy ldd ./binary — xác định những gì còn thiếu.
  2. Chạy find /usr /usr/local -name "libXXX.so*" — kiểm tra file có tồn tại không.
  3. File tồn tại nhưng linker không tìm thấy: thêm thư mục vào /etc/ld.so.conf.d/ và chạy sudo ldconfig.
  4. Symlink bị thiếu: tạo thủ công, rồi chạy sudo ldconfig.
  5. Library thực sự chưa được cài: lấy gói dev (apt install libfoo-dev trên Debian/Ubuntu, dnf install libfoo-devel trên RHEL/Fedora).
  6. Chỉ để kiểm thử: dùng LD_LIBRARY_PATH trong phiên hiện tại, không dùng ở nơi nào khác.

Lỗi shared library trông có vẻ khó hiểu cho đến khi bạn nắm được một điều: Linux dùng cache, không phải duyệt filesystem. Khi mô hình tư duy đó đã vào đầu, cách sửa hầu như luôn là một trong năm bước trên. Luôn bắt đầu bằng ldd — nó cho thấy chính xác những gì linker nhìn thấy và cắt bỏ hoàn toàn phần phán đoán mò mẫm.

Share: