Main

May 01, 2023

记一次艰难的 debug 历程

五一前,我遇到了一个非常难缠的 bug ,前后花了两天时间才把它解决。其刁钻程度,可以列入我职业生涯的前三,非常值得记录一下。

问题发现在节前两天,很多同事都请了假,我也打算好好休息一下,陪孩子玩几天。就在我例行更新游戏项目的仓库后,突然发现程序崩溃了。一开始,我并不以为意。因为我们的游戏是用 Lua 为主开发的,并不需要在更新后重新编译。我大约一周才会构建一次项目。或许这只是因为我太久没有 build 了,所以我随手构建了一下。但问题依然存在,只不过发生的概率偏低,大约启动三次有一次会出问题。这不寻常,因为我已经很久没见过 Segmentation fault 了。

May 08, 2018

断点单步跟踪是一种低效的调试方法

断点单步跟踪的交互式调试器是软件开发史上的一项重大发明。但我认为,它和图形交互界面一样,都是用牺牲效率来降低学习门槛。本质上是一种极其低效的调试方法。

我在年少的时候( 2005 年以前的十多年开发经历)都极度依赖这类调试器,从 Turbo C 到 Visual C++ ,各个版本都仔细用过。任何工具用上十年后熟能生巧是很自然的事。我认为自己已经可以随心所欲用这类工具高效的定位出 bug 了。但在 2005 年之后转向跨平台开发后,或许是因为一开始没能找到 Linux 平台上合适的图形工具,我有了一些时间反思调试方法的问题。GDB 固然强大,但当时的图形交互外壳并不像今天的版本这么完善。当时比较主流的 insight ddd 都有些小问题,用起来不是十分顺手。我开始转换自己平时做开发的方式。除了尽量提高自己的代码质量:写简洁的、明显没有问题的代码之外,多采用不断的代码复核(Code Review),有意识地增加日志输出,来定位 Bug 。

后来开发重心从客户端图形开发逐步转向服务器,更加显露出用调试器中断程序运行的劣势来。对于 C/S 结构的软件,中断一边的代码运行,用人的交互频率单步跟踪运行,而另一边是以机器的交互频率运作,像让软件运行流程保持正常是非常困难的。

这些年的工作中又慢慢加入一些 Windows 下的开发工作。我发现经过了再一个十年的训练,即使偶尔用上交互式调试器,也体会不到什么优势了。往往手指按在跟踪调试按键上机械的操作,脑子里想的却不是眼前看到的屏幕上的代码。往往都没执行到触发 Bug 的位置,已经恍然大悟发现写错的地方了。这种事情多了,自然会对过去的方法质疑,是什么导致了调试器的低效。

March 14, 2018

LoadLibrary 无法加载 DLL 的 bug 处理

今天我们的小伙伴在把 Open Asset Import Library 封装成 lua 库的时候遇到一些麻烦。他在 vs 集成环境中编译的版本可以用,在 mingw-gcc 下却出错,报告 “找不到指定的模块。” 或是 “找不到指定的程序。" 。

我不得不吐槽,微软真是太有钱了,雇了一大帮人把出错信息都给国际化,这叫中国程序员怎么 google 问题啊。不太懂 windows ,反正我用 export LANG= 切换了 locale 为 C 还是不能把这段用 FormatMessage 格式化出来的 GetLastError 获得的出错码的错误串变成英文。还请读者中的 windows 大牛在留言中赐教。写这篇 blog 的主要目的就是以后有人可以用上面的出错信息关键词 google 过来。让我们一起来吐槽微软。

September 20, 2017

Direct3D12 的接口设计 bug

昨天被 D3D12 的一个 bug 坑了一晚上,这个问题很值得一写。

最初是发现 LUID ID3D12Device::GetAdapterLuid() 这个函数有问题。我用 mingw64 gcc 编译后的程序,只要调用了一个 api ,d3d12device 设备对象的虚函数表就被破坏掉了。下一次对这个设备的任何 api 调用都会 crash 掉。

由于这个函数的实现在 d3d12.dll 中,是没有源码的,所以只能用 gdb 调试了一下。发现了一个问题:这个 api 的返回值是 LUID ,它是一个结构体。C/C++ 函数返回结构体是没有统一的调用规范的,按道理 COM 的实现应该避免设计这种 API 。

