第六章 BMC常用的调试工具

第六章 BMC常用的调试工具

1 curl

1.1 为什么测 BMC 离不开它

上一章讲过 Redfish 走的是网页那一套,既然是网页那套,能发请求的东西都能当探针。curl 就是这么个东西,命令行里敲一条就出结果,不用装客户端,不用写脚本,改个参数再敲一条就能对比。

对比项 curl 图形界面
响应头 看得到 看不到
状态码 看得到 多数不显示
耗时分段 看得到 看不到
造异常请求 改参数就行 只能点界面给的
批量执行 能进脚本 不行
上手难度 要记参数 点就行

它比图形化工具有三个好处。一是能看全,响应头、状态码、耗时全在,界面上只给你渲染后的结果。二是能进脚本,一条命令复制进批量巡检里就能跑几十台。三是能造异常,改个方法、改个请求头,造出界面上点不出来的情形。

text 复制代码
┌──────────────────────────────────────────────────────────┐
│ 一条 curl 能看到什么                                     │
├──────────────────────────────────────────────────────────┤
│ 一条命令能把六件事一次问清楚                             │
│                                                          │
│ ┌────────────────────────────────────────────────────┐   │
│ │  通不通      连不上先看网路、端口、防火墙          │   │
│ │  状态码      应用层给的答案,比通不通更细          │   │
│ │  响应头      内容类型、长度、连接怎么收            │   │
│ │  响应体      JSON 结构与字段,对不对得上           │   │
│ │  耗时分段    慢在哪一段,拆开才看得出来            │   │
│ │  证书        加密版本与有效期,安全项必查          │   │
│ └────────────────────────────────────────────────────┘   │
│                                                          │
│   六件里最容易被跳过的是耗时分段和证书                   │
└──────────────────────────────────────────────────────────┘

六件里最容易被跳过的是耗时分段。网页慢的时候所有人都在猜是网慢还是 BMC 慢,把时间按段拆开,哪一段慢一眼就看出来了。我碰到过一次,客户说网页要十几秒才开,各段一拆,加密握手占了大半,换掉证书套件之后降到一秒出头,跟 BMC 的性能一点关系都没有。

1.2 常用参数

text 复制代码
┌──────────────────────────────────────────────────────────┐
│ curl 高频参数                                            │
├──────────────────────────────────────────────────────────┤
│ 参数不用全记,常用的就这几条                             │
│                                                          │
│ +------------+--------------------+------------------+   │
│ |参数        |干什么              |看什么            |   │
│ +------------+--------------------+------------------+   │
│ |-k          |跳过证书校验        |临时排障用        |   │
│ |-i          |连响应头一起输出    |头部的字段        |   │
│ |-s          |不要进度条          |配合 -w 取值      |   │
│ |-o /dev/null|正文扔掉            |只要状态码        |   │
│ |-w          |自定义输出格式      |各段耗时          |   │
│ |-X          |指定方法            |POST 与 DELETE    |   │
│ |-H          |加请求头            |内容类型与令牌    |   │
│ |-d          |带请求体            |登录与配置下发    |   │
│ |-u          |带上用户名密码      |认证通不通        |   │
│ |-m          |整条命令的超时      |卡死时自己退出    |   │
│ +------------+--------------------+------------------+   │
└──────────────────────────────────────────────────────────┘

参数不用背全,常用的就这几条。测 BMC 的时候通常是这么组合,先看通不通,再看返回什么,接着看耗时。

一条典型的取值命令长这样,把正文扔掉,只要状态码。

text 复制代码
curl -k -s -o /dev/null -w "%{http_code}" https://<bmc>/redfish/v1/

想看耗时就把格式串换成几段拼起来,域名解析、建连、加密握手、首字节、总时间,五段分开打印。

text 复制代码
curl -k -s -o /dev/null -w "dns %{time_namelookup} tcp %{time_connect} tls %{time_appconnect} ttfb %{time_starttransfer} total %{time_total}\n" https://<bmc>/redfish/v1/

这两条命令是所有 Redfish 排查的起点,单独记下来能省很多时间。慢在哪一段,看数字就知道。域名解析慢查地址配置,建连慢查网路和防火墙,加密握手慢查证书套件,首字节慢才是 BMC 自己慢。

1.3 状态码怎么读

text 复制代码
┌──────────────────────────────────────────────────────────┐
│ 常见状态码怎么读                                         │
├──────────────────────────────────────────────────────────┤
│ 状态码是应用层给的第一手答案                             │
│                                                          │
│ +----------+--------------+--------------------------+   │
│ |状态码    |含义          |遇到时先怀疑什么          |   │
│ +----------+--------------+--------------------------+   │
│ |200       |成功          |正常往下走                |   │
│ |201       |创建成功      |资源真的建出来没有        |   │
│ |202       |已收下待处理  |有没有任务链接可以跟      |   │
│ |204       |成功但没有内容|删除类操作常见            |   │
│ |400       |请求本身有问题|字段或格式写错            |   │
│ |401       |没带身份      |令牌没传或已过期          |   │
│ |403       |带了但不够格  |权限表配错了              |   │
│ |404       |资源不存在    |路径拼错或机型没这个      |   │
│ |405       |方法不允许    |该资源只支持读            |   │
│ |429       |请求太密      |限流阈值是多少            |   │
│ |500       |服务端出错    |要抓日志配合看            |   │
│ |000       |压根没连上    |网路、端口、超时          |   │
│ +----------+--------------+--------------------------+   │
└──────────────────────────────────────────────────────────┘
对比项 401 403
含义 没带身份 带了不够格
常见原因 没传令牌或已过期 用户权限不够
重试有没有用 补上身份就行 重试多少次都一样
验的是哪一块 认证机制 权限模型

