惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

量子位
L
LINUX DO - 最新话题
TaoSecurity Blog
TaoSecurity Blog
S
Security Affairs
H
Hacker News: Front Page
Recent Commits to openclaw:main
Recent Commits to openclaw:main
Hacker News: Ask HN
Hacker News: Ask HN
T
The Exploit Database - CXSecurity.com
P
Proofpoint News Feed
Google DeepMind News
Google DeepMind News
Schneier on Security
Schneier on Security
云风的 BLOG
云风的 BLOG
I
InfoQ
The Register - Security
The Register - Security
T
Tor Project blog
T
Threat Research - Cisco Blogs
Spread Privacy
Spread Privacy
cs.AI updates on arXiv.org
cs.AI updates on arXiv.org
The GitHub Blog
The GitHub Blog
MongoDB | Blog
MongoDB | Blog
Webroot Blog
Webroot Blog
Recent Announcements
Recent Announcements
Vercel News
Vercel News
F
Fortinet All Blogs
CTFtime.org: upcoming CTF events
CTFtime.org: upcoming CTF events
SecWiki News
SecWiki News
G
Google Developers Blog
N
Netflix TechBlog - Medium
U
Unit 42
Martin Fowler
Martin Fowler
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
O
OpenAI News
博客园 - 叶小钗
T
Tailwind CSS Blog
爱范儿
爱范儿
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
Help Net Security
Help Net Security
A
About on SuperTechFans
Recorded Future
Recorded Future
Last Week in AI
Last Week in AI
Hugging Face - Blog
Hugging Face - Blog
K
KPMG report finds enterprise disconnect between AI and its ROI | CIO
D
DataBreaches.Net
D
Darknet – Hacking Tools, Hacker News & Cyber Security
T
The Blog of Author Tim Ferriss
PCI Perspectives
PCI Perspectives
F
Full Disclosure
美团技术团队
L
Lohrmann on Cybersecurity
H
Hackread – Cybersecurity News, Data Breaches, AI and More

重归混沌的BLOG

给silly实现了一个ernro模块 | 重归混沌的BLOG 给silly实现了一个ernro模块 | 重归混沌的BLOG 第一次在生产环境使用 Vibe Coding | 重归混沌的BLOG 第一次在生产环境使用 Vibe Coding | 重归混沌的BLOG API 设计的艰难抉择 | 重归混沌的BLOG API 设计的艰难抉择 | 重归混沌的BLOG 十年 | 重归混沌的BLOG 十年 | 重归混沌的BLOG 在Go语言中如何使XML加载内存无限趋近于0 | 重归混沌的BLOG 在Go语言中如何使XML加载内存无限趋近于0 | 重归混沌的BLOG 对跨服玩法中的分布式一致性问题进行简单抽象 | 重归混沌的BLOG 对跨服玩法中的分布式一致性问题进行简单抽象 | 重归混沌的BLOG Go语言逃逸分析之slice和map | 重归混沌的BLOG Go语言逃逸分析之slice和map | 重归混沌的BLOG 谈谈观测 | 重归混沌的BLOG 谈谈观测 | 重归混沌的BLOG 写了个AI Agent服务端 | 重归混沌的BLOG 写了个AI Agent服务端 | 重归混沌的BLOG 谈谈代码设计中“严丝合缝” | 重归混沌的BLOG 谈谈代码设计中“严丝合缝” | 重归混沌的BLOG 一次艰难的线上游戏服务器内存排查经历 | 重归混沌的BLOG 一次艰难的线上游戏服务器内存排查经历 如何基于LanguageServerProtocol来编写lint工具 谈谈游戏服务器中RPC模块的设计 谈谈游戏服务器代码抽象 最近碰到的一个分布式一致性问题 谈谈游戏服务器的自动化测试 对Raft协议的一点理解 使用mmap来学习/proc/pid/smaps 2023(完) 再次实现了一个Lua性能分析器 终于给Silly的定时器增加了取消功能 一次虚拟内存排查经历 游戏服务器分布式数据的一种同步的思路 为silly增加了互斥锁 2022(完) Go语言之闭包篇 一例误用unsafe包引起的内存问题 Go语言之内存篇 初识Go语言 重新抽象图形API 给Lua实现了一个数学库 谈谈跨平台图形API的抽象 寻路和Flocking算法的结合 行为树的一种高效实现 内测过程中Shader出现的问题 彻底解决多国语言 谈谈数据库的选型 再谈Lua热更新(终) 初窥Rust 关于游戏服务器的服务拆分 ECS的初步实现 ECS初探 屏幕空间(SreenSpace)的想象力 一些对辐射度量学的理解 深度缓冲和半透明渲染 Mysql的间隙锁 更新一些GPU相关知识 2020 地形渲染之爬过的坑 Lua5.3 GC源码阅读(5) 实现一个数据库存储队列 再学计算机图形学入门 再谈分布式服务架构 游戏上线一个月后的反思 一次并发Bug 双向链表的三种实现 谈谈随机数的使用 再谈性能优化 2019 Lua中的函数式编程 重构登录逻辑 Unity资源管理(续) 谈谈Unity的资源管理 一次关于Cache的性能分析 历史之2018 DC3算法 移动平台native代码遭遇的坑 从CPU层面谈谈优化 开卷有益(UNIX编程艺术篇) GC竞争问题 通过Mesh投影来实现贴花系统 谈谈我对数据同步的理解 又一个类型提升引起的Bug Lua5.3 GC源码阅读(4) Lua5.3 GC源码阅读(3) Lua5.3 GC源码阅读(2) Lua5.3 GC源码阅读(1) 三角形光栅化时遇到的坑 一次git事故 再见2017 客户端缓存落地方案 Paxos算法 HTTP服务器的特点 一次性能优化经历 关于CPU分支预测 C程序中让两个不同版本的库共存 实现了一个AOI模块 一个高可伸缩的游戏服务器架构 关于网络协议封装的一些新想法
又一个lua调试器
重归混沌 · 2017-12-17 · via 重归混沌的BLOG

