• 首页
  • 关于
  • 标签
  • 归档
  • 欢迎投稿
  • 跳至内容

    xtrace

    介绍

    xtrace是一个用于跟踪分析X11图形协议通信的工具,它可以监控和记录X11服务器上的各种场景,以帮助开发人员诊断和调试与图形界面相关的问题。作为一款强大的工具,xtrace可用于逆向工程、调试分析、性能分析等领域。在Linux X11系统中,xtrace能够记录一个程序在运行时所发起的X11协议请求和XServer发送给程序的事件,以及这些调用的参数。这对于在不阅读源码情况排查程序中的问题、理解程序行为、分析性能瓶颈以及进行协议审计都非常有用。笔者在工作过程中使用该工具深度剖析过腾讯会议、simplescreenrecorder等应用程序的实现,在没有阅读代码的前提下可以获得软件录屏的工作流程,配合阅读常规的X11录屏代码,可分析出其部分工功能异常原因,这些经验在xwayland适配X11应用程序截图录屏项目中通过实战解决了一系列问题。

    本文主要介绍的是X11协议的监视工具,希望您在读完本篇文章后可以对如何监控X11协议有比较深刻的认知,在工作中经常会碰到一些X11应用程序运行时功能异常,在没有应用程序源码的情况下通过xtrace进行调试是一个不错的选择,希望在阅读完这篇文章后,能丰富您的调试技巧。祝您阅读愉快!

    xtrace的安装和使用非常简单,打开终端像如下输入命令即可启动工具:

    // UOS上安装xtrace,deepin上没有可以在http://snapshot.debian.org/上下载
    sudo apt install xtrace
    
    // 运行之后协议电报内容会存储在/tmp/dde-calendar.xtrace中
    xtrace -n -o /tmp/dde-calendar.xtrace dde-calendar	
    
    命令行选项 含义或用途
    –display, -d 用于ssh远程调试,例如:xtrace -d :0 dde-calendar
    –outfile, -o 用于将通信内容转存到磁盘,例如:xtrace -o /tmp/x.log dde-calendar
    –stopwhendone, -s 进程退出后停止xtrace,例如:xtrace -s dde-calendar

    工作原理

    如图1所示,X11其主要有两种通信方式,在同一个计算机内主要使用unix domain socket进行通信;在不同计算机之间使用TCP/IP通信。只需要“截获”通信消息,将其转为易于读取的格式输出即可达到监控目的。这是xtrace工作的基本原理。

    图1. 不同客户端使用同一个XServer显示器框架图

    如下图2是X11窗口创建的基本流程,因为篇幅限制,图中只绘制了基础的请求和事件,但X11协议远比图中绘制的复杂,笔者在此只介绍一下基础的协议交互模型,方便在读者在查看xtrace追踪日志时有基础的认知。

    图2. X11协议交互流程

    总之xtrace是Xorg X Server自带的一个工具,通过分析xtrace的输出,你可以了解X Server是如何处理客户端应用程序的请求,以及可能的问题所在。xtrace 工具的作用:

    • 调试X问题:如果你遇到了与图形界面相关的问题,例如窗口无法正常显示、图形卡驱动问题等,你可以使用xtrace来捕获X Server的活动,帮助你找出问题所在。
    • 性能分析:xtrace可以记录X Server内部的操作,帮助你分析系统的图形性能,找出潜在的瓶颈。
    • 理解X协议交互:X Server与客户端应用程序之间的通信是通过X协议进行的。xtrace可以捕获这些通信,帮助你理解应用程序与X Server之间的交互方式。
    • 功能逆向:对于依赖X11协议的软件无法查看源码,可以通过观察通信协议,逆向其部分核心功能的实现 请注意,xtrace的输出可能会非常详细,因此在使用时需要注意过滤和分析输出,以便关注于你感兴趣的信息。

    X11在文件系统中暴露了通信用的socket套接字文件,这使得第三方应用程序可以监控套接字文件从而“窥视"X11客户端和服务端之间的通信,这一技术点是xtrace工作的主要基础。

    // X11 socket套接字文件在文件系统中的位置
    ls /tmp/.X11-unix
    X0  X9
    

    如下日志所示xtrace工作的本质就是不断地获取wrote和received的数据然后将其解析为易于阅读的描述语言打印出来。

    // 全量日志
    001:>:received 32 bytes
    001:>:09dc:32: Reply to InternAtom: atom=0x250("_NET_KDE_COMPOSITE_TOGGLING")
    001:>:wrote 32 bytes
    001:>:received 32 bytes
    001:>:09dc: Event XKEYBOARD-XkbEvent(85) type=2 time=0x018d8071 device=0x03 not-yet-supported=0x10,0x00,0x00,0x10,0x01,0x00,0x00,0x00,0x00,0x01,0x90,0x10,0x10,0x10,0x90,0x00,0x01,0x90,0x11,0x00,0x00,0x87,0x05;
    001:>:wrote 32 bytes
    001:>:received 32 bytes
    001:>:09dc: Event XKEYBOARD-XkbEvent(85) type=2 time=0x018d8072 device=0x03 not-yet-supported=0x10,0x00,0x00,0x10,0x00,0x00,0x00,0x00,0x00,0x00,0x10,0x10,0x10,0x10,0x10,0x00,0x01,0x90,0x11,0x00,0x00,0x87,0x05;
    001:>:wrote 32 bytes
    001:>:received 32 bytes
    001:>:09dc: Event XKEYBOARD-XkbEvent(85) type=2 time=0x018d8073 device=0x03 not-yet-supported=0x10,0x00,0x00,0x10,0x01,0x00,0x00,0x00,0x00,0x01,0x90,0x10,0x10,0x10,0x90,0x00,0x01,0x90,0x11,0x00,0x00,0x87,0x05;
    

    其工作流程大致如下:

    • 通过套接字地址族AF_INET判断是tcp通信还是本地domain socket通信,然后通过generateSocketName或者calculateTCPport拿到addr相关的信息
    • 进入mainqueue死循环读取socket通信数据
    • 通过parse_server(c)翻译Event事件通信,然后打印输出
    • parse_client(c)翻译Requst请求通信,然后打印输出

    相关的代码调用堆栈如下:

    Breakpoint 1, startline (c=0x4f8270, d=TO_SERVER, format=0x417a71 "%04x:%3u: Request(%hhu): %s ") at parse.c:52
    52              if( (print_timestamps || print_reltimestamps)
    (gdb) bt
    #0  startline (c=0x4f8270, d=TO_SERVER, format=0x417a71 "%04x:%3u: Request(%hhu): %s ") at parse.c:52
    #1  0x000000000040b5f5 in print_client_request (c=0x4f8270, bigrequest=false) at parse.c:1692
    #2  0x000000000040cc2d in parse_client (c=0x4f8270) at parse.c:1996
    #3  0x0000000000403b4a in mainqueue (listener=4) at main.c:406
    #4  0x00000000004045d4 in main (argc=4, argv=0x7fffffffdef8) at main.c:706
    (gdb) c
    Continuing.
    
    Breakpoint 1, startline (c=0x4f8270, d=TO_CLIENT, format=0x417b32 "%04x:%u: Reply to %s: ") at parse.c:52
    52              if( (print_timestamps || print_reltimestamps)
    (gdb) bt
    #0  startline (c=0x4f8270, d=TO_CLIENT, format=0x417b32 "%04x:%u: Reply to %s: ") at parse.c:52
    #1  0x000000000040c1f3 in print_server_reply (c=0x4f8270) at parse.c:1859
    #2  0x000000000040d0e5 in parse_server (c=0x4f8270) at parse.c:2064
    #3  0x00000000004035ed in mainqueue (listener=4) at main.c:336
    #4  0x00000000004045d4 in main (argc=4, argv=0x7fffffffdef8) at main.c:706
    
    // connect socket
    Breakpoint 2, generateSocketName (addr=0x7fffffffdd30, display=9) at x11common.c:114
    114             snprintf(addr->sun_path,sizeof(addr->sun_path),"/tmp/.X11-unix/X%d",display);
    (gdb) p display 
    $5 = 9
    (gdb) c
    Continuing.
    [Detaching after fork from child process 7682]
    Got connection from unknown(local)
    
    Breakpoint 2, generateSocketName (addr=0x7fffffffd9d0, display=0) at x11common.c:114
    114             snprintf(addr->sun_path,sizeof(addr->sun_path),"/tmp/.X11-unix/X%d",display);
    (gdb) p display 
    $6 = 0
    (gdb) bt
    #0  generateSocketName (addr=0x7fffffffd9d0, display=0) at x11common.c:114
    #1  0x0000000000404c92 in connectToServer (displayname=0x7fffffffe363 ":0", family=1, hostname=0x0, display=0) at x11client.c:77
    #2  0x0000000000402766 in acceptConnection (listener=3) at main.c:95
    #3  0x0000000000403edd in mainqueue (listener=3) at main.c:452
    #4  0x00000000004045d4 in main (argc=2, argv=0x7fffffffdf18) at main.c:706
    (gdb) 
    

    协议分析

    xtrace通过拦截X11协议通信来进行分析。它捕获传输到X服务器的请求以及服务器对这些请求的响应,将他们解析化以日志输出的形式打印出来。所以本质来说协议分析指的是X11协议交互分析,需要对 X11相关的协议做到非常了解,即每个协议有什么功能,在xcb中是如何处理,在xserver中又是如何处理。受限于篇幅,笔者在此不会阐述所有的协议分析,而是拿我们平时常用的一些软件做一些分析和介绍。

    日志流关键字 含义或用途
    Present-Request(148,1) 客户端用于GLX等送显
    Request(1): CreateWindow 请求创建X窗口,shm、glx都有
    GLX-Request(152,3): glXCreateContext 请求创建GLX上下文,可以用来判断客户端是否GLX应用
    MIT-SHM-Request(130,3): PutImage X11 shm客户端请求更新图像
    Event XKEYBOARD-XkbEvent(85) xserver键盘事件传递给X客户端
    Event Generic(35) XInputExtension 鼠标事件,后面带着ButtonPress、ButtonRelease、Motion等
    Request(36): GrabServer grab请求
    Request(37): UngrabServer 解除grab请求
    DeleteProperty 请求删除X11窗口的一些属性
    ChangeProperty 改变X11窗口的一些属性
    PropertyNotify 窗口属性改变发送事件通知客户端
    MIT-SHM-Request(130,4): GetImage shm方式获取屏幕图像
    Request(62): CopyArea 复制屏幕一部分区域图像(离屏)
    Request(53): CreatePixmap 创建图像(离屏)

    上述表格中只是介绍了常见部分的协议,X协议非常的丰富,完整的模块如下所示:

    • XCB BigRequests API 发送和接受超过请求长度(65535字节)限制的数据
    • XCB Composite API 支持窗口合成,将多个窗口的内容合成最终的显示图像
    • XCB Damage API 用于跟踪窗口或者绘图上下文的可视区域的改变
    • XCB DPMS API 用于管理显示器的电源管理功能,控制显示器电源模式
    • XCB DRI2 API 支持直接渲染和硬件加速
    • XCB DRI3 API 支持直接渲染和硬件加速,对DRI2的扩展和改进
    • XCB Glx API 提供GLX接口创建OpenGL上下文,进行图形渲染和交互
    • XCB Present API 实现高性能的图像呈现在屏幕上
    • XCB RandR API 管理显示器的分辨率、屏幕方向和显示器布局等
    • XCB Record API 记录X服务器的事件流,以便进行调试、分析等
    • XCB Render API xrender绘图
    • XCB ScreenSaver API 管理屏幕保护程序的行为和状态
    • XCB Shape API 用于创建和操作不规则窗口的形状
    • XCB Shm API 应用程序能够通过共享内存的方式高效的传输图像
    • XCB Sync API 实现同步操作和时间戳的管理,确保预期的时序
    • XCB XCMisc API 提供额外的杂项函数和功能,获取一些服务器信息
    • XCB Core API 核心部分,提供了基本通信功能和操作
    • XCB Xevie API 拦截和处理X服务器上的事件流
    • XCB XF86Dri API 直接渲染,直接访问图形硬件,提高图形性能和效率
    • XCB XFixes API 增强功能:光标、窗口形状、窗口属性、窗口位置
    • XCB Xinerama API 用于管理多个显示器的配置和操作
    • XCB Input API 处理输入事件(如键盘、鼠标、触摸屏等)交互
    • XCB xkb API 配置与操作键盘相关的设置,键盘布局和状态
    • XCB XPrint API 直接从应用程序打印文档、图像和其他内容
    • XCB API xcb基础功能
    • XCB SELinux API 在应用程序中管理selinux安全策略和执行安全操作
    • XCB Test API 测试协议,常用于远程控制
    • XCB Xv API xvideo相关,用于视频渲染、视频加速、获取视频信息
    • XCB XvMC API 在GPU上执行视频解码和运动补偿功能 笔者在工作中也有时对xtrace日志无法分析到有用信息,此时会查看XCB帮助文档,通过分析对比查找到相关的xcb函数,从而逐渐熟悉X11协议。

    总结

    总体来说xtrace是一个有用的工具,对于笔者来说经常会接触生态软件的图形显示问题,对于少部分软件开发商不愿意提供代码和问题复现最小demo,此时其软件对于笔者来说是一个黑盒,当问题边界靠近X相关的技术时,笔者会使用xtrace去详细分析该软件的详细功能,往往这可以在底层剖析软件的显示工作方式,配合系统上相关的组建库代码,可以方便地处理客户的紧急问题。 笔者编写这篇文档是希望可以鼓励更多的同事使用xtrace,这个工具可以帮助你熟悉X11的工具原理,同时也可以理解像qt、gtk等UI库的底层实现,在定位系统复制粘贴、图形显示、拖拽、窗口相关的问题时可以提供更加底层的日志,方便更加精准地定位问题根因!

    参考资料