Bài 39.730 phút đọc
ABI và những thứ phá vỡ nó
Sau bài này bạn sẽ làm được
- Phân biệt API với ABI bằng ví dụ cụ thể
- Chỉ ra chương trình chạy sai sau khi thêm một trường vào struct
- Kể sáu thay đổi phá vỡ ABI
- Kể ba thay đổi giữ được ABI
API là thứ trình biên dịch kiểm tra được. ABI là thứ không ai kiểm tra cho tới lúc chương trình chạy sai. Đó là toàn bộ lý do bài này tồn tại.
#API và ABI
API, giao diện mức mã nguồn
Tên hàm, danh sách tham số, kiểu trả về, tên trường. Nếu API tương thích thì mã nguồn cũ dịch lại được với thư viện mới.
ABI, giao diện mức nhị phân
Độ dời từng trường trong struct, kích thước struct, quy ước truyền tham số, cách trang trí tên ký hiệu. Nếu ABI tương thích thì tệp nhị phân cũ chạy được ngay với thư viện mới, không cần dịch lại.
| Thay đổi | API còn tương thích | ABI còn tương thích |
|---|---|---|
| Thêm một hàm mới | Có | Có |
| Thêm một trường vào CUỐI struct | Có | Không |
| Thêm một trường vào GIỮA struct | Có | Không |
| Đổi tên tham số trong nguyên mẫu | Có | Có |
| Đổi int thành long ở tham số | Có, thường thế | Không |
| Đổi thứ tự hai tham số | Không | Không |
| Đổi một macro hằng số | Có | Không |
| Sửa lỗi bên trong một hàm | Có | Có |
#Thí nghiệm: thêm một trường
cauhinh.h, bản 1
#ifndef CAUHINH_H
#define CAUHINH_H
typedef struct {
int rong;
int cao;
} CauHinh;
void ch_in(const CauHinh *c);
#endifapp.c, không bao giờ thay đổi trong cả thí nghiệm
#include <stdio.h>
#include "cauhinh.h"
int main(void) {
CauHinh c = { .rong = 1920, .cao = 1080 };
printf("app ghi : rong=%d cao=%d (sizeof=%d)\n",
c.rong, c.cao, (int)sizeof c);
ch_in(&c);
return 0;
}terminal
# Cả hai cùng dịch với bản 1: đúng như mong đợi
gcc -fPIC -c cauhinh.c && gcc -shared cauhinh.o -o libch.dll -Wl,--out-implib,libch.dll.a && gcc app.c -L. -lch -o app.exe && ./app.exe
app ghi : rong=1920 cao=1080 (sizeof=8) thu vien doc: rong=1920 cao=1080
cauhinh.h, bản 2: thêm một trường vào giữa
typedef struct {
int rong;
int sau; /* THEM VAO GIUA */
int cao;
} CauHinh;terminal
# Dịch lại CHỈ thư viện, app.exe giữ nguyên không đụng tới
gcc -fPIC -c cauhinh.c && gcc -shared cauhinh.o -o libch.dll -Wl,--out-implib,libch.dll.a && ./app.exe
app ghi : rong=1920 cao=1080 (sizeof=8) thu vien doc: rong=1920 cao=7742752
#Sáu thay đổi phá vỡ ABI
Một: đổi bố cục struct công khai
typedef struct { int a; int b; } S; /* v1, sizeof 8 */
typedef struct { int a; int c; int b; } S; /* v2: them, doi do doi */
typedef struct { long a; int b; } S; /* v2: doi kieu, doi kich thuoc */
typedef struct { int b; int a; } S; /* v2: doi thu tu */
/* Ca bon deu pha ABI. */Hai: đổi chữ ký hàm
int ham(int x); /* v1 */
long ham(long x); /* v2: pha ABI, va tren nhieu he thong
con khong pha API vi C tu chuyen kieu,
nen loi im lang hoan toan */
void f(int a, int b); /* v1 */
void f(int b, int a); /* v2: chi doi TEN tham so -> khong pha gi */
void f(int a, int b, int c);/* v2: them tham so -> pha ABI */Ba: xóa hoặc đổi tên hàm công khai
/* v1 co ml_dien_tich, v2 doi thanh ml_tinh_dien_tich */
/* Chuong trinh cu tim ky hieu ml_dien_tich luc chay:
./app: symbol lookup error: ./app: undefined symbol: ml_dien_tich
Day la loi duy nhat trong nhom nay ma he thong BAO RO,
nen no de sua nhat. */
/* Neu buoc phai doi ten, giu ban cu lam mot lop bao mong: */
double ml_dien_tich(double r) { return ml_tinh_dien_tich(r); }Bốn: đổi giá trị hằng số enum hoặc macro
typedef enum { MAU_DO, MAU_XANH, MAU_VANG } Mau; /* 0 1 2 */
typedef enum { MAU_DO, MAU_TIM, MAU_XANH, MAU_VANG } Mau; /* v2 */
/* Chuong trinh cu truyen 1 khi no nghi la MAU_XANH.
Thu vien moi doc 1 va hieu la MAU_TIM.
Cach dung: chi bao gio them vao CUOI: */
typedef enum { MAU_DO, MAU_XANH, MAU_VANG, MAU_TIM } Mau;
/* Voi macro thi giong het: */
#define ML_TOI_DA 100 /* v1, dong bang vao ma nguoi dung */
#define ML_TOI_DA 200 /* v2: nguoi dung cu van dung 100 */Năm: đổi ý nghĩa mà không đổi chữ ký
/* v1: tra ve 0 khi thanh cong, -1 khi loi */
int ml_luu(const char *tep);
/* v2: tra ve so byte da ghi, hoac so am khi loi */
int ml_luu(const char *tep);
/* Chu ky giong het. Trinh bien dich khong noi gi. Nhung:
if (ml_luu("a.txt") != 0) bao_loi(); <- gio bao loi khi THANH CONG
Day la dang pha vo nguy hiem nhat, vi khong cong cu nao bat duoc.
Neu ban phai doi ngu nghia thi hay DOI TEN HAM. */Sáu: đổi cờ dịch ảnh hưởng ABI
gcc -m32 / -m64 /* 32 hay 64 bit */
gcc -fshort-enums /* enum dung kieu nho nhat du chua */
gcc -fpack-struct /* bo het phan dem */
gcc -mfloat-abi=hard / softfp /* tren ARM: truyen so thuc bang thanh ghi nao */
gcc -std=c99 / -std=c11 /* it khi, nhung co the doi kich thuoc kieu */
/* Ban va nguoi dung thu vien PHAI dung cung nhung co nay.
Do la ly do he thong goi nhu pkg-config ton tai: no luu lai
dung bo co da dung de dich thu vien. Xem Bai 39.12. */#Những thay đổi giữ được ABI
Phá ABI
/* v2 pha ABI */
typedef struct { int a, b, c; } S; /* them c */
int ham(long x); /* doi kieu */
#define TOI_DA 200 /* doi hang so */
typedef enum { A, MOI, B } E; /* chen vao giua */Giữ ABI
/* v2 giu ABI */
void ham_moi(void); /* THEM ham la an toan */
typedef struct { int a, b; } S; /* khong dung toi */
int ham(int x) { /* sua ruot */ } /* sua cai dat la an toan */
typedef enum { A, B, MOI } E; /* them vao CUOI */
double ml_toi_da(void); /* hang so thanh HAM */| Thay đổi | An toàn | Ghi chú |
|---|---|---|
| Thêm một hàm mới | Có | Cách mở rộng an toàn nhất |
| Sửa lỗi bên trong một hàm | Có | Miễn là ngữ nghĩa không đổi |
| Thêm hằng số enum vào CUỐI | Có | Không bao giờ chèn vào giữa |
| Đổi tên tham số trong nguyên mẫu | Có | Tên tham số không vào ABI |
| Thêm const vào tham số con trỏ | Có | const không đổi cách truyền |
| Đổi hàm thành static inline trong header | Không | Người dùng cũ vẫn tìm ký hiệu |
| Thêm trường vào struct MỜ | Có | Đây là cả điểm của kiểu mờ, Bài 39.8 |
#Kiểm tra ABI tự động
# 1. abi-compliance-checker: so hai ban thu vien
abi-dumper libmath.so.1.0 -o v1.dump -lver 1.0
abi-dumper libmath.so.1.1 -o v2.dump -lver 1.1
abi-compliance-checker -l libmath -old v1.dump -new v2.dump
# Ket qua la mot bao cao HTML liet ke tung thay doi va muc do:
# Binary compatibility: 87.5%
# Removed Symbols: 1
# Problems with Data Types: High severity: 2
# 2. abidiff, cua bo cong cu libabigail
abidiff libmath.so.1.0 libmath.so.1.1
# Functions changes summary: 0 Removed, 1 Changed, 2 Added
# Variables changes summary: 0 Removed, 0 Changed, 0 Added
# 3. Kiem tra thu cong bang nm, cach nhanh va tho
nm -D --defined-only libmath.so.1.0 | awk '{print $3}' | sort > v1.txt
nm -D --defined-only libmath.so.1.1 | awk '{print $3}' | sort > v2.txt
comm -23 v1.txt v2.txt # ky hieu BI XOA -> pha ABI
comm -13 v1.txt v2.txt # ky hieu THEM MOI -> an toanTự làm thử
- Chạy lại thí nghiệm thêm trường vào giữa và ghi lại giá trị rác bạn nhận được.
- Thử thêm trường vào cuối và chứng minh nó vẫn phá vỡ mọi thứ khi dùng mảng.
- Đổi
intthànhlongở một tham số và xem trình biên dịch có nói gì không. - Chèn một hằng số enum vào giữa và quan sát chương trình cũ hiểu sai.
- Thêm ba dòng
_Static_assertchốt bố cục rồi cố tình phá. - Chạy
abidiffgiữa hai bản thư viện của bạn.
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
- API là thứ trình biên dịch kiểm tra được, ABI thì không ai kiểm tra cho tới lúc chạy sai.
- Thêm một trường vào giữa struct làm thư viện đọc ra
7742752thay vì1080, không sập và không cảnh báo. - Thêm vào cuối cũng không an toàn: kích thước đổi thì mảng, phép ghi và truyền theo giá trị đều hỏng.
- Đổi ngữ nghĩa mà giữ nguyên chữ ký là dạng phá vỡ nguy hiểm nhất, vì không công cụ nào bắt được.
- Rẻ nhất là chốt bố cục bằng
_Static_assert; tốt nhất là đừng lộ struct ra ngoài.