Hands-on Projects for the Linux Graphics Subsystem
|
To make a hands-on experiment we will use as example the first program of section 1.1 (actually the second version of this program we mentioned in section 1.1):
/* Simple Xlib application drawing a box in a window. To Compile: gcc -O2 -Wall -o test test.c -L /usr/X11R6/lib -lX11 -lm */ #include<X11/Xlib.h> #include<stdio.h> #include<stdlib.h> // prevents error for exit on line 18 when compiling with gcc int main() { Display *d; int s; Window w; XEvent e; /* open connection with the server */ d=XOpenDisplay(NULL); if(d==NULL) { printf("Cannot open display\n"); exit(1); } s=DefaultScreen(d); /* create window */ w=XCreateSimpleWindow(d, RootWindow(d, s), 10, 10, 100, 100, 1, BlackPixel(d, s), WhitePixel(d, s)); // Prosses Window Close Event through event handler so XNextEvent does Not fail Atom delWindow = XInternAtom( d, "WM_DELETE_WINDOW", 0 ); XSetWMProtocols(d , w, &delWindow, 1); /* select kind of events we are interested in */ XSelectInput(d, w, ExposureMask | KeyPressMask); /* map (show) the window */ XMapWindow(d, w); /* event loop */ while(1) { XNextEvent(d, &e); /* draw or redraw the window */ if(e.type==Expose) { XFillRectangle(d, w, DefaultGC(d, s), 20, 20, 10, 10); } /* exit on key press */ if(e.type==KeyPress) break; // Handle Windows Close Event if(e.type==ClientMessage) break; } /* destroy our window */ XDestroyWindow(d, w); /* close connection to server */ XCloseDisplay(d); return 0; } |
The aim is to use the gdb debugger in order to find the X protocol messages, which as we saw in section 1.3 are stored in the address indicated by the 'buffer' pointer until they are sent to the X Server. buffer is a field of the Display struct returned by XOpenDisplay(), as seen in section 1.5. The following figure, from section 1.3 shows the buffering of the X Protocol messages from the one created by XCreateSimpleWindow() to the message created by XMapWindow(), until finally they are flushed together to the server. As we see in the current section this assumption is rather theoretical since not all of the X Client messages are stored and then flushed in the same time but actually there are two groups of message flushing, called in this text flushing no 1 and flushing no 2.
At the previous source code we include one more header file, namely <X11/Xlibint.h>, which defines the Display struct. Since we want to examine the X protocol messages, which are stored as seen in section 1.3 in the buffer member of the Display struct we have to include the header file that defines Display in order to have access to the Display struct fields without getting "incomplete type" errors. By getting access we mean placing new instructions in the programs like the following:
printf("fd is %d\n", d->fd);With similar instructions we print the fields of the Display struct that d points. Certainly instead than writing the fields manually we could inspect them by using the capabilities of the gdb debugger.
The program becomes then:
/* Simple Xlib application drawing a box in a window. To Compile: gcc -O2 -Wall -o test test.c -L /usr/X11R6/lib -lX11 -lm */ #include<X11/Xlibint.h> #include<X11/Xlib.h> #include<stdio.h> #include<stdlib.h> // prevents error for exit on line 18 when compiling with gcc int main() { Display *d; int s; Window w; XEvent e; /* open connection with the server */ d=XOpenDisplay(NULL); if(d==NULL) { printf("Cannot open display\n"); exit(1); } printf("fd is %d\n", d->fd); /* or even better use gdb */ s=DefaultScreen(d); /* create window */ w=XCreateSimpleWindow(d, RootWindow(d, s), 10, 10, 100, 100, 1, BlackPixel(d, s), WhitePixel(d, s)); // Prosses Window Close Event through event handler so XNextEvent does Not fail Atom delWindow = XInternAtom( d, "WM_DELETE_WINDOW", 0 ); XSetWMProtocols(d , w, &delWindow, 1); /* select kind of events we are interested in */ XSelectInput(d, w, ExposureMask | KeyPressMask); /* map (show) the window */ XMapWindow(d, w); /* event loop */ while(1) { XNextEvent(d, &e); /* draw or redraw the window */ if(e.type==Expose) { XFillRectangle(d, w, DefaultGC(d, s), 20, 20, 10, 10); } /* exit on key press */ if(e.type==KeyPress) break; // Handle Windows Close Event if(e.type==ClientMessage) break; } /* destroy our window */ XDestroyWindow(d, w); /* close connection to server */ XCloseDisplay(d); return 0; } |
The Display struct is defined in Xlib.h as:
typedef struct _XDisplay Displaywhere _XDisplay is defined in the newly added header file Xlibint.h as:
struct _XDisplay { XExtData *ext_data; /* hook for extension to hang data */ struct _XFreeFuncs *free_funcs; /* internal free functions */ int fd; /* Network socket. */ int conn_checker; /* ugly thing used by _XEventsQueued */ int proto_major_version;/* maj. version of server's X protocol */ int proto_minor_version;/* minor version of server's X protocol */ char *vendor; /* vendor of the server hardware */ XID resource_base; /* resource ID base */ XID resource_mask; /* resource ID mask bits */ XID resource_id; /* allocator current ID */ int resource_shift; /* allocator shift to correct bits */ XID (*resource_alloc)( /* allocator function */ struct _XDisplay* ); int byte_order; /* screen byte order, LSBFirst, MSBFirst */ int bitmap_unit; /* padding and data requirements */ int bitmap_pad; /* padding requirements on bitmaps */ int bitmap_bit_order; /* LeastSignificant or MostSignificant */ int nformats; /* number of pixmap formats in list */ ScreenFormat *pixmap_format; /* pixmap format list */ int vnumber; /* Xlib's X protocol version number. */ int release; /* release of the server */ struct _XSQEvent *head, *tail; /* Input event queue. */ int qlen; /* Length of input event queue */ unsigned long last_request_read; /* seq number of last event read */ unsigned long request; /* sequence number of last request. */ char *last_req; /* beginning of last request, or dummy */ char *buffer; /* Output buffer starting address. */ char *bufptr; /* Output buffer index pointer. */ char *bufmax; /* Output buffer maximum+1 address. */ unsigned max_request_size; /* maximum number 32 bit words in request*/ struct _XrmHashBucketRec *db; int (*synchandler)( /* Synchronization handler */ struct _XDisplay* ); char *display_name; /* "host:display" string used on this connect*/ int default_screen; /* default screen for operations */ int nscreens; /* number of screens on this server*/ Screen *screens; /* pointer to list of screens */ unsigned long motion_buffer; /* size of motion buffer */ unsigned long flags; /* internal connection flags */ int min_keycode; /* minimum defined keycode */ int max_keycode; /* maximum defined keycode */ KeySym *keysyms; /* This server's keysyms */ XModifierKeymap *modifiermap; /* This server's modifier keymap */ int keysyms_per_keycode;/* number of rows */ char *xdefaults; /* contents of defaults from server */ char *scratch_buffer; /* place to hang scratch buffer */ unsigned long scratch_length; /* length of scratch buffer */ int ext_number; /* extension number on this display */ struct _XExten *ext_procs; /* extensions initialized on this display */ /* * the following can be fixed size, as the protocol defines how * much address space is available. * While this could be done using the extension vector, there * may be MANY events processed, so a search through the extension * list to find the right procedure for each event might be * expensive if many extensions are being used. */ Bool (*event_vec[128])( /* vector for wire to event */ Display * /* dpy */, XEvent * /* re */, xEvent * /* event */ ); Status (*wire_vec[128])( /* vector for event to wire */ Display * /* dpy */, XEvent * /* re */, xEvent * /* event */ ); KeySym lock_meaning; /* for XLookupString */ struct _XLockInfo *lock; /* multi-thread state, display lock */ struct _XInternalAsync *async_handlers; /* for internal async */ unsigned long bigreq_size; /* max size of big requests */ struct _XLockPtrs *lock_fns; /* pointers to threads functions */ void (*idlist_alloc)( /* XID list allocator function */ Display * /* dpy */, XID * /* ids */, int /* count */ ); /* things above this line should not move, for binary compatibility */ struct _XKeytrans *key_bindings; /* for XLookupString */ Font cursor_font; /* for XCreateFontCursor */ struct _XDisplayAtoms *atoms; /* for XInternAtom */ unsigned int mode_switch; /* keyboard group modifiers */ unsigned int num_lock; /* keyboard numlock modifiers */ struct _XContextDB *context_db; /* context database */ Bool (**error_vec)( /* vector for wire to error */ Display * /* display */, XErrorEvent * /* he */, xError * /* we */ ); /* * Xcms information */ struct { XPointer defaultCCCs; /* pointer to an array of default XcmsCCC */ XPointer clientCmaps; /* pointer to linked list of XcmsCmapRec */ XPointer perVisualIntensityMaps; /* linked list of XcmsIntensityMap */ } cms; struct _XIMFilter *im_filters; struct _XSQEvent *qfree; /* unallocated event queue elements */ unsigned long next_event_serial_num; /* inserted into next queue elt */ struct _XExten *flushes; /* Flush hooks */ struct _XConnectionInfo *im_fd_info; /* _XRegisterInternalConnection */ int im_fd_length; /* number of im_fd_info */ struct _XConnWatchInfo *conn_watchers; /* XAddConnectionWatch */ int watcher_count; /* number of conn_watchers */ XPointer filedes; /* struct pollfd cache for _XWaitForReadable */ int (*savedsynchandler)( /* user synchandler when Xlib usurps */ Display * /* dpy */ ); XID resource_max; /* allocator max ID */ int xcmisc_opcode; /* major opcode for XC-MISC */ struct _XkbInfoRec *xkb_info; /* XKB info */ struct _XtransConnInfo *trans_conn; /* transport connection object */ }; |
From the xterm we copy the program in file test.c using the gedit text editor and then we use:
gcc -g -o test test.c -L/usr/X11R6/lib -lX11to compile the program. The -g option is used to include in the program debug symbols.
Instead of running the program normally as:
./testwe run it through the gdb debugger as:
gdb test
In section 1.5 we have used
tcp sockets instead of Unix domain sockets as we do here. We use xhost to permit tcp
connections to the X Server from anywhere as:
xhost +and then we run the test program as: DISPLAY=tcp/192.168.1.101:0.0 ./testIn the previous command 192.168.1.101 is the IP of my Linux box. Then we invoce the gdb as: DISPLAY=tcp/192.168.1.101:0.0 gdb ./test |
The first command to run in gdb is 'break', which stops program execution at specific points, called breakpoints. We can use breakpoints to inspect the values of program variables, etc at program positions that are of interest. From the sourceware gdb tutorial we read: "The breakpoint will stop your program just before it executes any of the code in the specified location." First we use break to place a breakpoint at line 25, just after XOpenDisplay() is called:
(gdb)break 25The '(gdb)' is the gdb prompt. We start then running the program from gdb with 'run':
(gdb)runThe next figure snows the xterm window with the previous gdb commands:
Next we give the following command:
(gdb)print *dwhich as shown in the figure bellow prints the field values of the Display struct, where d points:
As we see buffer has the hex value 0x80512c8 and the same value has also bufptr. As we recall from section 1.3 buffer represents the starting address of the output buffer, the buffer the X client messages wait to be sent, and bufptr is a pointer to the next available position for a new message. At this point, just after XOpenDisplay() was called, no X message is placed in the buffer(*) and normally the two pointers coincide. Also the reason that virtual addresses of this example are found after the memory location 0x8048000 is explained in section 3.2.2.5.2.
(*) The XOpenDisplay() Xlib client routine actually sends and receives messages to and from
the X Server, for instance the initial handshake messages (see section 1), however at the end
of the routine as we see in
OpenDis.c,
the file that implements XOpenDisplay(), there is the following _XReply() call:
if (_XReply (dpy, (xReply *) &reply, 0, xFalse)) {For _XReply we read at the Xlib programming manual: _XReply flushes the output buffer and waits for an xReply packet to arrive._XReply() is implemented in XlibInt.c, where _XFlush() is called as: #else /* XTHREADS else */ _XFlush(dpy); #endifTherefore the messages required to complete the connection procedure are flushed to the server and after XOpenDisplay() the buffer is empty. |
Next another 'break' is placed on line 31, just after XCreateSimpleWindow() is called:
(gdb)break 31We continue then the program execution, using the 'cont' gdb command:
(gdb)contAt this point we enter again the command that prints the d variable contents (the fields of this Display struct):
(gdb)print *dand the result is shown in the following figure:
As we notice, 'buffer' didn't change any position (we expected so since it constantly point to the output buffer), however bufptr is advanced to memory location 0x80512f0. This means that the X protocol message that was placed to the output buffer has a length:
0x80512f0-0x80512c8 = 134550256-134550216 = 40 bytes
This is exactly the value we predicted theoretically in the figure of section 1.3, which includes 8 32-bit value for the XCreateWindowReq and 2 additional 32-bit length for the request's extra length, which gives a total of 40 bytes.
We could take a look at the contents of memory location 0x80512c8, where the X message created by XCreateSimpleWindow() starts, using the x command as:
(gdb)x /50xb 0x80512c8This permits us to view the next 50 bytes (more than enough, since we actually need to look mainly at the next 40) from the beginning of address 0x80512c8 in hex byte (xb) values.
The output of the previous gdb command is illustrated in the following figure:
As we referred in the previous sections the output buffer receives the X message request structs and possibly some extra bytes. Since the output of the last figure represents the xCreateWindowReq struct plus eight extra bytes we can easily identify the xCreateWindowReq field values. We remind the xCreateWindowReq struct as defined in Xproto.h:
typedef struct { CARD8 reqType; CARD8 depth; CARD16 length B16; Window wid B32, parent B32; INT16 x B16, y B16; CARD16 width B16, height B16, borderWidth B16; #if defined(__cplusplus) || defined(c_plusplus) CARD16 c_class B16; #else CARD16 class B16; #endif VisualID visual B32; CARD32 mask B32; } xCreateWindowReq; |
The following image shows the following field values from xCreateWindowReq:
reqType, x, y, width, height, borderWidth.
Certainly those values were previously set in the source code by the XCreateSimpleWindow() parameters:
/* create window */ w=XCreateSimpleWindow(d, RootWindow(d, s), 10, 10, 100, 100, 1, BlackPixel(d, s), WhitePixel(d, s)); |
Having in mind that this is a little-endian system the bytes are used in reverse order when forming 16-bit words. For instance as we see from the xterm output width is 0x64 0x00, which is translated to 0x0064, which is 100 decimal. The same way the value of x, the distance at the x axis the current window is placed from point (0,0) of the parent window, is 0x0a 0x00, which is translated to 0x000a, a 10 decimal.
From the XCreateSimpleWindow() man page we can find the correspondence between the XCreateSimpleWindow() arguments and parameters:
Window XCreateSimpleWindow(Display *display, Window parent, int x, int y, unsigned int width, unsigned int height, unsigned int bor- der_width, unsigned long border, unsigned long background); |
As we saw in the previous sections some of the XCreateSimpleWindow() arguments, for instance width, height, x, and y form the field values of the struct xCreateWindowReq.
We continue by placing another 'break' to the program execution at line 33 just after the XInternAtom() call in the X Client program. We issue a 'cont' to continue the execution and we examine the next 100 bytes starting from the address of 'buffer', which remains 0x80512c8:
As we see the contents of xCreateWindowReq remain there as before, which means no flushing occurred between XCreateSimpleWindow() and XInternAtom(). Notice that some bytes of xCreateWindowReq may be changed. The reason is that the current 'break' took place in a latter time than the previous. After 40 bytes, which is the total length (size of xCreateWindowReq plus the additional bytes) we find the reqType of the next request, which is marked with red frame in the following image and has the value 0x10. This is decimal 16 and by looking at the request types table of Xproto.h (see next paragraphs) we find with no surprise that this value corresponds to the xInternAtomReq request struct:
typedef struct { /* followed by padded string */ CARD8 reqType; BOOL onlyIfExists; CARD16 length B16; CARD16 nbytes B16; /* number of bytes in string */ CARD16 pad B16; } xInternAtomReq; |
The third and fourth byte of xInternAtomReq corresponds to the request length and it is marked in the following image with a green frame. They have the value 0x06 0x00, which in the little endian notation become 0x00 0x06 or just 6 decimal. This is a x4 byte value and thus it becomes 6x4 bytes or 24 bytes.
Placing the 8 bytes of xInternAtomReq in 'buffer' and also the additional bytes, which are indicated by the value of the nbytes field of xInternAtomReq, which is 0x10 or decimal 16, gives a total of 24 bytes (also returned by the 'length' field as we said previously. The instruction of our X Client program responsible for this is as we also mentioned previously:
Atom delWindow = XInternAtom( d, "WM_DELETE_WINDOW", 0 ); |
XInternAtom() is implemented in IntAtom.c.
We can certainly continue with the next request structs by placing another 'break' gdb instruction just after the following instruction of the X Client program:
XMapWindow(d, w);We run it as:
break 40 contWe examine then the next 100 bytes in the buffer, which again starts at address 0x80512c8:
The following image highlights the next three request structs, stored in the X Client's 'buffer' pointer, ready to be flushed to the X Server:
At the previous gdb output we see three request structs, placed at 'buffer' by the time the 'break' gdb instruction is issued. As we notice contents at 0x80512c8 and on are changed, which means that the previous request structs, for instance xCreateWindowReq, are already flushed to the X Server and the buffer became empty to store the current requests. With a red frame in the figure we marked the first byte of the struct, which is the request type (reqType) and with a green frame we mark the third and fourth bytes of the structs, the total length of the request, counted in x4 bytes (length).
The first request struct has as the first byte the value 0x12, which is 18 decimal. The first byte corresponds to the request type (field reqType of the request struct) and as we read in Xproto.h number 18 assigned to the request X_ChangeProperty.
/* Request codes */ #define X_CreateWindow 1 #define X_ChangeWindowAttributes 2 #define X_GetWindowAttributes 3 #define X_DestroyWindow 4 #define X_DestroySubwindows 5 #define X_ChangeSaveSet 6 #define X_ReparentWindow 7 #define X_MapWindow 8 #define X_MapSubwindows 9 #define X_UnmapWindow 10 #define X_UnmapSubwindows 11 #define X_ConfigureWindow 12 #define X_CirculateWindow 13 #define X_GetGeometry 14 #define X_QueryTree 15 #define X_InternAtom 16 #define X_GetAtomName 17 #define X_ChangeProperty 18 #define X_DeleteProperty 19 #define X_GetProperty 20 . . . |
The third and fourth bytes, marked with a green frame are 0x07 0x00. If we reverse the order as the little endian notation requires it becomes 0x00 0x07 or just 0x07. This is decimal 7 and if we multiply this by 4 we get 28, i.e. the total number of bytes of the request (the length). The request struct for this request type is xChangePropertyReq:
typedef struct { CARD8 reqType; CARD8 mode; CARD16 length B16; Window window B32; Atom property B32, type B32; CARD8 format; BYTE pad[3]; CARD32 nUnits B32; /* length of stuff following, depends on format */ } xChangePropertyReq; |
Placing this request struct in the 'buffer' (and the additional bytes as we see next) is the result of the following instruction from our X Client:
XSetWMProtocols(d , w, &delWindow, 1); |
as we see by reading the source code of XSetWMProtocols(), which encapsulates XChangeProperty().
Notice that the size of the request struct xChangePropertyReq is 24 bytes, however the total length of the request is 28, since the last field, nUnits, correspond to the additional byte (x4). Since from the source code of the X Client XSetWMProtocols() is called with the fourth argument value 1, this value is passed from XSetWMProtocols() to XChangeProperty(),where it becomes nUnits. Therefore the additional bytes are 1x4 and the total size becomes 24+4 bytes = 28 bytes.
Right after the 28th byte of the xChangePropertyReq (including the additional bytes) at the gdb output it starts the next request, whose first byte, corresponding to the reqType field of the request struct, is again marked with red frame. Since it has a value of 0x02 hexadecimal or 2 decimal we find from the previous table of Xproto.h that the request type is X_ChangeWindowAttributes:
typedef struct { CARD8 reqType; BYTE pad; CARD16 length B16; Window window B32; CARD32 valueMask B32; } xChangeWindowAttributesReq; |
The size of this struct - CARD8 + BYTE + CARD16 + Window(B32) + CARD32 - is 12 byte, however there are also additional bytes which make the total length of the request 16 bytes. We find that at the third and fourth bytes of the struct, the 'length', which is placed in a green frame and has the value 0x04 0x00. Again with the little endian notation this becomes 0x00 0x04, which is decimal 4. Since this is a x4 bytes value, finally it becomes 4x4 bytes or 16 bytes.
Placing the request struct xChangeWindowAttributesReq plus the additional bytes at the next byte of 'buffer', just after xChangePropertyReq and its additional bytes is the result of the following instruction in our X client program:
/* select kind of events we are interested in */ XSelectInput(d, w, ExposureMask | KeyPressMask); |
By reading the source code of XSelectInput we see that this Xlib routine prepares a xChangeWindowAttributesReq struct and also uses 4 additional bytes.
After the 16th byte of xChangeWindowAttributesReq (including the additional bytes) at the gdb output it start another request struct, whose first byte, the one marked with a red frame, which corresponds to reqType field of the request struct has a value of 0x08. This is 8 decimal and by looking the previous Xproto.h table for 8 we find that the request type is X_MapWindow. This is exactly the request type we excpected since the next instruction, after the XSelectInput() call in our X Client program is:
/* map (show) the window */ XMapWindow(d, w); |
If we read the source code of MapWindow() we see that this Xlib routine uses places at the buffer the xResourceReq struct:
typedef struct { CARD8 reqType; BYTE pad; CARD16 length B16; CARD32 id B32; /* a Window, Drawable, Font, GContext, Pixmap, etc. */ } xResourceReq; |
The xResourceReq size as CARD8 + BYTE + CARD16 + CARD32 is 8 bytes, which is exactly the value of the 'length' field in the third and fourth byte of the request, marked with a green frame. The third and fourth bytes are 0x02 0x00, which by using the little endian notation become 0x00 0x02, or 2 decimal. This is again a x4 bytes value or 2x4 bytes, finally 8 bytes. This means no additional bytes are placed by MapWindow().
Details of the last three request structs can also be returned by the xtrace instruction (see section 2.2):
000:<:000a: 28: Request(18): ChangeProperty mode=Replace(0x00) window=0x04200001 property=0x12b("WM_PROTOCOLS") type=0x4("ATOM") data=0x129("WM_DELETE_WINDOW"); 000:<:000b: 16: Request(2): ChangeWindowAttributes window=0x04200001 value-list= {event-mask=KeyPress,Exposure} 000:<:000c: 8: Request(8): MapWindow window=0x04200001 |
The next figure shows the X Protocol structs and the additional bytes, stored in the buffer, just before the first and the second flushing of those messages to the X Server.
The rest of the gdb output bytes are garbage, since by placing the second 'break' instruction just after we stopped the process at this point and did not allowed the remaining instructions after XMapWindow() to be executed. Certainly we could continue with a third 'break' and examine further the X Protocol messages.