401 和 403 最容易混。没带身份是 401,带了但权限不够是 403。测权限模型的时候这两个码必须分开验,只验一个等于没验。

202 也经常被误读。刷新固件、跑长任务这类操作返回 202,意思是收下了还没做完,响应里会给一个任务链接。脚本只判 202 就往下走,后面失败是抓不到的,得跟着任务链接轮询到终态。

000 不是状态码,是 curl 自己给的,意思是连接阶段就失败了。看到 000 别去查应用层,往网路、端口、超时那几个方向查。

1.4 上传固件与下发配置

刷机接口用 POST 带二进制体,请求头要指定内容类型。命令形态上跟登录那条差不多,区别在请求体是文件内容而不是 JSON。

text 复制代码
curl -k -u <user>:<pass> -H "Content-Type: application/octet-stream" -X POST --data-binary @<file> https://<bmc>/redfish/v1/UpdateService

这里有个容易踩的点。上传大文件的时候如果中间断了,curl 报的是传输错误,跟 BMC 拒收混在一起分不清。判定的办法是分开看,先确认返回码,再确认任务状态,两边对不上才是真失败。

1.5 订阅事件

事件订阅接口也是 POST,请求体里带回调地址和关心的事件类型。测的时候除了确认返回 201,还要确认订阅真的建出来了,方法是再拉一次订阅集合看条目在不在。

订阅条目有上限。超过上限之后的请求该返回明确的错误,如果静默丢掉老的,客户端那边以为订阅成功了,实际上收不到事件,这种缺陷很难被发现。

1.6 测试清单

项目 预期结果 判定标准
未认证访问 401 没带身份一律拒绝
低权限访问高权限资源 403 不能因为重试而变化
正常读取 200 且字段齐全 必选字段一个都不能少
刷新固件 202 且带任务链接 能跟到终态
上传坏包 明确的错误码 不能返回成功
证书过期 连接失败 不能静默放行
并发一百次读取 全部有响应 不能出现连接失败
超时设置生效 到点自己退出 不能无限等待
加密套件 弱套件被拒 安全项必查

第九项在安全评审里是硬指标,弱套件能不能禁掉,要用 curl 指定套件去试,试出来能连上就是缺陷。

2 lsof

2.1 它回答的四类问题

text 复制代码
┌──────────────────────────────────────────────────────────┐
│ lsof 能回答的四个问题                                    │
├──────────────────────────────────────────────────────────┤
│ 进程手里抓着什么,全在这张表里                           │
│                                                          │
│ ┌────────────────────────────────────────────────────┐   │
│ │  谁占着端口  端口起不来时第一问                    │   │
│ │  谁开着文件  配置改了不生效的原因                  │   │
│ │  句柄有多少  泄漏看的是趋势不是绝对值              │   │
│ │  删了还占着  空间没释放的经典情形                  │   │
│ └────────────────────────────────────────────────────┘   │
│                                                          │
│   四类问题对应的参数都不一样                             │
└──────────────────────────────────────────────────────────┘
问题 命令要点 结果怎么读
谁占着端口 加 -i 与端口号 进程号与进程名
谁开着文件 加目录参数 打开该文件的进程
句柄有多少 加 -p 与进程号 计数,看趋势
删了还占着 找已删除条目 有条目就是没释放

这四个问题看着不相干,底下的机制是同一个。系统里每个进程都有一张表,记着它开了哪些东西,lsof 干的事就是把这张表读出来给你看。

2.2 端口被谁占了

服务起不来报端口占用,是最常见的用法。对着端口号查一次,进程号、进程名、用户全出来。

text 复制代码
lsof -nP -i :443

加 -n 和 -P 是不让它去解析名字,解析会很慢,而且解析出来的名字有时候会误导人。直接看数字最省事。

查出来之后要确认一件事,占着端口的进程是不是你以为的那个。遇到过一次,占着 443 的是上一版留下的僵尸进程,新版本起不来,报的错却是配置问题,绕了很久。

2.3 句柄泄漏

句柄泄漏是稳定性测试里的常客。它的表现很有特点,服务一开始好好的,跑上几天就出问题,重启就好,过几天又来。