按 COM 的规定,所有 API 必须返回 HRESULT ,只有少量例外可以返回 ULONG 整数。其实之前的 D3D 版本在设计类似 API 的时候都符合了这个约定,例如 D3D9 就有一个类似的 API :

HRESULT GetAdapterLUID( [in] UINT Adapter, [in] LUID *pLUID )

August 16, 2017

Lua 5.3.4 的一个 bug

昨天我们一个项目发现了一处死循环的 bug ,经过一整晚的排查,终于确认是 lua 5.3.4 的问题。

起因是最近项目中接入了我前段时间写的一个库,用来给客户端加载大量配置表格数据 。它的原理是将数据表先转换为 C 结构,放在一块连续内存里。在运行时,可以根据需要提取出其中用到的部分加载都虚拟机中。这样做可以极大的提高加载速度。项目在用的时候还做了一点点小修改,把数据表都设置成 weaktable ,可以让暂时不用的数据项可以回收掉。

正式后面这个小修改触发了 bug 。

July 25, 2017

浮点运算潜在的结果不一致问题

昨天阿楠发现了项目中的一个 bug ,是因为浮点运算的前后不一致导致的。明明是完全相同的 C 代码,参数也严格一致,但是计算出了不相同的结果。我对这个现象非常感兴趣,仔细研究了一下成因。

原始代码比较繁杂。在弄清楚原理后,我简化了出问题的代码,重现了这个问题:

static void
foo(float x) {
    float xx = x * 0.01f;
    printf("%d\n", (int)(x * 0.01f));
    printf("%d\n", (int)xx);
}

int
main() {
    foo(2000.0f);
    return 0;
}

使用 gcc 4.9.2 ,强制使用 x87 浮点运算编译运行,你会发现令人诧异的结果。

gcc a.c -mfpmath=387

19
20

前一次的输出是 19 ,后一次是 20 。

May 27, 2017

epoll 的一个设计问题

问题的起因是 skynet 上的一个 issue ,大概是说 socket 线程陷入了无限循环,有个 fd 不断的产生新的消息,由于这条消息既不是 EPOLLIN 也不是 EPOLLOUT ,导致了 socket 线程不断地调用 epoll_wait 占满了 cpu 。

我在自己的机器上暂时无法重现问题,从分析上看,这个制造问题的 fd 是 0 ,也就是 stdin ,猜想和重定向有关系。

skynet 当初并没有处理 EPOLLERR 的情况(在 kqueue 中似乎没有对应的东西),这个我今天的 patch 补上了,不过应该并不能彻底解决问题。

我做了个简单的测试,如果强行 close fd 0 ,而在 close 前不把 fd 0 从 epoll 中移除,的确会造成一个不再存在的 fd (0) 不断地制造 EPOLLIN 消息(和 issue 中提到的不同,不是 EPOLLERR)。而且我也再也没有机会修复它。因为 fd 0 被关闭,所以无法在出现这种情况后从 epoll 移除,也无法读它(内核中的那个文件对象),消息也就不能停止。

May 08, 2017

用 gdb 分析 coredump 的一些技巧

前几天我们正在运营的一款产品发生了崩溃,我花了两天尝试用 gdb 分析了 coredump ,虽然最后还是没能找到 bug ,但还是觉得应该做一些总结。

产品是基于 skynet 开发的,由于历史原因,它基于的是 skynet 1.0 之前 2015 年中的一个版本,由于这两年一直没出过什么问题,所以维护人员懈怠而没有更新。

崩溃的时候,关于 Lua 部分的代码缺少调试符号信息,这加大了分析难度。现在的 skynet 在编译 lua 时,加入了 -g 选项,这应该可以帮助未来出现类似问题时更好的定位问题。

May 07, 2016

skynet 服务的沙盒保护

昨天我们新的 MMO 游戏第一次上线小规模测试,暴露了一些问题。

服务器在开服 3 小时后,突然内存暴涨,CPU 占用率提升不多。当时 SA 已经收到报警邮件,但刚巧在午餐时间,而游戏功能还正常,耽误了半个小时。处理不够及时,导致在最终没有能收集到足够在线数据前,服务器已不能正常操作。另外,忘记配置 core dump 文件输出是另一个原因。

