记一次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出现在历史模块中(不是我写的新模块),难道测试部就没一点责任?代码走查人员就没一点责任?不过 ...
随机推荐
- k8s 深入篇———— 一些容器操作的原理[三]
前言 简单介绍一下一些容器的操作原理. 正文 docker exec 是怎么做到进入容器里的呢. 比如说: 这里有一个容器,我们可以exec 进去: docker exec -it b265 /bin ...
- 重新点亮shell————函数[七]
前言 简单整理一下函数. 正文 自定义函数: function fname(){ 命令 } 函数的执行: fname 函数作用范围的变量: local 变量名 函数的参数 $1 $2 $3 .... ...
- 重新整理数据结构与算法(c#)—— 堆排序[二十一]
前言 将下面按照从小到大排序: int[] arr = { 4, 6, 8, 5, 9 }; 这时候可以通过冒泡排序,计数排序等. 但是一但数据arr很大,那么会产生排序过于缓慢,堆排序就是一个很好的 ...
- KubeOperator技术方案
KubeOperator技术方案 总体介绍︎ KubeOperator 是一个开源的轻量级 Kubernetes 发行版,专注于帮助企业规划.部署和运营生产级别的 Kubernetes 集群. Kub ...
- 如何在 ACK 中使用 MSE Ingress
简介: 本文将为大家分享一下 Ingress 标准 和 实现的趋势,介绍一下 MSE Ingress 在这个趋势下的优势和实践,为大家做关键入口选择多一些参考. 作者:彦林 随着云原生架构的普及,K8 ...
- 写给大家看的“不负责任” K8s 入门文档
前言 2019 年下半年,我做了一次转岗,开始接触到 Kubernetes,虽然对 K8s 的认识还非常的不全面,但是非常想分享一下自己的一些收获,希望通过本文能够帮助大家对 K8s 有一个入门的了解 ...
- 阿里云边缘云ENS再升级 四大场景应用加速产业数字化落地
简介: 云栖大会 | 于10月21日上午举办的边缘云应用升级与技术创新论坛中,阿里云边缘云ENS产品全面升级,从边缘云产品.技术.行业应用等维度全面阐述阿里云在边缘计算领域的技术积累.产品& ...
- [Go] flag package 指南: 命令行参数标记的解析
flag 是 Golang 的官方包. 支持用法有三种,不同之处是二三两种用法是 Var() 函数可以绑定 flag 到一个变量上. 直接调用指定类型的函数有多种,如 flag.String(), B ...
- SpringBoot项目添加2FA双因素身份认证
什么是 2FA(双因素身份验证)? 双因素身份验证(2FA)是一种安全系统,要求用户提供两种不同的身份验证方式才能访问某个系统或服务.国内普遍做短信验证码这种的用的比较少,不过在国外的网站中使用双因素 ...
- notepad运行python代码的步骤
notepad运行python代码的步骤: 1.用notepad++打开python文件.或者新建文件,保存为.py格式. 2.在菜单栏上面有一个运行,我们点击运行->运行,或者使用快捷键F5. ...