3.15 配置外部工具程序和参数
[adie: 这篇文章原是 GCC 手册中的一节, 3.15 是它在手册中的编号.]
gcc 是一个驱动式的程序. 它调用其它程序来依次进行编译, 汇编和链接.
GCC 分析命令行参数, 然后决定该调用哪一个子程序, 哪些参数应该传递给子程序.
所有这些行为都是由 SPEC 字符串(spec strings)来控制的.
通常情况下, 每一个 GCC 可以调用的子程序都对应着一个 SPEC 字符串,
不过有少数的子程序需要多个 SPEC 字符串来控制他们的行为.
编译到 GCC 中的 SPEC 字符串可以被覆盖,
方法是使用 -specs= 命令行参数来指定一个 SPEC 文件(spec file).
Spec 文件(Spec files) 就是用来配置 SPEC 字符串的.
它包含了一系列用空行分隔的指令.
指令的类型由一行的第一个非空格字符决定, 它们可能是:
- %command
- 定义 SPEC 文件的预处理命令, 预处理命令包括以下的命令:
- %include <file>
- 搜索文件, 并将其内容插入到 SPEC 文件的当前位置.
- %include_noerr <file>
- 和 '%include' 一样, 但是在没有找到文件的情况下不会产生错误消息.
- %renameold_name new_name
-
- 将 SPEC 字符串 old_name 改名为 new_name.
- *[spec_name]:
- 这条命令让编译器创建,覆盖或删除指定名字的 SEPC 字符串.
所有在这条指令之后到下一条指令或空行之前的文本都被认为是这个 SPEC 字符串的内容.
如果它的内容为空, 则这个名字将被删除(如果该名字不存在, 则什么都不会发生).
内容不为空时, 如果该名字不存在, 则会创建一个新的 SPEC 字符串, 如果已经存在了,
他们的内容将会被替换. 如果内容的第一个字符为 '+',
则这些内容会被追加到原来定义的内容后面, 而不是覆盖了.
- [suffix]:
- 创建一个新的 '[suffix] spec'(后缀处理) 组合.
这个指令之后到下一个指令或空行之前的行组成了这个后缀的 SPEC 字符串.
当编译器遇到指这种后缀的输入文件时, 它会依次执行 SPEC 字符串中的指令,
这些指令指明了如何编译那个文件. 例如:
.ZZ:
z-compile -input %i
它的意思是: 任何以 '.ZZ' 结尾的输入文件都将使用 'z-compile' 程序进行处理,
调用的时候将会使用 -input 开关以及 '%i' 替换后的结果(详见下文) 作为命令行参数.
作为 SPEC 字符串的内容, suffix 指令后的文本还可以是下面的某一个:
- @language
- 它表明这个后缀是某种已知语言的别名.
它和 GCC 命令行中用于指定语言的 -x 选项类似. 比如:
.ZZ:
@c++
这说明 .ZZ 文件实际上是 C++ 的源代码文件.
- #name
- 这会产生一条错误消息:
当前系统没有安装 name 编译器.
GCC 已经内建了一份巨大的后缀列表.
这条指令将后缀添加到列表的结尾处,
由于这个列表使用的时候是从结尾向后搜索的,
实际上可以使用这种技术来覆盖之前的条目.
GCC 已经内置了如下的 SPEC 字符串.
SPEC 文件可以覆盖他们或者创建新的.
注意, 某些 GCC 的实现也可能添加它们自己的 SEPC 字符串到这个列表里面.
asm 传递给汇编器的选项
asm_final 传递给汇编后处理器的选项
cpp 传递给 C 预处理器的选项
cc1 传递给 C 编译器的选项
cc1plus 传递给 C++ 编译器的选项
endfile 链接的最后需要包含的目标文件
link 传递给链接器的选项
lib 命令行传递给链接器的要包含的库
libgcc 决定给链接器传递哪个 GCC 支持库
linker 设置链接器的名字
predefines 传递给 C 预处理器的宏定义
signed_char 传递给 CPP 的用于说明 char 默认是否是有符号类型的宏
startfile 一开始就需要传递给链接器的目标文件
下面是一个简单的 SPEC 文件的例子:
%rename lib old_lib
*lib:
--start-group -lgcc -lc -leval1 --end-group %(old_lib)
这个例子把 'lib' 改名为
'old_lib' 然后用一个新的定义覆盖了之前的 'lib' 定义.
新定义在包含旧的定义文本之前添加了一些额外的命令行选项.
SEPC 字符串是传递给相应程序的命令行选项的列表.
另外, SEPC 字符串可以包含 '%' 作为前缀的字符串来表示变量.
变量可以用来代替一串文本, 或者作为向命令行插入文本的条件.
使用这些概念可以产生非常复杂的命令行.
下面是所有的用于 SPEC 字符串的预定义变量.
注意,这些变量在表示文本时不会自动在结果两边产生空格.
你可以在链接这些变量时使用常量字符串来一起链接.
- %%
- 在程序名字或参数中用于表示一个 '%'.
- %i
- 用来表示当前正在处理的输入文件名
- %b
- 表示输入文件的基本名字. 它不包含最后一个点号以及之后的内容, 也不包含路径名.
- %B
- 和 '%b' 相同, 但是它包含了后缀名(最后一个点号后的内容).
- %d
- 用包含或附加 '%d' 来标记一个临时文件名, GCC 正常退出后会自动删除带有这种标记的文件.
和 '%g' 不同, 这个变量不会在参数中产生文本内容.
- %gsuffix
- 产生指定后缀 suffix 的文件名, 每次编译产生一个.
和 '%d' 一样, 它也会被标记为临时文件.
为了减少拒绝服务攻击的可能性, 即使上一次产生的文件名已经知道了,
下一次产生的文件名也是无法预料的. 例如,
'%g.s ... %g.o ... %g.s' 可能被转换成
'ccUVUUAU.s ccXYAXZ12.o ccUVUUAU.s'.
后缀名需要可以和 '[.A-Za-z]*' 这样的正则表达式匹配或者使用特定的 '%O'.
'%O' 使用的时候假设它已经被处理过了.
以前, '%g' 只是简单的每次出现都被替换成一个文件名,
没有后缀的情况下使得攻击很容易成功.
- %usuffix
- 和 '%g' 一样, 但是每次出现就会产生一个新的临时文件名,
而不是每次编译的时候一个.
- %Usuffix
- 生成上一次使用 '%usuffix' 时产生的文件名,
如果之前没有过, 就生成一个新的.
在没有使用过 '%usuffix' 的情况下,
它和 '%gsuffix' 一样, 只是他们不会共享后缀名字空间.
因此, '%g.s ... %U.s ... %g.s ... %U.s' 会生成两个不同的文件名,
一个是 '%g.s' 产生的, 另一个是 '%U.s'.
以前, '%U' 只是替换成上一个的 '%u' 产生的文件名,
不会带任何的后缀.
- %jsuffix
- 如果存在 HOST_BIT_BUCKET, 并且它可写,
也没有使用 -save-temps, 这会被替换成 HOST_BIT_BUCKET 的名字.
否则, 产生一个临时文件名, 和 '%u' 效果相同.
这个临时文件并不用来在两个进程间通信, 只是作为垃圾进行处理.
[adie:
HOST_BIT_BUCKET 是 GCC 中配置宿主系统环境时用到一个参数.
它是由宿主系统定义的一个路径名, 可以被当作一个文件来进行写入,
但是所有写入的内容都会被丢弃. 这通常被称为 bit bucket(比特流垃圾桶)
或 null device(空设备), 在 UNIX 中它通常就是 /dev/null.
- %|suffix
- %msuffix
- 和 '%g' 类似,
只是可以使用 -pipe (管道).
使用管道时, '%|' 被替换成一个破折号,
'%m' 被替换成空.
这是让程序从标准输入读取数据和往标准输出写数据的常用方式.
如果你需要处理更复杂的情况, 你还可以使用 '%{pipe:X}':
参考 f/lang-specs.h 中的例子.
- %.SUFFIX
- 对于匹配后缀的参数进行替换. 后缀到空格或下一个 % 结束.
- %w
- 对包含或跟随有 '%w' 的参数标记为此次编译的输出文件.
这会将这个参数放到 '%o' 的参数序列里面.
- %o
- 替换所有输出文件的名字, 并自动在他们周围放置空格.
你仍然应该在 '%o' 的周围书写空格, 否则结果将是未定义的.
'%o' 用于运行链接器.
没有可识别后缀的输入文件将不会被编译, 但是他们被包含在输出文件里,
因此, 他们可以被链接.
- %O
- 为目标文件替换后缀. 注意, 当它紧接在 '%g, %u, 和 %U' 后面时会被特别处理,
因为它们需要完整的文件名. 处理的方法是假设 '%O' 已经被处理过了, 除非
'%g, %u, 和 %U' 当前不支持使用特殊的 '%O' 后缀, 例如, '.o'.
- %p
- 替换标准的预定义宏为当前的目标类型.
运行 cpp 程序时使用.
- %P
- 和 '%p' 类似,
只是会在每个预定义宏的名字前后加上 '__',
除非宏的名字是以 '__' 或 '_L' 开头的.
其中 L 是一个大写字母.
这是 ISO C 所要求的.
- %I
- 贴换所有的 -iprefix(来自 GCC_EXEC_PREFIX),
-isysroot(来自 TARGET_SYSTEM_ROOT),
-isystem(来自 COMPILER_PATH 和 -B 选项)
以及所需的 -imultilib.
- %s
- 当前参数是某个库或可执行文件的名字.
搜索标准的目录列表来查找这个文件名, 如果找到了, 就用全名来进行替换.
当前目录被包含在扫描的目录列表的第一位.
- %T
- 当前参数是一个连接脚本.
在库文件的目录列表里面搜索那个文件.
如果文件找到了, 插入一个 --script 选项和文件的全路径名到命令行中.
如果文件没找到将会产生一条错误信息. 注意, 当前目录不会被搜索.
- %estr
- 把 str 作为错误消息打印出来.
str 使用换行作为结束.
检测到不一致的选项时可以使用它.
- %(name)
- 贴换名字为 name 的 SPEC 字符串内容到当前位置.
- %x{option}
- 为 '%X' 添加一个选项.
- %X
- 输出用 -W1 或 '%x' 添加的所有链接器选项.
- %Y
- 输出用 -Wa 添加的所有汇编器选项.
- %Z
- 输出用 -Wp 添加的所有汇编器选项.
- %a
- 处理 asm spec.
用于计算传递给汇编器的开关.
- %A
- 处理 asm_final spec.
如果需要汇编后处理器的话, 用于计算传递给汇编后处理器的选项.
- %l
- 处理 link spec.
用于计算传递给链接器的命令行参数.
它一般是使用的 `%L %G %S %D 和 %E' 序列.
- %D
- 为所有 GCC 认为可能包含可执行文件的目录导出一个 -L 选项.
如果目标支持多库, 那么每个目录都会考虑多库目录.
- %L
- 处理 lib spec.
这个 SPEC 字符串决定了连接器的命令行需要包含的库文件.
- %G
- 处理 libgcc spec.
这个 SEPC 字符串决定了传递给连接器命令行参数中使用的 GCC 支持库.
- %S
- 处理 startfile spec.
这个 SEPC 字符串表示传递给链接器的第一个目标文件.
通常这个文件名是 crt0.o.
- %E
- 处理 endfile spec.
表示传递给链接器的最后一个目标文件.
- %C
- 处理 cpp spec.
用于构造传递给 C 预处理器的参数.
- %1
- 处理 cc1 spec.
用于构造传递给当前的 C 编译器('cc1') 的选项.
- %2
- 处理 cc1plus spec.
用于构造传递给当前的 C++ 编译器('cc1plus') 的选项.
- %*
- 替换匹配选项的可变部分.
详见下文.
注意, 替换字符串中的逗号会被空格取代.
- %<S
- 删除命令行中所有的 -S 选项.
注意这个命令是位置相关的.
在 SPEC 字符串中这个命令之前的 -S 会被删除, 之后的不会.
- %:function(args)
- 调用指定名字的函数, 并传递参数.
参数首先被当作嵌套的 SPEC 字符串处理,
然后分割成一个参数数组.
函数返回一个字符串, 插入到当前命令所在的位置.
下面是内建支持的 SPEC 函数:
- getenv
- getenv spec 函数带两个参数:
一个环境变量名称和一个字符串.
如果环境变量没有定义,
则会产生一个严重错误.
返回值是环境变量的值和字符串连接在一起的.
例如, 如果 TOPDIR 被定义为 /path/to/top,
那么:
%:getenv(TOPDIR /include)
将会展开成 /path/to/top/include.
- if-exists
- if-exists 函数带一个参数,
一个文件的绝对路径名.
如果文件存在, if-exists 返回路径名.
下面是一个用法的简单例子:
*startfile:
crt0%O%s %:if-exists(crti%O%s) crtbegin%O%s
- if-exists-else
- if-exists-else 函数和 if-exists 函数类似,
不同的是它带有两个参数.
第一个参数是文件的路径名.
如果文件存在, if-exists-else 函数返回路径名.
如果不存在, 返回第二个参数.
这样, if-exists-else 函数可以根据文件是否存在来选择这个文件或其它 .
下面是一个使用的例子:
*startfile:
crt0%O%s %:if-exists(crti%O%s) \
%:if-exists-else(crtbeginT%O%s crtbegin%O%s)
- replace-outfile
- replace-outfile 函数带有两个参数.
它在输出文件数组中查找第一个参数,
并用第二个参数来替换.下面是示例:
%{fgnu-runtime:%:replace-outfile(-lobjc -lobjc-gnu)}
- remove-outfile
- remove-outfile 函数带一个参数.
它在输出文件数组中查找这个参数并将其删除.
示例:
%:remove-outfile(-lm)
- pass-through-libs
- pass-through-libs 可以带任意数量的参数.
它查找 -l 选项和以 .a 结尾的非选项内容(确保是输入链接器的库文件),
将找到的所有参数加上 -plugin-opt=-pass-through= 前缀, 并添加空格分隔后返回.
这个列表用于传递给 LTO 链接器插件.
%:pass-through-libs(%G %L %G)
- print-asm-header
- print-asm-header 函数不带参数, 它只是简单的打印如下的标语:
Assembler options
=================
Use "-Wa,OPTION" to pass "OPTION" to the assembler.
它用于在使用 --target-help 的输出中分隔编译器选项和汇编器选项.
- %{S}
- 如果给 GCC 传递了 -S 选项, 替换这个选项.
如果么有, 它什么也不做.
注意这个选项的前导破折号, 替换的时候会被自动加上.
因此, SPEC 字符串 '%{foo}' 匹配命令行中的 -foo 选项,
并且输出一个 -foo.
- %W{S}
- 和 %{S} 类似, 只是会标记最后提供的那个参数为文件, 并且在失败的时候删除这个文件.
- %{S*}
- 贴换传递给 GCC 的所有以 -S 开头的选项, 通常它们都还带有一个参数.
这主要用于对 -o, -D, -I 等选项使用.
GCC 认为 -o foo 是一个选项, 它的名字以 'o' 开头.
'%{o*}' 替换这个, 包括空格. 因此, 将会产生两个参数.
- %{S*&T*}
- 和 %{S*} 类似,
只是保持 S 和 T 选项的顺序(在 SEPC 中 S 和 T 的顺序并不重要).
可以使用任意数量的 & 分隔的变量, 其中的每个字段都是可选的. 对 CPP 比较有用的例子如: '%{D*&U*&A*}'.
- %{S:X}
- 如果 GCC 中使用了 -S 选项, 用 X 来代替.
- %{!S:X}
- 如果没有在 GCC 中使用 -S 选项, 替换成 X.
- %{S*:X}
- 如果有一个或多个以 -S 开头的选项,
则替换成 X.
通常, 不管选项出现了多少次, 都只替换成一个 X.
但是, 如果 X 中包含了 '%*', 那么每一个选项都会替换成一个 X,
其中的 '%*' 被选项中匹配 '*' 的部分替代.
- %{.S:X}
- 如果处理的文件以 S 开头, 则替换成 X
- %{!.S:X}
- 如果没有处理以 S 开头的文件, 则替换成 X
- %{,S:X}
- 如果处理的文件是 S 语言的, 则替换成 X
- %{!,S:X}
- 如果没有处理 S 语言的文件, 则替换成 X
- %{S|P:X}
- 如果提供了 -S 或 -P 选项, 则替换成 X.
它也可以和 '!', '.', ',' 以及 * 进行组合, 虽然他们比 '|' 有更强的约束.
如果 X 中出现了 '%*',
所有的替换项都需要使用 '*',
并且只有第一个匹配的替换项被使用.
例如, 像下面这样的 SEPC 字符串:
%{.c:-foo} %{!.c:-bar} %{.c|d:-baz} %{!.c|d:-boggle}
则输入的命令行和对应的输出如下:
fred.c -foo -baz
jim.d -bar -boggle
-d fred.c -foo -baz -boggle
-d jim.d -bar -baz -boggle
- %{S:X; T:Y; :D}
- 如果 GCC 包含了 -S 选项, 替换成 X,
否则, 如果包含了 -T, 替换成 Y,
否则, 替换成 D.
你可以添加任意数量的子句.
它还可以和 '.', ',', '!', '|' 以及 '*' 进行组合.
在条件语句 %{S:X} 以及类似的结构中的 X 可以包含嵌套的 '%' 结构, 空格, 甚至是换行.
向以上描述的那样, 它们照常工作. 在 X 结尾处的空格会被忽略.
空格也可以出现在冒号的左边, 但不能出现在 '.' 或 '*' 与匹配单词之间.
在这些结构中, -O, -f, -m, 以及 -W 选项会被特殊处理.
如果有其他的 -O 选项或者有与 -f, -m, -W 起作用相反的选项, 之前的值将会被忽略,
除非 {S*} 中的 S 只有一个字母.
只有一个字母时所有的匹配项都可以通过.
'|' 字符出现在谓词的开头表示接下来的命令应该使用管道, 不过只有使用了 -pipe 时才能生效.
哪些选项需要使用参数是内置到 GCC 里面的.
(你或许在想, 通过使用不同的编译器 sepc 来定义哪些选项需要参数是很有用的.
但是, 这在目前是无法实现的.
如果不知道哪些选项需要参数的话,
GCC 无法决定哪个是需要编译的输入文件.
GCC 必须要知道输入文件才能决定使用哪个编译器).
GCC 也知道, 以 -l 开头的参数会被作为编译输出文件处理,
并按照它在其他输出文件中的位置传递给链接器.