记一次bug排除心得
问题背景
要做一个需求,大概是检测到某输入重启,于是写一个demo调试一下
c语言程序,交叉编译后在adb shell下运行
思路
用 am 命令直接重启
我们先手动验证一下,发现这个设备不支持am命令吗,遂排除

用 kill 命令杀掉进程,然后重新运行
- 写个demo,这个程序会循环等待输入,按q退出
void test_option()
{
}
int main(int argc, char **argv)
{
int ret;
char *sptr = NULL;
while(1)
{
sptr = input_fgets("Please input option: \n");
if(sptr[0] == 'q')
return 0;
else if (atoi(sptr) == 1)
test_option();
}
return 0;
}
- 另开一个cmd窗口,打开adb shell,查询进程号并杀掉重开

可以看到,通过另一个adb窗口可以很轻易地kill掉进程并重启
那么 直接用system函数写入命令行执行不就完事了?? ,我也是这么认为的。。。但是出问题了!!!
- 问题1: 我手动执行ps看进程号,但是写程序获得进程号呢?
ps | grep nwy_data_test | awk '{print $1}' | head -n 1
这句命令表示取出ps结果的第一列的第一行并打印!也就是上图中的22122
- 问题2:取到22122了,怎么传给命令行呢?

一种更简单的方法是判断输入为1 直接system(killall -9 rst_test && /data/rst_test)

发现会无限循环执行!!!

3. 经过我的调试!!!发现问题出在input_fgets函数!!!
现在已知:手动kill并重启 无论怎样都没问题;写在system里,重启的进程里带标准读取输入的都会出错!!
kill当前进程 重启一个不带input_fgets函数的情况下,会kill、会正常运行新程序
kill当前进程 重启一个带input_fgets函数的情况下,会kill、会忽略fgets的阻塞!!
也就是说 只要kill当前进程后,重启的进程带fgets这种获取命令行输入的都有错!!!
static inline char *input_fgets(char *msg, ...)
{
static char ptr[130] = { 0 };
va_list ap;
va_start(ap, msg);
vprintf(msg, ap);
va_end(ap);
memset(ptr, 0, sizeof(ptr));
fgets(ptr, sizeof(ptr), stdin);
if(strlen(ptr) > 0) {
if('\n' == ptr[strlen(ptr) - 1]) {
ptr[strlen(ptr) - 1] = '\0';
}
}
return ptr;
}
4. 于是,把上面函数的vprintf注释掉,发现:可以正常kill,正常启动,但是fgets直接跳过了,并没有读取到我输入的1!!!!


5. 到这里其实基本已经真相大白了,也就是手动输入命令和system输入命令是有差异的!!!
通过查询资料,发现错误大概率是由于system的底层是通过fork创建一个子进程的,而子进程会关联到父进程的输入输出流,通过kill杀掉了父进程,就影响了正常的输入输出流!!!
为了验证我的猜想
7.1 先去掉了system里的kill命令,发现一切正常!

7.2 kill后启动另一个不带stdin的进程,发现一切正常!

7.3 kill后启动另一个带输入的进程,读取标准输入流失败!!

我并没有输入任何内容!!也就是说 标准输入流有无数个空 自动读取了!!!
7.4 几个细节
scanf之前不会发生这个错误,scanf之后才发生这个错误!!
if (ferror(stdin)) {
printf("IO错误");
}
这就可以定位到,是scanf/fgets导致的错误,一般是因为stdin标准输入流中有问题!
用 exit 命令退出adb shell,然后重新开启
system是子进程,他执行exit只会退出他的shell 不会影响父进程!!!