在最后几分钟,我们收集到一些信息:某个 lua 服务陷入 C 代码中的死循环,在 skynet 控制台发 signal 无法中断它(skynet 的 signal 可以中断 lua vm 的运行 )。从 log 分析,内存暴涨是突发的,几乎在一瞬间吃光了所有内存,而并非累积。

第一次宕机后,迅速重启了服务器。同时在内网又同步运行了机器人压力测试,但是无论是外网环境和内网环境均无法重现故障。

February 03, 2016

opengl bug 一则

这是个老问题,但是公司不同的同学先后被坑,所以必须记录一笔。这样可以增加事后被 google 到的概率,千万别来第三次了。

我原本以为 opengl 在 bind VBO 后,如果修改了 VBO 的数据,是不需要重新 bind VBO 对象的。所以早先的 ejoy2d 在这里的处理就做了一点优化,并没有重复 bind ,可以减少一些 API 调用。

大约在 2015 年 6 月左右,由于需要跟进 mac osx 的系统更新,ejoy2d 增加了对 VAO 的支持。我再修改相关实现的时候发现在某些设备上,出现了 bug 。

当时猜想可能是新的手机的驱动做了一些过去没有做的奇怪的优化。我没有太多确认 opengl 是否对此有要求,不知道是否是驱动的 bug ,不过还是做了一些修改。

这个 patch 中,我在更新 VBO 的数据后,设置了脏标记,最后提交时会重新 bind VBO 。

当时并没有刻意做为一个独立 commit 提交,所以被同事疏忽了。

前不久,我们的 心动庄园 在新版锤子手机上出现花屏,同事追查过原因,发现只需要更新 ejoy2d 就解决了。定位了一下原因,就是上面提到的 patch 解决的。

前几天,另一个同事自己用 ejoy2d 开发的小游戏 爆裂方块 在 360 平台审核时被报告有显示 bug ,又查了好久,今天终于定位到是同一问题。

August 12, 2015

一个内存泄露 bug

起因是 skynet 的一个 Issue ,同时,这两天我们正在开发的一个项目也反应貌似有内存泄露。

我觉得两件事同时发生不太正常,就决定好好查一下。

其实在 skynet 里查内存泄露要比一般的项目容易的多。因为 skynet 天生就分成了很多小模块,叫作服务。模块申请的内存是独立的,内聚性很高。模块的生命期比整个进程短的多,模块的规模也不会太大,可以独立分析。一般说来,如果有内存申请没有归还,应该是 C 模块里的 bug 。而 skynet 会用到的 C 模块也很少,一旦有这样的问题,很快就能定位。

March 04, 2014

谈谈陌陌争霸在数据库方面踩过的坑( Redis 篇)

注:陌陌争霸的数据库部分我没有参与具体设计,只是参与了一些讨论和提出一些意见。在出现问题的时候,也都是由肥龙、晓靖、Aply 同学判断研究解决的。所以我对 Redis 的判断大多也从他们的讨论中听来,加上自己的一些猜测,并没有去仔细阅读 Redis 文档和阅读 Redis 代码。虽然我们最终都解决了问题,但本文中说描述的技术细节还是很有可能与事实相悖,请阅读的同学自行甄别。

在陌陌争霸之前,我们并没有大规模使用过 Redis 。只是直觉上感觉 Redis 很适合我们的架构:我们这个游戏不依赖数据库帮我们处理任何数据,总的数据量虽然较大,但增长速度有限。由于单台服务机处理能力有限,而游戏又不能分服,玩家在任何时间地点登陆,都只会看到一个世界。所以我们需要有一个数据中心独立于游戏系统。而这个数据中心只负责数据中转和数据落地就可以了。Redis 看起来就是最佳选择,游戏系统对它只有按玩家 ID 索引出玩家的数据这一个需求。

我们将数据中心分为 32 个库,按玩家 ID 分开。不同的玩家之间数据是完全独立的。在设计时,我坚决反对了从一个单点访问数据中心的做法,坚持每个游戏服务器节点都要多每个数据仓库直接连接。因为在这里制造一个单点毫无必要。

