Linux從源碼分析ldconfig命令對(duì)可執(zhí)行文件緩存信息的讀取原理(緩存文件的讀)
今日問題:Linux的ldconfig -p命令可打印出系統(tǒng)緩存已記錄的所有動(dòng)態(tài)庫的信息。那么這個(gè)功能是如何實(shí)現(xiàn)的?
本文主要通過解讀Linux的ldconfig命令的關(guān)鍵代碼,分析了ldconfig命令是如何實(shí)現(xiàn)讀取緩存文件 /etc/ld.so.cache 的內(nèi)容的。本文涉及到的ldconfig的cache.c 代碼文件網(wǎng)址[1],在參考資料里。
ldconfig 使用的 /etc/ld.so.cache 文件,曾出現(xiàn)過兩個(gè)版本:
1.老版本的緩存文件格式 老版本指libc5 格式的動(dòng)態(tài)庫,在glibc 2.0/2.1版本時(shí)采用的格式。緩存文件內(nèi)容由cache_file類型的數(shù)據(jù)結(jié)構(gòu)填充,其定義為
struct cache_file
{
char magic[sizeof CACHEMAGIC - 1];
unsigned int nlibs; /* 記錄的條數(shù)*/
struct file_entry libs[0];
};
2.新版本的的緩存文件格式 新版本指glibc 2.2及之后版本的。緩存文件內(nèi)容由cache_file_new數(shù)據(jù)結(jié)構(gòu)填充。定義為:
struct cache_file_new
{
char magic[sizeof CACHEMAGIC_NEW - 1];
char version[sizeof CACHE_VERSION - 1];
uint32_t nlibs; /* 記錄的條數(shù) */
uint32_t len_strings; /* Size of string table. */
/* flags & cache_file_new_flags_endian_mask is one of the values
cache_file_new_flags_endian_unset, cache_file_new_flags_endian_invalid,
cache_file_new_flags_endian_little, cache_file_new_flags_endian_big.
The remaining bits are unused and should be generated as zero and
ignored by readers. */
uint8_t flags;
uint8_t padding_unsed[3]; /* Not used, for future extensions. */
/* File offset of the extension directory. See struct
cache_extension below. Must be a multiple of four. */
uint32_t extension_offset;
uint32_t unused[3]; /* Leave space for future extensions
and align to 8 byte boundary. */
struct file_entry_new libs[0]; /* Entries describing libraries. */
/* After this the string table of size len_strings is found. */
};
glibc-ld.so.cache1.1??? 以上輸出信息確實(shí)以glibc-ld.so.cache開始,所以我用的Ubuntu22.04系統(tǒng)的ldconfig的緩存文件內(nèi)容是新格式的。
ldconfig代碼的cache.c 文件里是這樣根據(jù)magic的不同用if(){} else{}處理的:
if (memcmp (cache->magic, CACHEMAGIC, sizeof CACHEMAGIC - 1)) {///當(dāng)屬于老版本時(shí),按這里的方式處理 /* This can only be the new format without the old one. */ cache_new = (struct cache_file_new *) cache;
if (memcmp (cache_new->magic, CACHEMAGIC_NEW, sizeof CACHEMAGIC_NEW - 1)
在glibc-2.35的代碼中已用英文說明了,glibc2.2格式的,能兼容glibc2.2之前的緩存文件內(nèi)容。這里說的兼容,是依賴于代碼檢測(cè)實(shí)現(xiàn)的:由于兩種結(jié)構(gòu)體都以magic作為第一個(gè)項(xiàng)目,來識(shí)別緩存文件類型。再根據(jù)magic值的不同,對(duì)后續(xù)數(shù)據(jù)段采用不同的處理方式。老magic的定義為#define CACHEMAGIC "ld.so-1.7.0",新magic的定義為#define CACHEMAGIC_NEW "glibc-ld.so.cache"。也就是老版本 cache_file 的文件頭部以字符串ld.so-1.7.0開始,新版本cache_file_new 的文件頭部以字符串glibc-ld.so.cache開始。這點(diǎn)我們可以用head -c 命令查看下/etc/ld.so.cache文件的頭部30個(gè)字符串舊可以驗(yàn)證了:
# head -c 30 /etc/ld.so.cache
glibc-ld.so.cache1.1???
以上輸出信息確實(shí)以glibc-ld.so.cache開始,所以我用的Ubuntu22.04系統(tǒng)的ldconfig的緩存文件內(nèi)容是新格式的。
ldconfig代碼的cache.c 文件里是這樣根據(jù)magic的不同用if(){} else{}處理的:
if (memcmp (cache->magic, CACHEMAGIC, sizeof CACHEMAGIC - 1))
{///當(dāng)屬于老版本時(shí),按這里的方式處理
/* This can only be the new format without the old one. */
cache_new = (struct cache_file_new *) cache;
if (memcmp (cache_new->magic, CACHEMAGIC_NEW, sizeof CACHEMAGIC_NEW - 1)
|| memcmp (cache_new->version, CACHE_VERSION,
sizeof CACHE_VERSION - 1))
error (EXIT_FAILURE, 0, _("File is not a cache file.\n"));
check_new_cache (cache_new);
format = 1;
/* This is where the strings start. */
cache_data = (const char *) cache_new;
}
else
{//當(dāng)屬于新版本緩存文件的時(shí)候,按下面內(nèi)容處理
……省略
}
在知道了 緩存文件類型(magic標(biāo)記)后,就可以開始根據(jù)格式標(biāo)準(zhǔn),逐條讀/寫每條記錄了,這是ldconfig的重頭戲。
先看對(duì)cache文件的讀取效果,以 ldconfig -p命令打印出緩存文件的所有記錄的結(jié)果為例:
# ldconfig -p
1525 libs found in cache `/etc/ld.so.cache
……
libGLESv1_CM.so (libc6,x86-64) => /lib/x86_64-linux-gnu/libGLESv1_CM.so
libGL.so.1 (libc6,x86-64) => /lib/x86_64-linux-gnu/libGL.so.1
libGL.so (libc6,x86-64) => /lib/x86_64-linux-gnu/libGL.so
……
這里每條都是一個(gè)動(dòng)態(tài)庫的名稱、格式(libc6等格式)、CPU架構(gòu)、所在路徑的記錄。
緩存文件中的這么一條記錄,對(duì)應(yīng)的結(jié)構(gòu)體,舊版本的為file_entry,新版本的為file_entry_new。它們的定義分別為:
struct file_entry
{
int32_t flags; /* This is 1 for an ELF library. */
uint32_t key, value; /* String table indices. */
};
以及新版本的 file_entry格式:
struct file_entry_new ///文件記錄的新格式,增加了OS版本、硬件信息
{
union
{
/* Fields shared with struct file_entry. */
struct file_entry entry;
/* Also expose these fields directly. */
struct
{
int32_t flags; /* This is 1 for an ELF library. */
uint32_t key, value; /* String table indices. */
};
};
uint32_t osversion; /* Required OS version. */
uint64_t hwcap; /* Hwcap entry. */
};
繼續(xù)分析【讀緩存文件】的簡(jiǎn)要流程:
使用了 mmap() 函數(shù),將 /etc/ld.so.cache 緩存文件整體讀入內(nèi)存:
struct cache_file *cache
= mmap (NULL, st.st_size, PROT_READ, MAP_PRIVATE, fd, 0);
這是通過mmap()函數(shù),將打開的緩存文件(open(/etc/ld.so.cache)的句柄fd)的數(shù)據(jù)映射到內(nèi)存,由于文件數(shù)據(jù)就是按struct cache_file_new結(jié)構(gòu)體格式填充的,所以mmap()后,就可以按這個(gè)結(jié)構(gòu)體去解析各個(gè)條目。2. 判斷magic,新老magic分流處理。3. 如果是新的magic,則按struct cache_file_new數(shù)據(jù)結(jié)構(gòu)解析。4. 對(duì)于新格式,遍歷讀取數(shù)據(jù)、打?。?/p>
……
else{
struct cache_extension_all_loaded ext;
if (……)錯(cuò)誤處理;
/* Print everything. */
for (unsigned int i = 0; i < cache_new->nlibs; i++)
{
const char *hwcaps_string
= glibc_hwcaps_string (&ext, cache, cache_size,
&cache_new->libs[i]);
print_entry (cache_data + cache_new->libs[i].key,
cache_new->libs[i].flags,
cache_new->libs[i].osversion,
cache_new->libs[i].hwcap, hwcaps_string,
cache_data + cache_new->libs[i].value);
}
print_extensions (&ext);
}
這里關(guān)鍵內(nèi)容是:
- cache_data,代表了mmap()讀取到的緩存文件內(nèi)容;以cache_data的地址為初始地址,按偏移量cache_new->libs[i].key 相加后,可得到每條file_entry_new的入口,然后分別打印出記錄內(nèi)容,就實(shí)現(xiàn)了 ldconfig -p 的代碼功能。
- 動(dòng)態(tài)庫的條數(shù),等于 cache_new->nlibs 這個(gè)變量的值。作為for循環(huán)遍歷時(shí)的條件。
- cache_new->libs[i].key 這里的key,在struct file_entry_new中的定義是:
uint32_t key, value; /* String table indices. */
key相當(dāng)于第i條動(dòng)態(tài)庫記錄的目錄索引。通過索引可以查到value。在實(shí)現(xiàn)時(shí),key和value都是數(shù)字,這個(gè)數(shù)字代表字符串相對(duì)于cache_data這個(gè)首地址的字節(jié)偏移量,例如key->value 即 cache_new->libs[i].key, cache_new->libs[i].value 43256 -> 43234
總之,通過對(duì)結(jié)構(gòu)體的合理使用,將緩存文件內(nèi)容解析后,可打印出緩存文件中記錄的所有已知?jiǎng)討B(tài)庫文件的信息。
void print_cache (const char *cache_name) 的函數(shù)代碼結(jié)束之前,還做了一下內(nèi)存回收工作:
/* Cleanup. */
munmap (cache, cache_size);
close (fd);
首先使用munmap()函數(shù),將之前已映射內(nèi)存數(shù)據(jù)做一下清除;然后關(guān)閉打開的cache緩存文件描述符。
本文主要通過解讀Linux的ldconfig命令的關(guān)鍵代碼,分析了ldconfig命令是如何實(shí)現(xiàn)讀取緩存文件 /etc/ld.so.cache 的內(nèi)容的。本文涉及到的ldconfig的cache.c 代碼文件網(wǎng)址[1],在參考資料里。
參考資料
[1]ldconfig的cache.c 代碼文件網(wǎng)址: https://sourceware.org/git/?p=glibc.git;a=blob;f=elf/cache.c;h=8149f889bab9f9cb32a50e349991ba821e4db0dd;hb=HEAD