text 复制代码
┌──────────────────────────────────────────────────────────┐
│ 句柄数怎么涨才叫泄漏                                     │
├──────────────────────────────────────────────────────────┤
│ 句柄泄漏看的是曲线,不是某一个数                         │
│                                                          │
│ ┌────────────────────────────────────────────────────┐   │
│ │  第 1 天   180   |#####                            │   │
│ │  第 2 天   340   |###########                      │   │
│ │  第 3 天   520   |#################                │   │
│ │  第 4 天   700   |#######################          │   │
│ │  第 5 天   880   |#############################    │   │
│ └────────────────────────────────────────────────────┘   │
│                                                          │
│   每天涨的量差不多,就是有人开了不关                     │
│   第 5 天那一行已经贴到上限                              │
└──────────────────────────────────────────────────────────┘
观察点 正常 泄漏
随时间变化 涨到平台期就持平 一直往上
起伏跟什么相关 跟业务量相关 跟时间相关
重启之后 回到基线 回到基线再涨
类型分布 各类都有 单一类型居多

判定的关键在于趋势。单看一个数是看不出来的,句柄多用一点本身不是问题。要在几天里定时采同一个进程的句柄数,画成曲线,每天涨的量差不多,那就是有人在开了不关。

采数的时候就一条命令。

text 复制代码
lsof -p <pid> | wc -l

采回来的数要连着类型一起看,全是套接字说明是连接没关,全是文件说明是文件没关,两种的修法完全不一样。

2.4 删了文件空间没释放

这个情形在 BMC 上特别常见。日志把分区写满了,上去把日志文件删掉,空间没回来,分区还是满的。

原因是进程手里还抓着那个文件,删掉的是目录里的名字,数据块要等所有抓着它的进程都关掉才释放。查的办法是让 lsof 找已删除的条目。

text 复制代码
lsof | grep deleted

处理方式也有讲究。直接重启服务能解决,但现场常常不允许重启。有的实现支持信号触发重新打开日志,发了信号句柄就换了,空间也就回来了。验这个的时候要确认空间真的降下来,光看命令没报错不算数。

2.5 参数

text 复制代码
┌──────────────────────────────────────────────────────────┐
│ lsof 高频参数                                            │
├──────────────────────────────────────────────────────────┤
│  参数                                                    │
│                                                          │
│ +--------------+--------------------+----------------+   │
│ |参数          |干什么              |典型场合        |   │
│ +--------------+--------------------+----------------+   │
│ |-i            |只看网络连接        |端口被谁占了    |   │
│ |-i :443       |看某个端口          |服务起不来      |   │
│ |-p            |看某个进程          |句柄是不是在涨  |   │
│ |-c            |看某个名字的进程    |不确定进程号时  |   │
│ |-u            |看某个用户          |权限相关的排查  |   │
│ |+D            |看某个目录下        |配置改了不生效  |   │
│ |-n            |不要解析名字        |输出快很多      |   │
│ |-P            |不要解析端口名      |数字更直观      |   │
│ +--------------+--------------------+----------------+   │
└──────────────────────────────────────────────────────────┘

2.6 测试清单

项目 预期结果 判定标准
服务停止后端口 没有进程占着 不能留下监听
服务启动失败 能查出占用方 报错要指明进程
连续运行七天 句柄数持平 增长不超阈值
大量建连断开 句柄回收 跑完回到基线
日志轮转 老句柄关掉 不出现已删除占空间
删大文件 空间立刻回来 不用重启也能释放
分区写满 服务不崩 降级或拒绝并记录
权限不足的用户 查不到别的进程 不能越权看信息

第三项的阈值各家不一样。先跑一轮基线,取稳定运行时的句柄数,往上留百分之三十当上限。没有基线就只能拍脑袋定一个数,事后很难说清。

3 objdump

3.1 什么时候才会用到它

日常测试用不到这个工具,它出现在两类场合。一类是崩溃定位,日志里只有一个地址,没有函数名,要去二进制里翻。一类是版本核对,怀疑刷进去的文件跟预期不一致,要看看里面到底有什么。

场合 用哪件 拿到什么
只有一个地址 objdump 函数所在的区间
有地址且有符号 addr2line 文件名与行号
查函数在不在 nm 符号清单
看段与权限 readelf 段表
找版本号与路径 strings 可打印字符串
确认架构对不对 file 目标平台信息

多数 BMC 的固件是交叉编译出来的,跑在 ARM 上,编译在 x86 上。所以本机装的 objdump 未必能读,得用对应架构的那一个,工具链里通常会带好几个版本,名字带前缀。

3.2 五种用法

text 复制代码
┌──────────────────────────────────────────────────────────┐
│ objdump 的五种用法                                       │
├──────────────────────────────────────────────────────────┤
│ 同一个工具,换个参数就是另一件活                         │
│                                                          │
│ ┌────────────────────────────────────────────────────┐   │
│ │  -d        反汇编,看指令                          │   │
│ │  -t        符号表,看函数名与地址                  │   │
│ │  -h        段头,看各段位置与大小                  │   │
│ │  -x        全部头信息,比 -h 全                    │   │
│ │  -p        配合别的参数指定平台                    │   │
│ └────────────────────────────────────────────────────┘   │
│                                                          │
│   日常用得最多的是 -d 和 -t                              │
└──────────────────────────────────────────────────────────┘

日常用得最多的是反汇编和符号表这两样。符号表在不在,直接决定了后面定位的难易程度。带符号的二进制,函数名、行号都在里面,一步就能找到地方。被剥掉符号的,只剩一堆地址,得自己翻。

