xtrace 可以用来查看X clientX server之间的通信内容,并以易读的方式显示。

基本原理是建立一个 fake X server 并在启动客户程序前修改 DISPLAY 环境变量,这样客户程序启动时连接的是这个 fake X serverxtrace 在检测到有客户程序连接时会建立与 real X server 的连接,随后的工作就是转发客户程序与 real X server 之间的通信,并把通信的内容已易读的方式打印出来。

使用方法:

[yyuan@bcnode001 ~]$ xtrace --help
xtrace: Dump all X protocol data being tunneled from a fake X display to a real one.
usage: xtrace [options] [[--] command args ...]
--display, -d <display to connect to>
--fakedisplay, -D <display to fake>
--copyauthentication, -c        Copy credentials
--nocopyauthentication, -n      Do not copy credentials
--authfile, -f <file instead of ~/.Xauthority to get credentials from>
--newauthfile, -F <file instead of ~/.Xauthority to put credentials in>
--waitforclient, -W             wait for connection even if command terminates
--stopwhendone, -s              Return when last client disconnects
--keeprunning, -k               Keep running
--denyextensions, -e            Fake unavailability of all extensions
--readwritedebug, -w            Print amounts of data read/sent
--maxlistlength, -m <maximum number of entries in each list shown>
--outfile, -o <filename>        Output to file instead of stdout
--buffered, -b                  Do not output every line but only when buffer is full
  • −d name | −−display name
    转发与 X server 的连接给指定的 name 而不是通过环境变量 DISPLAY 指定
  • −D name | −−fakedisplay name
    建立一个 fake X server 于指定的name上,而不是环境变量 FAKEDISPLAY 或者 :9
  • −c | −−copyauthentication (默认)
    复制一份 real X server 用到的 authentication tokenfake X server,后面会详细解释
  • −n | −−nocopyauthentication
    不复制 authentication token
  • −f filename | −−authfile filename
    指定从哪个文件复制 authentication token
  • −F filename | −−newauthfile filename
    指定复制 authentication token 到哪个文件
  • -W | --waitforclient
    客户程序没有建立任何连接就退出时不要退出 xtrace
  • -s | --stopwhendone (默认)
    当所有连接的客户程序都断开时退出 xtrace
  • -k | --keeprunning
    当所有连接的客户程序都断开时继续等待新的连接
  • -e | --denyextensions
    xtrace 目前只支持一部分的 X 扩展, 因此遇到无法识别的 requestreply 时分别打印 unknownunexpected
  • -w | --readwritedebug
    打印每条转发信息的字节数
  • -m count | --maxlistlength count
    只打印 list 的前面 count 个元素 (这个还不是很清楚)
  • -o | --outfile filename
    指定输出文件
  • -b | --buffered
    指定输出时使用 full buffer, 默认使用 line buffer
  • --timestamps
    打印时间戳
  • --relative-timestamps
    打印的时间戳是相对于连接建立时的时间差
  • --monotonic-timestamps
    使用 clock_gettime() 获取时间而不是 gettimeofday
  • --print-offsets
    打印每个字段的 offset
  • --print-counts
    打印list中的元素个数

一个实际使用的例子:

