chromium 项目单测调试经验

1、单测

chromium 原生的单测程序能尽早地暴露我们在对网络库进行定制化开发过程中会引发的 Bug,因此认真对待单测可为网络库定制化开发保驾护航。

1.1 确认代码迁移引起的 case

跑 NQE、QUIC 迁移后的统一网络库的单测程序 net_unittests,出现较多 fail 和 少数 crash 的 case; 从 fail case 看,与迁移的代码看起来无直接关联,因此直接阅读源码来定位原因显得较为困难且耗时可能比较长;

那么首先要做的是,确认这些 case 是因为代码迁移引起的,所以需要对比迁移前后 net_unittests 的 case 的差异,只分析确实是迁移代码引起的 case,避免将时间花在不是代码迁移引起 fail/crash 的 case 上;

1.2 输出可单步调试的 net_unittests

chromium 项目输出的可执行文件默认都是没有调试符号的,需要将输出目录下的 args.gn 中的 symbol_level 的值改为 2,这样所有输出的可执行文件就带有了调试符号,从而可以使用 gdb 进行单步调试:
在这里插入图片描述

Ubuntu 下使用 gdb 调试 net_unittests 的效果:

1.3 选择性执行单测

当跑完 net_unittests 的全部单测时,它会汇总出一个 fail/crash 的信息(考虑版面大小,删除了下图部分 case),可以看到全部单测跑完需要差不多 半个小时,而当它给出汇总信息后,我们只需重新执行这些 fail/crash 的 case 就行了:

如何选择性执行单测?我们可以先看下 net_unittests 的帮助内容, “./net_unittests --help”,可以看到 --gtest_filter 应该就是我们需要的:

Test Selection:
  --gtest_list_tests
      List the names of all tests instead of running them. The name of
      TEST(Foo, Bar) is "Foo.Bar".
  --gtest_filter=POSTIVE_PATTERNS[-NEGATIVE_PATTERNS]
      Run only the tests whose name matches one of the positive patterns but
      none of the negative patterns. '?' matches any single character; '*'
      matches any substring; ':' separates two patterns.
  --gtest_also_run_disabled_tests
      Run all disabled tests too.

测试发现 --gtest_filter 的值需要在前后加上星号 “*” ,以便正则匹配,如果不加则是完全匹配:

./net_unittests --gtest_filter="*NetworkQualityEstimatorTest*"  // 测试包含 NetworkQualityEstimatorTest 的所有 case

./net_unittests --gtest_filter="NetworkQualityEstimatorTest.OnUpdatedSocketValues" // 测试完全匹配的 case

下图是执行精确匹配的执行情况:

1.4 vpython 引起的 fail case

net_unittests 执行过程会通过 vpython 启动本地的 server,因此需要将 vpython 的路径添加到 PATH 环境变量中,否则会提示找不到 vpython 而造成 case 失败,如下:
在这里插入图片描述
vpython 的路径为:src/third_party/depot_tools/vpython

export PATH=$PATH:/path/to/chromium/src/third_party/depot_tools/vpython

2、实际单测失败 case 定位过程

2.1 crash message
34827     QuicConnectionTests/QuicConnectionTest.CoalescerHandlesInitialKeyDiscard/T051_defer_NoStopWaiting (../../net/third_party/quiche/src/quic/core/quic_connection_test.cc:1461)
34828     QuicConnectionTests/QuicConnectionTest.CoalescerHandlesInitialKeyDiscard/T051_immediate_NoStopWaiting (../../net/third_party/quiche/src/quic/core/quic_connection_test.cc:1461)
34829     QuicConnectionTests/QuicConnectionTest.CoalescerHandlesInitialKeyDiscard/draft29_defer_NoStopWaiting (../../net/third_party/quiche/src/quic/core/quic_connection_test.cc:1461)
34830     QuicConnectionTests/QuicConnectionTest.CoalescerHandlesInitialKeyDiscard/draft29_immediate_NoStopWaiting (../../net/third_party/quiche/src/quic/core/quic_connection_test.cc:1461)

其中 T051 版本的两条 crash 位置:

33909 [ RUN      ] QuicConnectionTests/QuicConnectionTest.CoalescerHandlesInitialKeyDiscard/T051_defer_NoStopWaiting
33910 [17958:17958:0617/114206.744933:4977804956469:FATAL:quic_framer.cc(1031)] AppendCryptoFrame failed

draft29 的两条 crash 位置:

33870 [ RUN      ] QuicConnectionTests/QuicConnectionTest.CoalescerHandlesInitialKeyDiscard/draft29_immediate_NoStopWaiting
33871 [17948:17948:0617/114205.183844:4977803395380:FATAL:quic_framer.cc(1200)] AppendCryptoFrame failed:

虽然 crash 位置不同,但 crash 的代码一样:

1198      case CRYPTO_FRAME:
1199        if (!AppendCryptoFrame(*frame.crypto_frame, writer)) {
1200          QUIC_BUG << "AppendCryptoFrame failed: " << detailed_error();
1201          return 0;
1202        }
2.2 定位过程:

同时使用 gdb 单步执行 crash 的单测 case,分析两者的执行路径有什么不同;同时分析路径上是否涉及到本次代码迁移;

测试发现,未迁移版本只调用了一次,而迁移版本调用了两次;迁移版本是在第二次出现了 crash;

目前看是从要传输的数据量上有差别,调用两次次是因为数据没传完,调用一次是因为数据已经传完了;

为什么传不完?为什么要传这么多数据?为什么再次调用时写进 producer 中的 offset 是 0,而 length 是 76,但 AppendCryptoFrame 时 frame 的信息则是 offset 为 1286,length 为 76?

可以看下 BuildDataPacket 的入参 frame 在两次调用中调用栈传入时的差别:

在 QuicPacketCreator::ConsumeCryptoData 运行过程汇总 while 循环了两次,产出了两个 crypto frame;

未 crash 的情况 write_lenght 等于 total_bytes_consumed 即 1300 字节,而 crash 情况 total_bytes_consumed 为 1246;

因此在未 crash 的调用栈第一次调用后,在 quic_packet_creator.cc 1360 行加上断点,看看 frame 的创建过程与 crash 的有什么区别;

发现两者的区别在于 QuicPacketCreator 类的 max_plaintext_size_ 成员的值,未 crash 的为 1338,而 crash 的只有 1268;

现在要分析这个成员的值是怎么赋值的;为什么有这样的差异;它的赋值有三处:

153  max_plaintext_size_ = framer_->GetMaxPlaintextSize(max_packet_length_);
...
173  max_plaintext_size_ = framer_->GetMaxPlaintextSize(max_packet_length_);
...
213  max_plaintext_size_ = framer_->GetMaxPlaintextSize(length);

发现实际调用的是 173 这行代码,是在该类构造方法中被调用,而导致差异的原因是下面的修改:

// (Before Modified)
// const QuicByteCount kDefaultMaxPacketSize = 1350;
// (After Modified)
  const QuicByteCount kDefaultMaxPacketSize = 1280;

至此可知 crash 原因与这个值的修改有关,由于该值的变化导致原生代码的逻辑改变进而导致 crash,且该值的修改目前看并无特殊作用,因此恢复了该值为 1350,重新编译 net_unittests 后执行发现其他 Bbr2、QuicConnectionTests 相关的 fail case (见 1.3 小节图)都解决了。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值