判断有没有符号很简单,列符号表试一次,有内容就是有,报没有符号就是被剥了。

3.3 从地址到函数名

text 复制代码
┌──────────────────────────────────────────────────────────┐
│ 从地址到函数名                                           │
├──────────────────────────────────────────────────────────┤
│ 只知道一个地址,也能找到是哪段代码                       │
│                                                          │
│ ┌────────────────────────────────────────────────────┐   │
│ │  第一步    日志里拿到出错地址                      │   │
│ │  第二步    看段表,确定落在哪个段                  │   │
│ │  第三步    减去段起始,得到段内偏移                │   │
│ │  第四步    反汇编,按偏移找指令                    │   │
│ │  第五步    往上翻,找到函数入口                    │   │
│ └────────────────────────────────────────────────────┘   │
│                                                          │
│   有符号表的话,前四步都能省掉                           │
└──────────────────────────────────────────────────────────┘
步骤 输入 输出
拿地址 崩溃日志 一个十六进制地址
定段 地址与段表 落在哪个段
算偏移 地址减段起始 段内偏移
反汇编 段内偏移 附近几条指令
找入口 指令往上翻 函数名

这套流程看着长,其实核心就一句话,把绝对地址换成段内偏移,再拿着偏移去反汇编里找。段表告诉你每段从哪开始,减一下就得到偏移。

有符号表的时候这五步能省掉四步,直接拿地址问行号就行。这也是我一直建议开发版本保留符号的原因,剥掉符号省下的那点空间,远不如定位效率值钱。

3.4 几个工具怎么选

text 复制代码
┌──────────────────────────────────────────────────────────┐
│ 二进制工具怎么选                                         │
├──────────────────────────────────────────────────────────┤
│ 几个工具长得像,干的活不一样                             │
│                                                          │
│ +------------+--------------------+------------------+   │
│ |工具        |擅长什么            |什么时候用它      |   │
│ +------------+--------------------+------------------+   │
│ |objdump     |反汇编与段信息      |要逐条看指令      |   │
│ |readelf     |读文件头与段表      |只看结构不关心指令|   │
│ |nm          |列符号              |查函数在不在      |   │
│ |addr2line   |地址换行号          |有符号时一步到位  |   │
│ |strings     |抽可打印串          |找版本号与路径    |   │
│ |file        |看文件类型          |架构对不对先问它  |   │
│ +------------+--------------------+------------------+   │
└──────────────────────────────────────────────────────────┘

这几个工具经常被人混着用,其实各有分工。看结构用 readelf,看指令用 objdump,查符号用 nm,地址换行号用 addr2line,找版本号和路径用 strings 最快。

上手的时候先跑 file 看一眼架构。架构对不上,后面所有工具读出来的都是错的,而且不报错,能坑你半天。

3.5 测试清单

项目 预期结果 判定标准
发布包架构 与目标机一致 file 命令确认
符号表 按版本策略保留 调试版必须保留
动态库依赖 依赖都在包里 不能缺库
版本号字符串 与发布单一致 strings 能查到
段权限 没有可写可执行段 安全项必查
崩溃地址 能定位到函数 定位流程跑得通
刷机包完整性 校验值对得上 坏包必须拦住
关键函数 存在于二进制中 裁剪不能裁错

第五项跟安全加固有关。既可写又可执行的段是典型的危险配置,扫一遍就能发现,成本很低收益很高。

4 netstat

4.1 四件事

text 复制代码
┌──────────────────────────────────────────────────────────┐
│ netstat 能看的四件事                                     │
├──────────────────────────────────────────────────────────┤
│ 连接这件事,netstat 能拆成四块看                         │
│                                                          │
│ ┌────────────────────────────────────────────────────┐   │
│ │  监听        服务有没有把端口开出来                │   │
│ │  连接        谁连着,处在哪个状态                  │   │
│ │  路由        包往哪条路出去                        │   │
│ │  统计        重传、错包、丢包各有多少              │   │
│ └────────────────────────────────────────────────────┘   │
│                                                          │
│   绝大多数时候只看前两块                                 │
└──────────────────────────────────────────────────────────┘

绝大多数时候只看前两块。端口有没有开出来,连接处在什么状态,这两条能覆盖日常八成的排查。路由和统计留给网路相关的专项。

4.2 状态怎么读

text 复制代码
┌──────────────────────────────────────────────────────────┐
│ 几个关键状态的含义                                       │
├──────────────────────────────────────────────────────────┤
│ 状态停在哪个格子,决定了问题在谁身上                     │
│                                                          │
│ ┌────────────────────────────────────────────────────┐   │
│ │  LISTEN        等着被连,服务正常该在这            │   │
│ │  ESTABLISHED   正在通信,双方都在                  │   │
│ │  TIME_WAIT     本端主动关,等一会儿                │   │
│ │  CLOSE_WAIT    对端关了,本端没关                  │   │
│ │  SYN_RECV      握手收了一半                        │   │
│ └────────────────────────────────────────────────────┘   │
│                                                          │
│   CLOSE_WAIT 越堆越多,是本端自己没关                    │
└──────────────────────────────────────────────────────────┘
状态 责任方 处理方向
TIME_WAIT 多 正常现象 不用管
SYN_RECV 堆着 对端或链路 查网路与防火墙
CLOSE_WAIT 堆着 本端应用 查关闭逻辑
ESTABLISHED 涨 连接没回收 查超时与上限

