gcc linker简要手册

名词解释:
bfd (binary format description) : 是指GNU的bfd库项目,其目标是希望通过一种统一的接口来处理各种不同的目标文件。
Orphan sections : 是指包含在输入目标文件的 段 中,但是没有在链接脚本中指定的输入段。




链接脚本完成的工作:
W1 :  使用 MEMORY 命令将整个内存空间分块,这多见于嵌入式系统中。
W2 :  将输入目标文件中某些相同属性的 输入段 合并构成最终目标文件的 输出段。
W3 : 将各个 输出段 塞到 W1 分出来的内存块中。
其中,W2 是必选的,W1 和 W3 是可选的,windows下一般只有 W2,嵌入式系统中一般会都包括。


————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————
1. 调用方式(invocation)
1)直接通过ld.exe调用,
2)通过gcc或者g++间接调用(这里的gcc和g++为编译器驱动程序——compiler driver),此时的链接用参数需要通过-Wl选项引入,或者通过-Xlinker选项引入。如下,
gcc foo.o bar.o -Wl,-eENTRY -Wl,-Map=a.map
gcc foo.o bar.o -Wl,-eENTRY -Wl,-Map,a.map
gcc foo.o bar.o -Xlinker -e=ENTRY -Xlinker -Map=a.map
gcc foo.o bar.o -Xlinker -e -Xlinker ENTRY -Xlinker -Map -Xlinker a.map
Note1:通过gcc间接调用时,gcc直接可以解释的参数(不通过-Wl和-Xlinker传递)包括:
a)object-file-name,作为链接的输入目标文件,可以是.o文件(如果编译时输出文件后缀为.o)。
b)-llibrary 或 -l library,链接时寻找名为 liblibrary.a 的库文件(第二种方式仅仅为了与 POSIX 兼容,故而不推荐)。该选项的位置会对链接是有影响的,
链接器是根据库文件和目标文件的命令行出现的顺序进行搜寻和处理的,一般会将库文件放在目标文件的后面。
c)-nostartfiles,链接时不使用标准的系统启动文件(主要用于嵌入式系统)。
d)-nodefaultlibs,链接时使用标准的系统库(主要用于嵌入式系统)。仅将指定的库传递给链接器,用于指定系统库的选项 -static-libgcc 和 -shared-libgcc
被忽略。
e)-nostdlib,等价于同时使用 -nostartfiles 和 -nodefaultlibs。
f)-pie,在支持位置无关代码的机器上生成位置无关的可执行文件。为了确实得到位置无关代码,在编译时需要加入 -fpie 或者 fPIE 选项。
g)-s,从可执行文件中移除所有符号表和重定位信息。
h)-static,链接时强制使用静态库。
i)-shared,生成一个可以用于链接使用的共享目标对象文件(.so文件或者.dll文件),与-pie一样,在编译时需要加入 -fpie 或者 fPIE 选项。
j)-shared-libgcc 和 -static-libgcc,链接时使用静态还是动态的 libgcc 库文件。
k)-static-libstdc++,主要用于使用 gcc 链接包含c++的目标文件时。
l)-T script,指定自己的链接脚本文件,替代默认的。
m)-Xlinker option,将ld选项传递给链接器。当传递的ld选项包含选项名和参数时,需要使用两次-Xlinker,例如:-Xlinker -assert -Xlinker definitions。在
使用的是GNU的ld时,还可写作:-Xlinker -assert=definitions。
n)-Wl,option,将ld选项传递给链接器。格式为:-Wl,-Map,output.map 或者 -Wl,-Map=output.map。一次可以传递多个选项,例如,
gcc dlltest.c -shared -o dlltest.dll -Wl,--output-def,dlltest.def,--out-implib,dlltest.lib,--kill-at,--file-alignment,1024
o)-Ldir,添加目录 dir 到搜索库文件的路径。
Note2:关于选项,需要注意以下几点:
a)对于只有一个字母的选项,如果带有参数,则可以将选项和参数连起来写,比如 -eENTRY,表示链接后可执行文件的入口函数为“ENTRY”。
b)对于多字母选项,前缀可以是"-"或"--",两者完全等价。
c)对b)的一个例外是以字母"o"开始的选项(比如 --omagic),因为跟"-o"选项冲突,所以只能是"--"前缀。
d)对于多字母选项,如果带有参数,则有两种书写方式,--trace-symbol foo 和 --trace-symbol=foo。