根据我们事前对游戏数据量的估算,前期我们只需要把 32 个数据仓库部署到 4 台物理机上即可,每台机器上启动 8 个 Redis 进程。一开始我们使用 64G 内存的机器,后来增加到了 96G 内存。实测每个 Redis 服务会占到 4~5 G 内存,看起来是绰绰有余的。

由于我们仅仅是从文档上了解的 Redis 数据落地机制,不清楚会踏上什么坑,为了保险起见,还配备了 4 台物理机做为从机,对主机进行数据同步备份。

Redis 支持两种 BGSAVE 的策略,一种是快照方式,在发起落地指令时,fork 出一个进程把整个内存 dump 到硬盘上;另一种唤作 AOF 方式,把所有对数据库的写操作记录下来。我们的游戏不适合用 AOF 方式,因为我们的写入操作实在的太频繁了,且数据量巨大。

March 03, 2014

谈谈陌陌争霸在数据库方面踩过的坑(芒果篇)

我们公司开始用 mongodb 并不是因为开始的技术选型,而是我们代理的第一款游戏《 狂刃 》的开发商选择了它。这款游戏在我们代理协议签订后,就进入了接近一年的共同开发期。期间发现了很多和数据库相关的问题,迫使我们熟悉了 mongodb 。在那个期间,我们搭建的运营平台自然也选择了 mongodb 作为数据库,这样维护人员就可以专心一种数据库了。

经过一些简单的了解,我发现国内很多游戏开发者都不约而同的采用了 mongodb ,这是为什么呢?我的看法是这样的:

游戏的需求多变,很难在一开始就把数据结构设计清楚。而游戏领域的许多程序员的技术背景又和其他领域不同。在设计游戏服务器前,他们更多的是在设计游戏的客户端:画面、键盘鼠标交互、UI 才是他们花精力最多的地方。对该怎么使用数据库没有太多了解。这个时候,出现了 mongodb 这样的 NOSQL 数据库。mongodb 是基于文档的,不需要你设计数据表,和动态语言更容易结合。看起来很美好,你只需要把随便一个结构的数据对象往数据库里一塞,然后就祈祷数据库系统会为你搞定其它的事情。如果数据库干的不错,性能不够,那是数据库的责任,和我无关。看到那些评测数据又表明 mongodb 的性能非常棒,似乎没有什么可担心的了。

其实无论什么系统,在对性能有要求的环境下,完全当黑盒用都是不行的。

游戏更是如此。上篇我就谈过,我们绝对不可能把游戏里数据的变化全部扔到数据库中去做。传统数据库并非为游戏设计的。

比如,你把一群玩家的坐标同步到数据库,能够把具体某个玩家附近玩家列表查询出来么?mongodb 倒是提供了 geo 类型,可以用 near 或 within 指令查询得到附近用户。可他能满足 10Hz 的更新频率么?

我们可以把玩家的 buf 公式一一送入数据库,然后修改一些属性值,就可以查询到通过 buf 运算得到的结果么?

这类问题有很多,即使你能找到方法让数据库为你工作,那么性能也是堪忧的。当我们能在特定的数据库服务内一一去解决她们,最终数据库就是一个游戏服务器了。

February 15, 2014

一起 select 引起的崩溃

2 月 13 日,陌陌争霸 计划在下午 16:00 例行维护,之前已经稳定运行了很长时间了。没想到在 14:30 左右,有一台从机意外崩溃。再次之前,从机崩溃并不会引起系统坏死,只需要新启动一台从机即可。但这次似乎不一样,所有玩家均不能登陆游戏,不得已,提前进行了长达两小时的紧急维护(将例行维护的工作合并)。

这次从机崩溃进行的大规模事故,可简单描述为:当从机上 5K 玩家掉线后,引起这批用户立刻连接其它从机。而事故发生后另一个小 bug 导致没有及时从系统中把这批用户重置,导致这批用户不可能立刻重新进入系统,反复重试。而每台从机上的网关配置最大连接数过小,导致他们阻塞了其他用户的登陆。

维护后修正了后面几个问题,由于时间紧迫,没有细查崩溃的直接原因。(另一个小插曲是,core 文件被输出到一个容量很小的分区,分区容量不足,导致 core 文件被截断,增加了分析难度。)系统重开后,不到半小时又有一台从机崩溃,这次没有产生雪崩,立刻启用了备用机替换。可在 5 分钟后,新的一起崩溃事件在另一台从机上发生。这使我们意识到问题的严重性。