最初,我并没有打算为Silly提供一个lua调试器,因为我本人不是一个重度调试器使用者。在开发期间出bug,看一下代码,打几条log可能比使用调试器会更快的找到问题,尤其是服务端程序,在很多时候其实只有log这一原始工具可以使用。但就我周围的人来推断,应该还是有很多重度调试器使用者。

因此,最终还是计划为Silly增加一个lua调试器作为基本组件存在。但是这个调试器到底以哪种方式提供调试功能,我一直在纠结中。

就我个人而言,我用过至少两种完全不同的调试器,gdb和dtrace(严格来讲dtrace可能不算是调试器)。

gdb主要用于开发期间进行阻塞式调试,虽然也可以写调试脚本用在生产环境中调试,但是在gdb加载符号文件时,延迟比较明显。

dtrace提供的是动态跟踪手段,在程序中放置相应的探针,当程序执行到探针的位置时,就执行这个探针上的函数。在放置探针时几乎没有卡顿延迟的现象,而且在放置探针后其对程序的整体执行性能影响甚小。因此在特殊时期可以用于生产环境调试使用。

最开始我觉得也许应该为Silly增加一个类似dtrace的调试器。这个调试器只提供一个probe接口,probe有两个参数,参数1是代码位置(文件名与行号),参数2是一个探针函数。当调用probe注册一个探针之后,代码执行到指定行时,就会执行调用probe时注册的探针函数。这样当线上出问题时,就可以像dtrace一样编写一段代码注入到程序中,将我们所关心的信息全部dump出来,而不会影响程序的正常执行。

但是,最近重新仔细思考发现,dtrace之所以可以如此高效,是因为他可以有各种加速手段。比如在增加探针时,可以通过调试信息快速定位到要增加的探针在CPU指令的哪个位置,还可以通过int 3中断被动触发探针函数等(这些只是大概的猜测,具体实现也许不同,但是至少可以证明的是,他可以非常高效的确定探针函数是否应该被执行)。而在lua中,即使仅仅确定什么时间探针函数需要被执行,也至少需要在全局hook事件”call”,在每次触发”call”事件时,遍历检测是否有探针触发。这会极大的拖慢程序的整体性能。因此即使实现了探针机制也不适合在生产环境做调试使用。

既然不可以在生产环境使用,那么做成类dtrace的方式也就没有必要了。如果是在开发期间使用,还是Gdb那种阻塞交互式调试最方便易用。

这不是第一次实现lua调试器了,两年前我就实现了一个非常简易并且低效的阻塞式调试器

当时我们的客户端代码采用lua编写,偶尔会出现一些偶发性bug,并且出错的地方总是一些没有提前预料到的地方,因此没有任何log。而当我们在出错的地方加上详细的log再重启客户端后,问题又不能被重现了。