2. ld调用时的通用选项(仅涉及PE-i386 和 arm-elf相关的选项),使用 gcc 链接时可能需要通过 -Xlinker 和/或 Wl 选项将这些选项传递给ld
1)-e ENTRY 或 --entry=ENTRY,使用 ENTRY 作为你的可执行程序的入口点。
2)–exclude-modules-for-implib module,module,…,仅由于 PE-i386,防止自动导出变量名称。
3)-EB 和 -EL,大端模式和小端模式。
4)-i 或 -r,增量式链接(多次链接)。
5)-l namespec 或 --library=namespec,等价于 1 中 Note1 的 b),即:链接时寻找名为 libnamespec 的库。由于每个库文件只查询一次,所以同样也存在顺序的问题,
解决方案是使用 6)中的选项指定库文件,然后在寻找符号(变量或者函数)时就会多次查询这些库文件,从而避免顺序问题。
另外,同一个归档文件可以指定多次,因其位置不同,对其搜索的时机也不同。
6)-( archives -) 或 --start-group archives --end-group,其中的 archives 为一个由".a文件"和/或"-l属性"组成的列表(多个文件/属性之间以"空格"分隔)。使用该
选项会比较费资源,所以最好只用于多个归档文件(archive file)之间存在相互参照的情况。
7)-L searchdir 或 --library-path=searchdir,指定搜寻 归档文件(archive file)和 链接脚本文件(只有使用-T选项时有效) 的目录。有以下几个要点:
a)-L选项可以指定多次,其顺序就是搜寻归档文件时的搜索顺序。
b)所有的-L指定的目录都用于搜索所有的 -l 选项指定的归档文件(archive file)以及 -T 选项指定的链接脚本。
c)-L指定的目录要优先于默认的系统库目录搜索。
8)-M 或 --print-map,打印 map 信息到标准输出。
9)-Map=mapfile,打印 map 信息到文件。
10)-n 或 --nmagic,关闭 section 的页对齐属性,并禁止链接时使用动态库。
11)-N 或 --omagic,在 -n 的基础上,将 text 段和 data 段的属性设置为可读可写。
12)--no-omagic,将 text 端设置为只读,并使 data 段页对齐。
13)-o output 或 --output=output,指定输出文件名称。也可以通过链接脚本的 OUTPUT 命令指定输出文件名。默认的输出文件名为 a.out。
14)-O level,优化等级。
15)-s 或 --strip-all,从输出文件中移除所有的符号信息。
16)-S 或 --strip-debug,从输出文件中移除所有的 debug 信息。
17)-t 或 --trace,链接的过程中,打印出ld的输入目标文件。
18)-T scriptfile 或 --script=scriptfile,链接脚本文件。链接脚本文件的搜索次序为:当前目录 ---> -L指定为目录 ---> 系统指定的目录。
19)--unique[=SECTION],通过一个例子说明,
a)gcc  -Xlinker --unique=.text app.o main.o  -g0 -mwindows -T my.lds   表示所有输入目标文件和库文件中的 .text 段都单独成为一个独立的.text段,也就是
最终的目标文件中存在多个.text段,它们分别来自输入目标文件和库文件(按顺序),由于需要页对齐,所以最终的体积会比一般情况大。
b)gcc  -Xlinker --unique= app.o main.o  -g0 -mwindows -T my.lds  表示没有在 链接脚本 中定义的输入文件中段(这里指多个输入文件中都有,但链接脚本中没
指定的输入段),会在最终的输出文件中各自成段,比如 .CRT 段。
Note1:可以使用该选项多次以指定多个 SECTION 参数。
20)-Bdynamic 或 -dy 或 -call_shared,强制-l 选项使用动态库(这也是默认情况)。
21)-Bstatic 或 -dn 或 -non_shared 或 -static,强制-l 选项使用静态库,等同于链接时使用 gcc -static。
22)--cref,生成 交叉参考列表(每个符号出自哪一个目标文件),如果指定 -Map 选项,则将该列表打印到map文件;否则,将打印到标准输出。
23)--fatal-warnings 和 --no-fatal-warnings,是否将警告当做错误。
24)--force-exe-suffix,强制添加exe后缀。
25)--gc-sections 和 --no-gc-sections,是否从最终的目标文件中删除没有使用的输入段,一般会在输入目标文件编译时使用-ffunction-sections, -fdata-sections选项。
26)--print-gc-sections 和 --no-print-gc-sections,是否显示删除的输入段。该选项仅当启用了 --gc-sections 选项时才有用。
27)--print-output-format,打印出最终的目标文件的格式,windows下为 pei-i386。
28)--target-help,打印出所有与当前 target 相关的选项,下面为windows下 i386pe 相关的选项,
i386pe: 
 --base_file <basefile>             Generate a base file for relocatable DLLs
 --dll                              Set image base to the default for DLLs
 --file-alignment <size>            Set file alignment
 --heap <size>                      Set initial size of the heap
 --image-base <address>             Set start address of the executable
 --major-image-version <number>     Set version number of the executable
 --major-os-version <number>        Set minimum required OS version
 --major-subsystem-version <number> Set minimum required OS subsystem version
 --minor-image-version <number>     Set revision number of the executable
 --minor-os-version <number>        Set minimum required OS revision
 --minor-subsystem-version <number> Set minimum required OS subsystem revision
 --section-alignment <size>         Set section alignment
 --stack <size>                     Set size of the initial stack
 --subsystem <name>[:<version>]     Set required OS subsystem [& version]
 --support-old-code                 Support interworking with old code
 --[no-]leading-underscore          Set explicit symbol underscore prefix mode
 --thumb-entry=<symbol>             Set the entry point to be Thumb <symbol>
 --insert-timestamp                 Use a real timestamp rather than zero.
This makes binaries non-deterministic
 --add-stdcall-alias                Export symbols with and without @nn
 --disable-stdcall-fixup            Don't link _sym to _sym@nn
 --enable-stdcall-fixup             Link _sym to _sym@nn without warnings
 --exclude-symbols sym,sym,...      Exclude symbols from automatic export
 --exclude-all-symbols              Exclude all symbols from automatic export
 --exclude-libs lib,lib,...         Exclude libraries from automatic export
 --exclude-modules-for-implib mod,mod,...
Exclude objects, archive members from auto
export, place into import library instead.
 --export-all-symbols               Automatically export all globals to DLL
 --kill-at                          Remove @nn from exported symbols
 --out-implib <file>                Generate import library
 --output-def <file>                Generate a .DEF file for the built DLL
 --warn-duplicate-exports           Warn about duplicate exports.
 --compat-implib                    Create backward compatible import libs;
  create __imp_<SYMBOL> as well.
 --enable-auto-image-base           Automatically choose image base for DLLs
  unless user specifies one
 --disable-auto-image-base          Do not auto-choose image base. (default)
 --dll-search-prefix=<string>       When linking dynamically to a dll without
  an importlib, use <string><basename>.dll
  in preference to lib<basename>.dll 
 --enable-auto-import               Do sophisticated linking of _sym to
  __imp_sym for DATA references
 --disable-auto-import              Do not auto-import DATA items from DLLs
 --enable-runtime-pseudo-reloc      Work around auto-import limitations by
  adding pseudo-relocations resolved at
  runtime.
 --disable-runtime-pseudo-reloc     Do not add runtime pseudo-relocations for
  auto-imported DATA.
 --enable-extra-pe-debug            Enable verbose debug output when building
  or linking to DLLs (esp. auto-import)
 --large-address-aware              Executable supports virtual addresses
  greater than 2 gigabytes
 --disable-large-address-aware      Executable does not support virtual
  addresses greater than 2 gigabytes
 --enable-long-section-names        Use long COFF section names even in
  executable image files
 --disable-long-section-names       Never use long COFF section names, even
  in object files
 --dynamicbase             Image base address may be relocated using
