日本黄色一级经典视频|伊人久久精品视频|亚洲黄色色周成人视频九九九|av免费网址黄色小短片|黄色Av无码亚洲成年人|亚洲1区2区3区无码|真人黄片免费观看|无码一级小说欧美日免费三级|日韩中文字幕91在线看|精品久久久无码中文字幕边打电话

當(dāng)前位置:首頁 > 單片機(jī) > 程序喵大人
[導(dǎo)讀]對于靜態(tài)鏈接先提出兩個問題: Q: 每個目標(biāo)文件都有好多個段,目標(biāo)文件在被鏈接成可執(zhí)行文件時,輸入目標(biāo)文件中的各個段如何被合并到輸出文件? A: 合并相似的段,將所有的.text段合并到輸出文件的.text段,將所有的.data段合并到輸出文件的.data段。 Q: 鏈接


對于靜態(tài)鏈接先提出兩個問題:


Q:

每個目標(biāo)文件都有好多個段,目標(biāo)文件在被鏈接成可執(zhí)行文件時,輸入目標(biāo)文件中的各個段如何被合并到輸出文件?


A:

合并相似的段,將所有的.text段合并到輸出文件的.text段,將所有的.data段合并到輸出文件的.data段。




Q:

鏈接器如何為他們分配在輸出文件中的空間和地址?


A:

這里涉及到程序鏈接的兩個步驟:


  1. 空間與地址分配:掃描所有的輸入目標(biāo)文件,獲得它們每個段的長度屬性和位置,收集輸入目標(biāo)文件中的符號表中的所有符號定義和符號引用,統(tǒng)一放到一個全局符號表中,合并所有的段,計(jì)算出輸出文件中各個段合并后的長度和位置,并建立映射關(guān)系。


  2. 符號解析與重定位:使用第一步收集到的所有信息,讀取輸入文件中段的數(shù)據(jù)及重定位信息,進(jìn)行符號解析和重定位,調(diào)整代碼中的地址,將每個段中需要重定位的指令和數(shù)據(jù)進(jìn)行“修補(bǔ)”,使他們都指向正確的位置。




Tips:



外部符號指的是目標(biāo)文件需要引用的符號,但是定義在其它目標(biāo)文件中,鏈接前外部符號地址都是000000之類,鏈接后的可執(zhí)行文件就可以看見這些外部符號都是有地址的。鏈接就是把相似的段放在一起,先找到段的偏移地址,再找出符號在段中的偏移,這樣可以確定符號在整個可執(zhí)行程序中的地址。


對于那些需要重定位的符號,都會放在重定位表里,也叫重定位段,即.rel.data、.rel.text等,如果.text段有被重定位的地方,就有.rel.text段,如果.data段有被重定位的地方,就有.rel.data段。



可以使用objdump查看目標(biāo)文件的重定位表。


源代碼:

int main() { printf("程序喵\n"); return 0;}gcc -c test


objdump -r test.o
test.o: file format elf64-x86-64
RELOCATION RECORDS FOR [.text]:OFFSET TYPE VALUE0000000000000007 R_X86_64_PC32 .rodata-0x0000000000000004000000000000000c R_X86_64_PLT32 puts-0x0000000000000004

RELOCATION RECORDS FOR [.eh_frame]:OFFSET TYPE VALUE0000000000000020 R_X86_64_PC32 .text

使用nm也可以查看需要重定位的符號:

nm -u test.o U _GLOBAL_OFFSET_TABLE_ U puts

對于UND類型,這種未定義的符號都是因?yàn)樵撃繕?biāo)文件中有關(guān)于他們的重定位項(xiàng),在鏈接器掃描完所有的輸入目標(biāo)文件后,所有這種未定義的符號都應(yīng)該能在全局符號表中找到,否則報(bào)符號未定義錯誤。

注意:我們代碼里明明用的是printf,為什么它卻引用了puts的符號呢,因?yàn)榫幾g器默認(rèn)情況下會把只用一個字符串參數(shù)的printf替換成puts, 可以節(jié)省格式解析的時間,使用-fno-builtin會關(guān)閉這個內(nèi)置函數(shù)優(yōu)化選項(xiàng),如下:

