作家
登录

Go运行时,对bug的分析调试过程解析

作者: 来源: 2017-12-18 17:18:20 阅读 我要评论

这个脚本应用SHA-1来取目标文件名的散列值,然后早年32位中检查散列的给定位(由情况变量$BIT进行标识)。 如不雅这个位的值是0,则编译的时刻不带CONFIG_OPTIMIZE_INLINING, 如不雅是1,则带上CONFIG_OPTIMIZE_INLINING。 我发明内核大年夜约有685个目标文件,这须要大年夜约10个位来进行独一标识。 这种基于散列的办法有一个很好的属性:我可以选择产生崩溃可能性比较大年夜的结不雅(即位的值是0),因为要证实给定的内核不会崩溃是很艰苦的(因为崩溃是概率性出现的, 可能须要相当一段时光才会产生)。

我构建了32个内核,只花了29分钟的时光。然后,我开端对它们进行测试,每当产生崩溃的时刻,我都邑将可能的SHA-1散列的┞俘则表达式缩小到那些在这些特定地位上是0的散列。在产生了8次崩溃的时刻,我把范围缩小到4个目标文件。一旦出现了10次崩溃之后,就只剩下独一的一个了。

vDSO的代码。当然。

vDSO在捣乱

#!/bin/bashargs=("$@")doit=while [ $# -gt 0 ]; do        case "$1" in                -c)                        doit=1                        ;;                -o)                        shift                        objfile="$1"                        ;;        esac        shiftdoneextra=if [ ! -z "$doit" ]; then        sha="$(echo -n "$objfile" | sha1sum - | cut -d" " -f1)"        echo "${sha:0:8} $objfile" >> objs.txt        if [ $((0x${sha:0:8} & (0x80000000 >> $BIT))) = 0 ]; then                echo "[n]" "$objfile" 1>&2        else                extra=-DCONFIG_OPTIMIZE_INLINING                echo "[y]" "$objfile" 1>&2        fifiexec gcc $extra "${args[@]}"

换句话说,vDSO是用GCC编译的代码,与内核一路构建,最终与每个用户空间的应用法度榜样进行链接。它是用户空间的代码。这就说清楚明了为什么内核和它的编译器都与此有关:这并不是跟内核本身有关,而是与内核供给的共享库有关! Go应用vDSO来晋升机能。Go也正好有一个重建本身的标准库的┞方略,所以,它没有应用任何标准的Linux glibc的代率攀来调用vDSO,而是应用了本身的代码。

那么改变CONFIG_OPTIMIZE_INLINING的值对vDSO有什么感化呢?我们来看看这段汇编。

设置CONFIG_OPTIMIZE_INLINING = n:

设置CONFIG_OPTIMIZE_INLINING=y:

arch/x86/entry/vdso/vclock_gettime.o.inline_opt:     file format elf64-x86-64Disassembly of section .text:0000000000000000 <__vdso_clock_gettime>:   0:   55                      push   %rbp   1:   4c 8d 0d 00 00 00 00    lea    0x0(%rip),%r9        # 8 <__vdso_clock_gettime+0x8>   8:   83 ff 01                cmp    $0x1,%edi   b:   48 89 e5                mov    %rsp,%rbp   e:   74 66                   je     76 <__vdso_clock_gettime+0x76>  10:   0f 8e dc 00 00 00       jle    f2 <__vdso_clock_gettime+0xf2>  16:   83 ff 05                cmp    $0x5,%edi  19:   74 34                   je     4f <__vdso_clock_gettime+0x4f>  1b:   83 ff 06                cmp    $0x6,%edi  1e:   0f 85 c2 00 00 00       jne    e6 <__vdso_clock_gettime+0xe6>[...]

有趣的是,CONFIG_OPTIMIZE_INLINING=y这个本应当让GCC内联变少的标记,实际上却让内联变得更多:vread_tsc在该版本中内联,而不在CONFIG_OPTIMIZE_INLINING=n版本中。然则vread_tsc根本没有标记为内联,所以GCC完全有权限这么做。

我的标记本电脑和家用办事器都是〜amd64(非稳定版),而我的Xeon办事器是amd64(稳定版)。这意味着它们的GCC是不合的。我的标记本电脑和家用办事器都是gcc(Gentoo Hardened 6.4.0 p1.0)6.4.0,而我的Xeon是gcc(Gentoo硬件5.4.0-r3 p1.3,pie-0.6.5) 5.4.0。

但谁在乎函数是否内联了呢?真正的问题在哪里呢?那么,细心不雅察一下非内联版本吧……

30:   55                      push   %rbp  31:   48 89 e5                mov    %rsp,%rbp  34:   48 81 ec 20 10 00 00    sub    $0x1020,%rsp  3b:   48 83 0c 24 00          orq    $0x0,(%rsp)  40:   48 81 c4 20 10 00 00    add    $0x1020,%rsp

为什么GCC会分派跨越4KB的┞坊呢?这不是栈分派,这是栈探测,或者更具体地说是GCC-fstack-check 特点 的结不雅。

TEXT runtime·walltime(SB),NOSPLIT,$16    // Be careful. We're calling a function with gcc calling convention here.    // We're guaranteed 128 bytes on entry, and we've taken 16, and the    // call uses another 8.    // That leaves 104 for the gettime code to use. Hope that's enough!

实际上,104个字节并不是对每小我都够用,对我的内菏攀来说也一样。

Go爱好小巧的┞坊。

须要指出的是,vDSO的规范没有提到最大年夜的┞坊应用包管,所以,Go做了一个无效的假设。

结论

这完美地诠释了问题出现的原因。栈探测器是一个orq,它是跟0做逻辑或运算。这是一个无操作,但有效地探测了目标地址(如不雅它是未竽暌钩射的,就会出现段缺点)。然则我们没有在vDSO代码中看到段缺点,那么Go为什么会出现呢?实际上,跟0做逻辑或运算并不是真的无操作。因为orq不是一个原子指令,而实际上是CPU攫取内存地址,然后再写回来。这时刻就出现了竞争前提。如不雅其他线程在其他的CPU上并交运行,那么orq就可能会清除同时产生的内存写操作。因为写入超出了栈的界线,这可能会侵入其他线程的┞坊或随机数据。这也是为什么GOMAXPROCS=1可以或许解决这个问题的原因,因为这可以防止两个线程同时运行Go代码。

那么怎么修复呢?我把这留给了Go的开辟人员。他们最终的解决筹划是在调用vDSO函数之前 转到更大年夜的┞坊


  推荐阅读

  NVIDIA Titan V显卡拆解:211亿晶体管堆出巨型怪物

NVIDIA近日忽然宣布了第一款基于下代12nm Volta架构核心的显卡Titan V(此前的Tesla V100是计算卡),轻轻松松成为地球上最强大年夜的显卡。Titan V基于最高规格的GV100核心,集成211亿个晶>>>详细阅读


本文标题:Go运行时,对bug的分析调试过程解析

地址:http://www.17bianji.com/lsqh/39892.html

关键词: 探索发现

乐购科技部分新闻及文章转载自互联网,供读者交流和学习,若有涉及作者版权等问题请及时与我们联系,以便更正、删除或按规定办理。感谢所有提供资讯的网站,欢迎各类媒体与乐购科技进行文章共享合作。

网友点评
自媒体专栏

评论

热度

精彩导读
栏目ID=71的表不存在(操作类型=0)