address space layout randomization (ASLR)
 --forceinteg Code integrity checks are enforced
 --nxcompat Image is compatible with data execution prevention
 --no-isolation Image understands isolation but do not isolate the image
 --no-seh Image does not use SEH. No SE handler may
be called in this image
 --no-bind Do not bind this image
 --wdmdriver Driver uses the WDM model
 --tsaware                   Image is Terminal Server aware
29)-pie 或 --pic-executable,生成位置无关的可执行文件(仅适用于ELF文件)。
30)-shared ,生成dll时使用。
31)--split-by-file[=size],当同名的输入段的个数超过 size 时,生成一个新段,默认size为1。
32)--stats,显示链接时间和内存占用情况。
33)--dll-verbose 或 --verbose,打印出标准的链接脚本文件。
34)@file,从文件 file 中读取选项,插入到@file 所在的位置。
35)-x  或  --discard-all,删除所有局部符号 。
36)-X 或  --discard-locals,删除所有由“.L”或者“L”开头的局部符号。
37)-y symbol 或 --trace-symbol=symbol,打印出符号 symbol 出现在其中的输入文件名。
38)--oformat=output-format,指定输出文件的格式,可以用 ihex 指定生成 hex 文件。
39)-b bfdname 或 --format=bfdname ,指定输入文件的格式,(一般情况下,ld会自动辨识输入文件的格式,所以没什么用处)。

3. i386 PE Targets的专用选项,就是通用选项的 --target-help 打印出的哪些选项
1)--add-stdcall-alias,用于_stdcall 调用生成的 dll,给导出符号添加别名:源代码中的函数名=修饰后的函数名,以便动态调用 dll 时可以在 GetProcAddress
中使用原函数名。也就是说,会在dll中生成2个函数声明,一个是带@的 一个是不带@的。
2)--kill-at,类似于--add-stdcall-alias,区别在于只在dll中导出不带@的函数声明,也就是源代码中的函数名。
3)--dll 生成dll文件而不是exe文件,主要跟-shared选项配合使用,当然不用--dll选项也能生成dll文件。也就是说该选项用处不大。
4)--enable-long-section-names 和 --disable-long-section-names,是否使能大于8字符的段名,默认为--enable-long-section-names。
5)--enable-stdcall-fixup 和 --disable-stdcall-fixup,用于将 dll 链接进 exe 时使用,在找不到指定导出函数的情况下,链接器会去尝试查找 mangling/no 
mangling 的导出函数名。Disable 为不使能该功能。 
6)--export-all-symbols,生成dll时导出所有的符号,此时不管源代码中是否含有__declspec(dllexport),都没有任何作用,都会导出所有符号。
7)--exclude-all-symbols,不导出任何符号,仅适用于源代码中不含有__declspec(dllexport)时,如果代码中含有__declspec(dllexport),则该选项失效。
8)--exclude-symbols symbol,symbol,...,不导出某些符号,仅适用于源代码中不含有__declspec(dllexport)时,如果代码中含有__declspec(dllexport),则该选项失效。
9)--file-alignment size,指定映像文件中的 section 对齐到的字节数,默认为512字节。
10)--section-alignment,指定映像文件加载到内存后,每个 section 对齐到的字节数,默认为0x1000(即4k)。
11)--heap size,设置堆的大小。可参考 https://msdn.microsoft.com/zh-cn/library/yazf4083.aspx
12)--stack size,设置栈的大小。可参考 https://msdn.microsoft.com/zh-cn/library/35yc2tc3.aspx
13)--image-base value,指定 dll/exe 映像的基地址,默认情况下(跟gcc有关),dll 的基地址为 0x6C100000;exe 的基地址为 0x400000。
14)--major-image-version value 和 --minor-image-version value,设置映像文件(dll/exe)的大和小版本号。
15)--major-os-version value 和 --minor-os-version value,设置os的大和小版本号。
16)--major-subsystem-version value 和 --minor-subsystem-version value,设置 subsystem 的大和小版本号。
17)--enable-auto-image-base 和 --disable-auto-image-base,是否使能自动产生目标文件的基地址。默认情况是--enable-auto-image-base,此时的基地址设置为:
dll ---> 0x6C100000,exe ---> 0x400000。如果设置为 --disable-auto-image-base,则:dll ---> 0x10000000,exe ---> 0x400000。
18)--output-def file,当生成 dll 最终目标文件时,生成与之对应的 def 文件。使用 dlltool 和该 def 文件可以生成静态加载 dll 时的 lib 导入库 文件;该文件也可
作为动态加载 dll 时的参考。
19)--out-implib file,生成 dll 最终目标文件的同时,生成静态加载 dll 时的 lib 导入库 文件。这就省去了通过 dlltool 和 def 文件生成 lib 文件的步骤。
#pragma comment(lib,"dlltest.lib") 
20)--subsystem [name[:major[.minor]]],设置子系统类型,一般情况下会根据目标文件的类型自动设置,所以可以不设置。
21)--dynamicbase,加载可执行文件到内存时,可能使用 ASLR(address space layout randomization)技术进行重定位,只适用于 Vista 及之后的 windows 系统。可参考,
http://www.xuebuyuan.com/663525.html 
http://blog.csdn.net/better0332/article/details/5262990 
https://msdn.microsoft.com/zh-cn/library/bb384887.aspx
此选项修改可执行文件头,以指示是否应在加载时对应用程序随机重新设定基址。
它的原理就是在当一个应用程序或动态链接库时,如kernel32.dll,被加载时,如果其选择了被ASLR保护,那么系统就会将其加载的基址随机设定。这样,攻击者就无法事先
预知动态库,如kernel32.dll的基址,也就无法事先确定特定函数,如VirtualProtect,的入口地址了。也就是基地址不是0x6C100000(dll)和 0x400000(exe)了。
默认情况下,是不开启 ASLR 的,但是VC++里面是默认开启的。
22)--forceinteg,强制进行代码完整性检查。
23)--nxcompat,指示测试的可执行文件与 Windows 数据执行保护功能(DEP)兼容。可参考,
http://baike.baidu.com/link?url=0Ub8JfvKZyPxBck_LPGLv8gZdIEldQxH9d5lPuus2GuNeF3uI1EmhiTAtABn1-w2Fx8DuX8RazlphBmZEMp-i_glNcczKunZ6kU_BDhYs17
https://msdn.microsoft.com/zh-cn/library/ms235442.aspx
http://blog.163.com/cumt_xl/blog/static/19071504420138411411326/
https://msdn.microsoft.com/zh-cn/library/ms913190(winembedded.5).aspx
数据执行保护(DEP,Data Execution Prevention),也称为non-execute(NX),这就是--nxcompat中nx的由来。这是一种windows系统的内存保护机制,分为软DEP和硬DEP,
这里应该只是指软DEP,大概意思是:a)内存中非可执行区域的代码是不可执行的(比如.data段);b)堆和栈中的代码是不可执行的。如果试图执行这些区域的代码就会抛
出异常。
24)--no-seh,不使用SEH异常处理机制,主要针对C++。