~/test$ gcc -c -fno-builtin testlink.cc -o test.o~/test$ nm test.o U _GLOBAL_OFFSET_TABLE_0000000000000000 T main U printf


Tips:



現(xiàn)在的程序和庫通常來講都很大,一個目標(biāo)文件可能包含成百上千個函數(shù)或變量,當(dāng)需要用到某個目標(biāo)文件的任意一個函數(shù)或變量時,就需要把它整個目標(biāo)文件都鏈接進(jìn)來,也就是說那些沒有用到的函數(shù)也會被鏈接進(jìn)去,這會導(dǎo)致鏈接輸出文件變的很大,造成空間浪費(fèi)。



有一個編譯選項(xiàng)叫函數(shù)級別鏈接,可以使得某個函數(shù)或變量單獨(dú)保存在一個段里面,都鏈接器需要用到某個函數(shù)時,就將它合并到輸出文件中,對于沒用到的函數(shù)則將他們拋棄,減少空間浪費(fèi),但這會減慢編譯和鏈接過程,GCC編譯器的編譯選項(xiàng)是:
-ffunction-sections-fdata-sections

可能很多人都會以為程序都是由main函數(shù)開始執(zhí)行和結(jié)束的,但其實(shí)不是,在main函數(shù)調(diào)用之前,為了保證程序可以順利進(jìn)行,要先初始化進(jìn)程執(zhí)行環(huán)境,如堆分配初始化、線程子系統(tǒng)等,C++的全局對象構(gòu)造函數(shù)也是這一時期被執(zhí)行的,全局析構(gòu)函數(shù)是main之后執(zhí)行的。

Linux一般程序的入口是__start函數(shù),程序有兩個相關(guān)的段:

init段:進(jìn)程的初始化代碼,一個程序開始運(yùn)行時,在main函數(shù)調(diào)用之前,會先運(yùn)行.init段中的代碼。
fini段:進(jìn)程終止代碼,當(dāng)main函數(shù)正常退出后,glibc會安排執(zhí)行該段代碼。

如何指定程序入口

在ld鏈接過程中使用-e參數(shù)可以指定程序入口,由于一段簡短的printf函數(shù)其實(shí)都依賴了好多個鏈接庫,我們也不太方便使用鏈接腳本將目標(biāo)文件與所有這些依賴庫進(jìn)行鏈接,所以使用下面這段內(nèi)嵌匯編的程序來打印一段字符串,這段程序不依賴任何鏈接庫就可以打印出字符串內(nèi)容,讀者如果不懂其中的含義也不用擔(dān)心,只需要了解下面介紹的鏈接知識就好。


代碼如下:


const char* str = "hello";
void print() { asm("movl $13,%%edx \n\t" "movl str,%%ecx \n\t" "movl $0,%%ebx \n\t" "movl $4,%%eax \n\t" "int $0x80 \n\t" : :"r"(str):"edx", "ecx", "ebx");}

void exit() { asm("movl $42,%ebx \n\t" "movl $1,%eax \n\t" "int $0x80 \n\t");}
void nomain() { print(); exit();}

使用如下命令生成目標(biāo)文件:

gcc -c -fno-builtin test.cc


看下輸出的test.o的符號:

~/test$ nm -a test.o0000000000000000 b .bss0000000000000000 n .comment0000000000000000 d .data0000000000000000 d .data.rel.local0000000000000000 r .eh_frame0000000000000000 n .note.GNU-stack0000000000000000 r .rodata0000000000000000 t .text0000000000000026 T _Z4exitv0000000000000000 T _Z5printv0000000000000039 T _Z6nomainv0000000000000000 D str0000000000000000 a test.cc

這里由于我的源文件是.cc結(jié)尾,所以是以c++方式編譯的,所以符號變成了上面的形式,如果變成了test.c,符號如下:

~/test$ gcc -c -fno-builtin test.c -o test.o~/test$ nm -a test.o0000000000000000 b .bss0000000000000000 n .comment0000000000000000 d .data0000000000000000 d .data.rel.local0000000000000000 r .eh_frame0000000000000000 n .note.GNU-stack0000000000000000 r .rodata0000000000000000 t .text0000000000000026 T exit0000000000000039 T nomain0000000000000000 T print0000000000000000 D str0000000000000000 a test.c