- 一个有效的办法是用kill -9 $$ 结束adb shell , 但是经过测试,无论是用sh脚本配置还是system("kill -9 $$"),都不会正确执行,原因在于$$是获取当前进程id,直接手动输入表示结束adb shell,如果在sh脚本执行表示结束当前脚本,在system执行则表示结束当前子进程!!!!!
根因分析
- system命令类似于sh脚本,实际上也是在/bin/sh中写一个脚本并执行,往往是非交互式的脚本,这个过程是在调用处新开一个子进程完成的,恰好我执行的内容是杀掉父进程并启动一个新进程,父进程有用到标准io(获取输入1并执行) 直接把他干掉就导致stdin乱了!!!!!
直接kill/killall 会提示Terminated,加上-9 会提示Killed,这是因为kill指令是通过向目标进程发送相应信号 默认是15(会等待处理完在终止),9是强制杀死!!

最终方法
- 用killall -2 xxx ,相当于在前台程序时执行一个ctrl+c 但他实际上并不会杀掉父进程,因为kill -2十分温和,他会等到子进程结束在杀掉父进程,而子进程又想杀死父进程,父进程要等待子进程杀死自己再去死,就导致死不掉了。。。(有局限性,比如进程中有其他线程,ctrl+c不会退出线程!!)

- 用exec函数族实现,无论重启几次都是原来的进程号,但是会复位到初始状态!!!


kill -2也是一种可行的办法但实际上是覆盖,没关闭原来的进程,会导致资源浪费,exec函数是最根本的解决方法会取代!!!
总结
system命令几乎等于写一个sh脚本并执行,区别是system命令是新开子进程,手动执行sh脚本可以新开一个shell执行!!
子进程中杀掉父进程是非常危险的行为,会导致很多未知错误,一般不要这样做!!!!
一些优雅的发送信号方式,ctrl+z暂停,fg继续,ctrl+S挂到后台 ctrl+Q恢复到前台!

