【问题标题】:_XReply() terminates app with _XIOError()_XReply() 使用 _XIOError() 终止应用程序
【发布时间】:2014-05-26 13:41:33
【问题描述】:

我们正在开发一些复杂的应用程序,其中包含从我们定制的 .jar 文件与 java jni 调用(来自在 linux 二进制文件中创建的 JVM)集成的 linux 二进制文件。所有的 gui 工作都是由 java 部分实现和完成的。每次需要更改某些 gui 属性或必须重新绘制 gui 时,都是通过 jni 调用 JVM 来完成的。

在 JVM/java 可以处理的情况下,重新绘制(或刷新)完整的显示/gui。它以迭代和频繁的方式完成,每秒数百或数千次迭代。

在某个确切的时间后,应用程序以exit(1) 终止,我用gdb 捕获了它从_XIOError() 调用。这种终止可以在或多或少精确的时间段之后重复,例如在 x86 双核 2.5GHz 上运行 15 小时后。如果我使用一些速度较慢的计算机,它会持续更长时间,就像它与 cpu/gpu 速度成正比一样。一些结论是 xorg 的某些部分耗尽了某些资源或类似的东西。

这是我的回溯:

#0  0xb7fe1424 in __kernel_vsyscall ()
#1  0xb7c50941 in raise () from /lib/i386-linux-gnu/i686/cmov/libc.so.6
#2  0xb7c53d72 in abort () from /lib/i386-linux-gnu/i686/cmov/libc.so.6
#3  0xb7fdc69d in exit () from /temp/bin/liboverrides.so
#4  0xa0005c80 in _XIOError () from /usr/lib/i386-linux-gnu/libX11.so.6
#5  0xa0003afe in _XReply () from /usr/lib/i386-linux-gnu/libX11.so.6
#6  0x9fffee7b in XSync () from /usr/lib/i386-linux-gnu/libX11.so.6
#7  0xa01232b8 in X11SD_GetSharedImage () from /usr/lib/jvm/jre1.8.0_20/lib/i386/libawt_xawt.so
#8  0xa012529e in X11SD_GetRasInfo () from /usr/lib/jvm/jre1.8.0_20/lib/i386/libawt_xawt.so
#9  0xa01aac3d in Java_sun_java2d_loops_ScaledBlit_Scale () from /usr/lib/jvm/jre1.8.0_20/lib/i386/libawt.so

我在 liboverrides.so 中创建了自己的 exit() 调用,并在 abort()/SIGABRT 的帮助下将其与 LD​​_PRELOAD 一起用于捕获 gdb 中的 exit() 调用。 在对 libX11 和 libxcb 进行了一些调试之后,我注意到 _XReply() 得到了 NULL 回复(来自 xcb_wait_for_reply() 的响应),这导致了对 _XIOError()exit(1) 的调用。在 xcb_wait_for_reply() 函数中更深入地研究 libxcb,我注意到它可以返回 NULL 回复的原因之一是它检测到断开或关闭的套接字连接,这可能是我的情况。

出于测试目的,如果我更改 xcb_io.c 并忽略 _XIOError(),应用程序将不再工作。如果我在_XReply() 内重复请求,它每次都会失败,即在每个xcb_wait_for_reply() 上都得到 NULL 响应。

所以,我的问题是为什么会发生这种不受控制的应用程序终止,并从 _XReply() -> XIOError() -> exit(1) 退出(1),或者我如何找出发生的原因和原因,以便我可以修复它或做一些解决方法。

要重复这个问题,正如我上面写的,我必须等待大约 15 小时,但目前我的调试时间非常短,无法找到问题/终止的原因。 我们还尝试重新组织处理 gui/display 刷新的 java 部分,但问题没有解决。

一些 SW 事实:
- java jre 1.8.0_20,即使用java 7也可以重复问题
- libX11.so 1.5.0
- libxcb.so 1.8.1
- debian wheezy
- 内核 3.2.0