判断责任方主要靠状态。TIME_WAIT 多不用紧张,那是本端主动关连接之后的正常等待,等一会儿自己就收了。SYN_RECV 堆着要查对端,通常是握手包发不过来或者被丢了,问题在链路或者防火墙。

CLOSE_WAIT 是唯一一个能直接判定本端有问题的状态。它的含义是对端已经关了,本端还没关,堆着说明应用收到关闭之后没去处理。开篇那个句柄泄漏的案子,现场如果先数一下 CLOSE_WAIT,能早半天定位到方向。

4.3 堆积怎么看

数状态就一条命令,排序取前几名。

text 复制代码
netstat -n | awk '/^tcp/ {print $6}' | sort | uniq -c | sort -rn

采数跟句柄一样,要连着采。一次的数说明不了问题,隔十分钟采一次,看哪个状态在涨。涨的那个就是方向。

阈值这件事给不出统一答案。连接数跟机型、跟业务量都有关系,可行的办法是取稳定运行时的数值当基线,异常时跟基线比,涨了三倍以上再去查。

4.4 参数

text 复制代码
┌──────────────────────────────────────────────────────────┐
│ netstat 高频参数                                         │
├──────────────────────────────────────────────────────────┤
│ 参数                                                     │
│                                                          │
│ +------------+----------------------+----------------+   │
│ |参数        |干什么                |看什么          |   │
│ +------------+----------------------+----------------+   │
│ |-t          |只看 TCP              |大部分场合够用  |   │
│ |-u          |只看 UDP              |告警外发那条路  |   │
│ |-l          |只看监听              |服务起没起      |   │
│ |-n          |用数字不解析          |输出快、不误读  |   │
│ |-p          |带上进程信息          |哪个进程开的口  |   │
│ |-a          |全部连接              |含已建立的      |   │
│ |-s          |协议统计              |重传与错包      |   │
│ |-r          |路由表                |包从哪出去      |   │
│ +------------+----------------------+----------------+   │
└──────────────────────────────────────────────────────────┘

4.5 与 ss 的关系

新一点的系统上 ss 更快,原理不一样,读的是内核里另一套接口,连接多的时候差距很明显。命令参数长得像,输出内容也差不多。

对比项 netstat ss
数据来源 传统接口 内核新接口
连接多时速度 慢 快
参数形态 传统短参数 差不多
裁剪版系统 可能只留有 ss 也可能只留 netstat

两个都学会最稳。有的裁剪版系统里只留了一个,到现场发现没有 netstat 的时候不至于干瞪眼。写检查脚本的时候优先用有的那个,别写死。

4.6 测试清单

项目 预期结果 判定标准
服务启动后 443 与 80 在监听 缺一个就是缺陷
服务停止后 监听消失 不能残留
建连再断开 状态正常回收 不堆积 CLOSE_WAIT
异常断开 本端能感知 超时后要释放
大量并发 状态不失控 达到上限有保护
防火墙只放行一个口 另一个连不上 要跟策略一致
告警外发 有到监控机的连接 方向要对
长时间运行 各状态数持平 增长要有上限
统计里的重传 数值很低 涨了要查链路

第三项的验证要造异常断开。拔网线、杀客户端进程、让对端直接复位,三种情形都要试。只测正常关连接,等于没测断线重连。

5 GDB 与栈回溯

5.1 core 是怎么来的

core 是进程崩溃那一刻的内存快照,把它存下来,事后就能回到那个时刻看现场。要拿到 core 得满足几个条件,缺一个都拿不到。

一是开关。系统有个限制值,限制为零就不生成。测之前先确认这个值是放开的,不然等崩溃了才发现没存下来,只能再等下一次。

拿不到 core 的原因 表现 怎么办
限制值为零 崩了没有文件 先放开限制
存放路径不可写 没有文件 改到大分区
空间不够 文件只有一半 预留够空间
版本不对应 栈里全是乱的 找同版二进制

二是存放位置。生成的 core 往哪写、叫什么名字,由一个内核参数控制。有的系统默认写到当前目录,有的走统一的收集服务。测试环境里都把它指到一个固定的大分区,免得写满根分区。

三是空间。core 的体积跟进程用了多少内存有关,大的能到几百兆。分区不够就会写一半,写一半的 core 打不开,等于白等。

四是版本对应。core 是某一版程序跑出来的,拿另一版的二进制去读,栈里全是乱的。这一条最容易被忽略,也最浪费时间,因为栈看着能打出来,只是每一层都对不上号,不比对版本的人会一直沿着错的方向查下去。

5.2 五步