进程间通讯的方式之一 管道通信 实现方式是popen pclose
fork函数 exec函数族 还可以自定义io区等...很强
排查这个bug用了两天半,居然真的用上了操作系统的知识,爽歪歪!!!
记一次bug排除心得的更多相关文章
- 记Weblogic部署BUG(websocket)
将含有websocket的SSM项目部署在Weblogic上面,遇到websocket报错如下 java.lang.ClassCastException: org.springframework.se ...
- 【windows核心编程】注入DLL时BUG排除与调试
DLL注入排除bug的思路步骤. 1.在VS中监视输入err,hr检查DLL是否注入成功 2.OD断点loadlibraryW,loadlibraryA是否已经注入成功,eax是否有值. 3.检查路径 ...
- 记一次bug查找经历
系统采用cell插件显示汇总数据,然后发现个公司数据显示不出来,接到这个任务开始查找bug. 通过需求了解并不知道其他公司什么情况,因为就这个公司有了反馈: 本来以为很容易找到点的,毕竟数据显示不出来 ...
- 记一个逻辑bug
1 从数据库中找出一个学生能选的毕业设计(毕设的select or not 字段表示本题目是否已经被选 此时就按照其值为n来查询) 2 用户选择某个毕设后,先更新毕设表(select ...
- 谁记录了mysql error log中的超长信息(记pt-stalk一个bug的定位过程)
[问题] 最近查看MySQL的error log文件时,发现有很多服务器的文件中有大量的如下日志,内容很长(大小在200K左右),从记录的内容看,并没有明显的异常信息. 有一台测试服务器也有类似的问题 ...
- 记一款bug管理系统(bugdone.cn)的开发过程(4) - 新增BugTalk功能
测试人员提出一个Bug,如果开发人员对Bug有疑义,会直接面对面讨论或者通过QQ等线上聊天工具讨论,但过后再去找讨论记录会很麻烦.因此BugDone提出一个全新的概念:将问题的讨论留在问题内.BugD ...
- 记一款bug管理系统(bugdone.cn)的开发过程(3) - 永久免费化
BugDone永久免费了! BugDone(bug管理工具)已经发布有一阵子了,自发布以来注册用户量.项目创建量稳步提升,并且得到了很多用户的好评. 在开发BugDone工具之前,我们团队也曾为找不到 ...
- 记一款bug管理系统(bugdone.cn)的开发过程(2) -如何做好登录界面
一. 做了一个大胆的决定,官网首页便是登录界面 BugDone,Bug管理工具的定位就是一款非常易用的工具,所以我们没有像其它平台那样进官网首页都是一些功能和业务的介绍. 我们觉得方便用户快速进入工作 ...
- 记一款bug管理系统(bugdone.cn)的开发过程(1) -- 为什么要开发一款bug开发系统
对于从事软件研发行业的同学来说bug管理系统肯定不陌生.本人03年左右开始正式成为一名码农,工作期间接触过若干bug管理系统,如JIRA等,不过都是自行部署在公司内网的. 几年过去了,现在已经是互联网 ...
- 记一个小bug的锅
人生中的第一个线上bug 我参与的第一个项目就出现了.但是自己还觉得这锅也不全是自己的,毕竟那么明显的bug出现在历史模块中(不是我写的新模块),难道测试部就没一点责任?代码走查人员就没一点责任?不过 ...
随机推荐
- 重新整理数据结构与算法(c#)—— 顺序存储二叉树[十九]
前言 二叉树顺序存bai储是二叉树的一种存储方式.将二du叉树存储在一zhi个数组中,通过存储元素的下dao标反映元素之间的父子关系. 正文 这个概念比较简单,比如一个节点的在数组的index是x,那 ...
- CF-938(C-E)
CF-938 C 没啥好分析的,就记录一下我因为没有清空s[n+1].上取整写成了下取整卡了一个多小时(╬▔皿▔)╯ const int N=2e5+5; int a[N],p[N],s[N]; vo ...
- Django框架——模版层之标签、自定义过滤器 标签及inclusion_tag(了解)、模版的继承与导入、模型层之前期准备、ORM常用关键字
模版层之标签 {% if 条件1(可以自己写也可以用传递过来的数据) %} <p>今天又是周三了</p> {% elif 条件2(可以自己写也可以用传递过来的数据) %} &l ...
- js获取时间差,返回格式为01天02小时03秒
// 获取时间差 返回值格式:01天02小时30秒 export function caclulateDiffTime(start, end): string { start = new Date(s ...
- 力扣661(java)-图片平滑器(简单)
题目: 图像平滑器 是大小为 3 x 3 的过滤器,用于对图像的每个单元格平滑处理,平滑处理后单元格的值为该单元格的平均灰度. 每个单元格的 平均灰度 定义为:该单元格自身及其周围的 8 个单元格的 ...
- 力扣24(java&python)-两两交换链表中的节点(中等)
题目: 给你一个链表,两两交换其中相邻的节点,并返回交换后链表的头节点.你必须在不修改节点内部的值的情况下完成本题(即,只能进行节点交换) 示例 1: 输入:head = [1,2,3,4] 输出:[ ...
- EasyNLP集成K-BERT算法,借助知识图谱实现更优Finetune
导读 知识图谱(Knowledge Graph)的概念⾸次出现2012年,由Google提出,它作为⼀种⼤规模语义⽹络, 准确地描述了实体以及实体之间的关系.知识图谱最早应⽤于搜索引擎,⽤于准备返回⽤ ...
- 【实用教程】在配备持久内存的实例上部署Redis应用
简介:配备持久内存的实例(例如re7p.r7p.re6p)提供了超大CPU内存配比,Redis应用运行在这类实例上可以大幅度降低单GiB内存的成本.本文以部分操作系统为例,介绍如何在这类实例上快速部署 ...
- 技术干货 | jsAPI 方式下的导航栏的动态化修改
简介: 操作指导:通过 jsAPI 实现导航栏的动态修改. 很多开发同学在接入 H5 容器后都会对容器的导航栏进行深度定制,除了 Native 的定制化之外,还有很多场景是使用到 jsAPI 的 ...
- dotnet OpenXML 继承组合颜色的 GrpFill 属性
在 OpenXML 的颜色画刷填充,有特殊的填充是 GrpFill 属性,对应 OpenXML SDK 定义的 DocumentFormat.OpenXml.Drawing.GroupFill 类型 ...