再使用-e指定入口函數(shù)符號:

~/test$ ld -static -e nomain -o test test.o~/test$ ./testhello

如何使用自定義鏈接腳本實(shí)現(xiàn)自定義段的功能
在ld鏈接過程中使用-T參數(shù)可以指定鏈接腳本,通過ld -verbose可以查看默認(rèn)的鏈接腳本,原文太長,這里簡單截取了一部分:

$ ld -verboseGNU ld (GNU Binutils for Ubuntu) 2.30 Supported emulations: elf_x86_64 elf32_x86_64 elf_i386 elf_iamcu i386linux elf_l1om elf_k1om i386pep i386peusing internal linker script:==================================================/* Script for -z combreloc: combine and sort reloc sections *//* Copyright (C) 2014-2018 Free Software Foundation, Inc. Copying and distribution of this script, with or without modification, are permitted in any medium without royalty provided the copyright notice and this notice are preserved. */OUTPUT_FORMAT("elf64-x86-64", "elf64-x86-64", "elf64-x86-64")OUTPUT_ARCH(i386:x86-64)ENTRY(_start)SEARCH_DIR("=/usr/local/lib/x86_64-linux-gnu"); SEARCH_DIR("=/lib/x86_64-linux-gnu"); SEARCH_DIR("=/usr/lib/x86_64-linux-gnu"); SEARCH_DIR("=/usr/lib/x86_64-linux-gnu64"); SEARCH_DIR("=/usr/local/lib64"); SEARCH_DIR("=/lib64"); SEARCH_DIR("=/usr/lib64"); SEARCH_DIR("=/usr/local/lib"); SEARCH_DIR("=/lib"); SEARCH_DIR("=/usr/lib"); SEARCH_DIR("=/usr/x86_64-linux-gnu/lib64"); SEARCH_DIR("=/usr/x86_64-linux-gnu/lib");SECTIONS{ /* Read-only sections, merged into text segment: */ PROVIDE (__executable_start = SEGMENT_START("text-segment", 0x400000)); . = SEGMENT_START("text-segment", 0x400000) + SIZEOF_HEADERS;
.init : { KEEP (*(SORT_NONE(.init))) } .plt : { *(.plt) *(.iplt) } .plt.got : { *(.plt.got) } .plt.sec : { *(.plt.sec) } .text : { *(.text.unlikely .text.*_unlikely .text.unlikely.*) *(.text.exit .text.exit.*) *(.text.startup .text.startup.*) *(.text.hot .text.hot.*) *(.text .stub .text.* .gnu.linkonce.t.*) /* .gnu.warning sections are handled specially by elf32.em. */ *(.gnu.warning) } .fini : { KEEP (*(SORT_NONE(.fini))) } .rodata : { *(.rodata .rodata.* .gnu.linkonce.r.*) } /DISCARD/ : { *(.note.GNU-stack) *(.gnu_debuglink) *(.gnu.lto_*) }}

這里自定義一個簡單的鏈接腳本test.lds

ENTRY(nomain)
SECTIONS{ . = 0x8048000 + SIZEOF_HEADERS; tinytext : { *(.text) *(.data) *(.rodata) } /DISCARD/ : { *(.comment) }}

再使用-T指定鏈接腳本:

~/test$ ld -static -T test.lds -e nomain -o test test.o~/test$ ./testhello

上面的tinytext一行是指將.text段、.data段、.rodata段的內(nèi)容都合并到tinytext段中,使用readelf查看段的信息。