text 复制代码
┌──────────────────────────────────────────────────────────┐
│ 从 core 到结论                                           │
├──────────────────────────────────────────────────────────┤
│ 拿到 core 之后按这个顺序往下走                           │
│                                                          │
│ ┌────────────────────────────────────────────────────┐   │
│ │  第一步    确认 core 跟程序是同一版                │   │
│ │  第二步    载入,先打一次 bt                       │   │
│ │  第三步    看所有线程,找出事那条                  │   │
│ │  第四步    切到崩的那一帧,看变量                  │   │
│ │  第五步    往上看调用方,还原场景                  │   │
│ └────────────────────────────────────────────────────┘   │
│                                                          │
│   版本没对上,后面四步全是白看                           │
└──────────────────────────────────────────────────────────┘

第一步说的版本对应,验的办法是比对二进制的构建编号或者校验值。对不上就别往下走了,先去找对应版本的二进制。

5.3 栈回溯怎么读

text 复制代码
┌──────────────────────────────────────────────────────────┐
│ 栈回溯怎么读                                             │
├──────────────────────────────────────────────────────────┤
│ 栈回溯的每一行都长一个样,含义固定                       │
│                                                          │
│ ┌────────────────────────────────────────────────────┐   │
│ │  #0      崩的那一行,最该看                        │   │
│ │  #1      谁调了它                                  │   │
│ │  #2      再往上一层                                │   │
│ │  #N      一直排到线程入口                          │   │
│ └────────────────────────────────────────────────────┘   │
│                                                          │
│   一行里有四样东西:帧号、函数名、偏移、参数             │
└──────────────────────────────────────────────────────────┘
部分 含义 没有符号时
帧号 第几层,零号是崩点 照常显示
函数名 停在哪一个函数 显示成地址
偏移 函数内的位置 照常显示
参数 传进去的值 可能显示为地址

一行栈里有四样东西。帧号在最前,从零开始,零号就是崩的那一层。后面是函数名,再后面是偏移,最后括号里是参数。没有符号的时候函数名会显示成一串问号或者地址,那就只能回到 objdump 那套流程。

读栈的顺序也有讲究。先看零号确认崩在哪一句,再往上翻找调用方,一直翻到能认出来的业务函数为止。栈往往很长,底下那些是标准库和线程入口,一般不用管。

5.4 命令

text 复制代码
┌──────────────────────────────────────────────────────────┐
│ GDB 常用命令                                             │
├──────────────────────────────────────────────────────────┤
│ 命令不多,够用就行                                       │
│                                                          │
│ +-------------------+------------------+-------------+   │
│ |命令               |干什么            |什么时候敲   |   │
│ +-------------------+------------------+-------------+   │
│ |bt                 |打印调用栈        |进来第一件事 |   │
│ |bt full            |连带局部变量      |要看变量值   |   │
│ |frame N            |切到第 N 帧       |定位某一层   |   │
│ |info locals        |看当前帧的变量    |怀疑值不对   |   │
│ |print 表达式       |打印某个值        |看具体字段   |   │
│ |info threads       |列出线程          |多线程崩溃   |   │
│ |thread N           |切到某线程        |找到出事那条 |   │
│ |thread apply all bt|所有线程打栈      |死锁卡死     |   │
│ +-------------------+------------------+-------------+   │
└──────────────────────────────────────────────────────────┘

命令不多,够用就行。真正高频的就是前几条,打栈、切帧、看变量,三件事循环着做。

5.5 多线程与死锁

多线程崩溃要看所有线程的栈,出事的线程未必是当前那个。先列线程,再逐个看,或者一条命令把所有线程的栈都打出来。

死锁的情形跟崩溃长得不一样。进程还在,不退出,就是不干活。这种时候所有线程的栈都得看,重点找停在加锁那一步的线程,看它在等哪把锁,再看那把锁在谁手里。两下对起来,死锁的环就出来了。

卡死还有一种常见原因,线程卡在读写上。栈里会停在读或者写那一步,这时候去看它操作的那个句柄,配合 lsof 一起看,往往能发现对端早就不在了。

5.6 常见崩溃类型

类型 栈里的表现 常见原因
访问空指针 零号帧地址很小 指针没判空就用
越界写 崩在分配区相关函数 缓冲区溢出
重复释放 崩在释放函数 同一块内存放了两次
栈溢出 栈特别深或地址异常 递归或大局部数组
断言失败 停在断言处理函数 内部一致性检查
死锁 进程在但不动 加锁顺序不一致
除零 崩在算术指令 分母没保护
指令异常 地址看着不像代码 函数指针错或栈被踩

第六项没法从单次栈里看出来,要比较多个线程的状态,看它们各自卡在哪把锁上。

5.7 测试清单

项目 预期结果 判定标准
崩溃后生成 core 文件完整可读 不能写一半
core 与版本 能对上 对不上要能查到
栈回溯 能定位到函数 有符号时到行
多线程崩溃 能看出事线程 所有线程的栈都在
死锁 能看出锁的环 两条栈对得上
看门狗复位 留得下现场 复位前要能存
分区不足 有明确处理 不能静默丢弃
连续崩溃 有抑制机制 不能反复重启
敏感信息 core 里不含明文口令 安全项必查