Note1:上述选项中,1)~3)、6)~8)、18)、19)仅适用于生成dll文件。
  4)、9)~17)、21)~24)既适用于生成dll文件,又适用于生成exe文件。
  5)仅适用于exe文件。
  
Note2:14)~16)可以用于可执行文件(exe/dll)的版本控制。
Note3:21)~24)主要用于保证代码安全。

4. 使用gcc生成dll文件细则
1)如果整个源文件中都没有__declspec(dllexport),则默认情况下,所有的全局符号(全局变量和函数)都作为输出符号。
2)只要有一处出现__declspec(dllexport),则仅仅将__declspec(dllexport)声明的全局符号输出。
3)在VC/VS中,需要自己手写 def 文件来导出某些符号。而 gcc 中,可以通过选项生成满足自己要求的到处符号,并自动生成 def 文件,比VC/VS方便很多。
4)一个命令行调用示例:
gcc dlltest.c  -shared -o dlltest.dll -Wl,--output-def,dlltest.def,--out-implib,dlltest.lib,--kill-at,--file-alignment,1024
  |S1 |  S2     |   S3   |    S4        |                                            S5 |
  S1:compiler driver                           
  S2:源文件
  S3:生成动态库
  S4:输出文件名
  S5:传递给ld的选项,当然这些选项也可以通过多个 -Wl 和/或 -Xlinker 给出。
————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————   
5. 设置PE文件的入口点(程序中执行的第一条指令)的方式可以有以下几种,按优先级顺序给出
1)命令行的 -e 选项,---> 2 中的 1)
2)链接脚本中的 ENTRY(symbol)命令
3)如果定义了 *start* ,则就是其中的一个。对于 PE 而言,根据其 subsystem 的不同,入口点也不同,如下:
a)SUBSYSTEM:CONSOLE,入口为 mainCRTStartup 或 wmainCRTStartup (宽字符时)
b)SUBSYSTEM:WINDOWS,入口为 WinMainCRTStartup 或 wWinMainCRTStartup (宽字符时)
c)DllMain 使用 __stdcall 定义的dll,入口为 _DllMainCRTStartup 
对于嵌入式系统,一般为 _start 。
4)如果存在 .text 段,则为 .text 段的第一个字节
5)内存地址0

6. 用以处理文件的链接脚本命令
1)INCLUDE file_name ,在一个链接脚本中包含其它链接脚本file_name,类似于 c 语言中的 #include 。其中, file_name 的搜索目录为当前目录和 -L 选
项指定的目录。这里,INCLUDE最多嵌套10层。
2)INPUT(file1, file2, ...) 或 INPUT(file1 file2 ...) ,始终将括号内的文件作为链接时的输入文件,以避免每次在命令行输入。如果使用 INPUT (-lfile) 
,ld将会将名字转化为libfile.a,就像命令行参数 -l。
3)GROUP(file, file, ...) 或 GROUP(file file ...) ,等价于 2 中的 6)-( archives -)。
4)OUTPUT(file_name) ,设置输出可执行文件/目标文件的名称,等价于命令行的 -o filename , 但优先级比命令行选项 -o 低。可以用于设置默认的输出文件名
以代替 gcc 默认的 a.*。
5)SEARCH_DIR(path) ,等价于命令行的 -L path ,但命令行指定的目录先被搜索。
6)STARTUP(file_name) ,与 INPUT 命令作用相同,但用该指令指定的文件 file_name 将作为第一个被链接的文件。
Note1:1)~3)中指定的输入目标文件会出现在 插入 链接脚本文件的地方。比如,
链接脚本中有 INPUT(file1, file2),脚本名为 my.lds。命令行链接指令为 gcc a.o b.o ...options... -T my.lds c.o d.o,  则当链接器执行链接时,输入
目标文件的顺序为 a.o b.o file1 file2 c.o d.o
如果此时又指定了 STARTUP(c.o),则顺序为: c.o a.o b.o file1 file2 d.o


7. 用以处理目标文件格式的命令
1)OUTPUT_FORMAT(bfdname) 或 OUTPUT_FORMAT(default, big, little) ,指定最终输出目标文件的格式(BFD格式)。前者等价于命令行的 --oformat=bfdname
,但命令行的选项优先级高。可以通过 objdump -i 或 objcopy --info 查看可选的 bfdname , 要在Windows 下生成 PE 文件选择 pei-i386。
Note1:pei-i386 和 pe-i386 的区别,前者是映像文件(也就是最终的可执行文件格式),后者是普通的目标文件(也就是 .o 或 .a)。
Note2:生成 PE 文件时一般选择前一种格式,生成 arm 平台的目标文件时通常选择后一种格式。
Note3:后一种格式是与命令行的 -EB 和 -EL 选项相关的。说明如下,
a)如果没有 -EB 和 -EL 选项,则输出格式为 default 中指定的格式;
b)如果有 -EB 选项,则输出格式为 big 中指定的格式;
c)如果有 -EL 选项,则输出格式为 little 中指定的格式;
例如,如果有 OUTPUT_FORMAT ("elf32-littlearm", "elf32-littlearm", "elf32-littlearm") ,则总会生成elf32小端模式的指令代码。
2)TARGET(bfdname) ,等价于命令行选项 -b bfdname,指定输入文件的格式。一般很少用该命令。
Note1:如果使用了 TARGET(bfdname) ,而没有使用 OUTPUT_FORMAT , 则输入输出文件格式都是 bfdname 。


