阿里云环境部署Hyperledger Fabric之SIGSEGV问题分析和解决经验分享

本文介绍了一种在阿里云环境下部署Hyperledger Fabric时遇到的SIGSEV错误,并通过分析发现该错误与容器内的域名解析方式有关。通过调整环境变量,强制使用pure Go resolver而非cgo resolver,成功解决了此问题。

最近收到Hyperledger社区的一些朋友反馈在阿里云环境上部署开源区块链项目Hyperledger Fabric的过程中遇到了和SIGSEV相关的fatal error,正好笔者此前也遇到并解决过类似的问题,因此这里分享一下当时问题的分析过程和解决的经验,希望能为大家带来一点启发和帮助。


问题描述

在部署Hyperledger Fabric过程中,peer、orderer服务启动失败,同时cli容器上执行cli-test.sh测试时也报错。错误类型均是signal SIGSEGV: segmentation violation。错误日志示例如下:

2017-11-01 02:44:04.247 UTC [peer] updateTrustedRoots -> DEBU 2a0 Updating trusted root authorities for channel mychannel
fatal error: unexpected signal during runtime execution
[signal SIGSEGV: segmentation violation code=0x1 addr=0x63 pc=0x7f9d15ded259]
runtime stack:
runtime.throw(0xdc37a7, 0x2a)
        /opt/go/src/runtime/panic.go:566 +0x95
runtime.sigpanic()
        /opt/go/src/runtime/sigpanic_unix.go:12 +0x2cc
goroutine 64 [syscall, locked to thread]:
runtime.cgocall(0xb08d50, 0xc4203bcdf8, 0xc400000000)
        /opt/go/src/runtime/cgocall.go:131 +0x110 fp=0xc4203bcdb0 sp=0xc4203bcd70
net._C2func_getaddrinfo(0x7f9d000008c0, 0x0, 0xc420323110, 0xc4201a01e8, 0x0, 0x0, 0x0)

分析过程

我们进行了深入分析和试验,在Hyperledger Fabric这个bug https://jira.hyperledger.org/browse/FAB-5822的启发下,采用了如下workaround可以解决这个问题:

  • 在docker compose yaml里对peer、orderer、cli的环境变量加入GODEBUG=netdns=go

这个设置的作用是不采用cgo resolver (从错误日志里可看到是cgo resolver抛出的错误)而采用pure go resolver。

进一步分析golang在什么情况下会在cgo resolver和pure go resolver之间切换:

Name Resolution
The method for resolving domain names, whether indirectly with functions like Dial or directly with functions like LookupHost and LookupAddr, varies by operating system.
On Unix systems, the resolver has two options for resolving names. It can use a pure Go resolver that sends DNS requests directly to the servers listed in /etc/resolv.conf, or it can use a cgo-based resolver that calls C library routines such as getaddrinfo and getnameinfo.
By default the pure Go resolver is used, because a blocked DNS request consumes only a goroutine, while a blocked C call consumes an operating system thread. When cgo is available, the cgo-based resolver is used instead under a variety of conditions: on systems that do not let programs make direct DNS requests (OS X), when the LOCALDOMAIN environment variable is present (even if empty), when the RES_OPTIONS or HOSTALIASES environment variable is non-empty, when the ASR_CONFIG environment variable is non-empty (OpenBSD only), when /etc/resolv.conf or /etc/nsswitch.conf specify the use of features that the Go resolver does not implement, and when the name being looked up ends in .local or is an mDNS name.
The resolver decision can be overridden by setting the netdns value of the GODEBUG environment variable (see package runtime) to go or cgo, as in:
export GODEBUG=netdns=go    # force pure Go resolver
export GODEBUG=netdns=cgo   # force cgo resolver*

根据这一线索,我们对比了此前部署成功环境和最近部署失败环境各自的底层配置文件,最终找到了不同之处:

  • 在老环境(区块链部署成功)上的容器里,查看

    # cat /etc/resolv.conf 
    nameserver 127.0.0.11
    options ndots:0
  • 在新环境(区块链部署失败)上的容器里,查看

    # cat /etc/resolv.conf 
    nameserver 127.0.0.11
    options timeout:2 attempts:3 rotate single-request-reopen ndots:0

这个差异导致了老的成功环境是采用pure Go resolver的,而在新的失败环境被切换到cgo resolver, 这是因为含有pure Go resolver不支持的options single-request-reopen。

注:Pure Go resolver目前仅支持ndots, timeout, attempts, rotate
https://github.com/golang/go/blob/964639cc338db650ccadeafb7424bc8ebb2c0f6c/src/net/dnsconfig_unix.go

       case "options": // magic options
            for _, s := range f[1:] {
                switch {
                case hasPrefix(s, "ndots:"):
                    n, _, _ := dtoi(s[6:])
                    if n < 0 {
                        n = 0
                    } else if n > 15 {
                        n = 15
                    }
                    conf.ndots = n
                case hasPrefix(s, "timeout:"):
                    n, _, _ := dtoi(s[8:])
                    if n < 1 {
                        n = 1
                    }
                    conf.timeout = time.Duration(n) * time.Second
                case hasPrefix(s, "attempts:"):
                    n, _, _ := dtoi(s[9:])
                    if n < 1 {
                        n = 1
                    }
                    conf.attempts = n
                case s == "rotate":
                    conf.rotate = true
                default:
                    conf.unknownOpt = true
                }
            }