由于后来的崩溃拿到了完整的 core 文件,可以看到 C 层的崩溃发生在游戏服务器和我们的运营平台服务器(分属两个机房)的对接环节。由于机房间网络状态不稳定,发生了一次重连操作,再重新建立连接时,程序出错。但是程序调用栈已经被部分破坏。同时,错误 log 中有大量玩家领奖品失败的记录 。最快速的判断是,两者有一定的相关性。我们已经两个月没有修改 C 代码了,之前一直很稳定,过年期间无人守护都没有发生这种严重的事故,所以很难理解 bug 的成因。最近只在 lua 曾增加了一些业务逻辑,比如一些奖品发放。不太能确定到底是何为因何为果。

到了 13 日晚饭时间,依然没有头绪。为了减少晚上再次崩溃的可能,我决定关闭奖品领取模块,再慢慢追查 bug 。

January 06, 2014

一次内存越界的 bug

最近我们的手游上线, 一开始稳定的跑了好几天,最近两天开始平均 30 小时崩溃一次。这个周末我们一直在查这个 bug 。

第一次崩溃发生在周六凌晨 1:00 左右,没有收集到 core 文件,只有事故现场的寄存器的值(通过查看 kern.log 和 dmesg )。通过 EIP 寄存器最终定位到是在内存分配器中出的错。

我们用的 jemalloc ,崩溃发生在一段红黑树插入节点的代码中。 这里的 pathp 指针变成了 NULL 。

昨天在岩馆攀岩的时候,我脑子里一直在转这段代码,想为什么这个局部变量会变成 NULL 。反复推测的结果是 path 这个局部数组越界了,导致把 pathp 改写。

这段代码是用来展开一颗红黑树的,如果树的数据结构正常,128 一定够用,不会越界。比较可能的解释是插入了相同的节点,导致树结构绕接。虽然 jemalloc 在这里加了 assert ,但似乎在最终的程序中没有生效。

April 15, 2013

WM_CREATE 引起的 bug 一则

今天在维护一个 Windows 程序时,发现一个 bug ,记录一下。

这是一个简单的 Windows 程序,在注册给窗口的 WinProc 回调函数中处理了 WM_CREATEWM_PAINT WM_TIMER 等消息。

bug 的现象是,WM_CREATE 的流程没有走完就开始处理 WM_TIMER 等消息了。表现起来仿佛 WinProc 被重入了。

仔细排查后发现,不知道什么奇怪的原因,我的 Win7 系统在处理 WM_CREATE 消息时,默认捕获了异常。导致消息处理的流程没有走完,但进程却没有崩溃,窗口也被正确创建出来了。然后这个窗口可以继续接收 WM_TIMER 等消息。

January 30, 2013

内存异常排查

今天开电话会议,帮助合作方排查 C++ 开发的程序崩溃的原因。

现象是这样的:

有一个 C++ 对象,其中有一个 vector ,里面存放了大量对象指针。这个 C++ 对象在构造完毕后,没有任何改写操作,全部是只读的。

在某个退出环境中,C++ 对象被析构,析构函数需要调用 vector 中所有对象指针的删除方法。这时,这个长度大约为 100 多的 vector 第 95 个指针是错的。比它应该的指针位置偏移了一到三字节(多次运行现象不一,出错几率也不大)。

整个数组只有这一个指针错误。因为这个错误,引发了程序崩溃。

从电话中的描述,我推断:

这不太可能是由错误的调用此 C++ 对象的方法改写 vector 的内容导致的,因为这个对象全部是读方法,并加了 const 修饰。且,这个数组保存的是对象地址。一个由正常的内存分配器分配出来的地址都是对齐的,但此处错误的被改写为奇数。

January 22, 2013

内存泄露排查小记

最近我们的游戏服务器又发生了一起内存泄露事故。 由于泄露速度极其缓慢,所以有很大的隐蔽性。

Bug 最后是这样确认并解决的:

