Proxy between X11 display server (or Wayland compositor via Xwayland) and client applications, logging their communication and selectively altering message data based on user options. Is to X11 protocol traffic what strace is to Linux syscalls. Complete rebuild of the original Debian xtrace in C++17, with expanded features.
- improve personal understanding of X11 protocol
- revise xtrace output format
- add features to xtrace
- recording protocol traffic for a given X11 server (or Wayland compositor via Xwayland)
- debugging client communications with X11 server (or Wayland compositor via Xwayland)
- selective filtering of X11 extension use by clients
| recommended | developed with | |
|---|---|---|
git |
2.43.0 | |
cmake |
3.14+ | 3.28.3 |
g++/clang++ |
7+/4+ | 13.3.0/18.1.3 |
libstdc++/libc++ |
9+/7+ | 13.3.0/18.1.3 |
libxcb(with libxcb-dev) (XTRACEPP_BUILD_TESTS=ON) |
1.15 | |
doxygen (XTRACEPP_BUILD_DOCS=ON) |
1.9.8 | |
dot (graphviz) (XTRACEPP_BUILD_DOCS=ON) |
2.43.0 |
$ git clone https://github.com/allelomorph/xtracepp$ cd xtracepp
$ mkdir build_dir
$ cmake [-DXTRACEPP_BUILD_TESTS=ON] [-DXTRACEPP_BUILD_DOCS=ON] -S . -B build_dir
$ cmake --build build_dirFor documentation:
$ cmake --build build_dir --target doxygenOn POSIX systems that support X11, the environmental variable DISPLAY should contain an X display name to help clients connect with the server. It can be in the documented format for TCP:
[[protocol/]hostname|URL]:display_number[.screen_number]
or a second undocumented format for unix sockets (supported by libxcb):
[unix:]socket_path[.screen_number]
For xtracepp to act as a proxy server and stand between the clients and real X server, it must take a display name to connect with the server (defaulting to $DISPLAY if not passed with --display,) and also choose a proxy display name to give to client apps (defaulting to :9 if not passed with --proxydisplay or $PROXYDISPLAY.)
Normally xtracepp closes when it has no open connections. But an easy way to use it to trace multiple clients is to first set it running as a background process:
$ xtracepp [options...] --keeprunning &then launch as many X client apps as you like with DISPLAY set to the xtracepp proxy display (default :9 in this example):
$ DISPLAY=:9 xclockIf you want to prioritize tracing of a particular client without bothering to set its DISPLAY, you can launch it and xtracepp in a single command simply by using -- as a delimiter between the two commands. This launches the client as a child process on connection 0 (see formatting.)
$ xtracepp [options...] -- client_command [client args...]This can be combined with the --keeprunning option above to stay listening for connections after the child exits.
- notifications, errors to
stderr - logs to
stdoutby default, or to file with--outfile(-o)log_file - returns:
- normal exit: 0, or 1 on error
- stopped by signal: signal value + 128
- using child process (without
--keeprunning): child return value - using child process (without
--keeprunning,) child stopped by signal: signal value + 128
A typical log entry from xtracepp is one line for one protocol message, following a general format:
- prefix (uses
:as delimiter)C+ connection number (0-indexed, three digits, zero padded)- message size in bytes (four digits, zero padded) +
B - direction glyph
s<cclient to servers>cserver to client
S+ sequence number (1-indexed, five digits, zero padded)- description
- message data in mock JSON
{and}delimit structs[and]delimit lists- struct members appear as
name=value(brief description)=valueif data field is unnamed by protocolvaluemay be:- decimal integer
- hexadecmial integer
- enum name
- list of bitmask flag names
- quoted string
- (other special data formatting)
- formatting can be modified with options
For example, here is a message which is:
- 8 bytes long
- sent from client to server
- the first request (sequence 1) on connection 0
- encoded as GetWindowAttributes (core opcode 3)
- containing the data field
windowwith a value of 0
C000:0008B:s<c:S00001: Request GetWindowAttributes(3): { window=0 }
Messages during the establishment of a connection. In this phase, message descriptions in the log prefix are more phrase-like.
First the client attempts to initiate:
C000:0048B:s<c: client "unknown(local)" attempting connection: { byte-order=LSBFirst protocol-major-version=11 protocol-minor-version=0 authorization-protocol-name="MIT-MAGIC-COOKIE-1" authorization-protocol-data=(16 bytes) }
The server may refuse the connection, which ends communication, or request further authentication, at which point xtracepp ends the connection itself. But, if all goes well, the connection is accepted (lists are truncated in example):
C000:6740B:s>c: server accepted connection: { protocol-major-version=11 protocol-minor-version=0 release-number=12302006 resource-id-base=14680064 resource-id-mask=0x001fffff motion-buffer-size=256 maximum-request-length=65535 image-byte-order=LSBFirst bitmap-format-bit-order=LeastSignificant bitmap-format-scanline-unit=32 bitmap-format-scanline-pad=32 min-keycode=8 max-keycode=255 vendor="The X.Org Foundation" pixmap-formats=[ { depth=1 bits-per-pixel=1 scanline-pad=32 } ... ] roots=[ { root=1037 default-colormap=61 white-pixel=16777215 black-pixel=0 current-input-masks=KeyPress,StructureNotify,SubstructureNotify,SubstructureRedirect,PropertyChange,ColormapChange width-in-pixels=1680 height-in-pixels=1050 width-in-millimeters=444 height-in-millimeters=277 min-installed-maps=1 max-installed-maps=1 root-visual=62 backing-stores=WhenMapped save-unders=False root-depth=24 allowed-depths=[ { depth=24 visuals=[ { visual-id=62 class=TrueColor bits-per-rgb-value=8 colormap-entries=256 red-mask=0x00ff0000 green-mask=0x0000ff00 blue-mask=0x000000ff } ... ] } ] } ] }
After a connection is accepted, we enter into the normal phase of request/reply/event/error communication.
Client prompts for server behavior.
Core protocol requests will have a single opcode:
C000:0008B:s<c:S00001: Request GetWindowAttributes(3): { window=0 }
whereas extension requests will have both a major opcode that represents the extension, plus a minor opcode for the actual request:
C000:0004B:s<c:S00002: Request BIG-REQUESTS(133)-BigReqEnable(0): { }
Direct server responses to requests (only some requests expect replies.) A reply is logged with the sequence number of the relevant request.
xtracepp identifies the relevant request by connection and sequence number, and includes its name and opcode(s) in the message prefix:
C000:0032B:s<c:S00001: Reply to GetProperty(20): { format=8 type=31("STRING") bytes-after=0 value="" }
C000:0032B:s<c:S00002: Reply to BIG-REQUESTS(133)-BigReqEnable(0): { maximum-request-length=4194303 }
Server reports of state changes, often triggered by requests, but not directly tied to them. An event is logged with the sequence number of the relevant request.
Log line prefix will identify events by their event code:
C000:0032B:s>c:S00082: Event Expose(12): { window=14680075 x=0 y=0 width=164 height=164 count=0 }
Request SendEvent field event will be formatted as a normal event, but without a log prefix:
C000:0044B:s<c:S00001: Request SendEvent(25): { propagate=False destination=PointerWindow event-mask=StructureNotify event=KeyRelease(3) { detail=0 time=0x00000000 root=0 event=0 child=None root-x=0 root-y=0 event-x=0 event-y=0 state=0x0000 same-screen=False } }
Server responses to failed requests. An error is logged with the sequence number of the relevant request.
Log line prefix will identify errors by their error code:
C000:0032B:s>c:S00001: Error Value(2): { (bad value)=8 (minor opcode)=0 (major opcode)=35 }
Protocol extensions can be filtered:
- postively with one or more uses of
--denyextensions(-e)extension_name(named extensions are excluded, orallto disable all) - negatively with one or more uses of
--allowextensions(-e)extension_name(unnamed extensions excluded)
Any unsupported extensions are automatically denied. Extension support:
xtracepp version |
extension |
|---|---|
| 1.0+ | BIG-REQUESTS |
Enabled with --multiline(-m,) log entries are formatted for greater readability by setting data fields each on their own line. Indenting, brackets and braces will be formatted to visualize nesting hierarchy. Some structures/lists are exceptional and will always be single-line formatted. Can be combined with all other format options.
For example, this StoreColors request would go from:
C000:0032B:s<c:S00001: Request StoreColors(89): { cmap=0 items=[ { pixel=1 red=2 green=3 blue=4 (do rgb mask)=do-green } { pixel=5 red=6 green=7 blue=8 (do rgb mask)=do-red,do-blue } ] }
to this when using --multiline (items list is comprised of COLORITEM protocol structs which are always formatted singleline):
C000:0032B:s<c:S00001: Request StoreColors(89): {
cmap = 0
items = [
{ pixel=1 red=2 green=3 blue=4 (do rgb mask)=do-green }
{ pixel=5 red=6 green=7 blue=8 (do rgb mask)=do-red,do-blue }
]
}
Enabled with --verbose(-v,) log entries are formatted with more complete information about the actual message encoding:
- some data fields normally omitted are now logged (eg
opcodeand(total aligned units)in example below) - data fields appear as
name=value_hex(value)(hex at the appropriate width for the encoded integer size)
Can be combined with all other format options.
Using the previous example, this StoreColors request would go from:
C000:0032B:s<c:S00001: Request StoreColors(89): { cmap=0 items=[ { pixel=1 red=2 green=3 blue=4 (do rgb mask)=do-green } { pixel=5 red=6 green=7 blue=8 (do rgb mask)=do-red,do-blue } ] }
to this with --verbose:
C000:0032B:s<c:S00001: Request StoreColors(89): { opcode=0x59(89) (total aligned units)=0x0008(8) cmap=0x00000000(0) items=[ { pixel=0x00000001(1) red=0x0002(2) green=0x0003(3) blue=0x0004(4) (do rgb mask)=0x02(do-green) } { pixel=0x00000005(5) red=0x0006(6) green=0x0007(7) blue=0x0008(8) (do rgb mask)=0x05(do-red,do-blue) } ] }
Enabled with --systemtimeformat(-s,) TIMESTAMP data fields are interpreted as human-readable wall-clock time as would be shown by date +'%Y-%m-%dT%H:%M:%SZ%Z'. Can be combined with other formatting options.
For example, time in this PropertyNotify event is normally not interpreted:
C000:0032B:s>c:S00078: Event PropertyNotify(28): { window=12582922 atom=314(unrecognized atom) time=0x25405193 state=NewValue }
but with --systemtimestamps:
C000:0032B:s>c:S00078: Event PropertyNotify(28): { window=12582922 atom=314(unrecognized atom) time=0x25405193(2026-02-09T:07:44:34ZUTC) state=NewValue }
Enabled with --prefetchatoms(-p,) the chance that ATOM strings will be logged as they are stored in the server is greatly increased. (By default, any ATOM value not predefined by the protocol will appear as (unrecognized atom).)
Using the previous PropertyNotify example, atom is greater than the core protocol predefined max of 68, and so is unrecognized:
C000:0032B:s>c:S00078: Event PropertyNotify(28): { window=12582922 atom=314(unrecognized atom) time=0x25698a81 state=NewValue }
but with --prefetchatoms we can know the contents of the interned string:
C000:0032B:s>c:S00078: Event PropertyNotify(28): { window=12582922 atom=314("WM_STATE") time=0x25698a81 state=NewValue }
- Bernhard Link and the developers of the original xtrace
- Qiang Yu for their fork of
xtraceand development of DRI3 support