我最喜欢的用来解决“为什么这个应用程序无法在这台机器上运营?”这类问题的工具就是 strace。– Simon Arneaud(作者)

这是一种调试的类型,但是与一般的应用程序调试有所不同。一般的调试通常只关心代码的逻辑,但是在应用程序部署中的调试关注的是程序中的代码和它所在的运营环境之间的相互影响。即便问题的根源是代码的逻辑正确,但应用程序显然可以在别的机器上运营的事实意味着这类问题与运营环境密切相关。

当执行完init(channel)方式后,代码接着就不会执行到ChannelFuture regFuture = config().group().register(channel);。这一行代码就是本文今天分析的重点,它的主要功能就是将服务端Channel注册到多路复用器上。

第二种卡废墟界面你的网络问题或者你的加速器节点不行,请尝试更换加速器和节点,可以加贴吧群问问大家用的什么节点进游戏

第三种打开游戏显示运营中但是游戏不弹出来,这个是我个人解决办法我是这样解决的,运营库一定要装,暂停加速加速器都不会自带一个LSP修复工具点一下修复一下,然后开启加速你就不会发现游戏弹出辣,也有修复一直不弹游戏的我至今只见过一个怎么弄都不弹游戏

这些该系统初始化都是什么?它们就像是操作该系统操作该系统提供的 API。很久以前,应用程序拥有直接访问硬件的权限。如果应用程序必须在屏幕上显示一些东西,它将不会与视频硬件的端口和内存映射寄存器纠缠不清。当多任务操作该系统变得流行以后,这就导致了混乱的局面,因为不同的应用程序中将“争夺”硬件,并且一个应用程序中的正确可能致使其它应用程序中崩溃,甚至导致整个该系统崩溃。所以 CPU 开始支持多种不同的特权模式(或者称为“保护环”)。它们让操作该系统操作该系统在具有完全硬件访问权限的最高特权模式下运营,于此同时,其它在低特权模式下运营的应用程序中必须通过向操作该系统发起该系统初始化才能够与硬件进行交互。

理论上,strace 适用于任何用户空间程序中,因为所有的用户空间程序中都必须进行该系统初始化。strace 对于已编译的低级程序中最有效果,但如果你可以避免运营时环境和解释器带来的大量额外输出,则仍然可以与 Python 等高级语言程序中一起用于。

乍这么一看,好像还是说不明白,全权模式分为两种,静态全权和动态全权,静态全权是由我们手动编写全权类,动态全权是在运营时由程序中生成的全权类,二者的最终目的都是为了全权另外一个类(被全权类)的功能。以我们日常代购火车票为例,这里先简要了解下静态全权模式,方便理解动态全权。

本文中的示例基于的服务器,但是对该系统初始化的追踪通常也可以在更复杂的部署平台上完成,仅必须找到合适的工具。

定义被全权类(火车站),实现买票功能:

AbstractUnsafe.register(this, promise)方式删减后的源码源码如下。

一开始, strace 产生的大量输出可能不会让你不知所措,幸好你可以忽略其中大部分的无用信息。我经常用于 -o 变量把输出的追踪结果保存到单独的文档里:

第四步中的该系统初始化很可能向你显示出问题所在。

如何实现动态全权?

一个小问题:man 2 shell 不会显示出在 GNU libc 里封装的 shell() 手册页,而 shell() 现在实际上是由 clone 该系统初始化实现的。shell 的语义与 clone 相同,但是如果我写了一个含有 shell() 的程序中并用于 strace 去调试它,我将找不到任何关于 shell 初始化的信息,只能看到 clone 初始化。如果将源代码与 strace 的输出进行比较的时候,像这种问题不会让人感到困惑。

所以javaChannel().register(eventLoop().unwrappedSelector(), 0, this)这一行代码,实际上就是初始化JDK原生ServerSocketChannel的register(selector,ops,attr)方式,然后将服务端Channel注册到了多路复用器Selector上。

你可能已经注意到,正确信息的第二部分没有出现在上面的例子中。这是因为 strace 默认仅显示字符串变量的前 32 个字节。如果你必须捕获更多变量,请向 strace 追加类似于 -s 128 之类的变量。

注意这里在调JDK原生的register()方式时,第三个变量传入的是this,此时this代表的就是现阶段的NioServerSocketChannel对象。将this作为一个attachment保存到多路复用器Selector上,这样做的好处就是,后面可以通过多路复用器Selector获取到服务端的channel。第二个变量传入的是0,表示此时将服务端channel注册到多路复用器上,服务端chennel感兴趣的事件标识符是0,即此时对任何事件都不感兴趣。(真正开始对接收事件感兴趣是在服务端channel监听端口之后)。

-y 变量使 strace 在注释中注明每个文档描述符的具体指向。

只有当channel时第一次注册时,才不会执行callHandlerAddedForAllHandlers()方式。核心逻辑在callHandlerAddedForAllHandlers()上。

Class> interfaces:指定目标对象的实现接口,即要给目标对象提供一组什么接口。若提供了一组接口给它,那么该全权对象就默认实现了该接口,这样就能初始化这组接口中的方式