8. 其它的链接脚本命令
1)REGION_ALIAS(alias, region) ,给内存区域 region 起一个别名 alias 。主要用于嵌入式系统。其中的 region 是通过 MEMORY 指令指定的。
2)ASSERT(exp, message) ,类似于 C 语言中的 assert(exp) + perror(message)。
3)NOCROSSREFS(SECTION SECTION ...) ,:检查列出的输出 section ,如果发现他们之间有相互引用,则报错。对于某些系统,特别是内存较紧张的嵌入式系统,
某些 section 是不能同时存在内存中的,所以他们之间不能相互引用。
4)OUTPUT_ARCH(bfdarch) ,指定输出文件的 machine architecture(体系结构)。可以用命令 objdump -f a.exe 查看文件 a.exe 的 bfdarch 。例如, 32位
windows 平台(我的电脑)下的 bfdarch 为 i386。arm平台的 bfdarch 为 arm。
--------------------------------------------------------------------------------------------------------|
5-8 为简单的链接器脚本命令,复杂的参见  -   |
-----------------------------------------------------------------------------------------------------|  |
9. 链接脚本中的赋值语句 ( "." 是一个特殊的符号,是位置计数器(location counter)) |  |
1)简单的赋值操作,可以使用 C 语言中的任何赋值操作符,比如 = += -= /= <<= >>= $= |= 等。 \  /
symbol = expression ;  \/
Note1:赋值语句后面必须使用 ";",这根 C 语言是一样的。
Note2:位置计数器 "." 只能在 SECTION 内部使用该符号(P47)。
Note3:赋值语句,可以作为单独的部分,也可以作为’SECTIONS’命令中的一个语句,或者作为’SECTIONS’命令中输出段描述的一个部分。例如,
a floating_point = 0;
SECTIONS
{
.text :
{
*(.text)
b _etext = .;
}
c _bdata = (. + 3) & ~ 3;
.data : { *(.data) }
}
其中, a 处为作为单独的部分,floating_point 定义为0;
b 处为’SECTIONS’命令中输出段描述的一个部分, _etext 定义为输出 .text 段后面的第一个地址;
c 处为’SECTIONS’命令中的一个语句,_bdata 定义为 输出 .text 段后面对齐到 4 byte 的第一个地址。
2)使用 HIDDEN(symbol = expression) 修饰的赋值语句,只适用于 ELF 格式,也就是适用于所有的 arm (无论是否带操作系统)和 linux 操作系统。例如,把
1 中 Note3 中的 3 条赋值语句使用 HIDDEN 修饰,即:
HIDDEN(floating_point = 0); HIDDEN(_etext = .); HIDDEN(_bdata = (. + 3) & ~ 3);
则,这 3 个符号 —— floating_point、_etext 和 _bdata 只能在链接脚本中使用,而不能在 c/c++ 中通过 extern 对它们进行引用。
3)使用 PROVIDE(symbol = expression) 修饰的赋值语句。通过一个例子说明其用法,
SECTIONS
{
.text :
{
*(.text)
_etext = .;
PROVIDE(etext = .);
}
}
这里的 _etext 在 C 语言中是可以作为一个地址使用的(但不能在 C 语言中定义 _etext ,函数和变量都不行),并且一定会出现在符号表里面。而对于 
etext ,则既可以在 C 语言中定义,也可以只在 C 语言中使用。
用 ARMCC 的概念描述:不用任何修饰的符号是 强符号,而 PROVIDE 修饰的符号是 弱符号。
4)使用 PROVIDE HIDDEN(symbol = expression) 修饰的赋值语句。这种符号同时满足 HIDDEN 和 PROVIDE 的属性,也就是仅适用于 arm 平台。
5)在源代码中引用链接脚本中的符号。
在 C 语言中定义的变量,都会有一块内存区域与之对应,该变量名加入到符号表中,变量的值加入到对应的内存处。在符号表中,该变量名与变量值的内存区域
相对应。
而链接脚本中定义的变量/符号,仅会加入到符号表中,并没有相应的内存区域与之对应,也就是说它仅有地址没有值。例如,
C  语 言: foo = 1000;    (1) 会将 foo(也可能是_foo)加入到符号表中,并将 1000 加到给 foo 分配的内存中。
链接脚本: foo = 1000;    (2) 会将 foo 加入到符号表中,而 1000 是给 foo 分配的内存地址,里面什么都没有。所以,在 C 源文件中使用时,首先需
要使用 extern 声明是外部符号;其次要指定变量类型——占用多少内存,然后才能使用。比如,对于链接脚本中的 foo,
在 C 语言中的代码可以为:extern  int   foo; ,也可以为  extern  char   foo; ,还可以为 extern  int foo[4];


