Bazel笔记及调试时编译选项
一些链接
ubuntu安装Bazel直接参考:Bazel Install
官方教程:Bazel Tutorial
BUILD文件中rule的路径
BUILD文件中,在加入source或者headers的时候,文件路径都是在BUILD文件所在目录下的文件,跟CMake文件一样
关于target的label规则需要先看一下
很简单,但描述得很清晰
Use labels to reference targets
依赖图可视化Dependency Graph
之前不知道用指令输出的依赖图可以用在线工具GraphViz可视化
Include的一些规则
指明include目录
如果在source文件中include的文件不是相对于package的根目录,则需要使用copts=[-Ipath/to/include/dir"]
来指明include的目录
假设工作空间如下:
1 | └── my-project |
- 直接引用
如果some_lib.cc
在引用头文件的时候是#include "legacy/some_lib/include/some_lib.h"
,则bazel rule可以直接这样写
1 | cc_library( |
- 否则需要加入include的目录
1 | cc_library( |
Debug选项
调试程序的时候需要在编译时关闭编译器优化选项,这样在gdb调试的时候才能有较丰富的调试信息。
gcc/g++ 选项
说bazel的选项之前先说一下编译器的选项
-O0/O1/O2/O3/Og
编译器的4个优化级别,-O0表示没有优化,默认为-O1,-O3优化级别最高
gcc - 3.11 Options That Control Optimization
- -O0选项或者没有指定-O选项 - 完全不进行优化,可以提供准确的行号,程序打断点也是准确的,但是运行速度慢
- -O1/-O选项 - 由于进行优化,错误信息的行号可能不准确,但是相比O0速度会有显著提高
- -Og选项 - 进行适当的优化,但是为调试尽可能保存必要的信息。在官方文档中,建议调试时使用-Og选项而不是-O0选项
-g/-ggdb
gcc - 3.10 Options for Debugging Your Program
调试信息。可以和-O选项同时使用,更建议和-Og选项配合使用
- -g/-gdb选项 - 使得编译器在编译时会加入调试信息。并且调试信息可供gdb使用。这两者的具体区别不是很清楚
linker 选项
gnu-linker - Command Line Options
除了编译器需要使用选项来指定是否保存调试信息之外,链接器也有选项来选择是否要保留调试符号
strip
- -s/--strip-all 选项 - 去除所有符号信息
- -S/--strip-debug 选项 - 只去除调试器的符号信息
不过有一点没有搞清楚的是,-S似乎不会去除gcc -g生成的所有调试符号。也就是说,编译是用gcc -g -Wl,-S
并不是没有意义的,依旧会保留部分的调试符号。如果不加-Wl,-S
,则生成的文件会非常大
PS:使用strip --strip-debug bin
命令可以对二进制文件去除调试符号
-Wl
如果链接器不是单独调用,而是通过编译器驱动(比如gcc/g++),则链接器的选项需要在前面加上-Wl,
,比如:
1 | gcc -Wl,--start-group foo.o bar.o -Wl, --end-group |
如果不加-Wl,
,则编译器驱动会直接忽略掉链接器选项,导致错误的链接 原文说明
bazel选项
--strip(always | never | sometimes)
bazel-UserGuide-Options : strip
这一个选项对应链接器的-s/--strip-debug
选项。
默认值
--strip=sometimes
- 表示当--compilation_mode=fastbuild
的时候才会去除调试符号。也就是在不指定任何--strip
选项的时候,除非--compilation_mode=fastbuild
,否则不会去除调试信息--strip=always
- 相当于在编译时加上-Wl,--strip-debug
选项(-Wl
指明这个选项是链接器选项)--strip=never
- 指明不去除调试符号。--strip=sometimes
选项使得当--compilation_mode=fastbuild
的时候才会去除调试符号
注意这个选项只对应链接器的--strip-debug
选项,如果想要在链接时去掉所有符号,也就是链接器的--strip-all
选项,需要单独指定--linkopt=-Wl,--strip-all
。
--strip选项会覆盖--linkopt=-Wl,--strip-all`
--strip
只是指明链接阶段是否保留调试符号。但是编译阶段是否生成调试信息也需要单独指定。可以通过-c dbg
或者--copt -g
指定
--compilation_mode/-c (fastbuild | opt | dbg)
这个选项其实大概对应了编译器的几个优化等级,但又不完全一样
fastbuild
- 表示最快编译,不进行优化(-O0),并在链接阶段去掉调试符号(-Wl,--strip-debug/-S)dbg
- 表示开启编译时的-g
选项(看上文中编译器选项),生成调试信息opt
- 表示使用-O2
等级的编译器优化。与是否打开调试信息无关,调试信息可以另外指定--copt -g
开启(实际测试中好像会开启)。不仅如此,opt
模式还会在加入-DNDEBUG
选项定义一个NDEBUG
的宏。
bazel几种编译模式对应的编译器链接器选项
上述的关于--compilation_mode
的官方说明似乎跟实际中的不太一样。通过
1 | bazel aquery //target |
指令可以查看bazel执行的过程执行和参数。下面整理一下三种编译模式对应的编译器和链接器部分选项
- --compilation_mode=fastbuild - 文件大小65.36M
此时相当于
1 | -Wl,-S |
- --compilation_mode=dbg - 文件大小20.4M
此时相当于
1 | -g -O2 -Wl,-S |
可以看到,当使用dbg
编译模式的时候,相当于编译器开启调试符号(-g),二级优化(-O2),链接器去除调试信息(-Wl,-S) 但是要注意-S似乎不会去除gcc -g生成的所有调试符号。也就是说,编译是用gcc -g -Wl,-S
并不是没有意义的,依旧会保留部分的调试符号。如果不加-Wl,-S
,则生成的文件会非常大
- --compilation_mode=opt - 文件大小3.56M
此时相当于(其实还有很多其他选项)
1 | -g -O2 -DNDEBUG -Wl,-S -Wl,--gc-sections |
虽然还是用-S/--strip-debug
,但是加上其他选项后,实际上相当于去掉了所有调试符号(但还没有去掉所有符号)。使用strip
工具再进行--strip-debug
已经没有作用了。
- --compilation_mode=dbg --strip=never - 文件大小1.07
此时相当于
1 | -g -O2 |
此时由于保留了所有调试符号,所以文件非常大
- --compilation_mode=opt --strip=never - 文件大小1.05G
此时相当于opt
模式下,但是保留所有调试符号。文件也很大
总结
当需要进行调试的时候,可以选择几种模式
- fastbuild + -g
1 | bazel build -c fastbuild --copt -g //target |
优点:编译快,没有进行优化所以出错位置的行号绝对准确;开始调试信息并保留部分调试信息 缺点:由于没有进行优化,程序运行可能会慢很多。文件大小会稍大一点
- dbg
1 | bazel build -c dbg //target |
优点:开始二级优化,程序运行快,文件稍小。保留部分调试信息 缺点:二级优化(-O2)可能导致报错位置的行号会稍微不准确。
- dbg + strip=never
1 | bazel build -c dbg --strip=never |
优点:二级优化。保留所有调试信息! 缺点:编译过程可能较慢。最终的文件大小会非常大。
- 其他注意要点
在使用bazel的时候尽量还是用bazel推荐的方式,也就是上面的几个方式。bazel的几种编译模式除了指定上面的优化等级、是否开启调试信息、删除调试符号之外还加了很多其他的选项。这样也导致虽然默认都加了-Wl,-S
选项,但是不会完全删除掉调试符号。但是如果是自己手工指定--copt=-g
,则bazel会提示一下信息:
1 | WARNING: Stripping enabled, but '--copt=-g' (or --per_file_copt=...@-g) specified. Debug information will be generated and then stripped away. This is probably not what you want! Use ' |
并且编译出的二进制文件完全删除了调试符号。