-
Notifications
You must be signed in to change notification settings - Fork 0
/
local-search.xml
101 lines (48 loc) · 74.9 KB
/
local-search.xml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
<?xml version="1.0" encoding="utf-8"?>
<search>
<entry>
<title>CPP Debug Analyze 01</title>
<link href="/2022/11/02/CPP-Debug-Analyze-01/"/>
<url>/2022/11/02/CPP-Debug-Analyze-01/</url>
<content type="html"><![CDATA[<h1 id="C-Debug-x2F-Analyze-专题指南-01"><a href="#C-Debug-x2F-Analyze-专题指南-01" class="headerlink" title="C++ Debug/Analyze 专题指南-01"></a>C++ Debug/Analyze 专题指南-01</h1><h2 id="GDB篇"><a href="#GDB篇" class="headerlink" title="GDB篇"></a>GDB篇</h2><blockquote><p>GDB是Linux下非常好用且强大的调试工具,虽然它是命令行模式的调试工具,能够让用户在程序运行时观察程序的内部结构和内存的使用情况。</p></blockquote><p>一般来说,GDB主要帮助你完成下面四个方面的功能:</p><ol><li>按照自定义的方式启动运行需要调试的程序。</li><li>可以使用指定位置和条件表达式的方式来设置断点。</li><li>程序暂停时的值的监视。</li><li>动态改变程序的执行环境。</li></ol><h3 id="GDB的用法"><a href="#GDB的用法" class="headerlink" title="GDB的用法"></a>GDB的用法</h3><h4 id="常用调试命令"><a href="#常用调试命令" class="headerlink" title="常用调试命令"></a>常用调试命令</h4><p><code>GDB</code> 的主要功能就是监控程序的执行流程。这也就意味着,只有当源程序文件编译为可执行文件并执行时,并且该文件中必须包含必要的调试信息(比如各行代码所在的行号、包含程序中所有变量名称的列表(又称为符号表)等),<code>GDB</code>才会派上用场。</p><p>所以在编译时需要使用 <code>gcc/g++ -g</code> 选项编译源文件,才可生成满足 <code>GDB</code> 要求的可执行文件。</p><blockquote><p>此处可以参考之前的内存泄漏专题指南,描述了如何加入”-g”以及验证是否存在调试信息</p><p>调试技巧可以参考: <a href="https://www.cnblogs.com/ZY-Dream/p/14540499.html">https://www.cnblogs.com/ZY-Dream/p/14540499.html</a></p></blockquote><table><thead><tr><th>调试命令 (缩写)</th><th align="left">作用</th></tr></thead><tbody><tr><td>(gdb) break (b)</td><td align="left">在源代码指定的某一行设置断点,其中xxx用于指定具体打断点位置</td></tr><tr><td>(gdb) run (r)</td><td align="left">执行被调试的程序,其会自动在第一个断点处暂停执行。</td></tr><tr><td>(gdb) continue (c)</td><td align="left">当程序在某一断点处停止后,用该指令可以继续执行,直至遇到断点或者程序结束。</td></tr><tr><td>(gdb) next (n)</td><td align="left">令程序一行代码一行代码的执行。</td></tr><tr><td>(gdb) step(s)</td><td align="left">如果有调用函数,进入调用的函数内部;否则,和 next 命令的功能一样。</td></tr><tr><td>(gdb) until (u) <br />(gdb) until (u) location</td><td align="left">当你厌倦了在一个循环体内单步跟踪时,单纯使用 until 命令,可以运行程序直到退出循环体。 until n 命令中,n 为某一行代码的行号,该命令会使程序运行至第 n 行代码处停止。</td></tr><tr><td>(gdb) print (p)</td><td align="left">打印指定变量的值,其中 xxx 指的就是某一变量名。</td></tr><tr><td>(gdb) list (l)</td><td align="left">显示源程序代码的内容,包括各行代码所在的行号。</td></tr><tr><td>(gdb) finish(fi)</td><td align="left">结束当前正在执行的函数,并在跳出函数后暂停程序的执行。</td></tr><tr><td>(gdb) return(return)</td><td align="left">结束当前调用函数并返回指定值,到上一层函数调用处停止程序执行。</td></tr><tr><td>(gdb) jump(j)</td><td align="left">使程序从当前要执行的代码处,直接跳转到指定位置处继续执行后续的代码。</td></tr><tr><td>(gdb) quit (q)</td><td align="left">终止调试。</td></tr></tbody></table><h3 id="GDB调试执行异常崩溃的程序"><a href="#GDB调试执行异常崩溃的程序" class="headerlink" title="GDB调试执行异常崩溃的程序"></a>GDB调试执行异常崩溃的程序</h3><blockquote><p>在大多数情况下,我们遇到异常崩溃的程序才会使用GDB来定位问题,而GDB又有可能影响效率,所以若不确定能否必现问题,则coredump文件是较好的选择</p></blockquote><p>在<code>Linux</code>操作系统中,当程序执行发生异常崩溃时,系统可以将发生崩溃时的内存数据、调用堆栈情况等信息自动记录下载,并存储到一个文件中,该文件通常称为<code>core</code> 文件,<code>Linux</code> 系统所具备的这种功能又称为核心转储(<code>core dump</code>)</p><h4 id="生成Core方法"><a href="#生成Core方法" class="headerlink" title="生成Core方法"></a>生成Core方法</h4><p>需要确认当前会话的ulimit –c,若为0,则不会产生对应的coredump,需要进行修改和设置</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><code class="hljs bash">root@in-dev-docker:/apollo<span class="hljs-comment"># ulimit -c</span><br>0<br></code></pre></td></tr></table></figure><p>当为0时,即便程序core dump了也不会有core文件留下。我们需要让core文件能够产生,设置core大小为无限:</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs bash"><span class="hljs-built_in">ulimit</span> -c unlimited<br></code></pre></td></tr></table></figure><h5 id="更改core-dump生成路径"><a href="#更改core-dump生成路径" class="headerlink" title="*更改core dump生成路径"></a>*更改core dump生成路径</h5><blockquote><p>因为core dump默认会生成在程序的工作目录,但是有些程序存在切换目录的情况,导致core dump生成的路径没有规律,</p><p>所以最好是自己建立一个文件夹,存放生成的core文件。</p></blockquote><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><code class="hljs bash"><span class="hljs-built_in">mkdir</span> -p /tmp/coredump<br><span class="hljs-built_in">echo</span> /tmp/coredump/core.%e.%p > /proc/sys/kernel/core_pattern<br></code></pre></td></tr></table></figure><h4 id="调试程序和Core文件"><a href="#调试程序和Core文件" class="headerlink" title="调试程序和Core文件"></a>调试程序和Core文件</h4><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><code class="hljs bash">gdb 可执行程序<br>core-file path2corefile<br>bt 可以查看准确信息<br></code></pre></td></tr></table></figure><h3 id="VSCODE-GDB调试c-程序"><a href="#VSCODE-GDB调试c-程序" class="headerlink" title="VSCODE GDB调试c++程序"></a>VSCODE GDB调试c++程序</h3><blockquote><p>鉴于GDB需要手动交互略显麻烦,查看代码也不够清晰,基于Vscode GUI的DEBUG工具链就显得尤为简单</p><p>参考:<a href="https://code.visualstudio.com/docs/cpp/config-linux">https://code.visualstudio.com/docs/cpp/config-linux</a></p></blockquote><h4 id="Apollo-docker环境的GDB运行"><a href="#Apollo-docker环境的GDB运行" class="headerlink" title="Apollo docker环境的GDB运行"></a>Apollo docker环境的GDB运行</h4><blockquote><p>对于简单的c++程序,参考👆上面的链接即可,对于apollo这种以so被调用的形式,则需要定制相关的文件</p></blockquote><ol><li><p>在<code>/apollo/.vscode</code>目录下添加<code>launch.json</code>和<code>tasks.json</code>文件</p></li><li><p>修改tasks.json文件</p><blockquote><p>此处可以添加多个task,并且在launch.json中<code>preLaunchTask</code>处被预先执行,配置环境</p></blockquote><figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><code class="hljs json"><span class="hljs-punctuation">{</span><br> <span class="hljs-comment">// See https://go.microsoft.com/fwlink/?LinkId=733558</span><br> <span class="hljs-comment">// for the documentation about the tasks.json format</span><br> <span class="hljs-attr">"version"</span><span class="hljs-punctuation">:</span> <span class="hljs-string">"2.0.0"</span><span class="hljs-punctuation">,</span><br> <span class="hljs-attr">"tasks"</span><span class="hljs-punctuation">:</span> <span class="hljs-punctuation">[</span><br> <span class="hljs-punctuation">{</span><br> <span class="hljs-attr">"label"</span><span class="hljs-punctuation">:</span> <span class="hljs-string">"env"</span><span class="hljs-punctuation">,</span><br> <span class="hljs-attr">"type"</span><span class="hljs-punctuation">:</span> <span class="hljs-string">"shell"</span><span class="hljs-punctuation">,</span><br> <span class="hljs-attr">"command"</span><span class="hljs-punctuation">:</span> <span class="hljs-string">"source"</span><span class="hljs-punctuation">,</span><br> <span class="hljs-attr">"args"</span><span class="hljs-punctuation">:</span> <span class="hljs-punctuation">[</span><br> <span class="hljs-string">"/apollo/cyber/setup.bash"</span><br> <span class="hljs-punctuation">]</span><br> <span class="hljs-punctuation">}</span><br> <span class="hljs-punctuation">]</span><br><span class="hljs-punctuation">}</span><br></code></pre></td></tr></table></figure></li><li><p>修改launch.json文件</p><blockquote><p>此处定义了debug的程序和配置文件,同时指定了源码路径</p></blockquote><figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br></pre></td><td class="code"><pre><code class="hljs json"><span class="hljs-punctuation">{</span><br> <span class="hljs-comment">// Use IntelliSense to learn about possible attributes.</span><br> <span class="hljs-comment">// Hover to view descriptions of existing attributes.</span><br> <span class="hljs-comment">// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387</span><br> <span class="hljs-attr">"version"</span><span class="hljs-punctuation">:</span> <span class="hljs-string">"0.2.0"</span><span class="hljs-punctuation">,</span><br> <span class="hljs-attr">"configurations"</span><span class="hljs-punctuation">:</span> <span class="hljs-punctuation">[</span><br> <span class="hljs-punctuation">{</span><br> <span class="hljs-attr">"name"</span><span class="hljs-punctuation">:</span> <span class="hljs-string">"(gdb) 启动"</span><span class="hljs-punctuation">,</span><br> <span class="hljs-attr">"type"</span><span class="hljs-punctuation">:</span> <span class="hljs-string">"cppdbg"</span><span class="hljs-punctuation">,</span><br> <span class="hljs-attr">"request"</span><span class="hljs-punctuation">:</span> <span class="hljs-string">"launch"</span><span class="hljs-punctuation">,</span><br> <span class="hljs-attr">"preLaunchTask"</span><span class="hljs-punctuation">:</span> <span class="hljs-string">"env"</span><span class="hljs-punctuation">,</span><br> <span class="hljs-attr">"program"</span><span class="hljs-punctuation">:</span> <span class="hljs-string">"/apollo/bazel-bin/cyber/mainboard/mainboard"</span><span class="hljs-punctuation">,</span><br> <span class="hljs-attr">"args"</span><span class="hljs-punctuation">:</span> <span class="hljs-punctuation">[</span><br> <span class="hljs-string">"-d"</span><span class="hljs-punctuation">,</span><br> <span class="hljs-string">"modules/omnisense/drivers/lidar/innovusion/dag/01_single.dag"</span><br> <span class="hljs-punctuation">]</span><span class="hljs-punctuation">,</span><br> <span class="hljs-attr">"stopAtEntry"</span><span class="hljs-punctuation">:</span> <span class="hljs-literal"><span class="hljs-keyword">false</span></span><span class="hljs-punctuation">,</span><br> <span class="hljs-attr">"cwd"</span><span class="hljs-punctuation">:</span> <span class="hljs-string">"/apollo"</span><span class="hljs-punctuation">,</span><br> <span class="hljs-attr">"environment"</span><span class="hljs-punctuation">:</span> <span class="hljs-punctuation">[</span><span class="hljs-punctuation">]</span><span class="hljs-punctuation">,</span><br> <span class="hljs-attr">"externalConsole"</span><span class="hljs-punctuation">:</span> <span class="hljs-literal"><span class="hljs-keyword">false</span></span><span class="hljs-punctuation">,</span><br> <span class="hljs-attr">"MIMode"</span><span class="hljs-punctuation">:</span> <span class="hljs-string">"gdb"</span><span class="hljs-punctuation">,</span><br> <span class="hljs-attr">"setupCommands"</span><span class="hljs-punctuation">:</span> <span class="hljs-punctuation">[</span><br> <span class="hljs-punctuation">{</span><br> <span class="hljs-attr">"description"</span><span class="hljs-punctuation">:</span> <span class="hljs-string">"为 gdb 启用整齐打印"</span><span class="hljs-punctuation">,</span><br> <span class="hljs-attr">"text"</span><span class="hljs-punctuation">:</span> <span class="hljs-string">"-enable-pretty-printing"</span><span class="hljs-punctuation">,</span><br> <span class="hljs-attr">"ignoreFailures"</span><span class="hljs-punctuation">:</span> <span class="hljs-literal"><span class="hljs-keyword">true</span></span><br> <span class="hljs-punctuation">}</span><span class="hljs-punctuation">,</span><br> <span class="hljs-punctuation">{</span><br> <span class="hljs-attr">"description"</span><span class="hljs-punctuation">:</span> <span class="hljs-string">"将反汇编风格设置为 Intel"</span><span class="hljs-punctuation">,</span><br> <span class="hljs-attr">"text"</span><span class="hljs-punctuation">:</span> <span class="hljs-string">"-gdb-set disassembly-flavor intel"</span><span class="hljs-punctuation">,</span><br> <span class="hljs-attr">"ignoreFailures"</span><span class="hljs-punctuation">:</span> <span class="hljs-literal"><span class="hljs-keyword">true</span></span><br> <span class="hljs-punctuation">}</span><br> <span class="hljs-punctuation">]</span><br> <span class="hljs-punctuation">}</span><br> <span class="hljs-punctuation">]</span><br><span class="hljs-punctuation">}</span><br></code></pre></td></tr></table></figure></li><li><p>接着就可以按下<code>F5</code>,即可正常运行了,常用的断点等功能也能正常使用</p></li></ol><h2 id="UnitTest单元测试篇"><a href="#UnitTest单元测试篇" class="headerlink" title="UnitTest单元测试篇"></a>UnitTest单元测试篇</h2><blockquote><p>unittest的重要性不用多说,可以让你的程序代码质量大幅提升、协助你进行良好的程序设计。</p><p>而单元测试的覆盖率越高,则也可以认为代码可能存在的bug越少。</p><p>并且良好的单元测试也有利于code review。</p></blockquote><p>在工具上,我们会使用下面这些:</p><ul><li><p><a href="http://google.github.io/googletest/">Google Test</a></p></li><li><p><a href="https://gcc.gnu.org/onlinedocs/gcc/gcov.html">gcov</a>是由GCC工具链提供的代码覆盖率生成工具。</p><blockquote><p>对于代码覆盖率工具所做的工作,可以简单的理解为:标记一次运行过程中,哪些代码被执行过,哪些没有执行。</p><p>因此,即便没有测试代码,直接运行编译产物也可以得到代码的覆盖率。只不过,通常情况下这样得到的覆盖率较低罢了</p></blockquote></li><li><p><a href="http://ltp.sourceforge.net/coverage/lcov.php">lcov</a>是gcov工具的图形前端。它收集多个源文件的gcov数据,并生成描述覆盖率的HTML页面。生成的结果中会包含概述页面,以方便浏览。</p></li></ul><h3 id="UnitTest-Bazel实战"><a href="#UnitTest-Bazel实战" class="headerlink" title="UnitTest Bazel实战"></a>UnitTest Bazel实战</h3><blockquote><p>google test官网详细描述了bazel或cmake的使用流程,而Apollo中的bazel也集成了gtest</p></blockquote><p>当我们写完了unittest后,只需使用如下命令:</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><code class="hljs bash">./apollo.sh <span class="hljs-built_in">test</span> omnisense/path2module<br><span class="hljs-comment">#./apollo.sh test是对bazel test的封装,通过后不会输出过程信息,即--test_output=errors,可以使用如下命令直接运行原始命令,也可以做更多定制</span><br><span class="hljs-comment">#bazel test --test_output=all --test_tag_filters=-cpplint modules/omnisense/path2module/...</span><br></code></pre></td></tr></table></figure><p>待编译完成后,则会自动执行单元测试,若没有问题,则会输出<code>PASSED</code>,显示如下:</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><code class="hljs bash">(05:20:01) INFO: Analyzed 27 targets (0 packages loaded, 0 targets configured).<br>(05:20:01) INFO: Found 26 targets and 1 <span class="hljs-built_in">test</span> target...<br>(05:20:14) INFO: Elapsed time: 13.599s, Critical Path: 12.71s<br>(05:20:14) INFO: 2 processes: 1 internal, 1 <span class="hljs-built_in">local</span>.<br>(05:20:14) INFO: Build completed successfully, 2 total actions<br>//modules/omnisense/drivers/lidar/innovusion/driver:adapter_component_test PASSED <span class="hljs-keyword">in</span> 12.7s<br><br>(05:20:14) INFO: Build completed successfully, 2 total actions<br>==============================================<br>[ OK ] Done testing omnisense/drivers. Enjoy<br>==============================================<br></code></pre></td></tr></table></figure><h3 id="Coverage代码覆盖率"><a href="#Coverage代码覆盖率" class="headerlink" title="Coverage代码覆盖率"></a>Coverage代码覆盖率</h3><blockquote><p>在进行单元测试之后,我们当然希望能够直观的看到我们的测试都覆盖了哪些代码。</p><p>理论上,如果我们能做到100%的覆盖我们的所有代码,则可以说我们的代码是没有Bug的。</p><p>但实际上,100%的覆盖率要比想象得困难。对于大型项目来说,能够达到80% ~ 90%的语句覆盖率就已经很不错了。</p></blockquote><h4 id="覆盖率的类型"><a href="#覆盖率的类型" class="headerlink" title="覆盖率的类型"></a>覆盖率的类型</h4><p>先来看一下,当我们在说“覆盖率”的时候我们到底是指的什么。</p><p>实际上,代码覆盖率有下面几种类型:</p><ul><li><strong>函数覆盖率</strong>:描述有多少比例的函数经过了测试。</li><li><strong>语句覆盖率</strong>:描述有多少比例的语句经过了测试。</li><li><strong>分支覆盖率</strong>:描述有多少比例的分支(例如:<code>if-else</code>,<code>case</code>语句)经过了测试。</li><li><strong>条件覆盖率</strong>:描述有多少比例的可能性经过了测试。</li></ul><p>这其中,函数覆盖率最为简单,就不做说明了。</p><p>语句覆盖率是我们最常用的。因为它很直观的对应到我们写的每一行代码。</p><p>而分支覆盖率和条件覆盖率可能不太好理解</p><p>以下面这个C语言函数为例:</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><code class="hljs c++"><span class="hljs-function"><span class="hljs-type">int</span> <span class="hljs-title">foo</span> <span class="hljs-params">(<span class="hljs-type">int</span> x, <span class="hljs-type">int</span> y)</span> </span>{<br> <span class="hljs-type">int</span> z = <span class="hljs-number">0</span>;<br> <span class="hljs-keyword">if</span> ((x > <span class="hljs-number">0</span>) && (y > <span class="hljs-number">0</span>)) {<br> z = x;<br> }<br> <span class="hljs-keyword">return</span> z;<br>}<br></code></pre></td></tr></table></figure><p>这个函数中包含了一个<code>if</code>语句,因此<code>if</code>语句成立或者不成立构成了两个分支。所以如果只测试了<code>if</code>成立或者不成立的其中之一,其分支覆盖率只有 <code>1/2 = 50%</code>。</p><p>而条件覆盖率需要考虑每种可能性的情况。</p><p>对于<code>if (a && b)</code>这样的语句,其一共有四种可能的条件:</p><ol><li>a = true, b = true</li><li>a = true, b = false</li><li>a = false, b = true</li><li>a = false, b = false</li></ol><p><strong>综上,在编写代码的时候,尽可能的减少代码嵌套,并且简化逻辑运算是一项很好的习惯。便于测试的代码也是便于理解和维护的,反之则反。</strong></p><p>有了这些概念之后,我们就可以看懂测试报告中的覆盖率了。</p><h4 id="Coverage-Bazel实战"><a href="#Coverage-Bazel实战" class="headerlink" title="Coverage Bazel实战"></a>Coverage Bazel实战</h4><blockquote><p>由于Apollo Bazel中集成了LCOV,其他编译环境的配置则不在此展开,可以自行搜索。</p></blockquote><p>当完成上面的UintTest后,我们就可以检查覆盖率了:</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs bash">./apollo.sh coverage omnisense/path2module<br></code></pre></td></tr></table></figure><p>一切正常的话,会输出以下信息:</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><code class="hljs bash">Overall coverage rate:<br> lines......: 50.4% (1260 of 2499 lines)<br> <span class="hljs-built_in">functions</span>..: 59.9% (235 of 392 <span class="hljs-built_in">functions</span>)<br>==============================================<br>[ OK ] Done bazel coverage <span class="hljs-keyword">for</span> omnisense/drivers. <br>==============================================<br>[INFO] Coverage report was generated under /apollo/.cache/coverage<br></code></pre></td></tr></table></figure><p>能看到其中<code>50.4%</code>和<code>59.9%</code>则是输出的覆盖率了,相关的html格式报告则存放于<code>/apollo/.cache/coverage</code>路径下。</p><p>此时我们可以进入到路径<code>/apollo/.cache/coverage</code>,然后运行一个<strong>http server</strong>,则就可以在浏览器中查看细节了:</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs bash">python3 -m http.server [8000]<br></code></pre></td></tr></table></figure><p>此时打开浏览器,输入127.0.0.1:8000,则会显示类似如下的界面。</p><p><img src="%E6%88%AA%E5%B1%8F2022-11-02%2016.35.47.png" alt="截屏2022-11-02 16.35.47"></p><p>可以看到,除了第三方库httplib.h头文件,其他的代码覆盖率都能达到90%。</p><p>而如果先前也已经完成了内存泄漏的检查,到这里基本上可以认为自己的代码已经相当稳定了,是可以交付生产或者进行<strong>Code Review</strong>的了。</p><h2 id="Perf-x2F-Profiling性能-x2F-分析篇"><a href="#Perf-x2F-Profiling性能-x2F-分析篇" class="headerlink" title="Perf/Profiling性能/分析篇"></a>Perf/Profiling性能/分析篇</h2><blockquote><p>Bottlenecks occur in surprising places, so don’t try to second guess and put in a speed hack until you’ve proven that’s where the bottleneck is. - Rob Pike</p></blockquote><p>性能分析,Linux下有一个非常好用的工具,叫做perf。几乎每个发行版都有它的安装包。perf诞生于2009年,是一个内核级的工具;另外,还有一个工具叫做gperftools(pprof),是google的产品,是一个应用级别的产品。</p><p>不得不提的一点是,perf和gperftools对性能的影响,虽然不是特别大,但也尽量不要在线上环境使用它们,这个性能损耗率大概在30%左右。</p><blockquote><p>由于perf仅适用于linux,且对内核版本有要求,而gperftools则没有这个限制,使用也会更宽泛,我们便以此来示范</p></blockquote><h3 id="gperftool介绍"><a href="#gperftool介绍" class="headerlink" title="gperftool介绍"></a>gperftool介绍</h3><blockquote><p>gperftools包含了一套分析工具,在此我们只使用它的性能分析模块(cpu profiler)</p></blockquote><h4 id="安装"><a href="#安装" class="headerlink" title="安装"></a>安装</h4><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><code class="hljs bash">sudo apt-get update<br>sudo apt-get install google-perftools libgoogle-perftools-dev<br></code></pre></td></tr></table></figure><p>也可以使用源码安装,</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><code class="hljs bash">git <span class="hljs-built_in">clone</span> https://github.com/gperftools/gperftools.git<br><span class="hljs-built_in">cd</span> gperftools/<br>./autogen.sh<br>./configure<br>make<br>make install<br></code></pre></td></tr></table></figure><h4 id="使用"><a href="#使用" class="headerlink" title="使用"></a>使用</h4><blockquote><p>参考<a href="https://gperftools.github.io/gperftools/cpuprofile.html">https://gperftools.github.io/gperftools/cpuprofile.html</a></p></blockquote><p>它的使用较为简单,一般情况下仅需链接<code>-lprofiler</code>即可,动态库和静态库都支持</p><h4 id="Cmake"><a href="#Cmake" class="headerlink" title="Cmake"></a>Cmake</h4><blockquote><p>只需要**TARGET_LINK_LIBRARIES(${TARGET} tcmalloc profiler)**即可</p></blockquote><h4 id="用例"><a href="#用例" class="headerlink" title="用例"></a>用例</h4><p>一般情况下,我们使用性能分析有以下几种需求或场景</p><ul><li><p>程序运行即开始性能分析(即从启动程序到结束)</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs bash">CPUPROFILE=./path2save.prof ./execute<br></code></pre></td></tr></table></figure><blockquote><p> 此处的CPUPROFILE即是指定路径</p></blockquote></li><li><p>监控部分函数</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><code class="hljs c++"><span class="hljs-meta">#<span class="hljs-keyword">include</span> <span class="hljs-string"><gperftools/profiler.h></span></span><br><br><span class="hljs-function"><span class="hljs-type">int</span> <span class="hljs-title">main</span><span class="hljs-params">()</span></span>{<br> <span class="hljs-built_in">ProfilerStart</span>(<span class="hljs-string">"test.prof"</span>); <span class="hljs-comment">// 指定所生成的profile文件名</span><br> <span class="hljs-built_in">func_prof</span>();<br> <span class="hljs-built_in">ProfilerStop</span>(); <span class="hljs-comment">// 结束profiling</span><br> <span class="hljs-keyword">return</span> <span class="hljs-number">0</span>;<br>}<br></code></pre></td></tr></table></figure></li><li><p>分析一直运行的程序</p><blockquote><p>一直运行的程序由于不能正常退出,所以不能采用上面的方法。我们可以用信号量来开启/关闭性能分析</p></blockquote><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br></pre></td><td class="code"><pre><code class="hljs bash"><span class="hljs-comment">#include <gperftools/profiler.h></span><br><span class="hljs-comment">#include <stdio.h></span><br><span class="hljs-comment">#include <signal.h></span><br><span class="hljs-comment">#include <unistd.h></span><br><br>void gprofStartAndStop(int signum) {<br> static int isStarted = 0;<br> <span class="hljs-keyword">if</span> (signum != SIGUSR1) <span class="hljs-built_in">return</span>;<br><br> //通过isStarted标记未来控制第一次收到信号量开启性能分析,第二次收到关闭性能分析。<br> <span class="hljs-keyword">if</span> (!isStarted){<br> isStarted = 1;<br> ProfilerStart(<span class="hljs-string">"test.prof"</span>);<br> <span class="hljs-built_in">printf</span>(<span class="hljs-string">"ProfilerStart success\n"</span>);<br> }<span class="hljs-keyword">else</span>{<br> ProfilerStop();<br> <span class="hljs-built_in">printf</span>(<span class="hljs-string">"ProfilerStop success\n"</span>);<br> }<br>}<br><br>int <span class="hljs-function"><span class="hljs-title">main</span></span>(){<br> signal(SIGUSR1, gprofStartAndStop);<br><br> <span class="hljs-keyword">while</span>(1){<br> <span class="hljs-built_in">printf</span>(<span class="hljs-string">"call f\n"</span>);<br> func_prof();<br> <span class="hljs-built_in">sleep</span>(1);//为了防止死循环,导致信号处理函数得不到调度<br> }<br> <span class="hljs-built_in">return</span> 0;<br>}<br></code></pre></td></tr></table></figure><p><strong>通过kill命令发送信号给进程来开启/关闭性能分析:</strong></p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><code class="hljs bash">用top命令查看进程的PID<br><span class="hljs-built_in">kill</span> -s SIGUSR1 PID //第一次运行命令启动性能分析<br><span class="hljs-built_in">kill</span> -s SIGUSR1 PID //再次运行命令关闭性能分析,产生test.prof<br></code></pre></td></tr></table></figure><p>这种方式适合灵活关闭profile,不用重启启动服务,适合在线上查看。</p></li></ul><h4 id="分析"><a href="#分析" class="headerlink" title="分析"></a>分析</h4><blockquote><p>当生成了profile后,就可以使用google-pprof工具来转换为我们能分析的文件。</p></blockquote><p>以下为常用的几种目标输出:</p><figure class="highlight awk"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><code class="hljs awk">google-pprof bazel-bin<span class="hljs-regexp">/path2module.so /</span>tmp<span class="hljs-regexp">/cpu.prof --text > /</span>tmp/gprof.txt<br>google-pprof bazel-bin<span class="hljs-regexp">/path2module.so /</span>tmp<span class="hljs-regexp">/cpu.prof --svg > /</span>tmp/gprof.svg<br>google-pprof bazel-bin<span class="hljs-regexp">/path2module.so /</span>tmp<span class="hljs-regexp">/cpu.prof --web > /</span>tmp/gprof.html<br></code></pre></td></tr></table></figure><p>以输出的svg文件为例,我们可以使用浏览器打开,其中占用面积越大则意味着消耗了更多的cpu</p><p><img src="%E6%88%AA%E5%B1%8F2022-11-04%2011.35.12.png" alt="截屏2022-11-04 11.35.12"></p><p>树上的每个节点代表一个函数,节点数据格式:</p><ol><li>函数名 或者 类名+方法名</li><li>不包含内部函数调用的样本数 (百分比)</li><li>包含内部函数调用的样本数 (百分比) ,如果没有内部调用函数则这一项数据不显示</li></ol><h4 id="Apollo-Bazel实战"><a href="#Apollo-Bazel实战" class="headerlink" title="Apollo Bazel实战"></a>Apollo Bazel实战</h4><blockquote><p>Apollo Bazel中集成了pprof,其他的环境或者更详细的配置可以参考<a href="https://gperftools.github.io/gperftools/cpuprofile.html">https://gperftools.github.io/gperftools/cpuprofile.html</a></p></blockquote><p>当我们需要进行性能分析时,只需要使用如下命令编译:</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs bash">./apollo.sh build_prof omnisense/path2module<br></code></pre></td></tr></table></figure><blockquote><p>需要注意的事,一般类似的工具都需要运行流程可正常退出(此处若异常退出则无法统计结果)</p><p>当然,有时候我们只想要统计部分时间或者部分函数,那我们就需要参考👆上面的用例进行定制代码</p></blockquote><p>运行的时候,只需要增加环境变量,填入我们希望存放的.prof路径即可</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs bash">CPUPROFILE=/tmp/cpu.prof mainboard -d modules/omnisense/drivers/lidar/innovusion/dag/01_single.dag <br></code></pre></td></tr></table></figure><p>当运行结束或者我们Ctrl+C退出时,将会在命令行输出以下信息,即为收集到了相关信息:</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs bash">PROFILE: interrupts/evictions/bytes = 3851/899/52752<br></code></pre></td></tr></table></figure><h3 id="火焰图(Flame-Graphs)"><a href="#火焰图(Flame-Graphs)" class="headerlink" title="火焰图(Flame Graphs)"></a>火焰图(Flame Graphs)</h3><blockquote><p>火焰图(flame graph)是性能分析的利器,通过它可以快速定位性能瓶颈点。一般perf或gperftools产生的数据可以导出为火焰图,以方便将数据呈现得更为直观。</p></blockquote><h4 id="安装-1"><a href="#安装-1" class="headerlink" title="安装"></a>安装</h4><blockquote><p>火焰图时脚本工具,只需要将其仓库下载即可使用</p></blockquote><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><code class="hljs bash"><span class="hljs-built_in">mkdir</span> -p /tmp/FlameGraph && <span class="hljs-built_in">cd</span> /tmp/FlameGraph<br>git <span class="hljs-built_in">clone</span> https://github.com/brendangregg/FlameGraph.git<br></code></pre></td></tr></table></figure><h4 id="gperftools生成火焰图"><a href="#gperftools生成火焰图" class="headerlink" title="gperftools生成火焰图"></a>gperftools生成火焰图</h4><p>在上节生成了profile文件后,我们只需要:</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><code class="hljs bash">pprof bazel-bin/path2module.so /tmp/cpu.prof --collapsed > /tmp/flame.prof<br>/tmp/FlameGraph/flamegraph.pl /tmp/flame.prof > ./flame.svg<br></code></pre></td></tr></table></figure><p>将会在当前目录生成svg文件,这时候就可以用浏览器打开并交互了</p><p><img src="%E6%88%AA%E5%B1%8F2022-11-04%2013.37.51.png" alt="截屏2022-11-04 13.37.51"></p><p>火焰图有以下特征(这里以 on-cpu 火焰图为例):</p><ul><li>每一列代表一个调用栈,每一个格子代表一个函数</li><li>纵轴展示了栈的深度,按照调用关系从下到上排列。最顶上格子代表采样时,正在占用 cpu 的函数。</li><li>横轴的意义是指:火焰图将采集的多个调用栈信息,通过按字母横向排序的方式将众多信息聚合在一起。需要注意的是它并不代表时间。</li><li>横轴格子的宽度代表其在采样中出现频率,所以一个格子的宽度越大,说明它是瓶颈原因的可能性就越大。</li><li>火焰图格子的颜色是随机的暖色调,方便区分各个调用信息。</li><li>其他的采样方式也可以使用火焰图, on-cpu 火焰图横轴是指 cpu 占用时间,off-cpu 火焰图横轴则代表阻塞时间。</li><li>采样可以是单线程、多线程、多进程甚至是多 host</li></ul><h1 id="参考文献与推荐读物"><a href="#参考文献与推荐读物" class="headerlink" title="参考文献与推荐读物"></a>参考文献与推荐读物</h1><ul><li><a href="https://www.amazon.com/CMake-Cookbook-Building-packaging-software-ebook/dp/B079TVFDP5/">CMake Cookbook</a></li><li><a href="https://github.com/google/googletest">Google Test</a></li><li><a href="https://github.com/google/googletest/blob/master/googletest/README.md">googletest Generic Build Instructions</a></li><li><a href="https://github.com/google/googletest/blob/master/googletest/docs/primer.md">Googletest Primer</a></li><li><a href="https://www.ibm.com/developerworks/aix/library/au-googletestingframework.html">A quick introduction to the Google C++ Testing Framework</a></li><li><a href="https://qiangbo-workspace.oss-cn-shanghai.aliyuncs.com/2018-12-05-gtest-and-coverage/PlainGoogleQuickTestReferenceGuide1.pdf">Google Test Quick Reference</a></li><li><a href="http://www.network-theory.co.uk/docs/gccintro/gccintro_81.html">Coverage testing with gcov</a></li><li><a href="https://en.wikipedia.org/wiki/Code_coverage">Wikipedia: Code Coverage</a></li><li><a href="http://ltp.sourceforge.net/coverage/lcov.php">lcov - the LTP GCOV extension</a></li><li><a href="http://gcc.gnu.org/onlinedocs/gcc/Gcov.html">gcov—a Test Coverage Program</a></li></ul>]]></content>
<categories>
<category>tools</category>
</categories>
<tags>
<tag>C/C++</tag>
<tag>Debug</tag>
</tags>
</entry>
<entry>
<title>Memory Leak Analyze 01</title>
<link href="/2022/10/17/Memory-Leak-Analyze-01/"/>
<url>/2022/10/17/Memory-Leak-Analyze-01/</url>
<content type="html"><![CDATA[<h1 id="Memory-Leak-内存泄漏-专题指南-01"><a href="#Memory-Leak-内存泄漏-专题指南-01" class="headerlink" title="Memory Leak (内存泄漏)专题指南-01"></a>Memory Leak (内存泄漏)专题指南-01</h1><blockquote><p>本文着重分析常见memory leak的现象和原因,以及分享一些解决memory leak的思路和工具</p></blockquote><h2 id="何为内存泄漏"><a href="#何为内存泄漏" class="headerlink" title="何为内存泄漏"></a>何为内存泄漏</h2><blockquote><p>动态存储分配函数动态开辟的空间,在使用完毕后未释放,结果导致一直占据该内存单元</p></blockquote><h3 id="申请过多内存"><a href="#申请过多内存" class="headerlink" title="申请过多内存"></a>申请过多内存</h3><p> 首先内存只申请不释放未必就是内存泄漏,有可能是你的程序的确需要申请很多内存,这是正常的,然而如果是bug导致申请了很多内存,这就是内存泄漏了,或者也有人将其称为<strong>space leak</strong>,意思是申请的内存超过了正常所需;不管是有意无意,总之在这种情况下<strong>你依然保持对这些内存的引用,因此你总可以找到这些内存并删除它们</strong>,就看你删不删。</p><p> 有很多情况会导致这一问题,像重复使用的某个结构体/对象,当再次复用时没有清理上一次使用遗留的数据、系统中存在cache,但cache的过期策略设置不得当等等。</p><h3 id="内存无法删除"><a href="#内存无法删除" class="headerlink" title="内存无法删除"></a>内存无法删除</h3><p>另一类比较有趣的内存泄漏是说你申请了一些内存,但最终却没有什么指向它们:</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><code class="hljs text">void memory_leak() {<br> char* mem = (char*)malloc(1024);<br> // just return<br>}<br></code></pre></td></tr></table></figure><p>在这段代码中我们申请了1k内存,<strong>然而当memory_leak函数返回后你就再也不知道这段内存到底在哪里了</strong>!对于编程来说就是粗心大意的程序员申请了一些内存后最终“忘掉”了,再也不会有什么东西(变量/指针)指向这些内存,因此在<strong>这种情况下你没有办法再找到这些内存并将其删除</strong>。</p><h3 id="内存碎片"><a href="#内存碎片" class="headerlink" title="*内存碎片"></a>*内存碎片</h3><p> 这是一类特殊的内存泄漏,用停车场的例子来说就是两个停车位中间停靠了一辆小型老年代步车,导致尽管这两个停车位剩余的空间足够大但又恰好都没有办法再停靠一辆小汽车。因此看起来我们的程序占据的内存越来越多,尽管实际上程序可能并不需要那么多内存,仅仅是因为内存碎片的原因导致一部分内存无法被再次被利用起来。</p><p> 然而对于现代操作系统尤其具备虚拟内存能力的系统来说,内存碎片问题通常可能并不会和我们想象的那样严重,原因就在于分配的内存只需要在虚拟地址空间上连续而不必在物理内存上也连续。</p><p> 如果你的程序需要重复申请很多对象/数据/结构体,并在最后一次性全部释放,那么内存池是一个避免内存碎片不错的选择,原理在于尽管从内存池的角度看会有碎片,但当我们以内存池大小为单位从堆区中申请释放内存时,这种碎片将不复存在。</p><h2 id="内存泄漏有什么问题"><a href="#内存泄漏有什么问题" class="headerlink" title="内存泄漏有什么问题"></a>内存泄漏有什么问题</h2><p> 在现代操作系统中除非你的程序运行时间足够长或者申请的内存足够快足够多否则内存泄漏可能并不是什么大问题,你甚至可能都察觉不出来有内存泄漏,因为当进程运行结束后其占据的内存会被操作系统收回,在这种情况下你可能不必过于关心这个问题,但对于长时间运行的服务器端程序、数据库程序、操作系统等,内存泄漏就属于比较严重的问题了,因为这些程序必须时刻在线,任何微小的内存泄漏在时间的加持下都会非常明显。</p><p> <strong>内存持续泄漏会发生什么?</strong></p><p> 内存的申请速度会对系统性能产生很大的影响,当系统内存不足时,内存分配器找到一块满足要求的空闲内存块将更加困难耗时更多,当程序消耗的内存超过物理内存大小时虚拟内存系统(如果有的话)开始发挥作用,将进程地址空间中不常用的一部分swap出去,此时系统性能将快速下降,表现出来的就是程序员运行变慢、卡顿。</p><p> 当然,根据系统配置,像Linux系统,可能会将消耗内存很多的进程kill掉,这就是Out of Memory killer,简称oom killer。</p><h2 id="如何发现内存泄漏"><a href="#如何发现内存泄漏" class="headerlink" title="如何发现内存泄漏"></a>如何发现内存泄漏</h2><blockquote><p>不像程序崩溃Core dump,这类问题通过debug通常能获取一些线索,但内存泄漏问题就没那么直接了,尤其对于C/C++程序来说,这时我们将不得不借助必要的工具。</p></blockquote><p>下图是常用的一些内存检测工具:</p><p><img src="valgrind%20vs%20asan.jpg" alt="valgrind vs asan"></p><p>参数说明:</p><ul><li><em><strong>DBI</strong></em>: dynamic binary instrumentation(动态二进制插桩)</li><li><em><strong>CTI</strong></em>: compile-time instrumentation (编译时插桩)</li><li><em><strong>UMR</strong></em>: uninitialized memory reads (读取未初始化的内存)</li><li><em><strong>UAF</strong></em>: use-after-free (aka dangling pointer) (使用释放后的内存)</li><li><em><strong>UAR</strong></em>: use-after-return (使用返回后的值)</li><li><em><strong>OOB</strong></em>: out-of-bounds (溢出)</li><li><em><strong>x86</strong></em>: includes 32- and 64-bit.</li></ul><h3 id="Valgrind-不推荐,慢,但没有侵入性,检测效果一般"><a href="#Valgrind-不推荐,慢,但没有侵入性,检测效果一般" class="headerlink" title="Valgrind(不推荐,慢,但没有侵入性,检测效果一般)"></a>Valgrind(不推荐,慢,但没有侵入性,检测效果一般)</h3><blockquote><p><a href="https://valgrind.org/docs/manual/manual.html">https://valgrind.org/docs/manual/manual.html</a></p></blockquote><p>包含下列工具:</p><ol><li><p>memcheck:检查程序中的内存问题,如泄漏、越界、非法指针等。</p></li><li><p>callgrind:检测程序代码的运行时间和调用过程,以及分析程序性能。</p></li><li><p>cachegrind:分析CPU的cache命中率、丢失率,用于进行代码优化。</p></li><li><p>helgrind:用于检查多线程程序的竞态条件。</p></li><li><p>massif:堆栈分析器,指示程序中使用了多少堆内存等信息。</p></li><li><p>lackey:是一个示例程序,以其为模版可以创建你自己的工具。在程序结束后,它打印出一些基本的关于程序执行统计数据</p></li><li><p>nulgrind:</p></li></ol><p> 这几个工具的使用是通过命令:<code>valgrand --tool=name</code> 程序名来分别调用的,当不指定 tool 参数时默认是 <code>-–tool=memcheck</code></p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs bash">valgrind --tool=memcheck --leak-check=full ./mem_leak_test<br></code></pre></td></tr></table></figure><p><em><strong>该工具可以检测下列与内存相关的问题 :</strong></em></p><ul><li>未释放内存的使用</li><li>对释放后内存的读/写</li><li>对已分配内存块尾部的读/写</li><li>内存泄露</li><li>不匹配的使用malloc/new/new[] 和 free/delete/delete[]</li><li>重复释放内存</li></ul><p><strong>结果中包含以下信息:</strong></p><ul><li><p>HEAP SUMMARY,它表示程序在堆上分配内存的情况,2 allocs表示分配了2次内存,0 frees表示释放了0次,72,714 bytes allocated表示分配了72,714个字节</p></li><li><p>如果有泄漏,valgrind会报告是哪个位置发生了泄漏(main中cpp第8行)</p></li><li><p>LEAK SUMMARY,表示不同的内存丢失类型</p></li><li><ul><li>definitely loss: 确认丢失,<strong>需修复</strong>因为在程序运行完的时候,没有指针指向它,指向它的指针在程序中丢失了;</li><li>indirectly lost: 间接丢失,<strong>无须处理</strong>,当使用了含有指针成员的类或结构时可能会报这个错误。这类错误无需直接修复,他们总是与”definitely lost”一起出现,只要修复”definitely lost”即可;</li><li>possibly lost: 可能丢失,<strong>需修复</strong>,发现了一个指向某块内存中部的指针,而不是指向内存块头部。这种指针一般是原先指向内存块头部,后来移动到了内存块的中部,还有可能该指针和该内存根本就没有关系,检测工具只是怀疑有内存泄漏。</li><li>still reachable: 可以访问,需修复,未丢失但也未释放。如果程序是正常结束的,那么它可能不会造成程序崩溃。表示泄漏的内存在程序运行完的时候,仍旧有指针指向它,因而,这种内存在程序运行结束之前可以释放。一般情况下valgrind不会报这种泄漏,除非使用了参数 –show-reachable=yes。</li><li>suppressed:已被解决,<strong>无须处理</strong>,出现了内存泄露但系统自动处理了;可以无视这类错误。</li></ul></li></ul><h3 id="AddressSanitizer-推荐,快,需要链接库,检测效果较好"><a href="#AddressSanitizer-推荐,快,需要链接库,检测效果较好" class="headerlink" title="AddressSanitizer(推荐,快,需要链接库,检测效果较好)"></a>AddressSanitizer(推荐,快,需要链接库,检测效果较好)</h3><blockquote><p>AddressSanitizer是google开发一个应用内存检查工具,性能据说比valgrind要好不少,可以配合clang或者GCC编译器使用,GCC需要4.8及以上版本。</p><p><a href="https://github.com/google/sanitizers/wiki/AddressSanitizer">https://github.com/google/sanitizers/wiki/AddressSanitizer</a></p></blockquote><p>AddressSanitizer 是一个快速的内存错误检测器。它由一个编译器检测模块和一个运行时库组成。该工具可以检测以下类型的错误:</p><ul><li>Out-of-bounds accesses to heap, stack and globals</li><li>Use-after-free</li><li>Use-after-return (to some extent)</li><li>Double-free, invalid free</li></ul><p>使用方法很简单,只需使用<code>-fsanitize=address</code>标志编译和链接您的程序。<br>要获得合理的性能,请添加<code>-O1</code>或更高。<br>要在错误消息中获得更好的堆栈跟踪,请添加 <code>-fno-omit-frame-pointer</code>。<br>要获得完美的堆栈跟踪,您可能需要禁用内联(只需使用<code>-O1</code>)和尾调用消除(-fno-optimize-sibling-calls)。</p><p>然后运行可执行文件即可检测。</p><h2 id="如何避免内存泄漏"><a href="#如何避免内存泄漏" class="headerlink" title="如何避免内存泄漏"></a>如何避免内存泄漏</h2><blockquote><p>虽然上节提到的工具可以帮助我们解决大多问题,但仍然存在一些“精心编写”的代码,由于种种原因,产生了意想不到的负面影响</p></blockquote><p>以一个真实的反面教材为例,在紧张的开发完成后,经过了valgrind内存测试、性能测试和测试环境测试,并最终上线了生产环境,但却在一到两天后,日志显示系统异常。</p><p>现象是现场数十个小时后整个处理逻辑(传感器接入到事件算法完成输出)耗时过久,且<code>top</code>命令显示的内存一直在缓慢的增长。</p><p>对此我们分析了种种原因,最主要的猜测是生产环境所需的处理数据较大,而我们实际的<strong>运行环境资源非常紧张</strong>,无法积累太长时间的数据。</p><p>为了验证各种可能,我们开发并嵌入了更为详细的日志,以确定主要影响的代码段,最终定位到问题以下代码耗时过久。</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><code class="hljs c++">map<string, vector<<span class="hljs-keyword">struct</span> wgs_car>> evts; <br>...<br><span class="hljs-keyword">for</span> (<span class="hljs-keyword">auto</span> type : evts) {<br> <span class="hljs-keyword">if</span> (type.second.<span class="hljs-built_in">size</span>())<br> {<br> <span class="hljs-keyword">for</span> (<span class="hljs-keyword">auto</span> evt = type.second.<span class="hljs-built_in">begin</span>(); evt != type.second.<span class="hljs-built_in">end</span>();)<br> {<br> <span class="hljs-keyword">if</span> ((*evt).timestamp + <span class="hljs-number">15</span> < <span class="hljs-built_in">time</span>(<span class="hljs-literal">NULL</span>)) {<br> evt = type.second.<span class="hljs-built_in">erase</span>(evt);<br> } <span class="hljs-keyword">else</span><br> evt++;<br> }<br> }<br>}<br></code></pre></td></tr></table></figure><p>在设计中,此段代码应该只需处理1分钟内的数据量,剩下的将会主动清除,然而因为某一行(line 3,&type)的问题,导致没有清除成功,导致map越来越大,以至于影响整个系统。</p><h3 id="一些思路-x2F-经验"><a href="#一些思路-x2F-经验" class="headerlink" title="一些思路/经验"></a>一些思路/经验</h3><blockquote><p>人生三大错觉之一,我能管理好内存</p></blockquote><ul><li><p>尽量避免在堆上分配内存</p><blockquote><p>既然只有堆上会发生内存泄露,那第一原则肯定是避免在堆上面进行内存分配,尽可能的使用栈上的内存,由编译器进行分配和回收,这样当然就不会有内存泄露了,但需谨慎注意避免<strong>爆栈</strong></p></blockquote></li><li><p>善用 <a href="https://en.wikipedia.org/wiki/Resource_acquisition_is_initialization">RAII(Resource Acquisition Is Initialization)</a></p><blockquote><p><strong>RAII 可以帮助我们将管理堆上的内存,简化为管理栈上的内存,从而达到利用编译器自动解决内存回收问题的效果</strong>。此外,RAII 可以简化的还不仅仅是内存管理,还可以简化对资源的管理,例如 fd,锁,引用计数等等。</p><p>当我们需要在堆上分配内存时,我们可以同时在栈上面分配一个对象,让栈上面的对象对堆上面的对象进行封装,用时通过在栈对象的析构函数中释放堆内存的方式,将栈对象的生命周期和堆内存进行绑定。</p></blockquote></li><li><p>便于 Debug</p></li></ul><h2 id="内存泄漏实战-Apollo-Bazel"><a href="#内存泄漏实战-Apollo-Bazel" class="headerlink" title="内存泄漏实战(Apollo+Bazel)"></a>内存泄漏实战(Apollo+Bazel)</h2><p> 由于常见的valgrand以及AddressSanitizer等工具并未原生支持或嵌入bazel编译系统,所以需要debug前对整套系统进行一些“魔改”来使用,对于其他如Cmake,使用更为容易且相关资料丰富,这里不做补充。</p><h3 id="开启Debug模式-g"><a href="#开启Debug模式-g" class="headerlink" title="开启Debug模式(-g)"></a>开启Debug模式(-g)</h3><blockquote><p>Bazel 提供了<code>--compilation_mode (fastbuild|opt|dbg) (-c)</code>选项,即<code>gcc -g</code>选项,参考:</p><p>Path2apollo/docs/specs/apollo_build_and_test_explained.md</p><p><a href="https://docs.bazel.build/versions/main/user-manual.html#flag--compilation_mode">https://docs.bazel.build/versions/main/user-manual.html#flag--compilation_mode</a></p></blockquote><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><code class="hljs bash">./apollo.sh build_dbg omnisense<br><span class="hljs-comment"># 等价于 bazel build -c dbg modules/omnisense/...</span><br></code></pre></td></tr></table></figure><p>经过以上 <strong>-g</strong> 编译后的程序,gdb就可以看到源码并进行一系列操作:</p><ul><li><p>gdb可以读取到<strong>symbols</strong></p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br></pre></td><td class="code"><pre><code class="hljs bash">root@in-dev-docker:/apollo<span class="hljs-comment"># gdb bazel-bin/modules/omnisense/drivers/lidar/innovusion/driver/adapter_component_test</span><br>GNU gdb (Ubuntu 8.1.1-0ubuntu1) 8.1.1<br>Copyright (C) 2018 Free Software Foundation, Inc.<br>License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html><br>This is free software: you are free to change and redistribute it.<br>There is NO WARRANTY, to the extent permitted by law. Type <span class="hljs-string">"show copying"</span><br>and <span class="hljs-string">"show warranty"</span> <span class="hljs-keyword">for</span> details.<br>This GDB was configured as <span class="hljs-string">"aarch64-linux-gnu"</span>.<br>Type <span class="hljs-string">"show configuration"</span> <span class="hljs-keyword">for</span> configuration details.<br>For bug reporting instructions, please see:<br><http://www.gnu.org/software/gdb/bugs/>.<br>Find the GDB manual and other documentation resources online at:<br><http://www.gnu.org/software/gdb/documentation/>.<br>For <span class="hljs-built_in">help</span>, <span class="hljs-built_in">type</span> <span class="hljs-string">"help"</span>.<br>Type <span class="hljs-string">"apropos word"</span> to search <span class="hljs-keyword">for</span> commands related to <span class="hljs-string">"word"</span>...<br>Reading symbols from bazel-bin/modules/omnisense/drivers/lidar/innovusion/driver/adapter_component_test...done. <span class="hljs-comment">### 这里可以读取到symbols,即可以gdb</span><br>(gdb) l<br>1 <span class="hljs-comment">#include "adapter_component.h"</span><br>2<br>3 <span class="hljs-comment">#include <time.h></span><br>4<br>5 <span class="hljs-comment">#include <chrono></span><br>6 <span class="hljs-comment">#include <iostream></span><br>7<br>8 <span class="hljs-comment">#include "cyber/cyber.h"</span><br>(gdb)<br></code></pre></td></tr></table></figure></li><li><p>readelf查看到可执行文件是否带有调试功能</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><code class="hljs bash">root@in-dev-docker:/apollo<span class="hljs-comment"># readelf -S bazel-bin/modules/omnisense/drivers/lidar/innovusion/driver/adapter_component_test | grep debug</span><br> [30] .debug_info PROGBITS 0000000000000000 00150451<br> [31] .debug_abbrev PROGBITS 0000000000000000 0023f27f<br> [32] .debug_aranges PROGBITS 0000000000000000 002416ea<br> [33] .debug_ranges PROGBITS 0000000000000000 0025140a<br> [34] .debug_line PROGBITS 0000000000000000 0026271a<br> [35] .debug_str PROGBITS 0000000000000000 00281445<br></code></pre></td></tr></table></figure></li></ul><h4 id="链接AddressSanitizer"><a href="#链接AddressSanitizer" class="headerlink" title="链接AddressSanitizer"></a>链接AddressSanitizer</h4><ul><li><p>若使用<strong>apollo.sh</strong>进行编译,需定制<code>tools/bazel.rc</code>文件</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><code class="hljs bash"><span class="hljs-comment"># path2apollo/tools/bazel.rc</span><br><span class="hljs-comment"># line 49</span><br>-build:dbg -c dbg<br>+build:dbg -c dbg --linkopt=-fsanitize=address<br><span class="hljs-comment"># 或 build:dbg -c dbg --copt=-O1 --linkopt=-fsanitize=address</span><br></code></pre></td></tr></table></figure><p>随后使用如下命令编译</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><code class="hljs bash">./apollo.sh build_dbg path2module<br><span class="hljs-comment">#例: ./apollo.sh build_dbg omnisense/drivers</span><br></code></pre></td></tr></table></figure></li><li><p>若使用<strong>bazel</strong>直接编译,则使用如下命令</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><code class="hljs bash">bazel build -c dbg --linkopt=-fsanitize=address path2module/...<br><span class="hljs-comment"># 例: bazel build -c dbg --linkopt=-fsanitize=address modules/omnisense/drivers/...</span><br><span class="hljs-comment"># 可添加--copt=-O1加速</span><br></code></pre></td></tr></table></figure></li><li><p>CMake 编译,添加以下标志,然后编译命令加入<code>-DENABLE_ASAN=ON</code></p><figure class="highlight cmake"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><code class="hljs cmake"><span class="hljs-keyword">if</span>(ENABLE_ASAN)<br><span class="hljs-keyword">set</span>(CMAKE_CXX_FLAGS <span class="hljs-string">"${CMAKE_CXX_FLAGS} -fsanitize=[sanitizer_name] [additional_options] [-g] [-OX]"</span>)<br><span class="hljs-keyword">endif</span>()<br></code></pre></td></tr></table></figure><blockquote><p>Use <code>CMAKE_C_FLAGS</code> instead of <code>CMAKE_CXX_FLAGS</code> for C projects.</p></blockquote><p>设置 <code>[sanitizer_name]</code> 为以下功能,默认应使用<code>address</code>:</p><ul><li><code>address</code> 开启 AddressSanitizer</li><li><code>leak</code> 开启 LeakSanitizer</li><li><code>thread</code> 开启 ThreadSanitizer</li><li><code>undefined</code> 开启 UndefinedBehaviorSanitizer</li><li><code>memory</code> 开启 MemorySanitizeraddress</li></ul><p><code>[Additional_flags]</code> are other compilation flags, such as <code>-fno-omit-frame-pointer</code>, <code>fsanitize-recover/fno-sanitize-recover</code>, <code>-fsanitize-blacklist</code>, etc.</p><p>Use <code>[-g]</code> to have file names and line numbers included in warning messages.</p><p>Add optimization level <code>[-OX]</code> to get reasonable performance (see recommendations in the particular Sanitizer documentation).</p></li></ul><h3 id="执行UnitTest并分析内存泄漏(二进制模式)"><a href="#执行UnitTest并分析内存泄漏(二进制模式)" class="headerlink" title="执行UnitTest并分析内存泄漏(二进制模式)"></a>执行UnitTest并分析内存泄漏(二进制模式)</h3><blockquote><p>下面将在<strong>UnitTest</strong>测试中人为制造一个简单的内存泄漏,并希望通过工具准确检测到问题点</p></blockquote><p><img src="%E6%88%AA%E5%B1%8F2022-10-14%2011.00.20.png" alt="截屏2022-10-14 11.00.20"></p><p>若在上个章节使用并链接了AddressSanitizer,则只需直接运行可执行文件,将会自动在终端输出可能存在memory问题的地方,经过运行后,得到输出如下,其中可以成功定位到问题点:</p><p><img src="%E6%88%AA%E5%B1%8F2022-10-14%2010.57.23.png" alt="截屏2022-10-14 10.57.23"></p><h2 id="执行Mainboard并分析内存泄漏(动态库模式)"><a href="#执行Mainboard并分析内存泄漏(动态库模式)" class="headerlink" title="执行Mainboard并分析内存泄漏(动态库模式)"></a>执行Mainboard并分析内存泄漏(动态库模式)</h2><blockquote><p>由于Apollo架构使用dlopen/dlclose加载卸载动态库,所以cyber系列也需要重新加入asan链接</p></blockquote><p>经测试,CyberRT框架与AddressSanitizer有可能存在以下限制,建议优先在unittest中做好完整测试:</p><ol><li>各模块中<code>Write()</code>系列函数会导致AddressSanitizer异常,<strong>异常时</strong>需屏蔽相关代码</li><li>AddressSanitizer依赖于完整退出(Ctrl+C),所以<strong>需屏蔽各模块添加的<code>kill</code>族函数</strong></li></ol><p>确认按上述限制修复后,执行如下命令:</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><code class="hljs bash">./apollo.sh build_dbg cyber omnisense/drivers<br>mainboard -d modules/omnisense/drivers/lidar/innovusion/dag/01_single.dag<br></code></pre></td></tr></table></figure><p>正常可以定位到上节的内存泄漏点。</p><h2 id="注意事项"><a href="#注意事项" class="headerlink" title="注意事项"></a>注意事项</h2><ul><li>若使用<strong>cuda</strong>的时候需要用asan排查内存问题,cuda相关api无法正常使用,会出现<strong>out of memory</strong>的错误,需要添加<code>ASAN_OPTIONS=protect_shadow_gap=0</code>环境变量</li></ul>]]></content>
<categories>
<category>Memory Leak</category>
</categories>
<tags>
<tag>C/C++</tag>
<tag>Debug</tag>
</tags>
</entry>
<entry>
<title>Waymo Dataset Parse</title>
<link href="/2022/09/14/Waymo-Dataset-Parse/"/>
<url>/2022/09/14/Waymo-Dataset-Parse/</url>
<content type="html"><![CDATA[<h1 id="Waymo-Dataset-Parse"><a href="#Waymo-Dataset-Parse" class="headerlink" title="Waymo Dataset Parse"></a>Waymo Dataset Parse</h1><blockquote><p>for any native environment which can not install waymo-open-dataset(like <strong>macOS</strong> or <strong>Windows</strong>)</p></blockquote><h2 id="Easy-Way"><a href="#Easy-Way" class="headerlink" title="Easy Way"></a>Easy Way</h2><blockquote><p><a href="https://github.com/xixioba/waymo_easy_convert">https://github.com/xixioba/waymo_easy_convert</a></p></blockquote><h2 id="Detail-Way"><a href="#Detail-Way" class="headerlink" title="Detail Way"></a>Detail Way</h2><ol><li><p>clone <a href="https://github.com/waymo-research/waymo-open-dataset">https://github.com/waymo-research/waymo-open-dataset</a></p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs bash">git <span class="hljs-built_in">clone</span> https://github.com/waymo-research/waymo-open-dataset<br></code></pre></td></tr></table></figure></li><li><p>compile *.proto(need install google <strong>protoc</strong> manually)</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><code class="hljs bash">protoc ./waymo_open_dataset/dataset.proto --python_out=.<br>protoc ./waymo_open_dataset/label.proto --python_out=.<br>protoc waymo_open_dataset/protos/*.proto --python_out=.<br></code></pre></td></tr></table></figure></li><li><p>[optional] fix <code>from google.protobuf.internal import builder as _builder</code><br>refer to: <a href="https://stackoverflow.com/a/72494013">https://stackoverflow.com/a/72494013</a></p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><code class="hljs bash"><span class="hljs-comment">#install newest protobuf</span><br>pip install --upgrade protobuf<br><span class="hljs-comment">#Backup builder.py in google.protobuf.internal</span><br><span class="hljs-comment">#install compatible protobuf(3.19.4 is good to me)</span><br>pip uninstall protobuf<br>pip install protobuf==3.19.4<br></code></pre></td></tr></table></figure></li><li><p>Now you can start convert, detail see <a href="https://github.com/xixioba/waymo_easy_convert#convert-to-kitti-format">https://github.com/xixioba/waymo_easy_convert#convert-to-kitti-format</a></p></li></ol><h2 id="TroubleShoot"><a href="#TroubleShoot" class="headerlink" title="TroubleShoot"></a>TroubleShoot</h2><ul><li><p><strong>Error</strong> in <code>parse_range_image_and_camera_projection</code> </p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><code class="hljs bash">Traceback (most recent call last):<br>...<br> range_images, camera_projections, range_image_top_pose = parse_range_image_and_camera_projection(<br>ValueError: too many values to unpack (expected 3)<br></code></pre></td></tr></table></figure><p><code>parse_range_image_and_camera_projection</code> return <strong>4</strong> value, see in <code>frame_utils.py</code></p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><code class="hljs python"><span class="hljs-keyword">def</span> <span class="hljs-title function_">parse_range_image_and_camera_projection</span>(<span class="hljs-params"></span><br><span class="hljs-params"> frame: dataset_pb2.Frame</span>) -> ParsedFrame:<br> <span class="hljs-string">"""Parse range images and camera projections given a frame.</span><br><span class="hljs-string"></span><br><span class="hljs-string"> Args:</span><br><span class="hljs-string"> frame: open dataset frame proto</span><br><span class="hljs-string"></span><br><span class="hljs-string"> Returns:</span><br><span class="hljs-string"> range_images: A dict of {laser_name,</span><br><span class="hljs-string"> [range_image_first_return, range_image_second_return]}.</span><br><span class="hljs-string"> camera_projections: A dict of {laser_name,</span><br><span class="hljs-string"> [camera_projection_from_first_return,</span><br><span class="hljs-string"> camera_projection_from_second_return]}.</span><br><span class="hljs-string"> seg_labels: segmentation labels, a dict of {laser_name,</span><br><span class="hljs-string"> [seg_label_first_return, seg_label_second_return]}</span><br><span class="hljs-string"> range_image_top_pose: range image pixel pose for top lidar.</span><br><span class="hljs-string"> """</span><br></code></pre></td></tr></table></figure></li></ul>]]></content>
<categories>
<category>AI</category>
<category>3D</category>
</categories>
<tags>
<tag>Dataset</tag>
</tags>
</entry>
<entry>
<title>about</title>
<link href="/"/>
<url>/</url>
<content type="html"><![CDATA[]]></content>
</entry>
</search>