这个调试器就是这种情况下产生的,他的作用仅仅是当出现偶发性bug时,能够在不重启客户端的情况下,打印出我们想要的上下文。因此在实现时一切从简,只有s(单步步入)、c(继续运行)、 b(设置断点)和 d(删除断点功能)的功能,并且没有考虑整个调试器的效率。


这次实现,希望除了要具备上一个调试器所有功能以外,还要支持gdb中命令n(单步步过)的功能。此外,它还应该是高效的。

在Silly中,所有的代码都是被包在某一个coroutine中被执行的,整个程序运行期间,可能会有数十或上百个coroutine并发执行。因此在调试某一个coroutine中运行的代码时,应该尽可能的保证其他coroutine应该可以不受调试器的干扰而正常执行。

这样就不能使用之前调试器中的方式(直接阻塞等待命令输入)来实现阻塞式调试器。因为那样会塞住整个Silly进程,所有的coroutine都会被卡住。因此,整个lua调试器的命令输入均来源于socket, 这样可以充分利用Silly自身的高并发能力而减少阻塞。

我把整个调试器实现成了一个调试状态机,共分为三个状态,分别是:checkcall, checkline、checkbreak。

程序运行时,整个调试器处于checkcall状态。在checkcall状态,调试器会检测所有的coroutine的”call”事件。只检测“call”事件可以最大程度保证程序的运行效率,而之所以会检测所有的coroutine,是因为这时还不知道被断点的代码将会在哪个coroutine上被执行。

每次触发”call”事件后,都会检测此函数中是否被设置断点。如果被设置断点,则进入checkline状态。

进入checkline后,将程序中所有的coroutine的事件检测取消,只保留当前coroutine的”call/return/line”事件,并且此后,所有针对事件检测范围的修改均只影响此coroutine。

在每个line事件被触发后,都要检测此行代码是否被设置断点,如果有则进入checkbreak状态。

需要说明的这里有一个可能存在的优化,比如下面代码。

function foo(x)
...
end

function bar()
local x = 3
...
foo(x)
local y = 10
...
end

假设在checkcall状态下触发”call”事件的是bar函数,并且有断点设置在bar函数内。这时调试状态机会进入checkline状态,如果foo函数中没有断点的存在,理论上我们只需要检测bar函数中所有行是否触发断点即可,也就是在执行foo函数时,不必触发”line”事件。这也是在checkline状态,我们需要检测”call”和”return”事件的原因。

checkline状态中,如果触发了断点,则挂起当前coroutine并进入checkbreak状态。

checkbreak状态主要处理用户命令输入比如b(设置断点)、d(删除断点)、s(单步步入),n(单步步过)、c(继续运行)等命令。

b/d/c命令都没什么好说的,无非就是向断点列表中增加删除断点信息,及将因为触发断点而被挂起的coroutine唤醒。

命令s的实现是参照Intel x86 CPU架构中的TF标志的思路,即如果单步标志位被置起,则每执行一行都会有触发断点的效果(当前coroutine被挂起等待,用户下一次命令输入)。

命令n在实现时,其实费了颇多周折。如果这一行的代码是调用一个函数,命令s会直接进入到这个被调用的函数的上下文并暂停执行并等待用户输入命令。而命令n则是直到这个被调用的函数返回才会暂停执行。

最开始是通过设置临时断点的方式来实现的,每执行一行,就为下一行设置一个临时断点,后来发现当函数执行到最后一行时,下一行的临时断点就失效了。

经过多次分析总结之后,发现命令n和s惟一的差别仅在于函数调用这里,其余情况下,n与s的作用一模一样。

有了这个关系,问题就简单多了。引入一个变量calllevel代表调用层数,每当call事件触发时,calllevel加1,每当return事件触发时,calllevel则减1。这样calllevel为0时,命令n就可以和命令s的处理方式相同。

这里同样有优化的空间,在“call”事件响应中,如果calllevel大于1,说明我只关心”return”事件,就可以把“line”事件的检测取消。

虽然我前面说要做一个高效的调试器,然而这个版本其实还是有很多可以优化的空间,比如可以重新设置断点列表的数据结构,以使在checkcall状态时可以尽快检测当前函数中是否有断点等。不过,至少这个版本,在使用lua debug hook上,我已经做到了尽可能的高效。