由于 Skynet 本质上是由唯一的时钟模块驱动的,我们首先修改了时钟部分的代码,让系统可以以 10 倍速运作。这样,原本耗时几天的泄露现象,可以在半天内就确定了:一定存在某种内存泄露的 Bug, 而不是正常流程导致的内存占用持续上升。

October 12, 2012

并发问题 bug 小记

今天解决了一个遗留多时的 bug , 想明白以前出现过的许多诡异的问题的本质原因。心情非常好。

简单来说,今天的一个新需求导致了我重读过去的代码,发现了一个设计缺陷。这个设计缺陷会在特定条件下给系统的一个模块带来极大的压力。不经意间给它做了一次高强度的压力测试。通过这个压力测试,我完善了之前并发代码中写的不太合理的部分。

一两句话说不清楚,所以写篇 blog 记录一下。

最开始是在上周,阿楠同学发现:在用机器人对我们的服务器做压力测试时的一个异常状况:机器人都在线的时候,CPU 占用率不算特别高。但是一旦所以机器人都被关闭,系统空跑时,CPU 占用率反而飚升上去。但是,经过我们一整天的调试分析,却没有发现有任何线程死锁的情况。换句话说,不太像 bug 导致的。

但是,一旦出现这种情况,新的玩家反而登陆不进去。所以这个问题是不可以放任不管的。

后来,我们发现,只要把 skynet 的工作线程数降低到 CPU 的总数左右,问题就似乎被解决了。不会有 CPU 飚升的情况,后续用户也可以登陆系统。

虽然问题得到了解决,但我一直没想明白原因何在,心里一直有点不爽。

August 17, 2012

记录一个并发引起的 bug

今天发现 Skynet 消息处理的一个 bug ,是由多线程并发引起的。又一次觉得完全把多线程程序写对是件很不容易的事。我这方面经验还是不太够,特记录一下,备日后回顾。


Skynet 的消息分发是这样做的:

所有的服务对象叫做 ctx ,是一个 C 结构。每个 ctx 拥有一个唯一的 handle 是一个整数。

每个 ctx 有一个私有的消息队列 mq ,当一个本地消息产生时,消息内记录的是接收者的 handle ,skynet 利用 handle 查到 ctx ,并把消息压入 ctx 的 mq 。

ctx 可以被 skynet 清除。为了可以安全的清除,这里对 ctx 做了线程安全的引用计数。每次从 handle 获取对应的 ctx 时,都会对其计数加一,保证不会在操作 ctx 时,没有人释放 ctx 对象。

skynet 维护了一个全局队列,globalmq ,里面保存了若干 ctx 的 mq 。

这里为了效率起见(因为有大量的 ctx 大多数时间是没有消息要处理的),mq 为空时,尽量不放在 globalmq 里,防止 cpu 空转。

Skynet 开启了若干工作线程,不断的从 globalmq 里取出二级 mq 。我们需要保证,一个 ctx 的消息处理不被并发。所以,当一个工作线程从 globalmq 取出一个 mq ,在处理完成前,不会将它压回 globalmq 。

处理过程就是从 mq 中弹出一个消息,调用 ctx 的回调函数,然后再将 mq 压回 globalmq 。这里不把 mq 中所有消息处理完,是为了公平,不让一个 ctx 占用所有的 cpu 时间。当发现 mq 为空时,则放弃压回操作,节约 cpu 时间。

所以,产生消息的时刻,就需要执行一个逻辑:如果对应的 mq 不在 globalmq 中,把它置入 globalmq 。

需要考虑的另一个问题是 ctx 的初始化过程:

ctx 的初始化流程是可以发送消息出去的(同时也可以接收到消息),但在初始化流程完成前,接收到的消息都必须缓存在 mq 中,不能处理。我用了个小技巧解决这个问题。就是在初始化流程开始前,假装 mq 在 globalmq 中(这是由 mq 中一个标记位决定的)。这样,向它发送消息,并不会把它的 mq 压入 globalmq ,自然也不会被工作线程取到。等初始化流程结束,在强制把 mq 压入 globalmq (无论是否为空)。即使初始化失败也要进行这个操作。

July 26, 2011

地址对齐问题引起的 Bug 一则