进一步的,我们尝试分析是什么原因导致了新老容器内的resolv.conf的内容变化,发现了原来是最近宿主机ECS的配置文件发生了变化:

  • 失败的环境 - 新创建的ECS:

    # cat /etc/resolv.conf
    # Dynamic resolv.conf(5) file for glibc resolver(3) generated by resolvconf(8)
    #     DO NOT EDIT THIS FILE BY HAND -- YOUR CHANGES WILL BE OVERWRITTEN
    nameserver 100.100.2.138
    nameserver 100.100.2.136
    options timeout:2 attempts:3 rotate single-request-reopen
  • 成功的环境 - 原来的ECS:

    # cat /etc/resolv.conf
    # Dynamic resolv.conf(5) file for glibc resolver(3) generated by resolvconf(8)
    #     DO NOT EDIT THIS FILE BY HAND -- YOUR CHANGES WILL BE OVERWRITTEN
    nameserver 100.100.2.136
    nameserver 100.100.2.138

另一方面,我们也尝试分析为什么切换到cgo resolver之后会产生SIGSEGV的错误,以下这篇文章解释了static link cgo会导致SIGSEGV的错误:
https://tschottdorf.github.io/golang-static-linking-bug

而这个Hyperledger Fabric的bug则指出了Hyperledger Fabric的build(尤其是和getaddrinfo相关方法)正是static link的:
https://jira.hyperledger.org/browse/FAB-6403

至此,我们找到了问题的根源和复盘了整个问题发生的逻辑:

  • 近期新创建的ECS主机中的resolv.conf内容发生了变化 -> 导致Hyperledger Fabric的容器内域名解析从pure Go resolver切换至cgo resolver -> 触发了一个已知的由静态链接cgo导致的SIGSEGV错误 -> 导致Hyperledger Fabric部署失败。

解决方法建议

更新Hyperledger Fabric的docker compose yaml模板,为所有Hyperledger Fabric的节点(如orderer, peer, ca, cli等)添加环境变量GODEBUG=netdns=go以强制使用pure Go resolver

<think>嗯,用户要求用中文回答关于Linux下C语言调试core dump分析问题,还特别强调回答后必须提出3-5个相关问题并以特定格式输出。这个问题很专业,用户可能是正在处理崩溃问题的C程序员。 用户的需求很明确:需要实用的core dump分析指南。从“尤其是”这个措辞来看,用户可能正在被core dump问题困扰,急需解决方案。考虑到Linux环境,应该重点介绍GDB系统配置技巧。 关于core dump分析,核心是解释清楚这几个关键点:如何确保系统生成core文件、如何用GDB定位崩溃点、如何分析堆栈内存状态。用户可能遇到权限问题或路径配置错误,需要强调ulimit/proc/sys的配置方法。 对于调试方法,除了基本GDB使用,还应该提到valgrind——虽然它不直接处理core dump,但对内存错误预防很有帮助。用户可能不知道这个工具的存在。 最后设计相关问题时要覆盖延伸需求:第一个问题聚焦核心文件配置的细节,因为这是常见痛点;第二个针对GDB命令的深度使用;第三个引入替代工具;第四个考虑容器等现代环境;第五个则解决源码匹配问题——用户可能遇到行号不匹配的情况。 特别注意要严格遵循输出格式:代码用markdown标注,最后用“§§相关问题§§”开头且不换行。用户给出的示例显示需要精确的排版,可能是为了自动化处理。</think>在Linux环境下调试C语言程序,尤其是分析`core dump`问题,可遵循以下步骤: ### 一、启用Core Dump生成 1. **解除大小限制** ```bash ulimit -c unlimited # 当前会话生效 ``` 永久生效:在`/etc/security/limits.conf`中添加 ``` * soft core unlimited ``` 2. **设置Core文件路径** 修改`/proc/sys/kernel/core_pattern`(需root): ```bash echo "/tmp/core-%e-%p-%t" > /proc/sys/kernel/core_pattern ``` 参数说明: - `%e`:程序名 - `%p`:进程ID - `%t`:时间戳 ### 二、编译时添加调试信息 ```bash gcc -g -o my_program my_program.c # -g 选项嵌入调试符号 ``` ### 三、分析Core Dump文件 1. **启动GDB** ```bash gdb my_program core-file # core-file替换为实际core文件名 ``` 2. **关键调试命令** ```gdb (gdb) bt # 查看崩溃时的调用栈(backtrace) (gdb) frame N # 切换到栈帧N(N为bt输出的编号) (gdb) print variable_name # 检查变量值 (gdb) info registers # 查看寄存器状态 (gdb) x/10xw $sp # 检查栈内存(10个字) ``` ### 四、常见崩溃原因分析 | 错误类型 | GDB排查线索 | 修复方向 | |-------------------|----------------------------------|------------------------------| | 空指针解引用 | `bt`显示SIGSEGV` + 检查指针值 | 添加NULL检查 | | 内存越界 | 观察数组/指针操作地址 | 检查循环边界或缓冲区大小 | | 使用已释放内存 | `info registers`查看指针地址 | 使用valgrind检测 | | 栈溢出 | `bt`显示重复递归调用 | 优化递归或增大栈(`ulimit -s`)| ### 五、进阶工具推荐 1. **Valgrind内存检测** ```bash valgrind --leak-check=full ./my_program ``` 2. **Address Sanitizer (ASAN)** 编译时添加: ```bash gcc -fsanitize=address -g -o my_program my_program.c ``` ### 六、修复流程示例 1. GDB发现崩溃在`code.c:15`的`*ptr = 10` 2. 执行`print ptr`显示值为`0x0` 3. 回溯代码找到`ptr`未初始化的位置 4. 添加初始化逻辑:`int *ptr = &valid_var;`
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值