【问题讨论】:

    标签: java linux x11 xorg xcb


    【解决方案1】:

    这可能是 libX11 中有关处理用于 xcb_wait_for_reply 的请求号的已知问题。

    在引入 libxcb v1.5 代码以在内部各处使用 64 位序列号后的某个时间点,并添加了逻辑以在进入那些仍采用 32 位序列号的公共 API 时扩大序列号。

    这是来自submitted libxcb bug report 的引用(实际电子邮件已删除):

    我们有一个执行大量 XDrawString 和 XDrawLine 的应用程序。 几个小时后,应用程序因 XIOError 而退出。

    XIOError 在文件 xcb_io.c 中的 libX11 中调用,函数 _X回复。它没有得到 xcb_wait_for_reply 的响应。

    libxcb 1.5 很好,libxcb 1.8.1 不是。二等分 libxcb 指向 这个提交:

    提交 ed37b087519ecb9e74412e4df8f8a217ab6d12a9 作者:Jamey 截止日期:2010 年 10 月 9 日星期六 17:13:45 -0700

    xcb_in: Use 64-bit sequence numbers internally everywhere.
    
    Widen sequence numbers on entry to those public APIs that still take
    32-bit sequence numbers.
    
    Signed-off-by: Jamey Sharp <jamey@xxxxxx.xxx>
    

    在 1.8.1 之上恢复它会有所帮助。

    向 libxcb 添加跟踪我发现最后一个请求号用于 xcb_wait_for_reply 是这些:4294900463 和 4294965487(两个调用 _XReply 函数的 while 循环),半秒后:63215 (然后调用 XIOError)。 widen_request 也是 63215,我会 预计 63215+2^32。因此,请求似乎不是 正确加宽。

    上面的提交也将 poll_for_reply 中的比较从 XCB_SEQUENCE_COMPARE_32 到 XCB_SEQUENCE_COMPARE。也许是扩大 从未正常工作,但从未观察到,因为只有 比较了较低的 32 位。

    重现问题

    这是提交的错误报告中用于重现问题的原始代码 sn-p:

      for(;;) {
        XDrawLine(dpy, w, gc, 10, 60, 180, 20);
        XFlush(dpy);
      }
    

    显然这个问题可以用更简单的代码重现:

     for(;;) {
        XNoOp(dpy);
      }
    

    根据提交的 libxcb 错误报告,这些条件需要重现(假设重现代码在 xdraw.c 中):

    • libxcb >= 1.8(即包括提交 ed37b08)
    • 32位编译:gcc -m32 -lX11 -o xdraw xdraw.c
    • 序列计数器换行。

    建议的补丁

    可以在 libxcb 1.8.1 之上应用的建议补丁是这样的:

    diff --git a/src/xcb_io.c b/src/xcb_io.c
    index 300ef57..8616dce 100644
    --- a/src/xcb_io.c
    +++ b/src/xcb_io.c
    @@ -454,7 +454,7 @@ void _XSend(Display *dpy, const char *data, long size)
            static const xReq dummy_request;
            static char const pad[3];
            struct iovec vec[3];
    -       uint64_t requests;
    +       unsigned long requests;
            _XExtension *ext;
            xcb_connection_t *c = dpy->xcb->connection;
            if(dpy->flags & XlibDisplayIOError)
    @@ -470,7 +470,7 @@ void _XSend(Display *dpy, const char *data, long size)
            if(dpy->xcb->event_owner != XlibOwnsEventQueue || dpy->async_handlers)
            {
                    uint64_t sequence;
    -               for(sequence = dpy->xcb->last_flushed + 1; sequence <= dpy->request; ++sequence)
    +               for(sequence = dpy->xcb->last_flushed + 1; (unsigned long) sequence <= dpy->request; ++sequence)
                            append_pending_request(dpy, sequence);
            }
            requests = dpy->request - dpy->xcb->last_flushed;
    

    详细的技术说明

    请在下面找到包含detailed technical explanation by Jonas Petersen(也包含在上述错误报告中):

    嗨,

    这里有两个补丁。第一个修复了 32 位序列换行错误。 第二个补丁只对另一个相关语句添加了注释。

    补丁包含一些细节。这是谁的全部故事 可能有兴趣:

    Xlib (libx11) 将使应用程序崩溃,并显示“致命 IO 错误 11 (资源暂时不可用)”在 4 294 967 296 次请求后 服务器。这就是 Xlib 内部 32 位序列回绕的时候。

    大多数应用程序可能很难达到这个数字,但如果他们 做到了,他们就有机会神秘死去。例如 我正在处理的应用程序总是在大约 20 小时后崩溃 我开始做一些压力测试。它做了一些密集的绘图 通过使用 gktmm2、像素图和 gc 绘制的 Xlib,每帧 40 帧 全高清分辨率第二(在 Ubuntu 上)。做了一些优化 将宽限期延长到大约 35 小时,但它仍然会崩溃。

    接下来是令人沮丧的几周的挖掘和调试 意识到它不在我的应用程序中,也不在 gtkmm、gtk 或 glib 中 但这是自 2006-10-06 以来存在的 Xlib 中的这个小错误 显然。

    花了一段时间才发现数字 0x100000000 (2^32) 有 一些相关性。 (很多)后来证明它可以用 仅限 Xlib,例如使用以下代码:

    而(1){ XDrawPoint(display, drawable, gc, x, y); XFlush(显示); }

    可能需要一两个小时,但当它达到 42.94 亿 它会爆炸成“致命的 IO 错误 11”。

    然后我了解到,即使 Xlib 使用内部 32 位 他们在这个过程中(巧妙地)扩大到 64 位的序列号 这样 32 位序列可以在没有任何中断的情况下换行 加宽 64 位序列。显然肯定有什么问题 那个。

    在 _XReply() 中未收到致命 IO 错误 在应该有的地方回复,但原因在 _XSend() 中更早 在 Xlib 32 位序列号换行的那一刻。

    问题是当它换成 0 时,'last_flushed' 的值 仍将位于上限(例如 0xffffffff)。有两个 地点在 _XSend() (xcb_io.c) 在这种状态下失败,因为它们依赖于这些值一直是连续的,第一个位置是:

    requests = dpy->request - dpy->xcb->last_flushed;

    I case of request = 0x0 and last_flushed = 0xffffffff 它将分配 0xffffffff00000001 到“请求”,然后到 XCB 作为数字(金额) 的请求。这是主要杀手。

    第二个位置是这样的:

    for(sequence = dpy->xcb->last_flushed + 1; 序列 request; \ ++序列)

    I case of request = 0x0 (小于 last_flushed) 没有机会 永远进入循环,结果一些请求被忽略了。

    解决方案是在这两个位置“解包”dpy->request 并 从而保留last_flushed相关的序列。

    uint64_t unwrapped_request = ((uint64_t)(dpy->request xcb->last_flushed) request;

    它会创建一个临时的 64 位请求编号,如果该编号设置了第 8 位,则 'request' 小于 'last_flushed'。然后在两个中使用 位置而不是 dpy->request。

    我不确定使用该语句是否更有效 就地,而不是使用变量。

    require_socket() 中还有一行让我开始担心:

    dpy->xcb->last_flushed = dpy->request = sent;

    这是一个 64 位、32 位、64 位的赋值。它将截断“发送”到 将其分配给“请求”时为 32 位,然后还分配 截断值到(64 位)“last_flushed”。但这似乎很重要。 我添加了一个注释,解释下一个可怜的灵魂调试 序列问题... :-)

    • 乔纳斯

    Jonas Petersen (2): xcb_io: Fix Xlib 32-bit request number wrapping xcb_io: 添加注释解释混合类型双重赋值

    src/xcb_io.c | 14 +++++++++++--- 1 个文件已更改,11 次插入 (+), 3个删除(-)

    -- 1.7.10.4

    祝你好运!

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2015-04-21
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-01-13
      • 2019-08-05
      • 2011-08-29
      相关资源
      最近更新 更多