~/test$ readelf -S test~/test$ There are 6 section headers, starting at offset 0x482a0:
Section Headers: [Nr] Name Type Address Offset Size EntSize Flags Link Info Align [ 0] NULL 0000000000000000 00000000 0000000000000000 0000000000000000 0 0 0 [ 1] .eh_frame PROGBITS 00000000080480b0 000480b0 0000000000000078 0000000000000000 A 0 0 8 [ 2] tinytext PROGBITS 0000000008048128 00048128 0000000000000066 0000000000000000 WAX 0 0 8 [ 3] .shstrtab STRTAB 0000000000000000 0004826e 000000000000002e 0000000000000000 0 0 1 [ 4] .symtab SYMTAB 0000000000000000 00048190 00000000000000c0 0000000000000018 5 4 8 [ 5] .strtab STRTAB 0000000000000000 00048250 000000000000001e 0000000000000000 0 0 1Key to Flags: W (write), A (alloc), X (execute), M (merge), S (strings), l (large) I (info), L (link order), G (group), T (TLS), E (exclude), x (unknown) O (extra OS processing required) o (OS specific), p (processor specific)


工具小貼士

關(guān)于靜態(tài)鏈接庫:

ar rcs libxxx.a xx1.o xx2.o 打包靜態(tài)鏈接庫ar -t libc.a 查看靜態(tài)鏈接庫里都有什么目標(biāo)文件ar -x libc.a 會解壓所有的目標(biāo)文件到當(dāng)前目錄gcc --verbose 可以查看整個編譯鏈接步驟

關(guān)于objdump:

objdump -i 查看本機(jī)目標(biāo)架構(gòu)objdump -f 顯示文件頭信息objdump -d 反匯編程序objdump -t 顯示符號表入口,每個目標(biāo)文件都有什么符號objdump -r 顯示文件的重定位入口,重定位表objdump -x 顯示所有可用的頭信息,等于-a -f -h -r -tobjdump -H 幫助

關(guān)于分析ELF文件格式:

readelf -h 列出文件頭readelf -S 列出每個段readelf -r 列出重定位表readelf -d 列出動態(tài)段

關(guān)于查看目標(biāo)文件符號信息:

nm -a 顯示所有的符號nm -D 顯示動態(tài)符號nm -u 僅顯示沒有定義的外部符號nm -defined-only 僅顯示定義的符號

關(guān)于符號的說明:

如果符號類型是小寫的,表明符號是局部符號,大寫表示符號是全局符號。


A:該符號的值是絕對的,在以后的鏈接過程中,不允許進(jìn)行改變。這樣的符號值,常常出現(xiàn)在中斷向量表中,例如用符號來表示各個中斷向量函數(shù)在中斷向量表中的位置。
B:該符號的值出現(xiàn)在.bss段中,未初始化的全局和靜態(tài)變量。
C:該符號的值在COMMON段中,里面的都是弱符號。
D:該符號位于數(shù)據(jù)段中。
I:該符號對另一個符號的間接引用
N:debug符號
R:該符號位于只讀數(shù)據(jù)區(qū)
T:該符號位于代碼段
U:該符號在當(dāng)前文件未定義,定義在別的文件中
?:該符號類型沒有定義

參考資料

https://linuxtools-rst.readthedocs.io/zh_CN/latest/tool/

《程序員的自我修養(yǎng)》




c++11新特性,所有知識點(diǎn)都在這了!

你的c++團(tuán)隊(duì)還在禁用異常處理嗎?

內(nèi)存對齊之格式修訂版

c++11新特性之智能指針

gcc a.c 究竟經(jīng)歷了什么?

談?wù)劤绦蜴溄蛹胺侄文切┦?/span>

免責(zé)聲明:本文內(nèi)容由21ic獲得授權(quán)后發(fā)布,版權(quán)歸原作者所有,本平臺僅提供信息存儲服務(wù)。文章僅代表作者個人觀點(diǎn),不代表本平臺立場,如有問題,請聯(lián)系我們,謝謝!

本站聲明: 本文章由作者或相關(guān)機(jī)構(gòu)授權(quán)發(fā)布,目的在于傳遞更多信息,并不代表本站贊同其觀點(diǎn),本站亦不保證或承諾內(nèi)容真實(shí)性等。需要轉(zhuǎn)載請聯(lián)系該專欄作者,如若文章內(nèi)容侵犯您的權(quán)益,請及時聯(lián)系本站刪除。
換一批
延伸閱讀

LED驅(qū)動電源的輸入包括高壓工頻交流(即市電)、低壓直流、高壓直流、低壓高頻交流(如電子變壓器的輸出)等。