10. SECTIONS 命令
SECTIONS 命令用于指导链接器如何将 输入段 拼成 输出段,并在内存中对输出段进行合理的排列。结构如下,
____________________________________________________________________________________________________________________________________
| SECTIONS            | | 输出 section 描述: |
| {                    | SECTION-COMMAND 有四种: | sec_name [address] [(type)] : |
| SECTIONS-COMMAND | (1) ENTRY 命令 (第5部分) | [AT(lma)] [ALIGN(section_align) | ALIGN_WITH_INPUT] |
| SECTIONS-COMMAND    | (2) 符号赋值语句  (第9部分) | [SUBALIGN(subsection_align)] [constraint] |
| ...                 | (3) 输出 section 描述 | { |
| } |   (4) section 叠加描述(不常用) | output-section-command |
| | | output-section-command |
| | | ... |
| | | } [>region] [AT>lma_region] [:phdr :phdr ...] [=fillexp] |
————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————
| output-section-command 可以是下面四种中的一种: |
| (1) 符号赋值语句  (第9部分) |
| (2) 输入段描述 (本部分的第 2)小节) |
| (3) 直接包含的数据 (本部分的第 3)小节) |
| (4) 特殊的输出段关键字 (本部分的第 4)小节) |
————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————
1)输出段描述(Output Section Description)
上表中给出了 输出段描述 的通用结构,大多数的可选选项("[*]"里面的部分)都很少用到;其中的 空格、":"和"{/}"都是必须的,不能省略;"{"前面的换行 是可选的。
a) sec_name 为输出段的名字,在 windows 和 arm 下没有什么限制,按照 C 语言的变量名遵循的法则起名就可以。
b) address 为输出段的地址(这里指 VMA —— Virtual Memory Address),为可选项。如果不给定该地址,则将存在以下几种情况,
b1)如果指定了 >region ,则地址就会是 region 中下一个空闲块的第一个地址。
b2)如果没给出 >region ,但是 SECTIONS 命令前面已经用 MEMORY 指令将内存空间分块,则会将第一块属性与 sec_name 相同的块 作为 >region,然后和b1)一样。
b3)如果根本就没有使用 MEMORY 命令将内存空间分块(windows下默认就不分块),或者虽然用 MEMORY 指令分块了,但却没有与 sec_name 段属性相同的块。则,
sec_name 段的 VMA 会根据 地址计数器(location counter)——"."来确定。
示例:  
.text . : { *(.text) } ---->  其 VMA 精确地等于 地址计数器 ".";
.text : { *(.text) } ---->  其 VMA 等于  (. + 3) & ~ 3,即对齐到 4 字节处。
Note1:address 可以为任意有意义的表达式(第部分),例如 .text ALIGN(0x10) : { *(.text) } 就是将 VMA 对齐到 16 字节。
Note2:指定 VMA 将会改变 地址计数器 "." 的值。
Note3:ALIGN() 可能会改变 地址计数器 "." 的值。
c) type 为输出段的属性设置。
NOLOAD :程序运行时不加载到内存;
DSECT、COPY、INFO和OVERLAY :很少用,仅仅为了与以前的版本兼容。这种类型的section 必须被标记为“不可加载的”,以便在程序运行不为它们分配内存。
d) 输出 section 的 LMA(加载时的内存地址):通过 AT(expression) 或 AT>lma_region(MEMORY命令分的内存块名) 来指定(可选项)。如果不指定 LMA,则会使用
VMA 作为其 LMA。用处参见 ld手册 P60。
e) ALIGN() 和 ALIGN WITH INPUT ,增加对齐边界字节数。前者是指定对齐字节数,后者的对齐边界字节数等于 所有输入段中的最大值。
f) SUBALIGN 为设置一个输出端中 各输入段的对齐边界字节数,这将覆盖掉 各输入段 的默认值。
g) constraint 为设置该输出段生成的条件。当为 ONLY_IF_RO 时,指只有所有输入段只读时才生成输出段;当为ONLY_IF_RW,指只有所有输入段可读可写时生成输出段。
h) >region 表示运行时该输出段所在的 内存块名称。
i) :phdr :phdr ... 暂时不清楚什么意思。
j) =fillexp 表示任何输出 section 描述内的未指定的内存区域,都使用 fillexp 进行填充。这里的 fillexp 至少是4个字节(大端模式)。例如,
SECTIONS { .text : { *(.text) } =0x90909090 }

--------------------------------------------------------------------------------------------------------
|   下面 2)~4)小节为 output-section-command 的说明 |
--------------------------------------------------------------------------------------------------------
2)输入段描述(Input Section Description)
输入段描述是最常用的  output-section-command ,也是在链接脚本中最常进行的操作。
输入端描述主要是对两个部分的描述:
p1) 输入目标文件
p2) 输入目标文件中的 段(section)
基本的输入端描述语法格式: : *([EXCLUDE_FILE(filename1 filename2 ...) sec_name1 sec_name2 ...)  或
*([EXCLUDE_FILE(filename1 filename2 ...) sec_name1)  *([EXCLUDE_FILE(filename1 filename2 ...) sec_name2) ...  或
filename1(sec_name1 sec_name2 ...)  filename2(sec_name1 sec_name2 ...) ...  或
filename1(sec_name1) filename2(sec_name1) ...  filename1(sec_name2) filename2(sec_name2)...  或
filename1(sec_name1) filename1(sec_name2) ...  filename2(sec_name1) filename2(sec_name2)...
 
a) 通配符 "*" 的使用:*(.text) 表示所有输入目标文件中的 .text 段;a.o(*)表示输入目标文件 a.o 中的所有段,也可直接写作 a.o。
b) EXCLUDE_FILE(*)的使用:表示将 "()" 中的文件排除在外,也就是不包含其中的文件。
c) 上述各种语法格式都是将多个 输入目标文件 的 多个段 拼成一个 输出段,但是各输入段在输出段中的顺序不同。例如,
*(.text .rdata) : 表示先拼 第一个输入目标文件的 .text 和 .rdata 段,然后再拼第二个文件的,以此类推。
*(.text) *(.rdata) : 表示先拼所有输入目标文件(顺序问题参见 6 中的 Note1)的 .text 段,然后再拼所有的 .data 段。
d) 通配符 "?" 的使用,匹配任意一个字符。
e) 通配符 "[chars]" 的使用:表示任意一个 CHARS 内的字符,可用-号表示范围,如:a-z 。
f) 对 输入目标文件 和/或 输入段 进行排序,也就是改变了 c) 的拼接顺序。
SORT_BY_NAME : 按名字进行升序排列,其别名为 SORT;
SORT_BY_ALIGNMENT : 按照对齐进行降序排列,对齐数大的放在前面,小的放在后面,可节省空间。
SORT_BY_INIT_PRIORITY : 还不知道具体什么意思。
当嵌套使用排序时,外层的先起作用,比如 SORT_BY_NAME (SORT_BY_ALIGNMENT (wildcard section pattern)) ,就是先对各段 按名字进行升序排列,然后同名的
段 再按照 对齐大小 按降序排列。
g) 关于 COMMON 段,在很多目标文件中并没有单独的 COMMON 段,但是链接时却需要将这个 暗含的 COMMON 段拼到输出段中,一般会放到 .bss 段中,即
.bss { *(.bss) *(COMMON) }   ----> windows 和 arm 平台都是这么做的。
h) 防止孤立段 被删除(垃圾回收) 的 KEEP 命令。如果使用了链接选项 --gc-sections,则某些只定义但没引用的段会被优化掉。比如,嵌入式系统中的中断向量表,
此时需要使用
.isr_vector : {KEEP(*(.isr_vector))}
几个例子:
.DATA : { [A-Z]*(.data) }   ----> 所有以大写字母开始的输入目标文件的 .data 段构成输出 .DATA 段。
.data : { *(.data) }        ----> 所有输入目标文件的 .data 段构成输出 .data 段。
如果上述两个 输入段描述 出现在同一个 链接脚本中,则第二个的含义就变成:所有不以大写字母开始的输入目标文件的 .data 段构成输出 .data 段。