这两天一直在圈人一起玩 google+ 。由于众所周之的原因,在墙内推广这个东西阻力重重。还好不需要必须翻墙,google 在北京是有网关的,修改本机 hosts 文件,把相关的域名指过去即可。在这里就不列出方法了,希望下面的评论中也不要贴出来,私下交流即可。还好移动版 G+并没有封掉,究其原因可能是 m.google.com 是所有 google 移动服务的统一入口,如果封杀影响较大。而 https 协议让墙无法分析 url ,不可能做到部分干扰。如果有一天真封禁了,手机上都无法阅读 greader ,收取 gmail ,那还真是个悲剧。

我在拉我老爸玩 G+ 的过程中,他老人家反应了一个问题,家里用手机使用 android 的 app 发不了帖子。我估摸着可能是被墙了。虽然可以让他 root 掉手机修改 hosts ,但显然这不是一个简便的解决方案。我便着手远程登陆家里的网关。那是一台用 LinkStation Pro 改造的 linux debian 机器。拥有一块 Arm9 CPU ,在上面跑一个 DNS 服务,足以让家中的局域网解析到可以登陆 G+ 的 ip 地址。

August 28, 2010

记一个 Bug

今天周末,桌游店里却没客人,昨天打电话预约的朋友没来,所以我就奔到办公室测试上周写的代码。

上周的工作主要是设计了一个新的包格式,然后整合入前段时间实现的虚拟文件系统中。

这个工作和前段实现的 zipfs 有相似之处,所以做起来也很快。不过前面没仔细测试。今天比较闲,就设计了几组复杂的测试数据,感觉覆盖了各种边界情况。一测试果然发现了 Bug 。

这个 Bug 有点启发意义,所以在解决掉之后,决定记录一下。

August 06, 2010

Windows 下调试问题一则

今天跑昨天晚上写的一个小程序,莫名其妙的不能运行。开 gdb 调试没有任意异常,直接退出,连 main 函数都没有进。注释掉大量代码后依旧。把 gdb 从 6.3 升级到 6.8 后,再调试,捕获了一个不知名的异常。

又把代码注释干净,程序正常了。仔细考虑了一下,最后注释掉的代码间接 link 了 lua 的库。而 lua 的库引用了 lua 的动态库。程序运行时可能是因为没有找到 dll 而退出。

把 lua 的 dll 加到 path 中就正常了。但是奇怪的是,系统并没有弹出对话框提示。仔细想了一下,原来是我没有从 explore 启动控制台,而是在编辑器里启动的,可能是编辑器软件没有写好,把本应该系统弹出的缺少 dll 的对话框拦截了。

Soloist 同学说,他也碰到过一次 Total Commander 吃掉缺少 dll 无法启动程序的对话框。记录一下这个问题,祭奠我找问题逝去的半小时。

June 05, 2009

tcc 的一个 bug

tcc 是个好东西,我们的粒子系统把它当作可选模块,用于动态生成粒子控制代码。较之 lua 的版本,性能可以提高一个数量级(另外一个 gcc 版本的可选模块,会失去动态性)。为了引入这个库,我还好好研究了一下 LGPL 。

前段时间我还在抱怨 tcc 不能跑在 64 bit 环境下,结果今天因为查 bug ,去关心了一下新版本,发现 0.9.25 已经开始支持 64bit 了。

表扬到此为止,现在开始抱怨。

April 08, 2009

两个 bug

今天查出两个 bug ,都很典型。值得记录,警示后人。

其一是,我们的编辑器服务器的数据储存部分,采用向一个文件追加的方式,保证数据不丢失,同时用一个 index 文件记录每个数据的最新版本在这个数据文件中的偏移量,加快检索。

实现的时候,考虑过数据文件过大(比如超过 4G)的情况。但是由于疏忽,index 里的偏移量全部用的 32bit 而不是 64bit 。结果在服务器工作了很久后,终于碰上了溢出,index 文件损坏。

后果倒不严重,重建一下索引即可。

March 25, 2009

libstdc++ 卸载问题

今天,同事花了一下午时间,终于查到了 周末我碰到的问题 的根本原因。

是因为,GLU 是用 C++ 实现的,而导致 libstdc++.so 会跟随它卸载,而 libstdc++.so 本身有一个 bug ,导致程序崩溃。

