Bài 39.626 phút đọc
Điều khiển ký hiệu xuất ra
Sau bài này bạn sẽ làm được
- Đếm số ký hiệu một thư viện xuất ra
- Giấu bớt ký hiệu bằng visibility trên ELF
- Dùng __declspec(dllexport) trên Windows
- Nêu bốn lợi ích của việc giấu ký hiệu nội bộ
Mỗi hàm không khai báo static đều bị xuất ra khỏi thư viện của bạn, kể cả những hàm bạn viết cho riêng mình. Người dùng thư viện gọi được chúng, và từ lúc đó chúng thành một phần API mà bạn không định tạo ra.
#Mặc định xuất ra mọi thứ
src/noibo.c
#include "mathlib.h"
/* Ham nay chi de hai ham khac trong thu vien dung.
No KHONG co trong mathlib.h. */
int ham_noi_bo_khong_muon_xuat(int x) { return x * 2; }
double ham_cong_khai(double r) { return dt_hinh_tron(r); }terminal
gcc -shared build/*.o -o build/lib2.dll && objdump -p build/lib2.dll | sed -n '/Ordinal.Name Pointer. Table/,/^$/p'
[Ordinal/Name Pointer] Table [ 0] cv_hinh_tron [ 1] dt_hinh_tron [ 2] giai_thua [ 3] ham_cong_khai [ 4] ham_noi_bo_khong_muon_xuat [ 5] uoc_chung_lon_nhat
#Trên ELF: visibility
/* Bon muc hien thi, tu ro nhat toi kin nhat: */
__attribute__((visibility("default"))) /* xuat ra, mac dinh */
__attribute__((visibility("protected"))) /* xuat ra, nhung ben ngoai
khong de len duoc */
__attribute__((visibility("hidden"))) /* KHONG xuat ra khoi .so,
nhung cac .o trong .so dung duoc */
__attribute__((visibility("internal"))) /* nhu hidden, va them mot vai
bao dam ve ABI */Cách dùng: giấu hết rồi mở ra từng cái
# Dich TOAN BO thu vien voi mac dinh la an
gcc -fvisibility=hidden -fPIC -c src/*.c
gcc -shared *.o -o libmath.so
# Roi danh dau nhung ham CONG KHAI trong header:
/* mathlib.h */
#define ML_API __attribute__((visibility("default")))
ML_API double dt_hinh_tron(double r);
ML_API double cv_hinh_tron(double r);
ML_API long giai_thua(int n);
/* Ham noi bo thi khong danh dau gi, nen no theo mac dinh: an. */terminal
# Trên Linux, đo số ký hiệu trước và sau
nm -D --defined-only libmath.so | grep ' T ' | wc -l
6
gcc -fvisibility=hidden -fPIC -c src/*.c && gcc -shared *.o -o libmath.so && nm -D --defined-only libmath.so | grep ' T ' | wc -l
3
#Trên Windows: dllexport
terminal
# -fvisibility=hidden KHÔNG có tác dụng trên PE
gcc -shared -fvisibility=hidden build/*.o -o build/lib4.dll && objdump -p build/lib4.dll | sed -n '/Ordinal.Name Pointer. Table/,/^$/p'
[Ordinal/Name Pointer] Table [ 0] cv_hinh_tron [ 1] dt_hinh_tron [ 2] giai_thua [ 3] ham_cong_khai [ 4] ham_noi_bo_khong_muon_xuat [ 5] uoc_chung_lon_nhat
src/noibo2.c
#include "mathlib.h"
int ham_noi_bo_khong_muon_xuat(int x) { return x * 2; }
__declspec(dllexport)
double ham_cong_khai(double r) { return dt_hinh_tron(r); }terminal
gcc -shared build/hinhhoc_pic.o build/sohoc_pic.o build/noibo2.o -o build/lib3.dll && objdump -p build/lib3.dll | sed -n '/Ordinal.Name Pointer. Table/,/^$/p'
[Ordinal/Name Pointer] Table [ 0] ham_cong_khai
| ELF, tức Linux và macOS | PE, tức Windows | |
|---|---|---|
| Mặc định | Xuất ra tất cả | Xuất ra tất cả |
| Cách giấu | -fvisibility=hidden, rồi mở từng cái | Đánh dấu dllexport cho cái muốn xuất |
| Cần đánh dấu khi nhập | Không | Nên có dllimport, để tối ưu |
| Ký hiệu bị đè lúc chạy được | Có, đó là interposition | Không |
| Kịch bản phiên bản | Có | Không, nhưng có tệp .def |
| Kiểu đánh dấu | Thuộc tính GCC | Từ khóa mở rộng của MSVC |
Tệp .def, cách của Windows tương đương kịch bản phiên bản
; libmath.def
LIBRARY libmath
EXPORTS
dt_hinh_tron
cv_hinh_tron
giai_thua
uoc_chung_lon_nhat
; Dich:
; gcc -shared build/*.o libmath.def -o libmath.dll
;
; Uu diem so voi dllexport: danh sach nam o MOT cho, doc duoc,
; va sua duoc ma khong dung toi ma nguon.
;
; Va no cho phep dat SO THU TU co dinh cho tung ham:
; dt_hinh_tron @1
; cv_hinh_tron @2
; De giu ABI on dinh khi ban them ham moi.#Một macro cho cả hai
include/mathlib_api.h
#ifndef MATHLIB_API_H
#define MATHLIB_API_H
/* Ba truong hop:
MATHLIB_BUILD dang DICH chinh thu vien -> xuat ra
MATHLIB_STATIC dung ban tinh -> khong danh dau gi
mac dinh dang DUNG thu vien dong -> nhap vao */
#if defined(_WIN32) || defined(__CYGWIN__)
#if defined(MATHLIB_STATIC)
#define ML_API
#elif defined(MATHLIB_BUILD)
#define ML_API __declspec(dllexport)
#else
#define ML_API __declspec(dllimport)
#endif
#define ML_NOI_BO
#else
#if defined(MATHLIB_STATIC)
#define ML_API
#else
#define ML_API __attribute__((visibility("default")))
#endif
#define ML_NOI_BO __attribute__((visibility("hidden")))
#endif
#endifinclude/mathlib.h
#ifndef MATHLIB_H
#define MATHLIB_H
#include "mathlib_api.h"
#ifdef __cplusplus
extern "C" {
#endif
ML_API double dt_hinh_tron(double r);
ML_API double cv_hinh_tron(double r);
ML_API long giai_thua(int n);
ML_API int uoc_chung_lon_nhat(int a, int b);
#ifdef __cplusplus
}
#endif
#endif# Dich thu vien dong
gcc -DMATHLIB_BUILD -fvisibility=hidden -fPIC -c -Iinclude src/*.c
gcc -shared *.o -o libmath.so
# Dich thu vien tinh
gcc -DMATHLIB_STATIC -c -Iinclude src/*.c
ar rcs libmath.a *.o
# Nguoi dung ban dong: khong dinh nghia gi
gcc app.c -Iinclude -L. -lmath -o app
# Nguoi dung ban tinh
gcc -DMATHLIB_STATIC app.c -Iinclude -L. -l:libmath.a -o app#Bốn lợi ích
Xuất ra mọi thứ
$ nm -D --defined-only libfoo.so | wc -l
12043
/* Nguoi dung thay 12043 ky hieu va khong biet cai nao la API.
Tai lieu noi 340 ham, con lai la gi?
Va ban khong dam doi ten mot ham noi bo nao, vi biet dau
co nguoi dang goi no. */Chỉ xuất ra API
$ nm -D --defined-only libfoo.so | wc -l
340
/* Dung bang so ham trong tai lieu.
Ban doi ten, gop, tach, xoa bat cu ham noi bo nao ma khong
pha vo gi ca. Ranh gioi giua API va cai dat la ro rang
va duoc trinh lien ket thuc thi. */| Lợi ích | Mức độ |
|---|---|
| API rõ ràng, đúng bằng tài liệu | Đây là lợi ích lớn nhất, và nó là lợi ích thiết kế chứ không phải kỹ thuật |
| Thư viện nhỏ hơn | Mười tới hai mươi phần trăm với thư viện nhiều ký hiệu |
| Nạp nhanh hơn | Đáng kể khi có hàng nghìn ký hiệu |
| Tối ưu tốt hơn | Hàm hidden nội tuyến được và không qua PLT |
| Tránh xung đột tên | Hai thư viện cùng có hàm tên log_init không đụng nhau nữa |
#Kịch bản phiên bản
libmath.map
MATHLIB_1.0 {
global:
dt_hinh_tron;
cv_hinh_tron;
giai_thua;
uoc_chung_lon_nhat;
local:
*; # moi thu con lai deu an
};
MATHLIB_1.1 {
global:
ml_dt_elip; # ham them o ban 1.1
} MATHLIB_1.0; # ke thua tu 1.0gcc -shared *.o -Wl,--version-script=libmath.map -o libmath.so
# Ba uu diem so voi visibility:
#
# 1. Danh sach API nam o MOT tep, doc duoc, dua vao kho ma duoc
# 2. Khong phai sua ma nguon, nen dung duoc voi thu vien ben thu ba
# 3. Ho tro DANH PHIEN BAN cho tung ky hieu: mot .so co the chua
# HAI ban cua cung mot ham, cho ca chuong trinh cu lan moi
$ nm -D libmath.so | grep dt_hinh_tron
0000000000001110 T dt_hinh_tron@@MATHLIB_1.0
# Hai dau a cong nghia la ban MAC DINH khi lien ket moi.
# Mot dau a cong la ban cu, giu lai cho chuong trinh da lien ket truoc.Tự làm thử
- Đếm số ký hiệu thư viện của bạn xuất ra và so với số hàm trong header.
- Bật
-fvisibility=hiddentrên Linux và đếm lại. - Xác nhận
-fvisibility=hiddenkhông có tác dụng trên MinGW. - Đánh dấu một hàm bằng
__declspec(dllexport)và xem năm hàm kia biến mất. - Viết macro
ML_APIchạy được trên cả ba nền tảng. - Viết một kịch bản phiên bản và kiểm tra bằng
nm -D.
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
- Mặc định thư viện xuất ra mọi ký hiệu không
static, kể cả những cái không có trong header. - Trên ELF dùng
-fvisibility=hiddenrồi mở từng hàm; trên PE đánh dấudllexport, và chỉ cần một hàm được đánh dấu là năm hàm kia biến mất. - Giấu ký hiệu vừa giảm kích thước vừa tăng tốc, vì hàm
hiddennội tuyến được và không phải đi qua PLT. - Xung đột tên giữa hai thư viện là lỗi khó nhất mà việc giấu ký hiệu xóa bỏ hoàn toàn.
- Kịch bản phiên bản của ELF cho phép một tệp
.sochứa nhiều bản của cùng một hàm, và đó là cách glibc giữ tương thích ngược.