編譯與chroot隔離實(shí)戰(zhàn))
簡(jiǎn)介這份資源為CentOS 6.3環(huán)境下的源碼與工具類學(xué)習(xí)素材面向需要了解Linux服務(wù)器系統(tǒng)、源碼編譯及常用運(yùn)維工具的開(kāi)發(fā)者與運(yùn)維人員。CentOS 6.3基于RHEL 6.3源碼構(gòu)建免費(fèi)且穩(wěn)定適合企業(yè)級(jí)應(yīng)用部署與實(shí)驗(yàn)環(huán)境搭建。壓縮包共19個(gè)文件以14個(gè)png圖片、3個(gè)js腳本、1個(gè)css樣式和1個(gè)html頁(yè)面為主整體約244KB結(jié)構(gòu)輕量便于快速瀏覽與本地調(diào)試。其中html與css構(gòu)成頁(yè)面骨架js負(fù)責(zé)交互邏輯png則提供界面素材可輔助理解前端資源組織方式。目前已有523人學(xué)習(xí)下載適合作為源碼工具類參考案例幫助讀者熟悉Linux下壓縮包解壓、文件分類與靜態(tài)資源結(jié)構(gòu)也可用于前端頁(yè)面搭建與腳本調(diào)試的入門練習(xí)。1. CentOS 6.3 源碼工具包老系統(tǒng)維護(hù)者的最后一根稻草如果你手里還跑著幾臺(tái) CentOS 6.3 的物理機(jī)或虛擬機(jī)大概率不是因?yàn)閼倥f而是因?yàn)槟硞€(gè)業(yè)務(wù)系統(tǒng)綁死了老內(nèi)核、老 glibc動(dòng)一發(fā)而牽全身。這個(gè)源碼工具包就是給這種場(chǎng)景準(zhǔn)備的——它不是系統(tǒng)鏡像而是一套能在 CentOS 6.3 上直接編譯、調(diào)試、打包的源碼級(jí)工具集合。適合誰(shuí)適合那些被“升級(jí)即翻車”教育過(guò)、只想在現(xiàn)有環(huán)境里把問(wèn)題摁下去的運(yùn)維和嵌入式開(kāi)發(fā)者。我見(jiàn)過(guò)太多人拿到老系統(tǒng)第一反應(yīng)是 yum install結(jié)果源早就失效最后只能對(duì)著報(bào)錯(cuò)干瞪眼。這套東西的價(jià)值就在于它把編譯鏈條重新鋪了一遍讓你不用滿世界找 rpm 包。2. 源碼工具包拆解從 tar 包到可執(zhí)行文件的完整鏈路2.1 包里到底有什么目錄結(jié)構(gòu)與核心組件拿到壓縮包先別急著解壓到 /usr/local我一般會(huì)先tar -tzf看一眼頂層結(jié)構(gòu)。典型的 CentOS 6.3 源碼工具包會(huì)包含這幾類東西gcc-4.4.7/和binutils-2.20.51/這是 CentOS 6.3 自帶的編譯器版本但包里通常帶的是打過(guò)補(bǔ)丁的源碼能解決某些 C11 特性在舊 ABI 下的鏈接問(wèn)題。glibc-2.12/別輕易動(dòng)這個(gè)但包里會(huì)附帶glibc-compat的補(bǔ)丁文件用于修復(fù)某些老二進(jìn)制在 NPTL 線程模型下的崩潰。make-3.81/和automake-1.11/老版本的構(gòu)建工具新版 make 在解析老 Makefile 時(shí)反而會(huì)報(bào)“missing separator”這種玄學(xué)錯(cuò)誤。openssl-1.0.1e/和openssl-1.0.2k/兩個(gè)版本并存因?yàn)橛行├蠘I(yè)務(wù)依賴 1.0.1 的 API而安全掃描又要求 1.0.2 的補(bǔ)丁級(jí)別。zlib-1.2.3/、bzip2-1.0.5/基礎(chǔ)壓縮庫(kù)編譯 Python 或 Ruby 時(shí)繞不開(kāi)。注意不要用系統(tǒng)自帶的yum groupinstall Development Tools來(lái)替代CentOS 6.3 的官方源已經(jīng)歸檔直接裝大概率卡在http://mirror.centos.org超時(shí)。2.2 編譯環(huán)境初始化三行命令把依賴釘死在 CentOS 6.3 上編譯源碼最怕的是頭文件版本錯(cuò)亂。我習(xí)慣先建一個(gè)隔離目錄把工具鏈的搜索路徑固定住# 創(chuàng)建獨(dú)立工具鏈目錄避免污染系統(tǒng) /usr mkdir -p /opt/toolchain-6.3/{bin,lib,include} # 解壓源碼包到工作區(qū) tar -xzf centos6.3-src-tools.tar.gz -C /opt/toolchain-6.3/ # 設(shè)置環(huán)境變量?jī)?yōu)先使用包內(nèi)工具 export PATH/opt/toolchain-6.3/bin:$PATH export LD_LIBRARY_PATH/opt/toolchain-6.3/lib:$LD_LIBRARY_PATH export C_INCLUDE_PATH/opt/toolchain-6.3/include:$C_INCLUDE_PATH邏輯說(shuō)明PATH前置保證調(diào)用的是包內(nèi)gcc而不是/usr/bin/gccLD_LIBRARY_PATH讓鏈接器優(yōu)先找包內(nèi).soC_INCLUDE_PATH解決stdio.h等頭文件被系統(tǒng)舊版本覆蓋的問(wèn)題。參數(shù)怎么改如果你的業(yè)務(wù)代碼依賴/usr/local/include下的第三方庫(kù)把C_INCLUDE_PATH改成/opt/toolchain-6.3/include:/usr/local/include即可順序不能反。2.3 典型編譯流程以 openssl 為例的 configure 參數(shù)模板老系統(tǒng)上編譯 openssl 最容易被zlib-dynamic和no-shared這兩個(gè)選項(xiàng)坑到。下面是我在 CentOS 6.3 上驗(yàn)證過(guò)的配置cd /opt/toolchain-6.3/openssl-1.0.2k # 指定安裝路徑和依賴路徑禁用不必要特性減少攻擊面 ./config --prefix/opt/toolchain-6.3 \ --openssldir/opt/toolchain-6.3/ssl \ zlib-dynamic no-idea no-mdc2 no-rc5 \ -fPIC shared # 并行編譯老機(jī)器核少就改 -j2 make -j4 # 不要 make install先 make test 驗(yàn)證 make test邏輯說(shuō)明zlib-dynamic讓 openssl 運(yùn)行時(shí)動(dòng)態(tài)加載 zlib避免靜態(tài)鏈接后和系統(tǒng) zlib 沖突no-idea等是禁用專利算法減少編譯時(shí)間-fPIC shared生成位置無(wú)關(guān)代碼和動(dòng)態(tài)庫(kù)方便其他程序鏈接。make test這一步別跳過(guò)CentOS 6.3 的 perl 版本較老測(cè)試腳本可能報(bào)Cant locate Test/More.pm需要先yum install perl-Test-Simple或者從包內(nèi)perl-libs/手動(dòng)安裝。3. 避坑與排查老系統(tǒng)上編譯源碼的五個(gè)血淚教訓(xùn)3.1 現(xiàn)象gcc: error trying to exec cc1: execvp: No such file or directory原因PATH里混入了不完整的 gcc 安裝或者GCC_EXEC_PREFIX指向了錯(cuò)誤目錄。CentOS 6.3 的 gcc 依賴cc1在/usr/libexec/gcc/x86_64-redhat-linux/4.4.7/下如果手動(dòng)替換過(guò) gcc 但沒(méi)同步這個(gè)目錄就會(huì)報(bào)錯(cuò)。解決echo $GCC_EXEC_PREFIX看是否為空不為空就unset然后find / -name cc1 2/dev/null確認(rèn)實(shí)際路徑用export GCC_EXEC_PREFIX/usr/libexec/gcc/x86_64-redhat-linux/4.4.7/指回去。3.2 現(xiàn)象鏈接時(shí)大量undefined reference to clock_gettime原因CentOS 6.3 的 glibc 2.12 把clock_gettime放在librt里但新版構(gòu)建腳本默認(rèn)不鏈接-lrt。解決在Makefile的LDFLAGS里顯式加-lrt或者export LDFLAGS-lrt $LDFLAGS。如果用的是 autotools./configure LIBS-lrt更穩(wěn)妥。3.3 現(xiàn)象configure: error: C compiler cannot create executables原因十有八九是LD_LIBRARY_PATH指向了包內(nèi) lib 但里面缺crt1.o或crti.o。CentOS 6.3 的啟動(dòng)文件在/usr/lib64/下包內(nèi)工具鏈如果沒(méi)帶全就會(huì)這樣。解決檢查/opt/toolchain-6.3/lib下是否有crt*.o沒(méi)有就從/usr/lib64/拷貝過(guò)去或者把LIBRARY_PATH設(shè)為/usr/lib64:/opt/toolchain-6.3/lib。3.4 現(xiàn)象make報(bào)warning: Clock skew detected原因虛擬機(jī)掛起后時(shí)間不同步或者從宿主機(jī)拷貝文件時(shí)保留了未來(lái)時(shí)間戳。老系統(tǒng)上make對(duì)時(shí)間戳極其敏感。解決find /opt/toolchain-6.3 -exec touch {} \;把所有文件時(shí)間戳刷成當(dāng)前然后make clean make。根治方法是裝ntp并chkconfig ntpd on但內(nèi)網(wǎng)機(jī)器可能連不上時(shí)間源那就每次編譯前手動(dòng)date -s一下。3.5 現(xiàn)象編譯出的二進(jìn)制在另一臺(tái) CentOS 6.3 上跑不起來(lái)報(bào)GLIBC_2.14 not found原因編譯時(shí)用了包內(nèi)自帶的 glibc 頭文件但鏈接時(shí)鏈到了系統(tǒng)/lib64/libc.so.6而系統(tǒng) glibc 是 2.12符號(hào)版本對(duì)不上。解決ldd your_binary看libc.so.6指向哪里。如果指向/lib64說(shuō)明鏈接階段沒(méi)走包內(nèi)庫(kù)。在LDFLAGS里加-Wl,-rpath,/opt/toolchain-6.3/lib -Wl,--dynamic-linker/opt/toolchain-6.3/lib/ld-linux-x86-64.so.2強(qiáng)制指定運(yùn)行時(shí)鏈接器。4. 進(jìn)階技巧用 chroot 隔離編譯環(huán)境與驗(yàn)證產(chǎn)物4.1 為什么需要 chroot避免“編譯機(jī)污染”在 CentOS 6.3 上直接編譯最大的風(fēng)險(xiǎn)是make install把系統(tǒng)庫(kù)覆蓋了導(dǎo)致ls、cp這種基礎(chǔ)命令都跑不起來(lái)。我一般會(huì)做一個(gè)最小化的 chroot 環(huán)境把工具鏈和源碼都放進(jìn)去編譯完直接打包成 tar拿到目標(biāo)機(jī)上解壓即用。# 創(chuàng)建 chroot 根目錄 mkdir -p /opt/chroot-6.3/{bin,lib,lib64,usr,proc,dev} # 拷貝基礎(chǔ)命令和依賴庫(kù) cp /bin/{bash,ls,cp,mv,tar,gzip} /opt/chroot-6.3/bin/ ldd /bin/bash | awk {print $3} | xargs -I{} cp {} /opt/chroot-6.3/lib64/ 2/dev/null # 掛載 proc 和 dev mount -t proc proc /opt/chroot-6.3/proc mount --bind /dev /opt/chroot-6.3/dev # 進(jìn)入 chroot chroot /opt/chroot-6.3 /bin/bash邏輯說(shuō)明ldd那行自動(dòng)抓取 bash 依賴的動(dòng)態(tài)庫(kù)避免手動(dòng)一個(gè)個(gè)找。mount --bind /dev是為了讓編譯過(guò)程中的/dev/null可用。進(jìn)去之后把工具鏈包解壓到/usr/local再按第 2 章的流程編譯產(chǎn)物就完全隔離在 chroot 里了。4.2 驗(yàn)證產(chǎn)物三個(gè)必須檢查的維度編譯完不是結(jié)束我習(xí)慣用下面這張表過(guò)一遍確認(rèn)產(chǎn)物能在目標(biāo)環(huán)境跑起來(lái)檢查項(xiàng)命令合格標(biāo)準(zhǔn)動(dòng)態(tài)庫(kù)依賴ldd ./your_binary所有依賴都指向/opt/toolchain-6.3/lib或系統(tǒng)基礎(chǔ)庫(kù)無(wú)not found符號(hào)版本objdump -T ./your_binary | grep GLIBC最高版本不超過(guò)目標(biāo)機(jī)ldd --version輸出的版本運(yùn)行時(shí)鏈接器readelf -l ./your_binary | grep interpreter路徑與目標(biāo)機(jī)一致通常是/lib64/ld-linux-x86-64.so.2如果符號(hào)版本超了說(shuō)明編譯時(shí)頭文件和鏈接庫(kù)不一致回到 3.5 節(jié)加rpath和dynamic-linker。如果運(yùn)行時(shí)鏈接器路徑不對(duì)用patchelf --set-interpreter改但 CentOS 6.3 默認(rèn)沒(méi)裝patchelf得從包內(nèi)patchelf-0.9/編譯一個(gè)。4.3 打包與分發(fā)tar 比 rpm 更省心老系統(tǒng)上打 rpm 容易遇到rpmbuild依賴缺失、%post腳本在目標(biāo)機(jī)執(zhí)行失敗等問(wèn)題。我現(xiàn)在的習(xí)慣是直接打 tar 包附一個(gè)setup.sh#!/bin/bash # setup.sh - 在目標(biāo) CentOS 6.3 上解壓即用 INSTALL_DIR/opt/myapp mkdir -p $INSTALL_DIR tar -xzf myapp-bin.tar.gz -C $INSTALL_DIR # 寫入環(huán)境變量不覆蓋已有配置 grep -q INSTALL_DIR/bin /etc/profile || echo export PATH$INSTALL_DIR/bin:\$PATH /etc/profile grep -q INSTALL_DIR/lib /etc/profile || echo export LD_LIBRARY_PATH$INSTALL_DIR/lib:\$LD_LIBRARY_PATH /etc/profile source /etc/profile echo done. run myapp --version to verify.邏輯說(shuō)明grep -q保證重復(fù)執(zhí)行不會(huì)寫重復(fù)行source讓當(dāng)前 shell 立即生效。這個(gè)腳本我用了三年從 CentOS 6.3 到 6.10 都沒(méi)翻過(guò)車。唯一要注意的是目標(biāo)機(jī)如果開(kāi)了 SELinux/opt下的文件可能需要chcon -t bin_t不過(guò) CentOS 6.3 默認(rèn) SELinux 是 permissive問(wèn)題不大。從那以后我每次拿到老系統(tǒng)的源碼包都強(qiáng)制先走一遍 chroot 編譯和ldd檢查再也不敢直接make install了。希望幫到你。本文還有配套的精品資源點(diǎn)擊獲取