最后一项容易被漏。core 是内存快照,内存里有什么它就有什么,口令、令牌、密钥都在里面。core 往外传的时候要确认脱敏,或者干脆只在受控环境里分析。

6 ipmitool

6.1 三条路

text 复制代码
┌──────────────────────────────────────────────────────────┐
│ ipmitool 的三条路                                        │
├──────────────────────────────────────────────────────────┤
│ 同一个工具,换参数就换了条路                             │
│                                                          │
│ ┌────────────────────────────────────────────────────┐   │
│ │  本机      走系统接口,要在主机上跑                │   │
│ │  局域网    走网口,测试主力通道                    │   │
│ │  串口      走串口,网断后的备用                    │   │
│ └────────────────────────────────────────────────────┘   │
│                                                          │
│   通道不同,能用的命令范围也不一样                       │
└──────────────────────────────────────────────────────────┘
通道 在哪跑 什么时候用
局域网 任意可达的机器 日常主力
本机 主机操作系统里 验带内通不通
串口 串口线连着 网断后的手段

局域网那条路是测试主力,绝大多数用例都走它。命令里要带地址、用户名、密码,有的机型还要指定接口类型。

text 复制代码
ipmitool -I lanplus -H <addr> -U <user> -P <pass> <command>

本机那条路要在主机操作系统里跑,靠驱动跟 BMC 通信,不经过网口。这条路的价值在于能验证带内通不通,网口全断的时候它还能用。

串口那条路平时没人用,网断了、系统也进不去的时候,它是唯一剩下的手段。验收时至少确认一次它能通,别等到真出事才发现线序不对。

6.2 子命令五堆

text 复制代码
┌──────────────────────────────────────────────────────────┐
│ 子命令怎么分组                                           │
├──────────────────────────────────────────────────────────┤
│ 子命令按用途分成五堆                                     │
│                                                          │
│ ┌────────────────────────────────────────────────────┐   │
│ │  设备      设备信息、看门狗、复位                  │   │
│ │  传感器    读数、阈值、描述表                      │   │
│ │  日志      列出、清空、看容量                      │   │
│ │  资产      读写 FRU 那几块区域                     │   │
│ │  控制      电源、网络、用户、串口                  │   │
│ └────────────────────────────────────────────────────┘   │
│                                                          │
│   验收清单基本就是照这五堆往下排                         │
└──────────────────────────────────────────────────────────┘

验收清单基本就是照这五堆往下排。设备这堆先做,确认 BMC 本身活着。传感器和日志两堆是重点,用例最多。资产这堆放末尾,因为它带写操作,写坏了会影响别的。

6.3 完成码怎么读

第四章讲过完成码,这里补一句实操上的经验。ipmitool 对某些完成码会直接给出人能读的文字,对另一些只给十六进制。遇到十六进制的,对照完成码表查一下,别跳过。

判断依据 命令不支持 参数越界
换参数再试 还是同样的错 可能就对了
换机型再试 有的机型能过 都错
归属 固件裁剪 脚本写错

命令不支持和参数越界,这两种码特别容易混淆。前者说明这台机器压根没实现这条命令,后者说明命令有但参数写错了。区分开很重要,前者可能是机型裁剪,后者是脚本的问题。

6.4 与 curl 的分工

对比项 ipmitool curl 打 Redfish
走的协议 IPMI Redfish
数据形态 二进制命令 文本结构
认证 用户名密码加会话 会话令牌
读传感器 一条命令全出 要遍历资源
改配置 部分支持 覆盖更全
批量巡检 快,开销小 慢一点,结构清楚
老机型 普遍支持 不一定有
排障定位 看完成码 看状态码

分工上我这么安排,批量巡检和快速确认走 ipmitool,功能验证和自动化走 Redfish。两条路都通的时候对比一下结果,对不上就要查,多数是对不上才发现问题。

6.5 测试清单

项目 预期结果 判定标准
三种通道 都能通 串口那条别漏测
读设备信息 返回完整 固件版本要对得上
列传感器 条目数与规格一致 不能少项
读一个数 与界面一致 允许取整差
列日志 能列出全部 时间顺序正确
清日志 清完为空 低权限要被拒
读资产 字段完整 厂商型号不能空
写资产 写后能读回 校验和要正确
错误参数 返回对应完成码 不能返回成功
低权限 越权命令被拒 完成码要明确
连续执行两百次 全部有响应 不能出现超时
网口断开 报错明确 不能卡住不返回

最后一项往往被跳过。网断了之后命令是立刻报错还是卡到超时,直接决定上层脚本的健壮性,卡住不返回的那种要提缺陷。

7 收口

7.1 六件工具谁管什么

text 复制代码
┌──────────────────────────────────────────────────────────┐
│ 六件工具谁管什么                                         │
├──────────────────────────────────────────────────────────┤
│ 六件工具的分工                                           │
│                                                          │
│ +------------+------------------------+--------------+   │
│ |工具        |回答什么问题            |在哪一层      |   │
│ +------------+------------------------+--------------+   │
│ |curl        |接口通不通,返回什么    |对外接口      |   │
│ |ipmitool    |带外还能不能用          |对外接口      |   │
│ |netstat     |连接处在什么状态        |本机状态      |   │
│ |lsof        |进程抓着哪些东西        |本机状态      |   │
│ |objdump     |这个文件里有什么        |二进制        |   │
│ |GDB         |崩的那一刻在干什么      |二进制        |   │
│ +------------+------------------------+--------------+   │
└──────────────────────────────────────────────────────────┘