Note1:任何输入文件的任意段只能在 SECTIONS 内出现一次。如果多次给出,则仅第一次给出时起作用。
3)输出段中直接包含的数据
意思就是说,直接在链接脚本中往 内存 中填数据。BYTE, SHORT, LONG, QUAD/SQUAD 分别指往内存中写 1、2、4、8个字节(64位系统)。如果是 32 位系统,QUAD指将一
个32位的值扩充至64位写入内存(高32位为0), SQUAD 指将一个32位的值扩充至64位写入内存(高32位为1)。格式为,
BYTE(expression) 
SHORT(expression) 
LOGN(expression) 
QUAD(expression) 
SQUAD(expression)
在当前输出 section 内可能存在未描述的存储区域(比如由于对齐造成的空隙),可以用 FILL(expression) 命令决定这些存储区域的内容,expression 的第一个字节有效,
在必要时可以重复被使用以填充这类存储区域,如 FILL(0x9090)。在输出 section 描述中可以有 =FILEEXP 属性,它的作用如同 FILL 命令,但是 FILL 命令只作用于该
FILL 指令之后的 section 区域,而 =FILEEXP 属性作用于整个输出 section 区域,且 FILL 命令的优先级更高!!!
Note1:填充时需要考虑大小端的问题。
Note2:关于填充地址,会填充到 地址计数器 "." 开始的地址,填充结束后,地址计数器 "." 会自动变化。
Note3:这些填充指令只能出现在 "输出 section 描述" 中,而不能出现在它们之间。即
SECTIONS { .text : { *(.text) } LONG(1) .data : { *(.data) } }   ----> 错误
SECTIONS { .text : { *(.text) ; LONG(1) } .data : { *(.data) } }   ----> 正确
4)特殊的输出段关键字
也就是可以出现在 "output-section-command" 中的关键字。仅有 CREATE_OBJECT_SYMBOLS 和 CONSTRUCTORS,前者只适用于 a.out 的可执行文件,后者只适用于 C++ 。
5)特殊的 /DISCARD/ 输出段
链接器会丢弃 /DISCARD/ 中的所有输入段,将不会被包含在输出文件中。链接器会忽略在抛弃的输出段内的地址设置(参考Output Section Address),除非链接脚本
在输出段内定义了符号。这种情况下链接器会遵守地址赋值,有可能更新’.’即便段被抛弃了。
11. MEMORY命令
在默认情形下,连接器可以为 section 分配任意位置的存储区域。你也可以用 MEMORY 命令定义内存块,并通过输出 section 描述的 > region 属性显式地将某输出 section 
限定于某个内存块,当该内存块大小不能满足要求时,连接器会报告该错误。 MEMORY指令只能使用一次。
MEMORY 命令的文法如下, 
MEMORY 

NAME1 [(ATTR)] : ORIGIN = ORIGIN1, LENGTH = LEN2 
NAME2 [(ATTR)] : ORIGIN = ORIGIN2, LENGTH = LEN2 
... 

NAME  :存储区域的名字(仅在链接脚本内有意义),这个名字可以与符号名、文件名、section 名重复,因为它处于一个独立的名字空间。 
ATTR  :定义该存储区域的属性,在讲述 SECTIONS 命令时提到,当某输入 section 没有在 SECTIONS 命令内引用时,连接器会把该输入  section 直接拷贝成输出 section,
然后将该输出 section 放入内存区域内。如果设置了内存区域设置了 ATTR 属性,那么该区域只接受满足该属性的 section(怎么判断该section 是否满足输出 
section 描述内好象没有记录该 section 的读写执行属性)。ATTR 属性内可以出现以下 7 个字符, 
R  只读 section 
W  读/写 section 
X  可执行 section 
A ‘可分配的’section 
I  初始化了的 section 
L  同 I 
!  不满足该字符之后的任何一个属性的 section 
ORIGIN  :关键字,区域的开始地址,可简写成 org 或 o 
LENGTH  :关键字,区域的大小,可简写成 len 或 l 
 
例子, 
MEMORY 

rom (rx) : ORIGIN = 0, LENGTH = 256K 
ram (!rx) : org = 0x40000000, l = 4M 

此例中,把在 SECTIONS 命令内*未*引用的且具有读属性或写属性的输入 section 放入 rom 区域内,把其他未引用的输入 section 放入  ram。如果某输出 section 要被放
入某内存区域内,而该输出 section 又没有指明 ADDRESS 属性,那么连接器将该输出 section 放在该区域内下一个能使用位置。
Note1 : 对于在链接脚本中出现的输出段,如果显式地给定 memory region 则会放入该 memory region;如果不给出则会从当前地址开始存放。如果放不下,则都会报错。
Note2 : 对于未出现在了链接脚本中的输入 section 会直接拷贝成输出 section, 并将其放入 MEMORY 定义的块中具有相同的内存块中。


