Bazel笔记及调试时编译选项

一些链接

ubuntu安装Bazel直接参考:Bazel Install

官方教程:Bazel Tutorial

BUILD文件中rule的路径

BUILD文件中,在加入source或者headers的时候,文件路径都是在BUILD文件所在目录下的文件,跟CMake文件一样

关于target的label规则需要先看一下

很简单,但描述得很清晰

Use labels to reference targets

依赖图可视化Dependency Graph

Review the dependency graph

之前不知道用指令输出的依赖图可以用在线工具GraphViz可视化

Include的一些规则

指明include目录

如果在source文件中include的文件不是相对于package的根目录,则需要使用copts=[-Ipath/to/include/dir"]来指明include的目录

假设工作空间如下:

1
2
3
4
5
6
7
8
└── my-project
├── legacy
│ └── some_lib
│ ├── BUILD
│ ├── include
│ │ └── some_lib.h
│ └── some_lib.cc
└── WORKSPACE
  1. 直接引用

如果some_lib.cc在引用头文件的时候是#include "legacy/some_lib/include/some_lib.h",则bazel rule可以直接这样写

1
2
3
4
5
cc_library(
name = "some_lib",
srcs = ["some_lib.cc"],
hdrs = ["include/some_lib.h"]
)
  1. 否则需要加入include的目录
1
2
3
4
5
6
cc_library(
name = "some_lib",
srcs = ["some_lib.cc"],
hdrs = ["include/some_lib.h"],
copts = ["-Ilegacy/some_lib/include"],
)

Debug选项

调试程序的时候需要在编译时关闭编译器优化选项,这样在gdb调试的时候才能有较丰富的调试信息。


gcc/g++ 选项

说bazel的选项之前先说一下编译器的选项

gnc-gcc: Command Line Options

-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执行的过程执行和参数。下面整理一下三种编译模式对应的编译器和链接器部分选项

  1. --compilation_mode=fastbuild - 文件大小65.36M

此时相当于

1
2
3
4
-Wl,-S

# gcc在没有指定优化级别的时候应该相当于-O0,因此也相当于
-O0 -Wl,-S
  1. --compilation_mode=dbg - 文件大小20.4M

此时相当于

1
-g -O2 -Wl,-S

可以看到,当使用dbg编译模式的时候,相当于编译器开启调试符号(-g),二级优化(-O2),链接器去除调试信息(-Wl,-S) 但是要注意-S似乎不会去除gcc -g生成的所有调试符号。也就是说,编译是用gcc -g -Wl,-S并不是没有意义的,依旧会保留部分的调试符号。如果不加-Wl,-S,则生成的文件会非常大

  1. --compilation_mode=opt - 文件大小3.56M

此时相当于(其实还有很多其他选项)

1
-g -O2 -DNDEBUG -Wl,-S -Wl,--gc-sections

虽然还是用-S/--strip-debug,但是加上其他选项后,实际上相当于去掉了所有调试符号(但还没有去掉所有符号)。使用strip工具再进行--strip-debug已经没有作用了。

  1. --compilation_mode=dbg --strip=never - 文件大小1.07

此时相当于

1
-g -O2 

此时由于保留了所有调试符号,所以文件非常大

  1. --compilation_mode=opt --strip=never - 文件大小1.05G

此时相当于opt模式下,但是保留所有调试符号。文件也很大


总结

当需要进行调试的时候,可以选择几种模式

  1. fastbuild + -g
1
bazel build -c fastbuild --copt -g //target

优点:编译快,没有进行优化所以出错位置的行号绝对准确;开始调试信息并保留部分调试信息 缺点:由于没有进行优化,程序运行可能会慢很多。文件大小会稍大一点

  1. dbg
1
bazel build -c dbg //target

优点:开始二级优化,程序运行快,文件稍小。保留部分调试信息 缺点:二级优化(-O2)可能导致报错位置的行号会稍微不准确。

  1. dbg + strip=never
1
bazel build -c dbg --strip=never

优点:二级优化。保留所有调试信息! 缺点:编译过程可能较慢。最终的文件大小会非常大。

  1. 其他注意要点

在使用bazel的时候尽量还是用bazel推荐的方式,也就是上面的几个方式。bazel的几种编译模式除了指定上面的优化等级、是否开启调试信息、删除调试符号之外还加了很多其他的选项。这样也导致虽然默认都加了-Wl,-S选项,但是不会完全删除掉调试符号。但是如果是自己手工指定--copt=-g,则bazel会提示一下信息:

1
2
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 '
-c dbg' for debug mode, or use '--strip=never' to disable stripping

并且编译出的二进制文件完全删除了调试符号。