[yyuan@bcnode001 ~]$ xtrace --timestamps -w ~/TFLEX_ROOT/app/r2013.09.01/gui/appTflex
No display name to create specified, trying :9
1397101987.345 000:<: am lsb-first want 11:0 authorising with 'MIT-MAGIC-COOKIE-1' of length 16
1397101987.346 000:>: Success, version is 11:0 vendor='The X.Org Foundation' release=60900000 resource-id=0x02800000 resource-mask=0x001fffff mo      tion-buffer-size=256 max-request-len=65535 image-byte-order=LSBFirst(0x00) bitmap-bit-order=LeastSignificant(0x00) scanline-unit=32 scanline-pad      =32 min-keycode=0x08 max-keycode=0xa0 pixmap-formats={depth=1 bits/pixel=1 scanline-pad=32},{depth=4 bits/pixel=8 scanline-pad=32},{depth=8 bits      /pixel=8 scanline-pad=32},{depth=15 bits/pixel=16 scanline-pad=32},{depth=16 bits/pixel=16 scanline-pad=32},{depth=24 bits/pixel=32 scanline-pad      =32},{depth=32 bits/pixel=32 scanline-pad=32}; roots={root=0x00000057 default-colormap=0x0000003d white-pixel=0x00ffffff black-pixel=0x00000000       input-mask=KeyPress,KeyRelease,EnterWindow,LeaveWindow,ButtonMotion,StructureNotify,SubstructureNotify,SubstructureRedirect,FocusChange,Property      Change,ColormapChange width[pixel]=1674 height[pixel]=978 width[mm]=567 height[mm]=331 min-installed-maps=1 max-installed-maps=1 root=0x00000039       backing-stores=Always(0x02) save-unders=true(0x01) root-depth=24 allowed depths={depth=24 visuals={id=0x00000039 class=TrueColor(0x04) bits/rgb      -value=8 colormap-entries=256 red-mask=0x00ff0000 green-mask=0x0000ff00 blue-mask=0x000000ff},{id=0x0000003a class=TrueColor(0x04) bits/rgb-valu      e=8 colormap-entries=256 red-mask=0x00ff0000 green-mask=0x0000ff00 blue-mask=0x000000ff},{id=0x0000003b class=TrueColor(0x04) bits/rgb-value=8 c      olormap-entries=256 red-mask=0x00ff0000 green-mask=0x0000ff00 blue-mask=0x000000ff},{id=0x0000003c class=TrueColor(0x04) bits/rgb-value=8 colorm      ap-entries=256 red-mask=0x00ff0000 green-mask=0x0000ff00 blue-mask=0x000000ff};},{depth=1 visuals=;},{depth=4 visuals=;},{depth=8 visuals=;},{de      pth=15 visuals=;},{depth=16 visuals=;},{depth=32 visuals=;};};
1397101987.346 000:<:0001: 20: Request(98): QueryExtension name='BIG-REQUESTS'
1397101987.347 000:>:0001:32: Reply to QueryExtension: present=true(0x01) major-opcode=133 first-event=0 first-error=0
1397101987.347 000:<:0002:  4: BIG-REQUESTS-Request(133,0): Enable
1397101987.347 000:>:0002:32: Reply to Enable: maximum-request-length=4194303
1397101987.347 000:<:0003: 20: Request(55): CreateGC cid=0x02800000 drawable=0x00000057 values={background=0x00ffffff}
1397101987.347 000:<:0004: 24: Request(20): GetProperty delete=false(0x00) window=0x00000057 property=0x17("RESOURCE_MANAGER") type=0x1f("STRING      ") long-offset=0x00000000 long-length=0x05f5e100

...

1397102061.842 000:>:471a: Event PropertyNotify(28) window=0x0280082e atom=0xfa("_NET_WM_NAME") time=0x49c5a7d7 state=Deleted(0x01)
1397102061.842 000:>:471a:32: Reply to GetInputFocus: revert-to=Parent(0x02) focus=0x0260001f
000:>:wrote 32 bytes
000:<:got EOF
000:<:sent EOF

接下来看下具体的流程。

工作流程

int main(int argc, char *argv[])
{
    struct parser *parser;
    ...                             // 1. 解析传入参数.
    translate(parser, "all.proto"); // 2. 解析从搜索路径中找到的 all.proto 协议定义文件.
    ...
    // 3. 解析display name, 根据协议不同随后会创建不同的socket. 
    //    in_displayname 是 fake X server,out_displayname才是 real X server.
    msg = parseDisplay(in_displayname, &in_protocol, &in_hostname, &in_display, &in_screen, &in_family);
	msg = parseDisplay(out_displayname, &out_protocol, &out_hostname, &out_display, &out_screen, &out_family);
    // 4. 复制一份 real X server 用到的authentication token给 fake X server.
    copy_authentication(in_displayname, out_displayname, in_authfile, out_authfile);
    // 5. 启动 fake X server, 等待 X client连接
    listenForClients(in_displayname, in_family, in_display);
    // 6. 启动客户程序,启动时修改 DISPLAY 环境变量成 fake X server
    startClient(argv + optind)
    // 7. 主循环,侦听到有 X client 连接时建立与 real X server 的连接,
    //    随后转发客户程序与 real X server 的通讯,并通过 parser 解析通讯内容后输出.
    mainqueue(listener);
}

关键步骤是4,56

复制 authentication token

关于X Window authorization, 主要有三种:Host-based access, Cookie-based accessUser-based accessxtrace 目前只支持 Cookie-based access。 ### 什么是Cookie-based access? ### 简单来说就是 X client 尝试与 X server 建立连接时会传一个 magic cookie 过去进行验证。这些 cookie 是在 X server 启动时创建的并默认存储在 $HOME/.Xauthority 文件中(除非指定了 XAUTHORITY 环境变量)。我们可以使用 xauth list 命令查看这个文件的内容。