当然,显露这个 bug 还有一个前提,我的程序是纯 C 写的,完全没有用到 libstdc++ 。

有兴趣的同学可以查看这个帖子:http://unix.derkeiler.com/pdf/Mailing-Lists/FreeBSD/stable/2006-05/msg00719.pdf

March 22, 2009

Freebsd 下 glx 的一点问题

最近特别忙,经常连续工作超过 10 小时。所以情绪也有点不稳定,有的应该在半小时处理完的问题,会拖上几个小时。比如昨天就碰到一个。

由于底层代码由我一个人做了大规模重构(在不改变中间层的基础上做的),大约修改了一万多行代码,上百个 C 文件,其中重写了数千行。这样的变动实在太大,以至于出了罕见的问题后,很让人头痛。新的代码在 Windows 和 Ubuntu 上都一切正常,惟独在 Freebsd 上,程序退出的时候会引起一个 core dump 。

我对 gdb 调试器的高阶运用显然经验不足,加上这几年写代码也不怎么调试。(IMHO ,与其增加调试能力,不如提高设计能力和编写高质量代码的能力;让 bug 不容易出现,或是把 bug 限制在很小的范围,比解决 bug 更重要)在尝试过初步的调试后,我还是使用大脑做静态分析。确定问题出在 so 文件的显式关闭 (主动调用 dlclose)的环节。

February 13, 2009

ExtractAssociatedIcon 的一点问题

今天 卡牌对决 升级,没在 vista 下测试。晚上就有人报告 vista 下运行不了了。

netbug 同学在家里取了一份代码查看。据说错误是出在 ExtractAssociatedIcon 这个 api 的调用内部。是下面这样一行 api 调用:

hIncon = ExtractAssociatedIcon( inst, "loader.exe", &wIndex );

但是 msdn 上看不出所以然来。

之前,vista 下都是运行正常的。netbug 同学说,唯一的区别,以前是 vc6 编译的,而现在换成了 vc9 。

一般情况下,换编译器不会造成 api 调用失败呀?这是为什么呢?

April 03, 2008

记录几个近期碰到的 bug

自己的代码若出了问题,大多数情况我会重写。只要模块划分清楚,设计做好。重写的部分都不会太多。但是别人的代码出问题的话,很多情况下,就只能硬着头皮耐心找了。这就是我这几天的境遇。

前几天我们一个系统更新升级,在公网上一直不太稳定。这次产品上线有点仓促,不过也是内部测试过的。一直没碰到什么大问题,而在公网上情况毕竟复杂的多了,而且排错的压力也比较大,毕竟为玩家服务的程序,公布后就不能随便停掉收回来。这就依赖热更新,在运行期间查错了。

先说昨天晚上让我弄了一通宵的 bug ,留个记录,对以后可能出现类似的问题起点警示。

April 19, 2007

修正了 jpeg 解码器中的一个 bug

我一直喜欢重写代码,唯一写了一次就没再重写的是大学时做的 jpeg decoder ,因为它的大量代码都是汇编写的。想来这段代码已经用了快八年了。渗透到公司的各个项目中。

一直以来,总有同事反应这个 decoder 有点小问题,有些看起来正确的图片,解码会出错。但是换一个图像处理软件重新压缩一遍又好了。

我一直没太在意,直到今天问题又被发现了一次。这次一咬牙,调试吧。

结果只花了不到半个小时就找到了问题,而且是一个超级弱智的 bug 。真想打自己一耳光,当初居然这种错误也能犯。修正完以后急忙通知各个项目的代码仓库负责人,想来这套库的很多分支版本里都存在相同的问题了。

February 12, 2007

lua 近期的一个 bug

在 lua 的 maillist 上最近报告了一个 bug

看起来问题比较严重,因为稍具规模的 lua 程序都可能因此而出现问题。最近两周,我和我的同事都比较关注这个问题,并对 lua 的源代码做了相关的分析。

Roberto 作为 lua 委员会三巨头之一,在 mail 中已经表示追踪到 bug 的起因,但暂时还找不到合适的解决方案。直觉告诉我,这不会是一个简单的问题。如果容易修正的话,patch 早就有了,而不会只是发一个 bug report 而已。所以我们也并未尝试自己去修补这个 bug ,可以做的可能只有等待。