12. PHDRS 命令 (仅适用于ELF,暂时不做说明)

13. VERSION 命令 (仅适用于ELF,暂时不做说明)

14. 链接脚本中的表达式
表达式的语法与 C 语言的表达式语法一致,但都是整型运算。如果 ld 的运行主机和生成文件的目标机都是 32 位,则表达式是 32 位数据,否则是 64 位数据。 可以在表达
式内使用及设置符号的值。
1)常数,所有的常数都是 int 类型。下面 4 个符号的值相同
_fourk_1 = 4K; _fourk_2 = 4096; _fourk_3 = 0x1000; _fourk_4 = 10000o;
2)符号常数,可以通过使用CONSTANT(name)操作符引用一个目标特定的常数,name为下面之一,
MAXPAGESIZE:目标的最大页大小。
COMMONPAGESIZE:目标的默认页大小。
例如,
.text ALIGN (CONSTANT (MAXPAGESIZE)) : { *(.text) }  表示创建一个对齐到目标支持的最大页边界的代码段。
3)地址计数器 ".",仅出现在 SECTIONS 命令内部。任何普通符号可以出现在表达式中的位置都可以使用"."。示例,

--------------------------------------------------------
| SECTIONS | SECTIONS | a)左边的示例表示在输入目标文件 file1 ~ file3的 .text 段构成输出 output 段。并且,在输出段
| {   | { | 中,存在2个位于3个输入段之间的 1000 字节的空白区域,使用 0x12345678 进行填充。
| output : | . = 0x100 |
| { | .text: { | b)在右边的示例中,输出 .text 段的起始地址为 0x100,大小为 0x200(就算所有输入 .text 段加
| file1(.text) | *(.text) | 起来不够 0x200)。如果所有输入 .text 段加起来多于 0x200 则会报错。输出 .data 段的起始
| . = . + 1000; | . = 0x200 | 地址为 0x500,大小为 SIZEOF(*(.data)) + 0x600。 
| file2(.text) | } |
| . += 1000; | . = 0x500 |
| file3(.text) | .data: { |
| } = 0x12345678; | *(.data) |
| } | . += 0x600 |
| | } |
| | } |
---------------------------------------------------------

Note1:给"."赋值会改变其值,这可以在输出段中产生未定义的内存区域。另外,"."的值只能增加,不能减小。
Note2:当 "." 作为 output-section-command 的一部分时,它代表的是距离该 section 地址的偏移量,而不是程序地址空间的绝对地址。
Note3:当 "." 在 "输出 section 描述" 外部时,才表示程序地址空间的绝对地址。
Note4:当链接器需要放置 孤儿段(Orphan sections) 时,在 "输出 section 描述"外部将 "." 的值赋给一个符号是存在潜在危险的 (P72)。例如,

SECTIONS |
{ | a)如果没有标有 (x1) 的行。如果输入目标文件中含有 .rodata 段,而此脚本中并没有说明应该如何对其处理,故而
start_of_text = . ; | 可能将其放在输出段 .text 和 .data之间(可能是在 (x2) 行上方或者下方)。如果放在(x2) 行上方则没有问题,
.text: { *(.text) } | 但如果放在了 (x2) 行下方,则 start_of_data 的值就不是 .data 段的起始地址了,也就出错了。
end_of_text = . ; |
| b)如果含有标有 (x1) 的行,则不会出现上面的问题。
. = . ; (x1) |
start_of_data = . ; (x2) |
.data: { *(.data) } |
end_of_data = . ; |
} |
-----------------------------------------
4)运算符,支持标准的 C 运算符
5)内置函数
a) ABSOLUTE(exp) : 将表达式 exp 当做绝对值(非可重定位,而不是非负)看待,主要用于在 output-section-command 中使用绝对赋值。
b) ADDR(sec_name) : : 返回输出段 sec_name 的 VMA。例如,

SECTIONS |
{ | 这里,start_of_output_1、symbol_1 和 symbol_2 的值完全相同,都是指 .output1 段
... | 的起始地址。
.output1 : |
{ |
start_of_output_1 = ABSOLUTE(.); |
... |
} |
.output : |
{ |
symbol_1 = ADDR(.output1); |
symbol_2 = start_of_output_1; |
... |
} |
... |
} |
--------------------------------------------------------
c) ALIGN([exp,]align) : 返回位置计数器"."或者任意表达式对齐到下一个 align 指定边界的值。单操作数 ALIGN 不改变位置计数器的值——它仅进行数学运算。
双操作数ALIGN允许向上对齐一个任意表达式( ALIGN(align)等价于ALIGN(ABSOLUTE(.), align))。例子,

SECTIONS { ... | 这里, ALIGN(0x2000) 是指 .data 段的起始地址对齐到下一个 0x2000 字节数边界;
.data ALIGN(0x2000): | variable = ALIGN(0x8000)  
{ |
*(.data) |
variable = ALIGN(0x8000); |
} |
... } |
--------------------------------------------------------
d) ALIGNOF(sec_name) :

e) BLOCK(exp) : 等价于 ALIGN([exp,]align)

f) DEFINED(symbol) : 如果符号 symbol 已经定义则返回1,没有定义返回0。

g) LENGTH(memory) : 返回名为 memory 的内存块的大小。

h) LOADADDR(sec_name) : 返回输出段 sec_name 的 LMA。

i) LOG2CEIL(exp) : 等价于 C 语言中的 ceil(log2(exp))。

j) MAX(exp1, exp2) : 最大值。

k) MIN(exp1, exp2) : 最小值。

l) NEXT(exp) : 返回下一个未分配的、对齐到 exp 倍数的地址。如果不使用 MEMORY ,则 NEXT(exp) 等价于 ALIGN(exp)

m) ORIGIN(memory) : 返回名为 memory 的内存块的起始地址。

n) SIZEOF(sec_name) : 返回 sec_name 段的大小。

o) SIZEOF_HEADERS : 输出文件头的大小。

猜你喜欢

转载自blog.csdn.net/fdcp123/article/details/61922746
gcc