[yyuan@bcnode001 ~]$ xauth list
bcnode001.briontech.com/unix:13  MIT-MAGIC-COOKIE-1  da7eaeaad1d4738384247ead8b7a5a31
bcnode001.briontech.com/unix:14  MIT-MAGIC-COOKIE-1  285e32abbcdb869766376e7cfe26bb91
bcnode001.briontech.com/unix:9  MIT-MAGIC-COOKIE-1  285e32abbcdb869766376e7cfe26bb91
...
  • 第一列是 X serverdisplay name
  • 第二列是 cookie 的类型
  • 第三列是 cookie 的值

有两种类型的 cookie: MIT-MAGIC-COOKIE-1XDM-AUTHORIZATION-1。目前 xtrace 只支持 MIT-MAGIC-COOKIE-1

DISPLAY NAME

官方文档有详细介绍。 格式是 hostname:displaynumber.screennumber。 * hostname 指定了 X server 运行在哪里,为空的话表示运行在本地 * displaynumber 用于区分同一 hostname 上的不同 X server,从 0 开始,不能为空 * screennumber 用于区分不同的屏幕,从 0 开始,为空的话表示 0

默认的 display name 是存储在 DISPLAY 环境变量中的。
X clientX server之间有几种连接方式: localTCP/IPDECnet:
* local
这种方式下 hostname 为空,unix 或者 localhost,比如: :0.0unix:0.0localhost:0.0
local 方式下会使用 UNIX domain socket,该socket会使用 /tmp/.X11-unix/Xdisplaynumber 文件。这也是效率最高的一种通信方式。
* TCP/IP
这种方式下 hostname 可以为domain namenode nameIP address等,比如: x.org:0, expo:0192.168.1.100:0.1
在这种方式下会使用 TCP/IP 连接,并使用端口 6000 + displaynumber
* DECnet
这种方式下 hostname 必须是 node name, 并紧跟两个冒号,比如: foo::0.1

$HOME/.Xauthority 文件中的第一列的格式和 DISPLAY 环境变量的格式有一点不一样,但大体相同。 所以 bcnode001.briontech.com/unix:14 表示的是使用 local 方式通信,displaynumber 是 14,screennumber 没有指定默认是 0,会使用 /tmp/.X11-unix/X14 文件。

运行xtrace时出现以下错误怎么办?

[yyuan@bcnode001 ~]$ xtrace --timestamps -w ~/TFLEX_ROOT/app/r2013.09.01/gui/appTflex
No display name to create specified, trying :9
Error binding socket for ':9': 2=No such file or directory

这是因为没有指定 fake displayname 的情况下,xtrace 会使用 :9,因为 UNIX domain socket 需要使用 /tmp/.X11-unix/X9 文件,而 /tmp/.X11-unix 文件夹没有创建好所以导致失败。

可以通过 strace 验证下:

[yyuan@bcnode001 ~]$ strace xtrace --timestamps -w ~/TFLEX_ROOT/app/r2013.09.01/gui/appTflex
...
socket(PF_FILE, SOCK_STREAM, 0)         = 3
unlink("/tmp/.X11-unix/X9")             = -1 ENOENT (No such file or directory)
bind(3, {sa_family=AF_FILE, path="/tmp/.X11-unix/X9"}, 110) = -1 ENOENT (No such file or directory)
...

解决办法就是 * 手动创建 /tmp/.X11-unix 文件夹 * 或者修改源代码,添加创建该文件夹的代码

怎样复制 token

使用 xauth list display-name 获取 real X servertoken, 然后 xauth add display-name cookie-name cookie-value 添加一条新记录:

[yyuan@bcnode001 ~]$ xauth list :100
[yyuan@bcnode001 ~]$ xauth list :14
bcnode001.briontech.com/unix:14  MIT-MAGIC-COOKIE-1  285e32abbcdb869766376e7cfe26bb91
[yyuan@bcnode001 ~]$ xauth add :100 MIT-MAGIC-COOKIE-1  285e32abbcdb869766376e7cfe26bb91
[yyuan@bcnode001 ~]$ xauth list :100
bcnode001.briontech.com/unix:100  MIT-MAGIC-COOKIE-1  285e32abbcdb869766376e7cfe26bb91


-EOF-
含瀚家的老袁,转载请注明出处