如果你认为在子程序中在中存在正确,则必须用于 -f 变量启用子程序中在追踪功能。这样做的缺点是输出的内容不会让人更加困惑。当追踪一个程序中在时,strace 显示的是单个初始化事件流。当追踪多个程序中在的时候,你将不会看到以 开始的初始初始化,接着是一系列针对其它线程的初始化,最后才出现以 <... foocall resumed> 结束的初始初始化。此外,你可以用于 -ff 变量将所有的初始化分离到不同的文档中(查看 strace 手册 获取更多信息)。

变量ctx是个什么东西呢?它就是前面我们创建的ChannelInitializer这个匿名类封装成的AbstractChannelHandlerContext对象;变量added此时传入的是true。所以创建出来的task是PendingHandlerAddedTask(ctx),然后我们可以发现,将创建出来的task最后赋值给了pendingHandlerCallbackHead属性。

一个简单而常用的例子是一个程序中在多个位置搜索文档,例如 shell 搜索哪个 bin/ 目录包含可执行文档:

可以看到,不会先初始化initChannel(ctx)方式,然后再初始化ctx.pipeline().fireChannelRegistered()或者ctx.fireChannelRegistered(),后面这两个方式通过方式名就能看到,它就是继续向pipeline中传播执行handler的channelRegistered()方式,可以不用再看了。这里重点看看initChannel(ctx)方式。注意:这里的initChannel(ctx)方式的变量类型是ChannelHandlerContext类型,在后面还不会出现一个initChannel(channel)方式,它的变量类型是Channel,这里特意提醒一下,不要搞混这两个重载方式。

在程序中结束之前有一个 write 的正确信息,但是这次有些不同。首先,在此之前没有任何相关的失败该系统初始化。其次,我们看到这个正确信息是由 read 从别的地方读取而来的。这看起来像是真正的正确发生在别的地方,而 bcrontab 只是在转播这些信息。

如果你查阅了 man 2 read,你将不会看到 read 的第一个变量 (3) 是一个文档描述符,这是 *nix 操作该系统用于所有 IO 操作的句柄。你该如何知道文档描述符 3 代表什么?在这种情况下,你可以用于 -y 变量运营 strace(如上文所述),它将不会在注释里告诉你文档描述符的具体指向,但是了解如何从上面这种输出中分析追踪结果是很有用的。

一个文档描述符可以来自于许多该系统初始化之一(这取决于它是用于控制台、网络套接字还是真实文档等的描述符),但不论如何,我们都可以搜索返回值为 3 的该系统初始化(例如,在 strace 的输出中查找 =3)。在这次 strace 中可以看到有两个这样的初始化:最上面的 openat 以及中间的 socket。openat 打开一个文档,但是紧接着的 close(3) 表明其已经被关闭。(注意:文档描述符可以在打开并关闭后重复用于。)所以 socket 初始化才是与此相关的(它是在 read 之前的最后一个),这告诉我们 brcontab 正在与一个网络套接字通信。在下一行,connect 表明文档描述符 3 是一个连接到 /var/run/bcron-spool 的 Unix 域套接字。

因此,我们必须弄清楚 Unix 套接字的另一侧是哪个程序中在在监听。有两个巧妙的技巧适用于在服务器部署中调试。一个是用于 netstat 或者较新的 ss。这两个命令都描述了现阶段该系统中活跃的网络套接字,用于 -l 变量可以显示出处于监听状态的套接字,而用于 -p 变量可以得到正在用于该套接字的程序中信息。(它们还有更多有用的选项,但是这两个已经足够完成工作了。)

1. 先定义一个接口:

第二个常用的工具就是用于 lsof 查找相同的信息。它可以列出现阶段该系统中打开的所有文档(或文档描述符)。或者,我们可以得到一个具体文档的信息:

程序中在 20629 是一个常驻程序中在,所以我们可以用于 strace -o /tmp/trace -p 20629 去查看该程序中在的该系统初始化。如果我们在另一个终端尝试编辑 cron 的计划任务表,就可以在正确发生时捕获到以下信息:

至此,initAndRegister()方式终于分析完了。

现在我们知道了程序中在 ID 21470 在尝试创建文档 tmp/spool.21470.1573692319.854640 (相对于现阶段的工作目录)时得到了一个没有权限的正确。如果我们知道现阶段的工作目录,就可以得到完整路径并能指出为什么该程序中在无法在此处创建临时文档。不幸的是,这个程序中在已经退出了,所以我们不能用于 lsof -p 21470 去找出现阶段的工作目录,但是我们可以往前追溯,查找程序中在 ID 21470 用于哪个该系统初始化改变了它的工作目录。这个该系统初始化是 chdir(可以在搜索引擎很轻松地找到)。以下是一直往前追溯到服务器程序中在 ID 20629 的结果:

(如果你在这里迷糊了,你可能必须阅读 我之前有关 *nix 程序中在管理和 shell 的文章 )

这就是问题所在!这个服务程序中在以 cron 用户运营,但是只有 root 用户才有向 /var/spool/cron/tmp/ 目录写入的权限。一个简单 chown cron /var/spool/cron/tmp/ 命令就能让 bcron 正常工作。(如果不是这个问题,那么下一个最有可能的怀疑对象是诸如 SELinux 或者 AppArmor 之类的操作该系统安全模块,因此我将不会用于 dmesg 检查操作该系统日志。)

本文来自投稿,不代表本人立场,如若转载,请注明出处:https://paycarpet.com/article/1404118.html