六件工具不是六选一,多数时候是连着用。外面的现象往里追,里层的证据往外对,来回对上才算定位完成。

7.2 一轮排障怎么走

text 复制代码
┌──────────────────────────────────────────────────────────┐
│ 一轮排障怎么走                                           │
├──────────────────────────────────────────────────────────┤
│ 现象在外面,原因可能在任何一层                           │
│                                                          │
│ ┌────────────────────────────────────────────────────┐   │
│ │  第 1 步   ipmitool 确认带外还活着                 │   │
│ │  第 2 步   curl 打接口,看状态码                   │   │
│ │  第 3 步   netstat 看端口有没有开                  │   │
│ │  第 4 步   lsof 看进程抓着什么                     │   │
│ │  第 5 步   日志里找出错地址                        │   │
│ │  第 6 步   objdump 定位到函数                      │   │
│ │  第 7 步   core 加 GDB 看现场                      │   │
│ │  第 8 步   复现确认,再提缺陷                      │   │
│ └────────────────────────────────────────────────────┘   │
│                                                          │
│   前四步现场就能做,后四步多半要回实验室                 │
└──────────────────────────────────────────────────────────┘

前四步在现场就能做,不需要额外准备。后四步多半要回实验室,因为要拿 core、要拿带符号的二进制、要复现。

复现这一步没法省。定位到一个可疑函数,不改任何东西再跑一遍,看是不是还在同一个地方崩,确认了再提缺陷,不然很容易提错方向。

7.3 几个容易踩的地方

坑 表现 怎么绕
core 没生成 崩溃后找不到文件 测前先确认开关和位置
core 与版本不符 栈里全是乱的 先比对构建编号
符号被剥掉 函数名显示成问号 保留调试版二进制
架构不对 读出来是错的不报错 先用 file 确认架构
只看一次数 判断不了是不是泄漏 定时采数画曲线
000 当应用层错 查错方向 先查网路和端口
把 202 当成功 后面失败抓不到 跟着任务链接看终态
命令卡住不返回 脚本整轮挂死 必测断网情形
core 里带敏感信息 外传等于泄露 分析放在受控环境

第三条是吃过亏换来的。有一轮拿到 core 高高兴兴去打栈,栈里全是地址,回头问开发要带符号的版本,那版早就被清理了,只能从头复现一遍。那之后,测试环境的每一版二进制我都留一份带符号的。

7.4 回归顺序

做一轮完整的工具链回归,按这个顺序排最省事。

顺序 内容 为什么放这
1 ipmitool 确认带外可用 后面全靠它兜底
2 curl 打根路径与登录 先确认接口活着
3 跑一轮 Redfish 读取类 只读不改,先跑
4 跑一轮 IPMI 读取类 同上,不破坏状态
5 netstat 与 lsof 采基线 干净状态才有基线
6 跑写入类用例 会改状态,放后面
7 跑稳定性,定时采数 时间最长,放后面
8 造崩溃,验 core 流程 破坏性最大,放最后

第五步的基线一定要在写入之前采。写入用例跑完之后机器状态已经变了,那时候采的基线没有参考价值。

采什么 多久一次 用什么
句柄数 每小时 lsof 配进程号
连接状态 每小时 netstat 配排序
接口响应 每十分钟 curl 取状态码
内存占用 每小时 系统自带命令

第七步耗时长,安排的时候要留出足够的窗口。稳定性问题多数要跑三天以上才显现,跑一天就下结论,很容易放过泄漏类的缺陷。

相关推荐
小李不困还能学13 分钟前
VSCode_RemoteSSH_一直卡在下载服务器_解决教程
服务器·ide·vscode
瑞雪兆丰年兮14 分钟前
JavaWeb-day11-SpringBootWeb案例(文件上传)
java·运维·服务器·javaweb·oss
知已难寻12 小时前
Docker 入门:容器技术与 Docker 基础
java·运维·服务器·docker·云原生·容器
星恒随风19 小时前
MySQL 从零入门(三):表的约束详解
linux·服务器·数据库·笔记·mysql
坤坤子吖1 天前
Linux网络编程:自定义应用层协议与 JSON 序列化
linux·服务器·网络·c++·笔记·json
青瓦梦滋1 天前
Redis主从复制
服务器·数据库·redis·缓存·bootstrap
沫璃染墨1 天前
从零入门计算机网络系列(十八):TCP连接管理详解——从三次握手与四次挥手到TIME_WAIT机制
运维·服务器·网络·c++·网络协议·tcp/ip·tcp
Felven1 天前
华为鲲鹏920s芯片serdestool工具使用说明
linux·运维·服务器·华为·鲲鹏920·serdestool
LabVIEW开发1 天前
Omron PLC:接 LabVIEW 常踩的 5 个坑
服务器·网络·labview·labview知识·labview功能·labview程序