關(guān)鍵字: 驅(qū)動電源

在工業(yè)自動化蓬勃發(fā)展的當(dāng)下,工業(yè)電機(jī)作為核心動力設(shè)備,其驅(qū)動電源的性能直接關(guān)系到整個系統(tǒng)的穩(wěn)定性和可靠性。其中,反電動勢抑制與過流保護(hù)是驅(qū)動電源設(shè)計(jì)中至關(guān)重要的兩個環(huán)節(jié),集成化方案的設(shè)計(jì)成為提升電機(jī)驅(qū)動性能的關(guān)鍵。

關(guān)鍵字: 工業(yè)電機(jī) 驅(qū)動電源

LED 驅(qū)動電源作為 LED 照明系統(tǒng)的 “心臟”,其穩(wěn)定性直接決定了整個照明設(shè)備的使用壽命。然而,在實(shí)際應(yīng)用中,LED 驅(qū)動電源易損壞的問題卻十分常見,不僅增加了維護(hù)成本,還影響了用戶體驗(yàn)。要解決這一問題,需從設(shè)計(jì)、生...

關(guān)鍵字: 驅(qū)動電源 照明系統(tǒng) 散熱

根據(jù)LED驅(qū)動電源的公式,電感內(nèi)電流波動大小和電感值成反比,輸出紋波和輸出電容值成反比。所以加大電感值和輸出電容值可以減小紋波。

關(guān)鍵字: LED 設(shè)計(jì) 驅(qū)動電源

電動汽車(EV)作為新能源汽車的重要代表,正逐漸成為全球汽車產(chǎn)業(yè)的重要發(fā)展方向。電動汽車的核心技術(shù)之一是電機(jī)驅(qū)動控制系統(tǒng),而絕緣柵雙極型晶體管(IGBT)作為電機(jī)驅(qū)動系統(tǒng)中的關(guān)鍵元件,其性能直接影響到電動汽車的動力性能和...

關(guān)鍵字: 電動汽車 新能源 驅(qū)動電源

在現(xiàn)代城市建設(shè)中,街道及停車場照明作為基礎(chǔ)設(shè)施的重要組成部分,其質(zhì)量和效率直接關(guān)系到城市的公共安全、居民生活質(zhì)量和能源利用效率。隨著科技的進(jìn)步,高亮度白光發(fā)光二極管(LED)因其獨(dú)特的優(yōu)勢逐漸取代傳統(tǒng)光源,成為大功率區(qū)域...

關(guān)鍵字: 發(fā)光二極管 驅(qū)動電源 LED

LED通用照明設(shè)計(jì)工程師會遇到許多挑戰(zhàn),如功率密度、功率因數(shù)校正(PFC)、空間受限和可靠性等。

關(guān)鍵字: LED 驅(qū)動電源 功率因數(shù)校正

在LED照明技術(shù)日益普及的今天,LED驅(qū)動電源的電磁干擾(EMI)問題成為了一個不可忽視的挑戰(zhàn)。電磁干擾不僅會影響LED燈具的正常工作,還可能對周圍電子設(shè)備造成不利影響,甚至引發(fā)系統(tǒng)故障。因此,采取有效的硬件措施來解決L...

關(guān)鍵字: LED照明技術(shù) 電磁干擾 驅(qū)動電源

開關(guān)電源具有效率高的特性,而且開關(guān)電源的變壓器體積比串聯(lián)穩(wěn)壓型電源的要小得多,電源電路比較整潔,整機(jī)重量也有所下降,所以,現(xiàn)在的LED驅(qū)動電源

關(guān)鍵字: LED 驅(qū)動電源 開關(guān)電源

LED驅(qū)動電源是把電源供應(yīng)轉(zhuǎn)換為特定的電壓電流以驅(qū)動LED發(fā)光的電壓轉(zhuǎn)換器,通常情況下:LED驅(qū)動電源的輸入包括高壓工頻交流(即市電)、低壓直流、高壓直流、低壓高頻交流(如電子變壓器的輸出)等。

關(guān)鍵字: LED 隧道燈 驅(qū)動電源
關(guān)閉